179 lines
8.8 KiB
C#
179 lines
8.8 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
|
|
{
|
|
//TODO: rename as DirectorySearcher
|
|
[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>
|
|
/// Creates the connections to the server and returns a Boolean value that specifies
|
|
/// whether the specified username and password are valid.
|
|
/// </summary>
|
|
/// <param name="userName">The username that is validated on the server. See the Remarks section
|
|
/// for more information on the format of userName.</param>
|
|
/// <param name="password">The password that is validated on the server.</param>
|
|
/// <returns>True if the credentials are valid; otherwise, false.</returns>
|
|
public bool ValidateCredentials(string userName, string password)
|
|
{
|
|
using var context = new PrincipalContext(ContextType.Domain, ServerName, Root);
|
|
return context.ValidateCredentials(userName, password);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the connections to the server asynchronously and returns a Boolean value that specifies
|
|
/// whether the specified username and password are valid.
|
|
/// </summary>
|
|
/// <param name="userName">The username that is validated on the server. See the Remarks section
|
|
/// for more information on the format of userName.</param>
|
|
/// <param name="password">The password that is validated on the server.</param>
|
|
/// <returns>True if the credentials are valid; otherwise, false.</returns>
|
|
public Task<bool> ValidateCredentialsAsync(string userName, string password) => Task.Run(()
|
|
=> ValidateCredentials(userName, password));
|
|
|
|
//TODO: remove unnecessary DataResult
|
|
/// <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();
|
|
|
|
using 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 asynchronously.
|
|
/// </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 Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllAsync(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) => Task.Run(()
|
|
=> FindAll(searchRoot, filter, searchScope, sizeLimit, properties));
|
|
|
|
/// <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>
|
|
/// Finds all directory entries matching the specified filter asynchronously, 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 Task<DataResult<IEnumerable<ResultPropertyCollection>>> FindAllByUserCacheAsync(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) => Task.Run(()
|
|
=> FindAllByUserCache(username, filter, searchScope, sizeLimit, properties));
|
|
|
|
/// <summary>
|
|
/// Sets the search root in the cache.
|
|
/// </summary>
|
|
/// <param name="username">The directory entry username.</param>
|
|
/// <param name="password">The directory entry password.</param>
|
|
public void SetSearchRootCache(string username, string password)
|
|
{
|
|
if (_userCacheExpiration is DateTimeOffset cacheExpiration)
|
|
_memoryCache.Set(key: username, new DirectoryEntry(path: SearchRootPath, username: username, password: password), absoluteExpiration: cacheExpiration);
|
|
else
|
|
_memoryCache.Set(key: username, new DirectoryEntry(path: SearchRootPath, username: username, password: password));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the search root from the cache.
|
|
/// </summary>
|
|
/// <param name="username">The directory entry username.</param>
|
|
/// <returns>The cached <see cref="DirectoryEntry"/> if found; otherwise, null.</returns>
|
|
public DirectoryEntry? GetSearchRootCache(string username)
|
|
{
|
|
_memoryCache.TryGetValue(username, out DirectoryEntry? root);
|
|
return root;
|
|
}
|
|
}
|
|
} |