Die Cookie-basierte Autorisierung wurde mit DirectorySearchService eingerichtet.

This commit is contained in:
Developer 02 2024-06-13 16:12:07 +02:00
parent 2f41a993ee
commit 5846a7433c
20 changed files with 220 additions and 67 deletions

View File

@ -17,7 +17,7 @@ namespace EnvelopeGenerator.Application.Services
private readonly IMemoryCache _cache;
private readonly ILogger<ConfigService> _logger;
public ConfigService(IConfigRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper, IMemoryCache memoryCache, ILogger<ConfigService> logger) : base(repository, localizer, mapper)
public ConfigService(IConfigRepository repository, IMapper mapper, IMemoryCache memoryCache, ILogger<ConfigService> logger) : base(repository, mapper)
{
_cache = memoryCache;
_logger = logger;

View File

@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services
{
public class DocumentReceiverElementService : BasicCRUDService<IDocumentReceiverElementRepository, DocumentReceiverElementDto, DocumentReceiverElement, int>, IDocumentReceiverElementService
{
public DocumentReceiverElementService(IDocumentReceiverElementRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
public DocumentReceiverElementService(IDocumentReceiverElementRepository repository, IMapper mapper)
: base(repository, mapper)
{
}
}

View File

@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services
{
public class DocumentStatusService : BasicCRUDService<IDocumentStatusRepository, DocumentStatusDto, DocumentStatus, int>, IDocumentStatusService
{
public DocumentStatusService(IDocumentStatusRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
public DocumentStatusService(IDocumentStatusRepository repository, IMapper mapper)
: base(repository, mapper)
{
}
}

View File

@ -14,8 +14,8 @@ namespace EnvelopeGenerator.Application.Services
{
public class EmailTemplateService : BasicCRUDService<IEmailTemplateRepository, EmailTemplateDto, EmailTemplate, int>, IEmailTemplateService
{
public EmailTemplateService(IEmailTemplateRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
public EmailTemplateService(IEmailTemplateRepository repository, IMapper mapper)
: base(repository, mapper)
{
}

View File

@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeCertificateService : BasicCRUDService<IEnvelopeCertificateRepository, EnvelopeCertificateDto, EnvelopeCertificate, int>, IEnvelopeCertificateService
{
public EnvelopeCertificateService(IEnvelopeCertificateRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
public EnvelopeCertificateService(IEnvelopeCertificateRepository repository, IMapper mapper)
: base(repository, mapper)
{
}
}

View File

@ -12,7 +12,7 @@ namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeDocumentService : BasicCRUDService<IEnvelopeDocumentRepository, EnvelopeDocumentDto, EnvelopeDocument, int>, IEnvelopeDocumentService
{
public EnvelopeDocumentService(IEnvelopeDocumentRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper) : base(repository, localizer, mapper)
public EnvelopeDocumentService(IEnvelopeDocumentRepository repository, IMapper mapper) : base(repository, mapper)
{
}
}

View File

@ -15,7 +15,7 @@ namespace EnvelopeGenerator.Application.Services
public class EnvelopeHistoryService : CRUDService<IEnvelopeHistoryRepository, EnvelopeHistoryCreateDto, EnvelopeHistoryDto, EnvelopeHistoryDto, EnvelopeHistory, long>, IEnvelopeHistoryService
{
public EnvelopeHistoryService(IEnvelopeHistoryRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
: base(repository, mapper)
{
}

View File

@ -13,9 +13,12 @@ namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeReceiverService : BasicCRUDService<IEnvelopeReceiverRepository, EnvelopeReceiverDto, EnvelopeReceiver, int>, IEnvelopeReceiverService
{
private readonly IStringLocalizer<Resource> _localizer;
public EnvelopeReceiverService(IEnvelopeReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
: base(repository, mapper)
{
_localizer = localizer;
}
public async Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true)

View File

@ -14,8 +14,8 @@ namespace EnvelopeGenerator.Application.Services
public class EnvelopeService : BasicCRUDService<IEnvelopeRepository, EnvelopeDto, Envelope, int>, IEnvelopeService
{
private readonly ILogger _logger;
public EnvelopeService(IEnvelopeRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper, ILogger<EnvelopeService> logger)
: base(repository, localizer, mapper)
public EnvelopeService(IEnvelopeRepository repository, IMapper mapper, ILogger<EnvelopeService> logger)
: base(repository, mapper)
{
_logger = logger;
}

View File

@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeTypeService : BasicCRUDService<IEnvelopeTypeRepository, EnvelopeTypeDto, EnvelopeType, int>, IEnvelopeTypeService
{
public EnvelopeTypeService(IEnvelopeTypeRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
public EnvelopeTypeService(IEnvelopeTypeRepository repository, IMapper mapper)
: base(repository, mapper)
{
}
}

View File

@ -13,7 +13,7 @@ namespace EnvelopeGenerator.Application.Services
public class ReceiverService : BasicCRUDService<IReceiverRepository, ReceiverDto, Receiver, int>, IReceiverService
{
public ReceiverService(IReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
: base(repository, mapper)
{
}
}

View File

@ -12,7 +12,7 @@ namespace EnvelopeGenerator.Application.Services
public class UserReceiverService : BasicCRUDService<IUserReceiverRepository, UserReceiverDto, UserReceiver, int>, IUserReceiverService
{
public UserReceiverService(IUserReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
: base(repository, mapper)
{
}
}

View File

@ -0,0 +1,111 @@
using DigitalData.Core.Contracts.Application;
using DigitalData.Core.DTO;
using DigitalData.UserManager.Application.Contracts;
using DigitalData.UserManager.Application.DTOs.User;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using System.Security.Claims;
using DigitalData.UserManager.Application.DTOs.Auth;
using DigitalData.UserManager.Application;
using Microsoft.AspNetCore.Authorization;
namespace EnvelopeGenerator.GeneratorAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IUserService _userService;
private readonly IDirectorySearchService _dirSearchService;
private readonly IStringLocalizer<Resource> _localizer;
public AuthController(ILogger<AuthController> logger, IUserService userService, IDirectorySearchService dirSearchService, IStringLocalizer<Resource> localizer)
{
_logger = logger;
_userService = userService;
_dirSearchService = dirSearchService;
_localizer = localizer;
}
//TODO: When a user group is created for signFlow, add a process to check if the user is in this group (like "PM_USER")
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LogInDto login)
{
try
{
bool isValid = _dirSearchService.ValidateCredentials(login.Username, login.Password);
if (!isValid)
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));
//find the user
var uRes = await _userService.ReadByUsernameAsync(login.Username);
if (!uRes.IsSuccess || uRes.Data is null)
{
return Unauthorized(uRes);
}
UserReadDto user = uRes.Data;
// Create claims
var claims = new List<Claim>
{
new (ClaimTypes.NameIdentifier, user.Guid.ToString()),
new (ClaimTypes.Name, user.Username),
new (ClaimTypes.Surname, user.Name!),
new (ClaimTypes.GivenName, user.Prename!),
new (ClaimTypes.Email, user.Email!),
};
// Create claimsIdentity
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
// Create authProperties
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
AllowRefresh = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(60)
};
// Sign in
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
_dirSearchService.SetSearchRootCache(user.Username, login.Password);
return Ok();
}
catch(Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred.\n{ErrorMessage}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[Authorize]
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
try
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred.\n{ErrorMessage}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[AllowAnonymous]
[HttpGet("check")]
public IActionResult IsAuthenticated() => Ok(User.Identity?.IsAuthenticated ?? false);
}
}

View File

@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.GeneratorAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -9,10 +9,40 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.15" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="ClientApp\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="DigitalData.Core.API">
<HintPath>..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.API.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Application">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Application\bin\Debug\net7.0\DigitalData.Core.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Contracts">
<HintPath>..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.Contracts.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.DTO">
<HintPath>..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.DTO.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Infrastructure\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Application">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -1,12 +1,45 @@
using DigitalData.Core.API;
using DigitalData.Core.Application;
using DigitalData.UserManager.Application;
using DigitalData.UserManager.Infrastructure.Repositories;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var config = builder.Configuration;
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
// Authentication
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
options.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
});
// User manager
builder.Services.AddUserManager<EGDbContext>();
// LDAP
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService();
// Localizer
builder.Services.AddCookieBasedLocalizer() ;
var app = builder.Build();
// Configure the HTTP request pipeline.
@ -16,12 +49,16 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI();
}
// Localizer
app.UseCookieBasedLocalizer("de-DE", "en-US");
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();

View File

@ -1,13 +0,0 @@
namespace EnvelopeGenerator.GeneratorAPI
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -5,5 +5,17 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"DirectorySearchOptions": {
"ServerName": "DD-VMP01-DC01",
"Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works",
"UserCacheExpirationDays": 1,
"CustomSearchFilters": {
"User": "(&(objectClass=user)(sAMAccountName=*))",
"Group": "(&(objectClass=group)(samAccountName=*))"
}
}
}

View File

@ -66,6 +66,9 @@ namespace DigitalData.UserManager.Infrastructure.Repositories
modelBuilder.Entity<Envelope>().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS"));
modelBuilder.Entity<EnvelopeHistory>().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS"));
//configure model builder for user manager tables
modelBuilder.ConfigureUserManager();
base.OnModelCreating(modelBuilder);
}
}

View File

@ -32,6 +32,9 @@
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Infrastructure\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
</Reference>
</ItemGroup>
</Project>