Compare commits

..

36 Commits

Author SHA1 Message Date
Developer 02
f0e82b4210 Merge branch 'master' of http://git.dd:3000/AppStd/DigitalData.ActiveDirectory 2025-08-06 18:57:25 +02:00
62ea2628b9 Update src/DigitalData.ActiveDirectory/README.md 2025-08-06 18:11:03 +02:00
164c7e11b0 Update src/DigitalData.ActiveDirectory/README.md 2025-08-06 18:09:15 +02:00
8c4ac321da Update src/DigitalData.ActiveDirectory/README.md 2025-08-06 18:08:33 +02:00
Developer 02
cf0741ea8a refactor(ActiveDirectoryOptions): Umbenennen in DirectoryEntryQuery 2025-08-06 18:01:07 +02:00
Developer 02
6215642fcf feat(ActiveDirectoryOptions): AuthenticationType-Eigenschaft hinzufügen
- AuthenticationType-Eigenschaft zu SearchRoot in DirectorySearchQueryHandler hinzufügen
2025-08-06 17:43:39 +02:00
48f9379f2a Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 17:23:34 +02:00
ce3698d8e1 Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 17:21:45 +02:00
32116d8687 Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 17:20:33 +02:00
bd9f002757 Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 17:11:36 +02:00
b8d49b4f47 Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 17:02:14 +02:00
cc3a62c477 Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 16:54:47 +02:00
92fec186cd Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 16:54:23 +02:00
52cac4758e Update src/DigitalData.ActiveDirectory/README.md 2025-08-05 16:46:36 +02:00
Developer 02
a34d19e1bd Merge branch 'master' of http://git.dd:3000/AppStd/DigitalData.ActiveDirectory 2025-08-05 16:10:42 +02:00
Developer 02
f73730ba65 refactor(DirectorySearchQuery): Root-Datensatz definieren.
- SearchRoot-Eigenschaft hinzufügen
 - Zuordnungsprofil zwischen DirectorySearchQuery.Root und DirectoryEntry hinzufügen
 - Logik hinzufügen, um den Standardwert SearcdhRoot zu verwenden, wenn DirectorySearchQuery.Root null ist
2025-08-05 15:41:01 +02:00
Developer 02
d0de978c7d fix(DirectorySearchQuery): Umbenennen von „properties-property“ in „Property“ 2025-08-05 14:28:09 +02:00
de442da233 Add src/DigitalData.ActiveDirectory/README.md 2025-08-05 14:20:43 +02:00
Developer 02
25812a0a7d refactor: add to-do 2025-08-05 14:16:21 +02:00
Developer 02
b7839c4d44 refactor: update doc comment 2025-08-05 14:15:25 +02:00
Developer 02
492f9a82ca refactor(ADConfigurationOptions): MediatRLicenseKey in LPLicenseKey umbenennen 2025-08-05 14:12:16 +02:00
Developer 02
168dff5f60 feat(DirectorySearchQuery): integrate auto-mapper.
- configure automapper
2025-08-05 14:09:41 +02:00
b69867468d feat(DirectorySearchQuery): Optionen in AfterInit umbenennen 2025-08-04 16:54:02 +02:00
160a3cb568 chore(API): arrange dependency versions 2025-08-04 15:55:50 +02:00
d2180c912b chore: update the version of AutoMapper
- update controller to return iactionresult
2025-08-04 15:27:38 +02:00
c178aa7fba chore: arrange dependency versions 2025-08-04 15:16:37 +02:00
6bc96205ce chore: ADConfigurationOptions mit appsettings verknüpfen 2025-08-04 15:03:56 +02:00
Developer 02
6a4f8a12c7 add not-found exception to handle on endpoint 2025-08-03 09:31:36 +02:00
Developer 02
1b9dac93a5 add ExceptionHandlingMiddleware 2025-08-03 09:27:19 +02:00
Developer 02
2bdc8ebafd feat(ActiveDirectoryController): add with GetAll endpoint 2025-08-03 09:18:07 +02:00
Developer 02
52d36004ae feat(API): Initialisierung, um .NET 7, 8 und 9 unterstützen zu können 2025-08-03 09:07:10 +02:00
Developer 02
452cc6345c feat(DirectorySearchQuery): Implementierung von DirectorySearchQueryHandler mit Eigenschaftsfilterung und Ergebnissammlung
- Aktualisierung von DirectorySearchQuery, um Eigenschaften zu akzeptieren und IEnumerable<ResultPropertyCollection> zurückzugeben
- Implementierung von DirectorySearchQueryHandler, um die Suche mit benutzerdefinierten Filtern, Bereichen und Eigenschaftsauswahl auszuführen
- Hinzufügen einer Logik, um nur angeforderte Eigenschaften zu laden, wenn diese angegeben sind
- Ersetzen von NotImplementedException durch die tatsächliche DirectorySearcher-Logik
2025-08-03 08:53:23 +02:00
Developer 02
e75127c74e feat(DirectorySearchQuery): create to handle directory search
- define query model
2025-08-02 21:24:19 +02:00
Developer 02
39982139d3 refactor(ADConfigurationOptions): rename MediatRLicense as MediatRLicenseKey 2025-08-02 19:33:10 +02:00
Developer 02
409e43460d feat(DependencyInjection): Add MediatR and configure dependency injection 2025-08-02 19:29:53 +02:00
Developer 02
78021a95a4 feat(ActiveDirectoryOptions): add configuration inc connection parameters 2025-08-02 11:06:35 +02:00
14 changed files with 397 additions and 52 deletions

View File

@@ -7,7 +7,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.ActiveDirectory", "src\DigitalData.ActiveDirectory\DigitalData.ActiveDirectory.csproj", "{26B810C2-8D06-42CF-999F-CB508C9D2D01}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.ActiveDirectory", "src\DigitalData.ActiveDirectory\DigitalData.ActiveDirectory.csproj", "{26B810C2-8D06-42CF-999F-CB508C9D2D01}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.ActiveDirectory.API", "src\DigitalData.ActiveDirectory.API\DigitalData.ActiveDirectory.API.csproj", "{2B3A9620-E298-00ED-CC4B-A6A83F822A01}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.ActiveDirectory.API", "src\DigitalData.ActiveDirectory.API\DigitalData.ActiveDirectory.API.csproj", "{4994DDB6-8106-4290-85B3-99C9C98FA0A7}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -31,25 +33,25 @@ Global
{26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x64.Build.0 = Release|Any CPU {26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x64.Build.0 = Release|Any CPU
{26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x86.ActiveCfg = Release|Any CPU {26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x86.ActiveCfg = Release|Any CPU
{26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x86.Build.0 = Release|Any CPU {26B810C2-8D06-42CF-999F-CB508C9D2D01}.Release|x86.Build.0 = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|Any CPU.Build.0 = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|x64.ActiveCfg = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|x64.ActiveCfg = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|x64.Build.0 = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|x64.Build.0 = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|x86.ActiveCfg = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|x86.ActiveCfg = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Debug|x86.Build.0 = Debug|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Debug|x86.Build.0 = Debug|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|Any CPU.ActiveCfg = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|Any CPU.Build.0 = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|Any CPU.Build.0 = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|x64.ActiveCfg = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|x64.ActiveCfg = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|x64.Build.0 = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|x64.Build.0 = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|x86.ActiveCfg = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|x86.ActiveCfg = Release|Any CPU
{2B3A9620-E298-00ED-CC4B-A6A83F822A01}.Release|x86.Build.0 = Release|Any CPU {4994DDB6-8106-4290-85B3-99C9C98FA0A7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{26B810C2-8D06-42CF-999F-CB508C9D2D01} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {26B810C2-8D06-42CF-999F-CB508C9D2D01} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{2B3A9620-E298-00ED-CC4B-A6A83F822A01} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {4994DDB6-8106-4290-85B3-99C9C98FA0A7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2F00C94F-CA5D-4F53-A608-B935F0FD4B46} SolutionGuid = {2F00C94F-CA5D-4F53-A608-B935F0FD4B46}

View File

@@ -0,0 +1,18 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.ActiveDirectory.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ActiveDirectoryController : ControllerBase
{
private readonly IMediator _mediator;
public ActiveDirectoryController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] DirectorySearchQuery query) => Ok(await _mediator.Send(query));
}

View File

@@ -1,14 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.18" /> <ProjectReference Include="..\DigitalData.ActiveDirectory\DigitalData.ActiveDirectory.csproj" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +0,0 @@
@DigitalData.ActiveDirectory.API_HostAddress = http://localhost:5092
GET {{DigitalData.ActiveDirectory.API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,84 @@
using DigitalData.Core.Exceptions;
using System.Net;
using System.Text.Json;
namespace DigitalData.ActiveDirectory.API.Middleware;
//TODO: Fix and use DigitalData.Core.Exceptions.Middleware
/// <summary>
/// Middleware for handling exceptions globally in the application.
/// Captures exceptions thrown during the request pipeline execution,
/// logs them, and returns an appropriate HTTP response with a JSON error message.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions.Middleware")]
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionHandlingMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="logger">The logger instance for logging exceptions.</param>
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
/// <summary>
/// Invokes the middleware to handle the HTTP request.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // Continue down the pipeline
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _logger);
}
}
/// <summary>
/// Handles exceptions by logging them and writing an appropriate JSON response.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <param name="exception">The exception that occurred.</param>
/// <param name="logger">The logger instance for logging the exception.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
{
context.Response.ContentType = "application/json";
string message;
switch (exception)
{
case BadRequestException badRequestEx:
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
message = badRequestEx.Message;
break;
case NotFoundException notFoundEx:
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
message = notFoundEx.Message;
break;
default:
logger.LogError(exception, "Unhandled exception occurred.");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
message = "An unexpected error occurred.";
break;
}
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
message
}));
}
}

View File

@@ -1,12 +1,25 @@
using DigitalData.ActiveDirectory;
using DigitalData.ActiveDirectory.API.Middleware;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddActiveDirectory(options =>
{
options.ConfigRootDirectoryEntry(builder.Configuration.GetSection("RootDirectoryEntry"));
options.LPLicenseKey = builder.Configuration["LPLicense"];
});
var app = builder.Build(); var app = builder.Build();
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
@@ -16,29 +29,8 @@ if (app.Environment.IsDevelopment())
app.UseHttpsRedirection(); app.UseHttpsRedirection();
var summaries = new[] app.UseAuthorization();
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () => app.MapControllers();
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run(); app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

View File

@@ -4,8 +4,8 @@
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:34411", "applicationUrl": "http://localhost:40890",
"sslPort": 44378 "sslPort": 44353
} }
}, },
"profiles": { "profiles": {
@@ -14,7 +14,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "http://localhost:5092", "applicationUrl": "http://localhost:5215",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@@ -24,7 +24,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "https://localhost:7221;http://localhost:5092", "applicationUrl": "https://localhost:7001;http://localhost:5215",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@@ -5,5 +5,11 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"RootDirectoryEntry": {
"Path": "LDAP://DD-VMP01-DC01/DC=dd-gan,DC=local,DC=digitaldata,DC=works",
"Username": "FABRIK19-User01",
"Password": "9bWOr0UGuHn_7VkC"
},
"LPLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg"
} }

View File

@@ -0,0 +1,61 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.ActiveDirectory;
public static class DependencyInjection
{
public static IServiceCollection AddActiveDirectory(this IServiceCollection services, Action<ADConfigurationOptions>? options = null)
{
var cOptions = new ADConfigurationOptions(services);
options?.Invoke(cOptions);
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblies(typeof(DependencyInjection).Assembly);
cfg.LicenseKey = cOptions.LPLicenseKey;
});
services.AddAutoMapper(cfg =>
{
#if NET8_0_OR_GREATER
cfg.LicenseKey = cOptions.LPLicenseKey;
#endif
cfg.AddMaps(typeof(DependencyInjection).Assembly);
});
if (!cOptions.IsADConfigured)
services.Configure<DirectoryEntryQuery>(_ => { });
return services;
}
public class ADConfigurationOptions
{
private readonly IServiceCollection _services;
internal bool IsADConfigured { get; private set; } = false;
internal ADConfigurationOptions(IServiceCollection services) => _services = services;
/// <summary>
/// Gets or sets the license key used to activate Lucky Penny Software libraries.
/// </summary>
/// <remarks>
/// For more information, visit: https://luckypennysoftware.com/
/// </remarks>
public string? LPLicenseKey { get; set; }
private void EnsureSingleMappingConfiguration(Action action)
{
if (IsADConfigured)
throw new InvalidOperationException("Mapping configuration has already been set.");
action();
IsADConfigured = true;
}
public void ConfigRootDirectoryEntry(IConfiguration config) => EnsureSingleMappingConfiguration(() => _services.Configure<DirectoryEntryQuery>(config));
public void ConfigRootDirectoryEntry(Action<DirectoryEntryQuery> options) => EnsureSingleMappingConfiguration(() => _services.Configure(options));
}
}

View File

@@ -1,9 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="MediatR" Version="13.0.0" />
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.1" />
<PackageReference Include="MediatR" Version="13.0.0" />
<PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.7" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.7" />
<PackageReference Include="MediatR" Version="13.0.0" />
<PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,16 @@
using MediatR;
using System.DirectoryServices;
namespace DigitalData.ActiveDirectory;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public class DirectoryEntryQuery: IRequest
{
public string Path { get; set; } = null!;
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public IEnumerable<AuthenticationTypes> AuthenticationType { get; set; } = new List<AuthenticationTypes>() { AuthenticationTypes.None };
}

View File

@@ -0,0 +1,55 @@
using AutoMapper;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Extensions.Options;
using System.DirectoryServices;
namespace DigitalData.ActiveDirectory;
public record DirectorySearchQuery(string? Filter = null, SearchScope Scope = SearchScope.Subtree, int SizeLimit = 5000, params string[] Property)
: IRequest<IEnumerable<ResultPropertyCollection>>
{
public DirectoryEntryQuery? SearchRoot { get; set; }
public Action<DirectorySearcher>? AfterInit { get; set; }
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public class DirectorySearchQueryHandler : IRequestHandler<DirectorySearchQuery, IEnumerable<ResultPropertyCollection>>
{
private readonly IOptions<DirectoryEntryQuery> _options;
private readonly IMapper _mapper;
public DirectorySearchQueryHandler(IOptions<DirectoryEntryQuery> options, IMapper mapper)
{
_options = options;
_mapper = mapper;
}
//TODO: add resolver to handle SearchRoot and AfterInit mapping
public Task<IEnumerable<ResultPropertyCollection>> Handle(DirectorySearchQuery request, CancellationToken cancellationToken = default)
{
return Task.Run(() =>
{
using var searcher = _mapper.Map<DirectorySearcher>(request);
searcher.SearchRoot ??= new()
{
Path = _options.Value.Path,
Username = _options.Value.Username,
Password = _options.Value.Password,
AuthenticationType = _options.Value.AuthenticationType.Aggregate((a, b) => a | b)
};
request.AfterInit?.Invoke(searcher);
if (request.Property.Length > 0)
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.AddRange(request.Property.Where(p => p is not null).ToArray());
var res = searcher.FindAll().Cast<SearchResult>().Select(r => r.Properties);
return res.Any() ? res : throw new NotFoundException();
});
}
}

View File

@@ -0,0 +1,14 @@
using AutoMapper;
using System.DirectoryServices;
namespace DigitalData.ActiveDirectory;
public class MappingProfile : Profile
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public MappingProfile()
{
CreateMap<DirectorySearchQuery, DirectorySearcher>();
CreateMap<DirectoryEntry, DirectoryEntry>();
}
}

View File

@@ -0,0 +1,55 @@
# DirectorySearchQuery
**`DirectorySearchQuery`** ist ein Abfrageobjekt, das zum Durchführen von Suchvorgängen in Active Directory (AD) verwendet wird. Dieses Objekt ist mit der integrierten **`DirectorySearcher`**-Klasse von .NET verknüpft (gemappt) und stellt die erforderlichen Parameter zum Konfigurieren der Suche bereit. Die folgenden Funktionen können hinzugefügt werden. Weitere Informationen finden Sie im entsprechenden [Microsoft-Artikel](https://learn.microsoft.com/de-de/dotnet/api/system.directoryservices.directorysearcher?view=net-8.0).
## Eigenschaften
Die folgenden Eigenschaften werden verwendet, um Ihre AD-Abfrage anzupassen:
* **`Filter`** (`string`): Dies ist die LDAP-Filterzeichenfolge, die für die Suche verwendet wird. Beispielsweise sucht `(objectClass=user)` nach allen Benutzerobjekten, während `(&(objectClass=user)(cn=Test*))` nach Benutzern sucht, deren Name mit „Test” beginnt. Weitere Informationen zur Verwendung von Filtern finden Sie in der [LDAP-Filtersyntaxdokumentation von Microsoft](https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax).
* **`Scope`** (`SearchScope`): Gibt an, auf welcher Ebene die Suche durchgeführt werden soll. Es gibt drei verschiedene Werte:
* **`0. Base`**: Sucht nur nach dem Startobjekt der Suche.
* **`1. OneLevel`**: Sucht nur nach Objekten, die sich direkt unter dem Startobjekt befinden.
* **`2. Subtree`** (Standard): Durchsucht den gesamten Unterbaum einschließlich des Startobjekts.
* **`SizeLimit`** (`int`): Gibt die maximale Anzahl von Objekten an, die als Ergebnis einer Suche zurückgegeben werden sollen. Der Standardwert ist 5000. Dieser Wert ist wichtig für die Leistungsoptimierung bei der Arbeit mit großen Datensätzen.
* **`Property`** (`string[]`): Eine Liste der Attribute, die in den Suchergebnissen zurückgegeben werden sollen. Beispielsweise gibt `[ "cn", "sAMAccountName", "mail" ]` nur die angegebenen Attribute zurück. Durch die Auswahl nur der benötigten Attribute werden der Netzwerkverkehr und der Speicherverbrauch reduziert und die Leistung verbessert.
* **`SearchRoot`** (`DirectoryEntryQuery`): Gibt den Active Directory-Pfad an, an dem die Suche beginnen soll. Dies entspricht der Eigenschaft **`SearchRoot`** des Objekts **`DirectorySearcher`**. Optional können Benutzername und Passwort als spezielle Anmeldedaten angegeben werden. In DirectorySearchQuery können die folgenden drei Eigenschaften hinzugefügt werden. Weitere Informationen finden Sie im entsprechenden [Microsoft-Artikel](https://learn.microsoft.com/de-de/dotnet/api/system.directoryservices.directoryentry?view=windowsdesktop-9.0).
* **`Path`**: Ruft den Pfad für diesen DirectoryEntry ab oder legt diesen fest.
* **`Username`**: Ruft den für die Clientauthentifizierung zu verwendenden Benutzernamen ab oder legt diesen fest.
* **`Password`**: Legt das Kennwort fest, mit dem der Client authentifiziert werden soll.
* **`AuthenticationType`**: Diese Enumeration wird verwendet, um das Authentifizierungsverhalten beim Zugriff auf Active Directory über LDAP zu steuern. Mehrere Werte können mit OR kombiniert werden. Weitere Informationen finden Sie im entsprechenden [Microsoft-Artikel](https://learn.microsoft.com/de-de/dotnet/api/system.directoryservices.authenticationtypes?view=windowsdesktop-9.0).
# AuthenticationTypes (System.DirectoryServices)
| Wert | Name | Beschreibung |
|------|-----------------------|--------------|
| `0` | **None** | Standardverhalten. Es wird keine spezielle Authentifizierung verwendet. |
| `1` | **Secure** | Führt eine sichere Authentifizierung mit NTLM oder Kerberos durch. Anmeldeinformationen werden verschlüsselt übertragen. |
| `2` | **Encryption** | Veraltet. Siehe `SecureSocketsLayer`. Hat denselben Wert. |
| `2` | **SecureSocketsLayer**| Verwendet SSL (LDAPS) über Port 636 für eine verschlüsselte Verbindung. |
| `4` | **ReadonlyServer** | Gibt an, dass eine Verbindung zu einem schreibgeschützten Server (z.B. Global Catalog) hergestellt wird. |
| `16` | **Anonymous** | Anonyme Verbindung ohne Anmeldeinformationen. Aus Sicherheitsgründen oft deaktiviert. |
| `32` | **FastBind** | Überspringt die Sicherheitsüberprüfung beim Binden. Verbessert die Performance, schränkt aber die Funktionalität ein. |
| `64` | **Signing** | Erzwingt die digitale Signierung der Authentifizierungsdaten. |
| `128`| **Sealing** | Verschlüsselt die gesamte Kommunikation mit dem Server. Bietet Vertraulichkeit. |
| `256`| **Delegation** | Ermöglicht die Weitergabe von Anmeldeinformationen an andere Server (z.B. bei Impersonation-Szenarien). |
| `512`| **ServerBind** | Erzwingt eine Authentifizierung durch den Server beim Binden. Wird selten verwendet. |
## Anwendungsbeispiel
Das folgende Beispiel zeigt, wie ein `DirectorySearchQuery`-Objekt erstellt:
```json
{
"filter": "(&(objectClass=user)(sAMAccountName=john.doe))",
"scope": 2,
"sizeLimit": 500,
"property": [ "cn", "mail", "description" ],
"searchRoot": {
"path": "LDAP://OU=Users,DC=example,DC=com",
"username": "MustermannM",
"password": "Must3rM@nn!"
}
}
```