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 = "")] 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 CustomSearchFilters { get; } /// /// Initializes a new instance of the class. /// /// The options for directory search. /// The memory cache. /// /// Thrown if the server name or root directory is not configured. /// public DirectorySearchService(IOptions 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); } /// /// Validates the credentials of a directory entry. /// /// The directory entry username. /// The directory entry password. /// True if the credentials are valid; otherwise, false. public bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword) { using var context = new PrincipalContext(ContextType.Domain, ServerName, Root); return context.ValidateCredentials(dirEntryUsername, dirEntryPassword); } /// /// Finds all directory entries matching the specified filter. /// /// The search root. /// The search filter. /// The search scope. /// The size limit. /// The properties to load. /// A containing the results. public DataResult> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) { List 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>(list); } /// /// Finds all directory entries matching the specified filter, using the user cache. /// /// The username. /// The search filter. /// The search scope. /// The size limit. /// The properties to load. /// A containing the results. public DataResult> 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>(); return FindAll(searchRoot, filter, searchScope, sizeLimit, properties); } /// /// Sets the search root in the cache. /// /// The directory entry username. /// The directory entry password. 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)); } /// /// Gets the search root from the cache. /// /// The directory entry username. /// The cached if found; otherwise, null. public DirectoryEntry? GetSearchRootCache(string dirEntryUsername) { _memoryCache.TryGetValue(dirEntryUsername, out DirectoryEntry? root); return root; } } }