diff --git a/EnvelopeGenerator.Application/Services/ConfigService.cs b/EnvelopeGenerator.Application/Services/ConfigService.cs index eab5e595..57317fc1 100644 --- a/EnvelopeGenerator.Application/Services/ConfigService.cs +++ b/EnvelopeGenerator.Application/Services/ConfigService.cs @@ -17,7 +17,7 @@ namespace EnvelopeGenerator.Application.Services private readonly IMemoryCache _cache; private readonly ILogger _logger; - public ConfigService(IConfigRepository repository, IStringLocalizer localizer, IMapper mapper, IMemoryCache memoryCache, ILogger logger) : base(repository, localizer, mapper) + public ConfigService(IConfigRepository repository, IMapper mapper, IMemoryCache memoryCache, ILogger logger) : base(repository, mapper) { _cache = memoryCache; _logger = logger; diff --git a/EnvelopeGenerator.Application/Services/DocumentReceiverElementService.cs b/EnvelopeGenerator.Application/Services/DocumentReceiverElementService.cs index 6f25514a..0c8e0c3d 100644 --- a/EnvelopeGenerator.Application/Services/DocumentReceiverElementService.cs +++ b/EnvelopeGenerator.Application/Services/DocumentReceiverElementService.cs @@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services { public class DocumentReceiverElementService : BasicCRUDService, IDocumentReceiverElementService { - public DocumentReceiverElementService(IDocumentReceiverElementRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + public DocumentReceiverElementService(IDocumentReceiverElementRepository repository, IMapper mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/DocumentStatusService.cs b/EnvelopeGenerator.Application/Services/DocumentStatusService.cs index 3de05d6f..019197ff 100644 --- a/EnvelopeGenerator.Application/Services/DocumentStatusService.cs +++ b/EnvelopeGenerator.Application/Services/DocumentStatusService.cs @@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services { public class DocumentStatusService : BasicCRUDService, IDocumentStatusService { - public DocumentStatusService(IDocumentStatusRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + public DocumentStatusService(IDocumentStatusRepository repository, IMapper mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/EmailTemplateService.cs b/EnvelopeGenerator.Application/Services/EmailTemplateService.cs index 4ca4ee2e..d31e1430 100644 --- a/EnvelopeGenerator.Application/Services/EmailTemplateService.cs +++ b/EnvelopeGenerator.Application/Services/EmailTemplateService.cs @@ -14,8 +14,8 @@ namespace EnvelopeGenerator.Application.Services { public class EmailTemplateService : BasicCRUDService, IEmailTemplateService { - public EmailTemplateService(IEmailTemplateRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + public EmailTemplateService(IEmailTemplateRepository repository, IMapper mapper) + : base(repository, mapper) { } diff --git a/EnvelopeGenerator.Application/Services/EnvelopeCertificateService.cs b/EnvelopeGenerator.Application/Services/EnvelopeCertificateService.cs index 807ba729..adf1680e 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeCertificateService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeCertificateService.cs @@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services { public class EnvelopeCertificateService : BasicCRUDService, IEnvelopeCertificateService { - public EnvelopeCertificateService(IEnvelopeCertificateRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + public EnvelopeCertificateService(IEnvelopeCertificateRepository repository, IMapper mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/EnvelopeDocumentService.cs b/EnvelopeGenerator.Application/Services/EnvelopeDocumentService.cs index 36e880cc..8ecdb540 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeDocumentService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeDocumentService.cs @@ -12,7 +12,7 @@ namespace EnvelopeGenerator.Application.Services { public class EnvelopeDocumentService : BasicCRUDService, IEnvelopeDocumentService { - public EnvelopeDocumentService(IEnvelopeDocumentRepository repository, IStringLocalizer localizer, IMapper mapper) : base(repository, localizer, mapper) + public EnvelopeDocumentService(IEnvelopeDocumentRepository repository, IMapper mapper) : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs b/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs index eb2cd5f6..ab9431cc 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs @@ -15,7 +15,7 @@ namespace EnvelopeGenerator.Application.Services public class EnvelopeHistoryService : CRUDService, IEnvelopeHistoryService { public EnvelopeHistoryService(IEnvelopeHistoryRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + : base(repository, mapper) { } diff --git a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs index a194db7b..337adc86 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs @@ -13,9 +13,12 @@ namespace EnvelopeGenerator.Application.Services { public class EnvelopeReceiverService : BasicCRUDService, IEnvelopeReceiverService { + private readonly IStringLocalizer _localizer; + public EnvelopeReceiverService(IEnvelopeReceiverRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + : base(repository, mapper) { + _localizer = localizer; } public async Task>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true) diff --git a/EnvelopeGenerator.Application/Services/EnvelopeService.cs b/EnvelopeGenerator.Application/Services/EnvelopeService.cs index 82d69486..2c394713 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeService.cs @@ -14,8 +14,8 @@ namespace EnvelopeGenerator.Application.Services public class EnvelopeService : BasicCRUDService, IEnvelopeService { private readonly ILogger _logger; - public EnvelopeService(IEnvelopeRepository repository, IStringLocalizer localizer, IMapper mapper, ILogger logger) - : base(repository, localizer, mapper) + public EnvelopeService(IEnvelopeRepository repository, IMapper mapper, ILogger logger) + : base(repository, mapper) { _logger = logger; } diff --git a/EnvelopeGenerator.Application/Services/EnvelopeTypeService.cs b/EnvelopeGenerator.Application/Services/EnvelopeTypeService.cs index c43a0cb8..d8b00ebc 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeTypeService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeTypeService.cs @@ -11,8 +11,8 @@ namespace EnvelopeGenerator.Application.Services { public class EnvelopeTypeService : BasicCRUDService, IEnvelopeTypeService { - public EnvelopeTypeService(IEnvelopeTypeRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + public EnvelopeTypeService(IEnvelopeTypeRepository repository, IMapper mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/ReceiverService.cs b/EnvelopeGenerator.Application/Services/ReceiverService.cs index 77a2d33d..f58727e4 100644 --- a/EnvelopeGenerator.Application/Services/ReceiverService.cs +++ b/EnvelopeGenerator.Application/Services/ReceiverService.cs @@ -13,7 +13,7 @@ namespace EnvelopeGenerator.Application.Services public class ReceiverService : BasicCRUDService, IReceiverService { public ReceiverService(IReceiverRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.Application/Services/UserReceiverService.cs b/EnvelopeGenerator.Application/Services/UserReceiverService.cs index 9fe3ee72..d241d5bf 100644 --- a/EnvelopeGenerator.Application/Services/UserReceiverService.cs +++ b/EnvelopeGenerator.Application/Services/UserReceiverService.cs @@ -12,7 +12,7 @@ namespace EnvelopeGenerator.Application.Services public class UserReceiverService : BasicCRUDService, IUserReceiverService { public UserReceiverService(IUserReceiverRepository repository, IStringLocalizer localizer, IMapper mapper) - : base(repository, localizer, mapper) + : base(repository, mapper) { } } diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs new file mode 100644 index 00000000..e5fe5722 --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs @@ -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 _logger; + private readonly IUserService _userService; + private readonly IDirectorySearchService _dirSearchService; + private readonly IStringLocalizer _localizer; + + public AuthController(ILogger logger, IUserService userService, IDirectorySearchService dirSearchService, IStringLocalizer 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 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 + { + 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 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); + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/WeatherForecastController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/WeatherForecastController.cs deleted file mode 100644 index 8fed5c8f..00000000 --- a/EnvelopeGenerator.GeneratorAPI/Controllers/WeatherForecastController.cs +++ /dev/null @@ -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 _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable 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(); - } - } -} diff --git a/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj b/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj index 0a1d16d1..a459a23f 100644 --- a/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj +++ b/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj @@ -9,10 +9,40 @@ + + + + + + + + + + + + ..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.API.dll + + + ..\..\WebCoreModules\DigitalData.Core.Application\bin\Debug\net7.0\DigitalData.Core.Application.dll + + + ..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.Contracts.dll + + + ..\..\WebCoreModules\DigitalData.Core.API\bin\Debug\net7.0\DigitalData.Core.DTO.dll + + + ..\..\WebCoreModules\DigitalData.Core.Infrastructure\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll + + + diff --git a/EnvelopeGenerator.GeneratorAPI/Program.cs b/EnvelopeGenerator.GeneratorAPI/Program.cs index 5db61a9a..e135f99e 100644 --- a/EnvelopeGenerator.GeneratorAPI/Program.cs +++ b/EnvelopeGenerator.GeneratorAPI/Program.cs @@ -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(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(); + +// LDAP +builder.ConfigureBySection(); +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(); diff --git a/EnvelopeGenerator.GeneratorAPI/WeatherForecast.cs b/EnvelopeGenerator.GeneratorAPI/WeatherForecast.cs deleted file mode 100644 index 87207a86..00000000 --- a/EnvelopeGenerator.GeneratorAPI/WeatherForecast.cs +++ /dev/null @@ -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; } - } -} diff --git a/EnvelopeGenerator.GeneratorAPI/appsettings.json b/EnvelopeGenerator.GeneratorAPI/appsettings.json index 10f68b8c..bd95de5c 100644 --- a/EnvelopeGenerator.GeneratorAPI/appsettings.json +++ b/EnvelopeGenerator.GeneratorAPI/appsettings.json @@ -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=*))" + } + } } diff --git a/EnvelopeGenerator.Infrastructure/EGDbContext.cs b/EnvelopeGenerator.Infrastructure/EGDbContext.cs index 1436cb82..dd008507 100644 --- a/EnvelopeGenerator.Infrastructure/EGDbContext.cs +++ b/EnvelopeGenerator.Infrastructure/EGDbContext.cs @@ -66,6 +66,9 @@ namespace DigitalData.UserManager.Infrastructure.Repositories modelBuilder.Entity().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS")); modelBuilder.Entity().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS")); + //configure model builder for user manager tables + modelBuilder.ConfigureUserManager(); + base.OnModelCreating(modelBuilder); } } diff --git a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj index 0c54eb88..d1ca8d73 100644 --- a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj +++ b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj @@ -32,6 +32,9 @@ ..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll + + ..\..\WebUserManager\DigitalData.UserManager.Infrastructure\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll +