Compare commits

33 Commits

Author SHA1 Message Date
Developer 02
3832351dd1 add Jenkinsfile
Some checks failed
AppStd/WorkFlow/pipeline/head There was a failure building this commit
2025-05-22 16:03:12 +02:00
Developer 02
99237cbecc refactor(IISProfile): Verzeichnis aktualisiert, um die Namenskonventionen für digitale Daten anzuwenden 2025-03-25 15:38:33 +01:00
Developer 02
cb2edffe91 chore: Separate IIS-Konfiguration für .Net 7 und 8 hinzufügen 2025-03-24 16:12:39 +01:00
Developer 02
4d3768248e chore: IIS-Veröffentlichungsprofile getrennt nach Framework hinzufügen 2025-03-13 12:06:56 +01:00
Developer 02
fb38bc1fd4 chore(API): Hinzufügen von .net 7-Unterstützung für API 2025-03-13 10:12:55 +01:00
Developer 02
10b557374d chore: Hinzufügen von .net 7-Unterstützung für Domäne, Infrastruktur und Anwendung 2025-03-13 10:04:40 +01:00
Developer 02
f266e6728f chore: Upgrade auf DigitalData.Auth.Client 1.3.3 2025-03-13 09:29:07 +01:00
Developer 02
3373fceef3 chore(API): Hochgestuft auf 1.1.0 2025-03-11 17:17:08 +01:00
Developer 02
f7eaa0f7de refactor: IssuerSigningKeyResolver wurde aktualisiert, um die Konfiguration über serviceProvider anstelle eines separaten öffentlichen Schlüssels zu ermöglichen. 2025-03-11 16:22:54 +01:00
Developer 02
d5b1ee41a0 fix: Arranged auth Authentication Scheme 2025-03-11 10:48:04 +01:00
Developer 02
c3f5d90b6a refactor(PlaceholderAuthController): Update auf Login nur über Body und ohne Cookie 2025-03-11 10:32:28 +01:00
Developer 02
753eb18b71 updated(AuthController): Aktualisiert, um als Platzhalter für auth api in swagger zu funktionieren.
- umbenennen PlaceholderAuthController
2025-03-11 10:17:27 +01:00
Developer 02
17d8373739 feat: JwtBearerEvents hinzugefügt, um Token aus Cookie oder Query-String lesen zu können 2025-03-11 09:36:32 +01:00
Developer 02
d6ccc10244 fix: Aktualisiertes Präfix für den Namen der NLog-Protokolldatei als workFlow.API 2025-03-10 15:47:58 +01:00
Developer 02
3dccf82710 fix: Aktualisiertes Präfix für den Namen der NLog-Protokolldatei als workFlow.API 2025-03-10 15:36:42 +01:00
Developer 02
c7d8b67ccb refactor Aktualisierte AuthPublicKey-Konfiguration, die getrennt von AuthClaimPrams konfiguriert werden kann, um sie als JWT-Barriere-Konfiguration zu verwenden 2025-03-10 13:53:15 +01:00
Developer 02
b76043fa24 chore: Aktualisierung von DigitalData.Core.Application und UserManager.Application auf 2.0.0
- Aktualisiert auf Dienste als aktuelle Core.Application
2025-03-10 10:53:40 +01:00
Developer 02
e28f4560d6 Refactor: Inline AuthHubClient Optionen durch Konfigurationsabschnitt ersetzen 2025-03-10 09:48:10 +01:00
Developer 02
97d5156bbb feat(auth): Integration von AuthHubClient und JWT-basierter Authentifizierung
- Abhängigkeit `DigitalData.Auth.Client` hinzugefügt
- `AuthHubClient` mit konfigurierbarem öffentlichen Schlüssel für Authentifizierung integriert
- Cookie-basierte Authentifizierung durch JWT-Bearer-Authentifizierung ersetzt
- Token-Validierung so konfiguriert, dass dynamisch auflösbare Signaturschlüssel verwendet werden
2025-03-07 16:10:10 +01:00
Developer 02
40cf8f3f10 chore: Konfigurierte Paket-ID, Version, Firma, Produkt und Titel 2024-10-29 14:50:18 +01:00
Developer 02
a325d07c6b refactor: AuthController-Methoden optimieren und Login-Logik verbessern
- AuthController aktualisiert, um eine klarere Struktur zu implementieren.
- Login-Methode vereinfacht, um die Benutzerauthentifizierung direkt zu behandeln.
- Neue LoginById-Methode für den Benutzerlogin über ID eingeführt.
- Fehlerprotokollierung und -behandlung in den Login-Methoden verbessert.
- Überflüssigen Code entfernt und Lesbarkeit verbessert.
- TODO für weitere Integration mit UserManager hinzugefügt.
2024-10-29 14:24:13 +01:00
Developer 02
69abd3afa2 feat(DIExtensions.AddAPIKeyAuth): if options.Key is null return true in default isValidKey function 2024-10-29 12:44:53 +01:00
Developer 02
cbdd6ee295 feat(APIKeyAuthOptions): Schlüsselattribut wird löschbar gemacht.
- isValidKey-Eintrag wird löschbar gemacht.
 - wenn der Schlüssel null ist und der X-API-Schlüssel nicht existiert, wird die Anfrage authirezred.
2024-10-29 12:23:10 +01:00
Developer 02
2c1abaaf32 feat(DisableAPIKeyAuth): Optionen als bool zu appsettings hinzugefügt. Konfiguriert mit app 2024-10-29 11:43:25 +01:00
Developer 02
8038ff74dd feat(Swagger): OpenApiInfo über appsettings konfiguriert. 2024-10-29 10:44:20 +01:00
Developer 02
edcf3781b7 feat(APIKeyAuthAttribute): Zu allen Controllern hinzugefügt 2024-10-29 10:21:10 +01:00
Developer 02
6ea053be36 feat(APIKeyAuthHeaderOpFilter): Implementierung der SwaggerGen.IOperationFilter-Schnittstelle, um das API-Schlüsselfeld hinzuzufügen.
- Eigenschaft swagger-description hinzugefügt
2024-10-29 10:00:06 +01:00
Developer 02
67a62d7311 feat(APIKeyAuthOptions): Datenmodell zur Konfiguration der Autorisierung mit API-Schlüssel erstellt.
- DI-Erweiterung hinzugefügt
2024-10-29 09:29:14 +01:00
Developer 02
e17875dad7 feat(API): Methode zur Injektion von Abhängigkeiten hinzugefügt, um API-Schlüssel-Filter hinzuzufügen 2024-10-28 16:58:50 +01:00
Developer 02
b460de4e37 feat: Attribut zur API-Schlüssel-Authentifizierung hinzufügen
- ApiKeyAuthAttribute hinzugefügt und mit ApiKeyAuthFilter verknüpft.
- Dieses Attribut wird verwendet, um die API-Schlüssel-Überprüfung in Controllern anzuwenden.
2024-10-28 16:48:58 +01:00
Developer 02
1ca336abe0 feat: API-Schlüssel-Authentifizierungsfilter implementieren
- ApiKeyAuthFilter hinzugefügt, um API-Schlüssel aus den Anforderungs-Headern zu validieren.
- Der Filter überprüft das Vorhandensein eines API-Schlüssels und dessen Gültigkeit, bevor die Anforderung autorisiert wird.
- Der Standardname des API-Schlüssel-Headers ist auf "X-API-Key" festgelegt.
2024-10-28 16:45:22 +01:00
Developer 02
6e4a575864 feat(API): NLogger hinzugefügt 2024-10-28 16:27:32 +01:00
Developer 02
d664adf000 refactor: Unnötige Verzeichnissuch-Cachinge-Prozesse entfernen 2024-10-28 16:13:55 +01:00
44 changed files with 796 additions and 472 deletions

View File

@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Filters;
namespace WorkFlow.API.Attributes
{
//TODO: move APIKeyAuthAttribute to Core.API
public class APIKeyAuthAttribute : ServiceFilterAttribute
{
public APIKeyAuthAttribute()
: base(typeof(APIKeyAuthFilter))
{
}
}
}

View File

@@ -1,154 +0,0 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using DigitalData.UserManager.Application.Contracts;
using DigitalData.UserManager.Application.DTOs.User;
using Microsoft.AspNetCore.Authorization;
using DigitalData.UserManager.Application;
using DigitalData.Core.Abstractions.Application;
using Microsoft.Extensions.Localization;
using DigitalData.Core.DTO;
using WorkFlow.API.Models;
namespace WorkFlow.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController(IUserService userService, IGroupOfUserService gouService, IDirectorySearchService directorySearchService, IStringLocalizer<Resource> localizer, ILogger<AuthController> logger) : ControllerBase
{
private readonly IUserService _userService = userService;
private readonly IGroupOfUserService _gouService = gouService;
private readonly IDirectorySearchService _dirSearchService = directorySearchService;
private readonly IStringLocalizer<Resource> _localizer = localizer;
private readonly ILogger<AuthController> _logger = logger;
[AllowAnonymous]
[HttpGet("check")]
public IActionResult CheckAuthentication()
{
try
{
return Ok(User.Identity?.IsAuthenticated ?? false);
}
catch(Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] Login login)
{
try
{
var username = string.Empty;
DataResult<UserReadDto>? uRes = null;
if(login.Username is not null && login.UserId is not null)
return BadRequest("Invalid request: either 'UserId' or 'Username' must be provided, but not both.");
else if(login.Username is not null)
username = login.Username;
else if(login.UserId is int userId)
{
uRes = await _userService.ReadByIdAsync(userId);
if (!uRes.IsSuccess || uRes.Data is null)
{
return Unauthorized(uRes);
}
}
else
return BadRequest("Invalid request: either 'UserId' or 'Username' must be provided, but not both.");
bool isValid = _dirSearchService.ValidateCredentials(username, login.Password);
if (!isValid)
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));
var gouMsg = await _gouService.HasGroup(username, "PM_USER", caseSensitive: false);
if (!gouMsg.IsSuccess)
return Unauthorized(Result.Fail().Message(_localizer[Key.UnauthorizedUser]));
//find the user
uRes ??= await _userService.ReadByUsernameAsync(username);
if (!uRes.IsSuccess || uRes.Data is null)
{
_logger.LogNotice(uRes.Notices);
return Unauthorized();
}
UserReadDto user = uRes.Data;
// Create claimsIdentity
var claimsIdentity = new ClaimsIdentity(user.ToClaimList(), 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, "{Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[Authorize]
[HttpGet("user")]
public async Task<IActionResult> GetUserWithClaims()
{
try
{
// Extract the username from the Name claim.
string? username = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
if (string.IsNullOrEmpty(username))
return Unauthorized();
return await _userService.ReadByUsernameAsync(username)
.ThenAsync(Ok, IActionResult (m, n) =>
{
_logger.LogNotice(n);
return NotFound(Result.Fail().Message(_localizer[Key.UserNotFound]));
});
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", 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, "{Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}

View File

@@ -1,16 +1,20 @@
using DigitalData.Core.API; using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Config; using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ConfigController : CRUDControllerBaseWithErrorHandling<IConfigService, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>
{ {
[Route("api/[controller]")] public ConfigController(ILogger<ConfigController> logger, IConfigService service) : base(logger, service)
[ApiController]
[Authorize]
public class ConfigController(ILogger<ConfigController> logger, IConfigService service) : CRUDControllerBaseWithErrorHandling<IConfigService, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>(logger, service)
{ {
} }
} }

View File

@@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims; using System.Security.Claims;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers
{ {
[APIKeyAuth]
public static class ControllerExtensions public static class ControllerExtensions
{ {
public static bool TryGetUserId(this ControllerBase controller, out int? id) public static bool TryGetUserId(this ControllerBase controller, out int? id)

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WorkFlow.API.Models;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers;
//TODO: implement up-to-date AuthController in UserManager
[APIKeyAuth]
[Route("api/Auth")]
[ApiController]
[Tags("Auth")]
public class PlaceholderAuthController : ControllerBase
{
[HttpPost]
public IActionResult CreateTokenViaBody([FromBody] Login login)
{
throw new NotImplementedException();
}
[HttpGet("check")]
[Authorize]
public IActionResult Check() => Ok();
}

View File

@@ -1,16 +1,20 @@
using DigitalData.Core.API; using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Profile; using WorkFlow.Application.DTO.Profile;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileController : CRUDControllerBaseWithErrorHandling<IProfileService, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>
{ {
[Route("api/[controller]")] public ProfileController(ILogger<ProfileController> logger, IProfileService service) : base(logger, service)
[ApiController]
[Authorize]
public class ProfileController(ILogger<ProfileController> logger, IProfileService service) : CRUDControllerBaseWithErrorHandling<IProfileService, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>(logger, service)
{ {
} }
} }

View File

@@ -2,117 +2,125 @@
using DigitalData.Core.DTO; using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF; using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTFService, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
{ {
[Route("api/[controller]")] private readonly ILogger<ProfileControlsTFController> logger;
[ApiController]
[Authorize] public ProfileControlsTFController(ILogger<ProfileControlsTFController> logger, IProfileControlsTFService service) : base(logger, service)
public class ProfileControlsTFController(ILogger<ProfileControlsTFController> logger, IProfileControlsTFService service) : CRUDControllerBase<IProfileControlsTFService, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(logger, service)
{ {
[NonAction] this.logger = logger;
public override Task<IActionResult> GetAll() => base.GetAll(); }
[NonAction] [NonAction]
public override Task<IActionResult> Update(ProfileControlsTFUpdateDto updateDto) => base.Update(updateDto); public override Task<IActionResult> GetAll() => base.GetAll();
[HttpGet] [NonAction]
public async Task<IActionResult> GetAsync( public override Task<IActionResult> Update(ProfileControlsTFUpdateDto updateDto) => base.Update(updateDto);
bool withProfile = true, bool withUser = false,
int? profileId = null, int? objId = null, bool? profileActive = null) [HttpGet]
public async Task<IActionResult> GetAsync(
bool withProfile = true, bool withUser = false,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
try
{ {
try if (!this.TryGetUserId(out int? id))
{ {
if (!this.TryGetUserId(out int? id)) logger.LogError("Authorization failed: User ID claim not found.");
{ return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadAsync(
withProfile: withProfile, withUser: withUser,
userId: id,
profileId: profileId, objId: objId, profileActive: profileActive)
.ThenAsync(
Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(),
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
} }
catch (Exception ex) else if (id is null)
{ {
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message); logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred."); return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
} }
}
[HttpPost] return await _service.ReadAsync(
public override async Task<IActionResult> Create([FromBody] ProfileControlsTFCreateDto createDto) withProfile: withProfile, withUser: withUser,
{ userId: id,
try profileId: profileId, objId: objId, profileActive: profileActive)
{ .ThenAsync(
if (!this.TryGetUserId(out int? id)) Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(),
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id)
return Unauthorized();
return await base.Create(createDto);
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpDelete]
public override async Task<IActionResult> Delete([FromRoute] int id)
{
try
{
if (!this.TryGetUserId(out int? userId))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),
Fail: IActionResult (msg, ntc) => Fail: IActionResult (msg, ntc) =>
{ {
_logger.LogNotice(ntc); logger.LogNotice(ntc);
return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError); return NotFound();
}); });
} }
catch (Exception ex) catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpPost]
public override async Task<IActionResult> Create([FromBody] ProfileControlsTFCreateDto createDto)
{
try
{
if (!this.TryGetUserId(out int? id))
{ {
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred."); return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
} }
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id)
return Unauthorized();
return await base.Create(createDto);
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpDelete]
public override async Task<IActionResult> Delete([FromRoute] int id)
{
try
{
if (!this.TryGetUserId(out int? userId))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),
Fail: IActionResult (msg, ntc) =>
{
_logger.LogNotice(ntc);
return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError);
});
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
} }
} }
} }

View File

@@ -2,17 +2,26 @@
using DigitalData.Core.DTO; using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState; using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers
{ {
[APIKeyAuth]
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class ProfileObjStateController(ILogger<ProfileObjStateController> logger, IProfileObjStateService service) : CRUDControllerBaseWithErrorHandling<IProfileObjStateService, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>(logger, service) public class ProfileObjStateController : CRUDControllerBaseWithErrorHandling<IProfileObjStateService, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>
{ {
private readonly ILogger<ProfileObjStateController> logger;
public ProfileObjStateController(ILogger<ProfileObjStateController> logger, IProfileObjStateService service) : base(logger, service)
{
this.logger = logger;
}
[NonAction] [NonAction]
public override Task<IActionResult> GetAll() => base.GetAll(); public override Task<IActionResult> GetAll() => base.GetAll();

View File

@@ -1,16 +1,20 @@
using DigitalData.Core.API; using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.State; using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class StateController : CRUDControllerBaseWithErrorHandling<IStateService, StateCreateDto, StateDto, StateUpdateDto, State, int>
{ {
[Route("api/[controller]")] public StateController(ILogger<StateController> logger, IStateService service) : base(logger, service)
[ApiController]
[Authorize]
public class StateController(ILogger<StateController> logger, IStateService service) : CRUDControllerBaseWithErrorHandling<IStateService, StateCreateDto, StateDto, StateUpdateDto, State, int>(logger, service)
{ {
} }
} }

View File

@@ -2,43 +2,53 @@
using DigitalData.UserManager.Application.Contracts; using DigitalData.UserManager.Application.Contracts;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class UserController : ControllerBase
{ {
[Route("api/[controller]")] private readonly ILogger<UserController> logger;
[ApiController] private readonly IUserService userService;
[Authorize]
public class UserController(ILogger<UserController> logger, IUserService userService) : ControllerBase public UserController(ILogger<UserController> logger, IUserService userService)
{ {
[HttpGet] this.logger = logger;
public async Task<IActionResult> GetAsync() this.userService = userService;
}
[HttpGet]
public async Task<IActionResult> GetAsync()
{
try
{ {
try if (!this.TryGetUserId(out int? id))
{ {
if (!this.TryGetUserId(out int? id)) logger.LogError("Authorization failed: User ID claim not found.");
{ return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if(id is int id_int)
return await userService.ReadByIdAsync(id_int).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
else
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
} }
catch (Exception ex) else if(id is int id_int)
return await userService.ReadByIdAsync(id_int).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
else
{ {
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message); logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred."); return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
} }
} }
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
} }
} }

View File

@@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using WorkFlow.API.Filters;
using WorkFlow.API.Models;
namespace WorkFlow.API.Extensions
{
public static class DIExtensions
{
public static IServiceCollection AddAPIKeyAuth(this IServiceCollection services, Func<string?, bool> isValidKey, string headerName = "X-API-Key")
=> services.AddSingleton<APIKeyAuthFilter>(provider => new(isValidKey: isValidKey, headerName: headerName));
public static IServiceCollection AddAPIKeyAuth(this IServiceCollection services, APIKeyAuthOptions options, bool configureOptions = true)
{
if(configureOptions)
services.TryAddSingleton(Options.Create(options));
return services.AddAPIKeyAuth(isValidKey: key => options.Key is null || options.Key == key, headerName: options.HeaderName);
}
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using WorkFlow.API.Models;
namespace WorkFlow.API.Filters;
public class APIKeyAuthHeaderOpFilter : IOperationFilter
{
private readonly APIKeyAuthOptions apiKeyAuthOptions;
private readonly IWebHostEnvironment environment;
public APIKeyAuthHeaderOpFilter(IOptions<APIKeyAuthOptions> options, IWebHostEnvironment environment)
{
this.environment = environment;
apiKeyAuthOptions = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var param = new OpenApiParameter
{
Name = apiKeyAuthOptions.HeaderName,
In = ParameterLocation.Header,
Required = true,
AllowEmptyValue = false,
Schema = new OpenApiSchema
{
Type = "string"
}
};
if(environment.IsDevelopment())
param.Schema.Default = new OpenApiString(apiKeyAuthOptions.Key);
if (apiKeyAuthOptions.SwaggerDescription is not null)
param.Description = apiKeyAuthOptions.SwaggerDescription;
operation.Parameters.Add(param);
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace WorkFlow.API.Filters;
public class APIKeyAuthFilter : IAuthorizationFilter
{
private readonly Func<string?, bool> isValidKey;
private readonly string headerName;
public APIKeyAuthFilter(Func<string?, bool> isValidKey, string headerName = "X-API-Key")
{
this.isValidKey = isValidKey;
this.headerName = headerName;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!isValidKey(context.HttpContext.Request.Headers[headerName]))
context.Result = new UnauthorizedResult();
}
}

10
WorkFlow.API/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,10 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'dotnet build'
}
}
}
}

View File

@@ -0,0 +1,18 @@
namespace WorkFlow.API;
public class LazyServiceProvider : IServiceProvider
{
private Lazy<IServiceProvider>? _serviceProvider;
public Func<IServiceProvider> Factory
{
set => _serviceProvider = new(value);
}
public object? GetService(Type serviceType)
{
if (_serviceProvider is null)
throw new InvalidOperationException("GetService cannot be called before _serviceProvider is set.");
return _serviceProvider.Value.GetService(serviceType);
}
}

View File

@@ -0,0 +1,11 @@
namespace WorkFlow.API.Models
{
public class APIKeyAuthOptions
{
public string? Key { get; init; } = null;
public string HeaderName { get; init; } = "X-API-Key";
public string? SwaggerDescription { get; init; } = null;
}
}

View File

@@ -0,0 +1,12 @@
namespace WorkFlow.API.Models;
public class AuthTokenKeys
{
public string Cookie { get; init; } = "AuthToken";
public string QueryString { get; init; } = "AuthToken";
public string Issuer { get; init; } = "auth.digitaldata.works";
public string Audience { get; init; } = "work-flow.digitaldata.works";
}

View File

@@ -5,13 +5,14 @@ namespace WorkFlow.API.Models
{ {
public static class ModelExtensions public static class ModelExtensions
{ {
public static List<Claim> ToClaimList(this UserReadDto user) => [ public static List<Claim> ToClaimList(this UserReadDto user) => new()
{
new (ClaimTypes.NameIdentifier, user.Id.ToString()), new (ClaimTypes.NameIdentifier, user.Id.ToString()),
new (ClaimTypes.Name, user.Username), new (ClaimTypes.Name, user.Username),
new (ClaimTypes.Surname, user.Name ?? ""), new (ClaimTypes.Surname, user.Name ?? ""),
new (ClaimTypes.GivenName, user.Prename ?? ""), new (ClaimTypes.GivenName, user.Prename ?? ""),
new (ClaimTypes.Email, user.Email ?? "") new (ClaimTypes.Email, user.Email ?? "")
]; };
public static Dictionary<string, object> ToClaimDictionary(this UserReadDto user) => user.ToClaimList().ToDictionary(claim => claim.Type, claim => (object) claim.Value); public static Dictionary<string, object> ToClaimDictionary(this UserReadDto user) => user.ToClaimList().ToDictionary(claim => claim.Type, claim => (object) claim.Value);
} }

View File

@@ -2,64 +2,160 @@ using WorkFlow.Application;
using DigitalData.UserManager.Application; using DigitalData.UserManager.Application;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WorkFlow.Infrastructure; using WorkFlow.Infrastructure;
using Microsoft.AspNetCore.Authentication.Cookies;
using DigitalData.Core.API; using DigitalData.Core.API;
using DigitalData.Core.Application; using DigitalData.Core.Application;
using DigitalData.UserManager.Application.DTOs.User; using DigitalData.UserManager.Application.DTOs.User;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using WorkFlow.API.Models; using WorkFlow.API.Models;
using System.Security.Claims; using NLog;
using NLog.Web;
using WorkFlow.API.Extensions;
using WorkFlow.API.Filters;
using Microsoft.OpenApi.Models;
using DigitalData.Auth.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using WorkFlow.API;
using Microsoft.Extensions.Options;
using DigitalData.Core.Abstractions.Security;
var builder = WebApplication.CreateBuilder(args); var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
var config = builder.Configuration; logger.Info("Logging initialized.");
// Add services to the container. try
var cnn_str = config.GetConnectionString("Default") ?? throw new ("Default connection string not found.");
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
builder.Services.AddWorkFlow().AddUserManager<WFDBContext>();
builder.Services.AddCookieBasedLocalizer();
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService();
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
{ {
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object) var builder = WebApplication.CreateBuilder(args);
}); var config = builder.Configuration;
builder.Services.AddControllers(); // Add NLogger
builder.Logging.ClearProviders();
builder.Host.UseNLog();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) // Add services to the container
.AddCookie(options => var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found.");
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
builder.Services.AddWorkFlow().AddUserManager<WFDBContext>();
builder.Services.AddCookieBasedLocalizer();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
{ {
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
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";
options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // timeout.
options.SlidingExpiration = true; //refreshes the expiration time on each request.
options.Cookie.Name = "AuthSession";
}); });
builder.Services.AddEndpointsApiExplorer(); bool disableAPIKeyAuth = config.GetValue<bool>("DisableAPIKeyAuth") && builder.IsDevOrDiP();
builder.Services.AddSwaggerGen(); if (disableAPIKeyAuth)
builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions());
else
if (config.GetSection("APIKeyAuth").Get<APIKeyAuthOptions>() is APIKeyAuthOptions options)
builder.Services.AddAPIKeyAuth(options);
else
throw new("The API Key Authorization configuration is not available in the app settings, even though the app is not in development or DiP mode and API Key Authorization is not disabled.");
var app = builder.Build(); var lazyProvider = new LazyServiceProvider();
// Configure the HTTP request pipeline. builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
if (app.IsDevOrDiP() && app.Configuration.GetValue<bool>("EnableSwagger"))
{ builder.Services.AddControllers();
app.UseSwagger();
app.UseSwaggerUI(); var authTokenKeys = config.GetSection(nameof(AuthTokenKeys)).Get<AuthTokenKeys>() ?? new();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(setupAct =>
{
setupAct.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
setupAct.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
if (!disableAPIKeyAuth)
setupAct.OperationFilter<APIKeyAuthHeaderOpFilter>();
if (config.GetSection("OpenApiInfo").Get<OpenApiInfo>() is OpenApiInfo openApiInfo)
setupAct.SwaggerDoc(openApiInfo?.Version ?? "v1", openApiInfo);
});
var app = builder.Build();
lazyProvider.Factory = () => app.Services;
// Configure the HTTP request pipeline.
if (app.IsDevOrDiP() && app.Configuration.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookieBasedLocalizer("de-DE");
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
logger.Error(ex, "Stopped program because of exception.");
throw;
} }
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookieBasedLocalizer("de-DE");
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>4fb33592-ef0d-47c3-9cde-03b2ef12be00</ProjectGuid>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\workFLOW\API\net7\$(Version)\workFLOW.API.zip</DesktopBuildPackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>WorkFlow.API</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>4fb33592-ef0d-47c3-9cde-03b2ef12be00</ProjectGuid>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\workFLOW\API\net8\$(Version)\workFLOW.API.zip</DesktopBuildPackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>WorkFlow.API</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -12,7 +12,7 @@
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "http://localhost:5130", "applicationUrl": "http://localhost:5130",
"environmentVariables": { "environmentVariables": {
@@ -22,7 +22,7 @@
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "https://localhost:7120;http://localhost:5130", "applicationUrl": "https://localhost:7120;http://localhost:5130",
"environmentVariables": { "environmentVariables": {
@@ -31,7 +31,7 @@
}, },
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

8
WorkFlow.API/WFKey.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace WorkFlow.API
{
public static class WFKey
{
public static readonly string WrongPassword = nameof(WrongPassword);
public static readonly string UserNotFoundOrWrongPassword = nameof(UserNotFoundOrWrongPassword);
}
}

View File

@@ -1,13 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<PackageId>WorkFlow.API</PackageId>
<Version>1.1.0</Version>
<Company>Digital Data GmbH</Company>
<Product>WorkFlow.API</Product>
<Title>WorkFlow.API</Title>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Core.API" Version="2.0.0" /> <_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfile - Copy.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet7.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet8.pubxml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.3" />
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup> </ItemGroup>

View File

@@ -1,12 +1,56 @@
{ {
"DiPMode": true, "DiPMode": true,
"EnableSwagger": true, "EnableSwagger": true,
"DisableAPIKeyAuth": false,
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\LogFiles\\Digital Data\\workFlow.API",
"logFileNamePrefix": "${shortdate}-workFlow.API"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},
// Trace, Debug, Info, Warn, Error and *Fatal*
"rules": [
{
"logger": "*",
"minLevel": "Info",
"maxLevel": "Warn",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Error",
"writeTo": "errorLogs"
},
{
"logger": "*",
"level": "Fatal",
"writeTo": "criticalLogs"
}
]
},
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;" "Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
@@ -19,5 +63,29 @@
"User": "(&(objectClass=user)(sAMAccountName=*))", "User": "(&(objectClass=user)(sAMAccountName=*))",
"Group": "(&(objectClass=group) (samAccountName=*))" "Group": "(&(objectClass=group) (samAccountName=*))"
} }
},
"APIKeyAuth": {
"Key": "ULbcOUiAXAoCXPviyCGtObZUGnrCHNgDmtNbQNpq5MOhB0EFQn18dObdQ93INNy8xIcnOPMJfEHqOotllELVrJ2R5AjqOfQszT2j00w215GanD3UiJGwFhwmdoNFsmNj",
"HeaderName": "X-API-Key",
"SwaggerDescription": "Required header for API key authentication. Enter a valid API key."
},
"OpenApiInfo": {
"Title": "WorkFlow API",
"Contact": {
"Email": "info-flow@digitaldata.works",
"Name": "Digital Data GmbH",
"Url": "https://digitaldata.works/"
}
},
"AuthClientParams": {
"Url": "https://localhost:7192/auth-hub",
"PublicKeys": [
{
"Issuer": "auth.digitaldata.works",
"Audience": "work-flow.digitaldata.works",
"Content": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QCd7dH/xOUITFZbitMa/xnh8a0LyL6ZBvSRAwkI9ceplTRSHJXoM1oB+xtjWE1kOuHVLe941Tm03szS4+/rHIm0Ejva/KKlv7sPFAHE/pWuoPS303vOHgI4HAFcuwywA8CghUWzaaK5LU/Hl8srWwxBHv5hKIUjJFJygeAIENvFOZ1gFbB3MPEC99PiPOwAmfl4tMQUmSsFyspl/RWVi7bTv26ZE+m3KPcWppmvmYjXlSitxRaySxnfFvpca/qWfd/uUUg2KWKtpAwWVkqr0qD9v3TyKSgHoGDsrFpwSx8qufUJSinmZ1u/0iKl6TXeHubYS4C4SUSVjOWXymI2ZQIDAQAB-----END PUBLIC KEY-----"
}
],
"RetryDelay": "00:00:05"
} }
} }

View File

@@ -4,7 +4,7 @@ using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts namespace WorkFlow.Application.Contracts
{ {
public interface IConfigService : ICRUDService<ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int> public interface IConfigService : ICRUDService<ConfigCreateDto, ConfigDto, Config, int>
{ {
} }
} }

View File

@@ -5,7 +5,7 @@ using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts namespace WorkFlow.Application.Contracts
{ {
public interface IProfileControlsTFService : ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int> public interface IProfileControlsTFService : ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>
{ {
Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync( Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false, bool withProfile = true, bool withUser = false,

View File

@@ -5,7 +5,7 @@ using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts namespace WorkFlow.Application.Contracts
{ {
public interface IProfileObjStateService : ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int> public interface IProfileObjStateService : ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>
{ {
Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync( Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync(
bool withProfile = true, bool withUser = true, bool withState = true, bool withProfile = true, bool withUser = true, bool withState = true,

View File

@@ -4,7 +4,7 @@ using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts namespace WorkFlow.Application.Contracts
{ {
public interface IProfileService : ICRUDService<ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int> public interface IProfileService : ICRUDService<ProfileCreateDto, ProfileDto, Profile, int>
{ {
} }
} }

View File

@@ -4,7 +4,7 @@ using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts namespace WorkFlow.Application.Contracts
{ {
public interface IStateService : ICRUDService<StateCreateDto, StateDto, StateUpdateDto, State, int> public interface IStateService : ICRUDService<StateCreateDto, StateDto, State, int>
{ {
} }
} }

View File

@@ -6,11 +6,12 @@ using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services;
public class ConfigService : CRUDService<IConfigRepository, ConfigCreateDto, ConfigDto, Config, int>,
IConfigService, ICRUDService<ConfigCreateDto, ConfigDto, Config, int>
{ {
public class ConfigService(IConfigRepository repository, IMapper mapper) public ConfigService(IConfigRepository repository, IMapper mapper) : base(repository, mapper)
: CRUDService<IConfigRepository, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>(repository, mapper),
IConfigService, ICRUDService<ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>
{ {
} }
} }

View File

@@ -9,10 +9,13 @@ using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services
{ {
public class ProfileControlsTFService(IProfileControlsTFRepository repository, IMapper mapper) public class ProfileControlsTFService : CRUDService<IProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>,
: CRUDService<IProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(repository, mapper), IProfileControlsTFService, ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>
IProfileControlsTFService, ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
{ {
public ProfileControlsTFService(IProfileControlsTFRepository repository, IMapper mapper) : base(repository, mapper)
{
}
public async Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync( public async Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false, bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null, int? userId = null, string? username = null,

View File

@@ -7,26 +7,28 @@ using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services;
public class ProfileObjStateService : CRUDService<IProfileObjStateRepository, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>,
IProfileObjStateService, ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>
{ {
public class ProfileObjStateService(IProfileObjStateRepository repository, IMapper mapper) public ProfileObjStateService(IProfileObjStateRepository repository, IMapper mapper) : base(repository, mapper)
: CRUDService<IProfileObjStateRepository, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>(repository, mapper),
IProfileObjStateService, ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>
{ {
public async Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync( }
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
var pos_list = await _repository.ReadAsync(
isReadonly: true,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive);
var post_dto_list = _mapper.Map<IEnumerable<ProfileObjStateDto>>(pos_list); public async Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync(
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
var pos_list = await _repository.ReadAsync(
isReadonly: true,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive);
return Result.Success(post_dto_list); var post_dto_list = _mapper.Map<IEnumerable<ProfileObjStateDto>>(pos_list);
}
return Result.Success(post_dto_list);
} }
} }

View File

@@ -5,11 +5,12 @@ using WorkFlow.Application.DTO.Profile;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services;
public class ProfileService : CRUDService<IProfileRepository, ProfileCreateDto, ProfileDto, Profile, int>,
IProfileService, ICRUDService<ProfileCreateDto, ProfileDto, Profile, int>
{ {
public class ProfileService(IProfileRepository repository, AutoMapper.IMapper mapper) public ProfileService(IProfileRepository repository, AutoMapper.IMapper mapper) : base(repository, mapper)
: CRUDService<IProfileRepository, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>(repository, mapper),
IProfileService, ICRUDService<ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>
{ {
} }
} }

View File

@@ -6,11 +6,12 @@ using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services;
public class StateService : CRUDService<IStateRepository, StateCreateDto, StateDto, State, int>,
IStateService, ICRUDService<StateCreateDto, StateDto, State, int>
{ {
public class StateService(IStateRepository repository, IMapper mapper) public StateService(IStateRepository repository, IMapper mapper) : base(repository, mapper)
: CRUDService<IStateRepository, StateCreateDto, StateDto, StateUpdateDto, State, int>(repository, mapper),
IStateService, ICRUDService<StateCreateDto, StateDto, StateUpdateDto, State, int>
{ {
} }
} }

View File

@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Core.Application" Version="2.0.0" /> <PackageReference Include="DigitalData.Core.Application" Version="3.2.0" />
<PackageReference Include="UserManager.Application" Version="2.0.0" /> <PackageReference Include="UserManager.Application" Version="3.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="UserManager.Domain" Version="2.0.0" /> <PackageReference Include="UserManager.Domain" Version="3.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -6,7 +6,10 @@ using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories namespace WorkFlow.Infrastructure.Repositories
{ {
//TODO: Make the db context type generic so that it can be used by other projects with different db contexts. //TODO: Make the db context type generic so that it can be used by other projects with different db contexts.
public class ConfigRepository(WFDBContext dbContext) : CRUDRepository<Config, int, WFDBContext>(dbContext, dbContext.Configs), IConfigRepository, ICRUDRepository<Config, int> public class ConfigRepository : CRUDRepository<Config, int, WFDBContext>, IConfigRepository, ICRUDRepository<Config, int>
{ {
public ConfigRepository(WFDBContext dbContext) : base(dbContext, dbContext.Configs)
{
}
} }
} }

View File

@@ -6,8 +6,12 @@ using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories namespace WorkFlow.Infrastructure.Repositories
{ {
public class ProfileControlsTFRepository(WFDBContext dbContext) : CRUDRepository<ProfileControlsTF, int, WFDBContext>(dbContext, dbContext.ProfileControlsTFs), IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int> public class ProfileControlsTFRepository : CRUDRepository<ProfileControlsTF, int, WFDBContext>, IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int>
{ {
public ProfileControlsTFRepository(WFDBContext dbContext) : base(dbContext, dbContext.ProfileControlsTFs)
{
}
protected override IQueryable<ProfileControlsTF> ReadOnly() => base.ReadOnly().Include(pctf => pctf.Profile).Include(pctf => pctf.User); protected override IQueryable<ProfileControlsTF> ReadOnly() => base.ReadOnly().Include(pctf => pctf.Profile).Include(pctf => pctf.User);
protected IQueryable<ProfileControlsTF> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, int? profileId = null, int? userId = null, string? username = null, int? objId = null, bool? profileActive = null) protected IQueryable<ProfileControlsTF> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, int? profileId = null, int? userId = null, string? username = null, int? objId = null, bool? profileActive = null)

View File

@@ -4,56 +4,59 @@ using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories namespace WorkFlow.Infrastructure.Repositories;
public class ProfileObjStateRepository : CRUDRepository<ProfileObjState, int, WFDBContext>, IProfileObjStateRepository, ICRUDRepository<ProfileObjState, int>
{ {
public class ProfileObjStateRepository(WFDBContext dbContext) : CRUDRepository<ProfileObjState, int, WFDBContext>(dbContext, dbContext.ProfileObjStates), IProfileObjStateRepository, ICRUDRepository<ProfileObjState, int> public ProfileObjStateRepository(WFDBContext dbContext) : base(dbContext, dbContext.ProfileObjStates)
{ {
protected override IQueryable<ProfileObjState> ReadOnly() => base.ReadOnly().Include(pos => pos.Profile).Include(pos => pos.State);
protected IQueryable<ProfileObjState> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, bool withState = true, int? profileId = null, int? userId = null, string? username = null, int? stateId = null, int? objId = null, bool? profileActive = null)
{
var query = isReadonly ? _dbSet.AsNoTracking() : _dbSet.AsQueryable();
if (withProfile)
query = query.Include(pctf => pctf.Profile);
if (withUser)
query = query.Include(pctf => pctf.User);
if (withState)
query = query.Include(pctf => pctf.State);
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
if (stateId is null)
query = query.Where(pctf => pctf.State!.Id == stateId);
if (objId is not null)
query = query.Where(pctf => pctf.ObjId == objId);
if (profileActive is not null)
query = query.Where(pctf => pctf.Profile!.Active == profileActive);
return query;
}
public async Task<IEnumerable<ProfileObjState>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(
isReadonly: isReadonly,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive)
.ToListAsync();
} }
protected override IQueryable<ProfileObjState> ReadOnly() => base.ReadOnly().Include(pos => pos.Profile).Include(pos => pos.State);
protected IQueryable<ProfileObjState> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, bool withState = true, int? profileId = null, int? userId = null, string? username = null, int? stateId = null, int? objId = null, bool? profileActive = null)
{
var query = isReadonly ? _dbSet.AsNoTracking() : _dbSet.AsQueryable();
if (withProfile)
query = query.Include(pctf => pctf.Profile);
if (withUser)
query = query.Include(pctf => pctf.User);
if (withState)
query = query.Include(pctf => pctf.State);
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
if (stateId is null)
query = query.Where(pctf => pctf.State!.Id == stateId);
if (objId is not null)
query = query.Where(pctf => pctf.ObjId == objId);
if (profileActive is not null)
query = query.Where(pctf => pctf.Profile!.Active == profileActive);
return query;
}
public async Task<IEnumerable<ProfileObjState>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(
isReadonly: isReadonly,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive)
.ToListAsync();
} }

View File

@@ -3,9 +3,11 @@ using DigitalData.Core.Infrastructure;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories namespace WorkFlow.Infrastructure.Repositories;
public class ProfileRepository : CRUDRepository<Profile, int, WFDBContext>, IProfileRepository, ICRUDRepository<Profile, int>
{ {
public class ProfileRepository(WFDBContext dbContext) : CRUDRepository<Profile, int, WFDBContext>(dbContext, dbContext.Profiles), IProfileRepository, ICRUDRepository<Profile, int> public ProfileRepository(WFDBContext dbContext) : base(dbContext, dbContext.Profiles)
{ {
} }
} }

View File

@@ -2,9 +2,12 @@
using DigitalData.Core.Infrastructure; using DigitalData.Core.Infrastructure;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
namespace WorkFlow.Infrastructure.Repositories;
public class StateRepository : CRUDRepository<State, int, WFDBContext>, IStateRepository, ICRUDRepository<State, int>
{ {
public class StateRepository(WFDBContext dbContext) : CRUDRepository<State, int, WFDBContext>(dbContext, dbContext.States), IStateRepository, ICRUDRepository<State, int> public StateRepository(WFDBContext dbContext) : base(dbContext, dbContext.States)
{ {
} }
} }

View File

@@ -4,38 +4,43 @@ using DigitalData.UserManager.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure namespace WorkFlow.Infrastructure;
public class WFDBContext : DbContext, IUserManagerDbContext
{ {
public class WFDBContext(DbContextOptions options) : DbContext(options), IUserManagerDbContext public DbSet<Config> Configs { get; set; }
public DbSet<ProfileControlsTF> ProfileControlsTFs { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<ProfileObjState> ProfileObjStates { get; set; }
public DbSet<State> States { get; set; }
public DbSet<GroupOfUser> GroupOfUsers { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<ModuleOfUser> ModuleOfUsers { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserRep> UserReps { get; set; }
public DbSet<ClientUser> ClientUsers { get; set; }
public WFDBContext(DbContextOptions options) : base(options)
{ {
public DbSet<Config> Configs { get; set; } }
public DbSet<ProfileControlsTF> ProfileControlsTFs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//configure model builder for user manager tables
modelBuilder.ConfigureUserManager();
public DbSet<Profile> Profiles { get; set; } base.OnModelCreating(modelBuilder);
public DbSet<ProfileObjState> ProfileObjStates { get; set; }
public DbSet<State> States { get; set; }
public DbSet<GroupOfUser> GroupOfUsers { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<ModuleOfUser> ModuleOfUsers { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserRep> UserReps { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//configure model builder for user manager tables
modelBuilder.ConfigureUserManager();
base.OnModelCreating(modelBuilder);
}
} }
} }

View File

@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.0.0" /> <PackageReference Include="DigitalData.Core.Infrastructure" Version="2.0.0" />
<PackageReference Include="UserManager.Infrastructure" Version="2.0.0" /> <PackageReference Include="UserManager.Infrastructure" Version="3.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>