140 lines
6.3 KiB
C#

using DigitalData.Core.Abstractions.Application;
using System.Diagnostics.CodeAnalysis;
using System.DirectoryServices;
using Microsoft.Extensions.Caching.Memory;
using System.DirectoryServices.AccountManagement;
using DigitalData.Core.DTO;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Application
{
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public class DirectorySearchService : IDirectorySearchService
{
private readonly IMemoryCache _memoryCache;
public string ServerName { get; }
public string Root { get; }
public string SearchRootPath { get; }
private readonly DateTimeOffset? _userCacheExpiration;
public Dictionary<string, string> CustomSearchFilters { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DirectorySearchService"/> class.
/// </summary>
/// <param name="options">The options for directory search.</param>
/// <param name="memoryCache">The memory cache.</param>
/// <exception cref="InvalidOperationException">
/// Thrown if the server name or root directory is not configured.
/// </exception>
public DirectorySearchService(IOptions<DirectorySearchOptions> options, IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
var dirSearchOptions = options.Value;
ServerName = dirSearchOptions.ServerName;
Root = dirSearchOptions.Root;
SearchRootPath = $"LDAP://{ServerName}/{Root}";
CustomSearchFilters = dirSearchOptions.CustomSearchFilters;
if(dirSearchOptions.UserCacheExpirationDays is double expirationDays)
_userCacheExpiration = DateTimeOffset.Now.Date.AddDays(expirationDays);
}
/// <summary>
/// Validates the credentials of a directory entry.
/// </summary>
/// <param name="dirEntryUsername">The directory entry username.</param>
/// <param name="dirEntryPassword">The directory entry password.</param>
/// <returns>True if the credentials are valid; otherwise, false.</returns>
public bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword)
{
using var context = new PrincipalContext(ContextType.Domain, ServerName, Root);
return context.ValidateCredentials(dirEntryUsername, dirEntryPassword);
}
/// <summary>
/// Finds all directory entries matching the specified filter.
/// </summary>
/// <param name="searchRoot">The search root.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
public DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties)
{
List<ResultPropertyCollection> list = new();
var searcher = new DirectorySearcher()
{
Filter = filter,
SearchScope = searchScope,
SizeLimit = sizeLimit,
SearchRoot = searchRoot
};
if (properties.Length > 0)
{
searcher.PropertiesToLoad.Clear();
foreach (var property in properties)
if(property is not null)
searcher.PropertiesToLoad.Add(property);
}
foreach (SearchResult result in searcher.FindAll())
{
ResultPropertyCollection rpc = result.Properties;
list.Add(rpc);
}
return Result.Success<IEnumerable<ResultPropertyCollection>>(list);
}
/// <summary>
/// Finds all directory entries matching the specified filter, using the user cache.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="filter">The search filter.</param>
/// <param name="searchScope">The search scope.</param>
/// <param name="sizeLimit">The size limit.</param>
/// <param name="properties">The properties to load.</param>
/// <returns>A <see cref="DataResult{T}"/> containing the results.</returns>
public DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties)
{
_memoryCache.TryGetValue(username, out DirectoryEntry? searchRoot);
if (searchRoot is null)
return Result.Fail<IEnumerable<ResultPropertyCollection>>();
return FindAll(searchRoot, filter, searchScope, sizeLimit, properties);
}
/// <summary>
/// Sets the search root in the cache.
/// </summary>
/// <param name="dirEntryUsername">The directory entry username.</param>
/// <param name="dirEntryPassword">The directory entry password.</param>
public void SetSearchRootCache(string dirEntryUsername, string dirEntryPassword)
{
if (_userCacheExpiration is DateTimeOffset cacheExpiration)
_memoryCache.Set(key: dirEntryUsername, new DirectoryEntry(path: SearchRootPath, username: dirEntryUsername, password: dirEntryPassword), absoluteExpiration: cacheExpiration);
else
_memoryCache.Set(key: dirEntryUsername, new DirectoryEntry(path: SearchRootPath, username: dirEntryUsername, password: dirEntryPassword));
}
/// <summary>
/// Gets the search root from the cache.
/// </summary>
/// <param name="dirEntryUsername">The directory entry username.</param>
/// <returns>The cached <see cref="DirectoryEntry"/> if found; otherwise, null.</returns>
public DirectoryEntry? GetSearchRootCache(string dirEntryUsername)
{
_memoryCache.TryGetValue(dirEntryUsername, out DirectoryEntry? root);
return root;
}
}
}