using DigitalData.Core.Contracts.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; } public DirectorySearchService(IOptions options, IMemoryCache memoryCache) { _memoryCache = memoryCache; var dirSearchOptions = options.Value; ServerName = dirSearchOptions.ServerName ?? throw new InvalidOperationException("The server name for directory search is not configured. Please specify the 'DirectorySearch:ServerName' in the configuration."); Root = dirSearchOptions.Root ?? throw new InvalidOperationException("The root for directory search is not configured. Please specify the 'DirectorySearch:Root' in the configuration."); SearchRootPath = $"LDAP://{ServerName}/{Root}"; CustomSearchFilters = dirSearchOptions.CustomSearchFilters; var dayCounts = dirSearchOptions.UserCacheExpirationDays; if (dayCounts == default) _userCacheExpiration = default; else _userCacheExpiration = DateTimeOffset.Now.Date.AddDays(dayCounts); } public bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword) { using var context = new PrincipalContext(ContextType.Domain, ServerName, Root); return context.ValidateCredentials(dirEntryUsername, dirEntryPassword); } 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); } public DataResult> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) { List list = new(); _memoryCache.TryGetValue(username, out DirectoryEntry? searchRoot); if (searchRoot is null) return Result.Fail>(); return FindAll(searchRoot, filter, searchScope, sizeLimit, properties); } public void SetSearchRootCache(string dirEntryUsername, string dirEntryPassword) { if (_userCacheExpiration == default) _memoryCache.Set(key: dirEntryUsername, new DirectoryEntry(path: SearchRootPath, username: dirEntryUsername, password: dirEntryPassword)); else _memoryCache.Set(key: dirEntryUsername, new DirectoryEntry(path: SearchRootPath, username: dirEntryUsername, password: dirEntryPassword), absoluteExpiration: _userCacheExpiration); } public DirectoryEntry? GetSearchRootCache(string dirEntryUsername) { _memoryCache.TryGetValue(dirEntryUsername, out DirectoryEntry? root); return root; } } }