Compare commits

...

27 Commits

Author SHA1 Message Date
Developer 02
65ad9e6da0 feat: Cookie-basierte Authentifizierung zur Anwendung hinzufügen
- `CookieAuthenticationDefaults.AuthenticationScheme` zur Benutzerauthentifizierung integriert.
- Cookie-Einstellungen konfiguriert, um die Sicherheit zu erhöhen:
  - `HttpOnly`-Flag gesetzt, um den Zugriff von clientseitigen Skripten zu verhindern.
  - `SecurePolicy` so eingestellt, dass Cookies nur über HTTPS-Anfragen gesendet werden.
  - `SameSite` auf `Strict` gesetzt, um CSRF-Angriffe zu mindern.
- Benutzerdefinierte Anmelde-(`/api/auth/login`) und Abmeldepfade (`/api/auth/logout`) definiert.
2024-10-25 01:45:17 +02:00
Developer 02
0ef327a059 feat: ProfileObjStateController für verbesserte CRUD-Funktionalität aktualisiert
- `GetAsync`-Methode mit zusätzlichen Filteroptionen für Profil-, Benutzer- und Zustandsdetails erweitert.
- Verbesserte Autorisierungsprüfungen mit detaillierter Fehlerprotokollierung bei fehlenden oder ungültigen Benutzer-ID-Ansprüchen.
- Identitätsprüfung in den Create- und Delete-Methoden hinzugefügt, um unbefugten Zugriff zu verhindern.
- Fehlerbehandlung und Antwort verfeinert für robustere serverseitige Verarbeitung.
2024-10-25 01:43:14 +02:00
Developer 02
2a9e0a8f17 feat(ProfileControlsTFController): HttpDelete-Attribut zur Delete-Methode hinzugefügt 2024-10-25 01:39:32 +02:00
Developer 02
d6aac0b400 refactor: ProfileControlsTFController aktualisiert, um CRUD-Operationen zu verbessern
- Create-, Update- und Delete-Methoden verfeinert, um eine bessere Validierung der Benutzeridentität zu gewährleisten
- Autorisierungsprüfungen für benutzerbezogene Operationen basierend auf Claims hinzugefügt
- Verbesserte Fehlerbehandlung und Protokollierung für detaillierteres Feedback
- Fehlerbehandlungs-Basisklasse entfernt, Übergang zu direkten CRUD-Methoden
2024-10-25 01:38:16 +02:00
Developer 02
6d25f8d3bd refactor: ProfileControlsTFController mit benutzerdefinierter GetAsync-Methode erweitert
- Neue GetAsync-Methode hinzugefügt, um komplexe Filter mit optionalen Parametern zu unterstützen
- Verbesserte Fehlerprotokollierung und Validierung für die Extraktion der Benutzeridentität
- Benutzer-ID in den Service-Schichtenoperationen integriert
- Basismethode GetAll entfernt, um eine bessere Kontrolle über das Datenabrufen zu gewährleisten
2024-10-25 01:07:59 +02:00
Developer 02
eb45c6aefa feat(UserConroller): Added method to get authorized user. 2024-10-25 00:39:05 +02:00
Developer 02
79167a7f9d refactor(AuthController): Verwendeter Primär-Konstruktor 2024-10-24 22:11:41 +02:00
Developer 02
2a81f33340 feat: Erweiterungsmethoden zum Extrahieren von Benutzerdetails in ControllerExtensions hinzugefügt
- Methoden implementiert, um Benutzer-ID, Benutzernamen, Nachnamen, Vornamen und E-Mail aus den Claims zu extrahieren
- Null-Überprüfungen und Parsing-Logik bereitgestellt, um eine gültige Extraktion sicherzustellen
2024-10-24 22:10:02 +02:00
Developer 02
4e5a68fa89 feat: robuste Authentifizierungslogik in AuthController implementiert
- Login-Endpunkt mit claims-basierter Identität und Cookie-Authentifizierung hinzugefügt
- Gruppenvalidierungslogik mit Autorisierungsprüfungen integriert
- Fehlerbehandlung und Authentifizierungsfluss mit Logging versehen
- Logout-Funktionalität mit ordnungsgemäßem Cookie-Abmeldungsprozess bereitgestellt
2024-10-24 21:04:16 +02:00
Developer 02
2d2f35c972 refactor(API): Ersetzte CRUDControllerBase mit CRUDControllerBaseWithErrorHandling auf allen Controllern. 2024-10-24 20:50:42 +02:00
Developer 02
c606fe4480 feat(API): StateController initialisiert. 2024-10-24 20:46:37 +02:00
Developer 02
fa26fad600 feat(API): ProfileObjStateController initialisiert. 2024-10-24 20:45:42 +02:00
Developer 02
6da7f33437 feat(API): ProfileControlsTF initialisiert. 2024-10-24 20:43:37 +02:00
Developer 02
6fcddfc7b9 feat(API): ProfileController initialisiert. 2024-10-24 20:42:37 +02:00
Developer 02
ae59ffe73b feat(API): ConfigController initialisiert. 2024-10-24 20:40:27 +02:00
Developer 02
94a2d414d3 feat(API): Dienste Work Flow und User Manager hinzugefügt. 2024-10-24 20:25:26 +02:00
Developer 02
c7f1be7c58 feat(WFDBContext): Implementiert UserManagerDbContext. 2024-10-24 20:16:08 +02:00
Developer 02
70d7ed7415 feat(API): DB-Kontext zu den Diensten hinzugefügt 2024-10-24 20:02:16 +02:00
Developer 02
0351f8733d feat(API): Erforderliche Abhängigkeiten hinzugefügt. 2024-10-24 19:10:06 +02:00
Developer 02
26b57e5475 feat(API): Initalisiert. 2024-10-24 19:08:35 +02:00
Developer 02
2c66112d4d feat(ProfileObjStateService): ReadAsync-Methode als Schnittstellenimplementierung erstellt. 2024-10-24 18:57:39 +02:00
Developer 02
5327249f5e feat(ProfileControlsTFService): ReadAsync-Methode als Schnittstellenimplementierung erstellt. 2024-10-24 15:40:23 +02:00
Developer 02
f300b640a2 refactor: Ersetzt 'ProfileControlsTFRepository' durch 'IProfileControlsTFRepository'. 2024-10-24 15:27:10 +02:00
Developer 02
6a062045bb refactor: Ersetzt 'ProfileControlsTFRepository' durch 'IProfileControlsTFRepository'. 2024-10-24 15:26:39 +02:00
Developer 02
22f69589c9 refactor: Ersetzte 'usrId' durch 'userId'. 2024-10-24 15:22:32 +02:00
Developer 02
ca94368d0b feat(ProfileObjStateRepository): Zwei asynchrone Lesemethoden wurden zusammengeführt. 2024-10-24 15:15:41 +02:00
Developer 02
05701c10d2 feat(ProfileControlsTFRepository): Zwei asynchrone Lesemethoden wurden zusammengeführt. 2024-10-24 15:04:46 +02:00
31 changed files with 817 additions and 37 deletions

View File

@ -0,0 +1,145 @@
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.UserManager.Application.DTOs.Auth;
using DigitalData.Core.Abstractions.Application;
using Microsoft.Extensions.Localization;
using DigitalData.Core.DTO;
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] LogInDto login)
{
try
{
bool isValid = _dirSearchService.ValidateCredentials(login.Username, login.Password);
if (!isValid)
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));
var gouMsg = await _gouService.HasGroup(login.Username, "PM_USER", caseSensitive: false);
if (!gouMsg.IsSuccess)
return Unauthorized(Result.Fail().Message(_localizer[Key.UnauthorizedUser]));
//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.Id.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, "{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

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

View File

@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace WorkFlow.API.Controllers
{
public static class ControllerExtensions
{
public static bool TryGetUserId(this ControllerBase controller, out int? id)
{
var value = controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (value is null)
{
id = default;
return false;
}
if(int.TryParse(value, out int id_int))
{
id = id_int;
return true;
}
else
{
id = null;
return false;
}
}
public static bool TryGetUsername(this ControllerBase controller, out string username)
{
var value = controller.User.FindFirstValue(ClaimTypes.Name);
if (value is null)
{
username = string.Empty;
return false;
}
else
{
username = value;
return true;
}
}
public static bool TryGetName(this ControllerBase controller, out string name)
{
var value = controller.User.FindFirstValue(ClaimTypes.Surname);
if (value is null)
{
name = string.Empty;
return false;
}
else
{
name = value;
return true;
}
}
public static bool TryGetPrename(this ControllerBase controller, out string prename)
{
var value = controller.User.FindFirstValue(ClaimTypes.GivenName);
if (value is null)
{
prename = string.Empty;
return false;
}
else
{
prename = value;
return true;
}
}
public static bool TryGetEmail(this ControllerBase controller, out string email)
{
var value = controller.User.FindFirstValue(ClaimTypes.Email);
if (value is null)
{
email = string.Empty;
return false;
}
else
{
email = value;
return true;
}
}
}
}

View File

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

View File

@ -0,0 +1,118 @@
using DigitalData.Core.API;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileControlsTFController(ILogger<ProfileControlsTFController> logger, IProfileControlsTFService service) : CRUDControllerBase<IProfileControlsTFService, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(logger, service)
{
[NonAction]
public override Task<IActionResult> GetAll() => base.GetAll();
[NonAction]
public override Task<IActionResult> Update(ProfileControlsTFUpdateDto updateDto) => base.Update(updateDto);
[HttpGet]
public async Task<IActionResult> GetAsync(
bool withProfile = true, bool withUser = false,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
try
{
if (!this.TryGetUserId(out int? id))
{
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)
{
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("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) =>
{
_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

@ -0,0 +1,118 @@
using DigitalData.Core.API;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileObjStateController(ILogger<ProfileObjStateController> logger, IProfileObjStateService service) : CRUDControllerBaseWithErrorHandling<IProfileObjStateService, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>(logger, service)
{
[NonAction]
public override Task<IActionResult> GetAll() => base.GetAll();
[NonAction]
public override Task<IActionResult> Update(ProfileObjStateUpdateDto updateDto) => base.Update(updateDto);
[HttpGet]
public async Task<IActionResult> GetAsync(
bool withProfile = true, bool withUser = true, bool withState = true,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
try
{
if (!this.TryGetUserId(out int? id))
{
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, withState,
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)
{
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] ProfileObjStateCreateDto createDto)
{
try
{
if (!this.TryGetUserId(out int? id))
{
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) =>
{
_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

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

View File

@ -0,0 +1,44 @@
using DigitalData.Core.DTO;
using DigitalData.UserManager.Application.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace WorkFlow.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class UserController(ILogger<UserController> logger, IUserService userService) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAsync()
{
try
{
if (!this.TryGetUserId(out int? id))
{
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)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
}
}

47
WorkFlow.API/Program.cs Normal file
View File

@ -0,0 +1,47 @@
using WorkFlow.Application;
using DigitalData.UserManager.Application;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Infrastructure;
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
// Add services to the container.
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.AddControllers();
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";
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:56180",
"sslPort": 44397
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5130",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7120;http://localhost:5130",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.API" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=g+2edXEbMbujCUjh7INZRQ==;Password=Bz/n9pu8EyzlVqicaMRQGQ==;Encrypt=false;TrustServerCertificate=True;"
}
}

View File

@ -1,4 +1,5 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
@ -6,5 +7,9 @@ namespace WorkFlow.Application.Contracts
{
public interface IProfileControlsTFService : ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
{
Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -1,4 +1,5 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
@ -6,5 +7,9 @@ namespace WorkFlow.Application.Contracts
{
public interface IProfileObjStateService : ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>
{
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);
}
}

View File

@ -2,7 +2,7 @@
{
public record ProfileControlsTFCreateDto(int Id,
int ProfileId,
int UsrId,
int UserId,
long ObjId,
string ObjType,
string AttrName,

View File

@ -5,7 +5,7 @@ namespace WorkFlow.Application.DTO.ProfileControlsTF
{
public record ProfileControlsTFDto(int Id,
int ProfileId,
int UsrId,
int UserId,
long ObjId,
string ObjType,
string AttrName,

View File

@ -2,7 +2,7 @@
{
public record ProfileObjStateCreateDto(
int ProfileId,
int UsrId,
int UserId,
long ObjId,
int StateId,
string? State2 = null,

View File

@ -6,7 +6,7 @@ namespace WorkFlow.Application.DTO.ProfileObjState
{
public record ProfileObjStateDto(int Id,
int ProfileId,
int UsrId,
int UserId,
long ObjId,
int StateId,
string AddedWho,

View File

@ -1,16 +1,32 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Repositories;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services
{
public class ProfileControlsTFService(ProfileControlsTFRepository repository, IMapper mapper)
: CRUDService<ProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(repository, mapper),
public class ProfileControlsTFService(IProfileControlsTFRepository repository, IMapper mapper)
: CRUDService<IProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(repository, mapper),
IProfileControlsTFService, ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
{
public async Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
var pctf_list = await _repository.ReadAsync(
isReadonly: true,
withProfile: withProfile, withUser: withUser,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive);
var pctf_dto_list = _mapper.Map<IEnumerable<ProfileControlsTFDto>>(pctf_list);
return Result.Success(pctf_dto_list);
}
}
}

View File

@ -1,6 +1,7 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
@ -12,5 +13,20 @@ namespace WorkFlow.Application.Services
: 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);
return Result.Success(post_dto_list);
}
}
}

View File

@ -18,7 +18,7 @@ namespace WorkFlow.Domain.Entities
[Required]
[Column("USR_ID")]
public required int UsrId { get; init; }
public required int UserId { get; init; }
[Required]
[Column("OBJ_ID")]
@ -62,7 +62,7 @@ namespace WorkFlow.Domain.Entities
[ForeignKey("ProfileId")]
public Profile? Profile { get; init; } = default;
[ForeignKey("UsrId")]
[ForeignKey("UserId")]
public User? User { get; set; } = default;
}
}

View File

@ -18,7 +18,7 @@ namespace WorkFlow.Domain.Entities
[Required]
[Column("USR_ID")]
public required int UsrId { get; init; }
public required int UserId { get; init; }
[Required]
[Column("OBJ_ID")]
@ -48,7 +48,7 @@ namespace WorkFlow.Domain.Entities
[ForeignKey("ProfileId")]
public Profile? Profile { get; init; } = null;
[ForeignKey("UsrId")]
[ForeignKey("UserId")]
public User? User { get; init; } = null;
[ForeignKey("StateId")]

View File

@ -5,8 +5,10 @@ namespace WorkFlow.Infrastructure.Contracts
{
public interface IProfileControlsTFRepository : ICRUDRepository<ProfileControlsTF, int>
{
Task<IEnumerable<ProfileControlsTF>> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = false, int? profileId = null, int? objId = null, bool? profileActive = null);
Task<ProfileControlsTF?> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = false, int? usrId = null, string? username = null, int? profileId = null, int? objId = null, bool? profileActive = null);
Task<IEnumerable<ProfileControlsTF>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -5,8 +5,10 @@ namespace WorkFlow.Infrastructure.Contracts
{
public interface IProfileObjStateRepository : ICRUDRepository<ProfileObjState, int>
{
Task<IEnumerable<ProfileObjState>> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = true, bool withState = true, int? profileId = null, int? objId = null, bool? profileActive = null);
Task<ProfileObjState?> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = false, bool withState = true, int? usrId = null, string? username = null, int? profileId = null, int? objId = null, bool? profileActive = null);
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);
}
}

View File

@ -5,6 +5,7 @@ using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
{
//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>
{
}

View File

@ -10,7 +10,7 @@ namespace WorkFlow.Infrastructure.Repositories
{
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? usrId = 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)
{
var query = isReadonly ? _dbSet.AsNoTracking() : _dbSet.AsQueryable();
@ -23,8 +23,8 @@ namespace WorkFlow.Infrastructure.Repositories
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (usrId is not null)
query = query.Where(pctf => pctf.UsrId == usrId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
@ -38,11 +38,16 @@ namespace WorkFlow.Infrastructure.Repositories
return query;
}
public async Task<IEnumerable<ProfileControlsTF>> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = true, int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(isReadonly: isReadonly, withProfile: withProfile, withUser: withUser, profileId: profileId, objId: objId, profileActive: profileActive).ToListAsync();
public async Task<ProfileControlsTF?> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = false, int? usrId = null, string? username = null, int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(isReadonly: isReadonly, withProfile: withProfile, withUser: withUser, usrId: usrId, username: username, profileId: profileId, objId: objId, profileActive: profileActive)
.FirstOrDefaultAsync();
public async Task<IEnumerable<ProfileControlsTF>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(
isReadonly: isReadonly,
withProfile: withProfile, withUser: withUser,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive)
.ToListAsync();
}
}

View File

@ -10,7 +10,7 @@ namespace WorkFlow.Infrastructure.Repositories
{
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? usrId = null, string? username = null, int? stateId = null, int? objId = null, bool? profileActive = null)
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();
@ -26,8 +26,8 @@ namespace WorkFlow.Infrastructure.Repositories
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (usrId is not null)
query = query.Where(pctf => pctf.UsrId == usrId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
@ -44,11 +44,16 @@ namespace WorkFlow.Infrastructure.Repositories
return query;
}
public async Task<IEnumerable<ProfileObjState>> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = true, bool withState = true, int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(isReadonly: isReadonly, withProfile: withProfile, withUser: withUser, withState: withState, profileId: profileId, objId: objId, profileActive: profileActive).ToListAsync();
public async Task<ProfileObjState?> ReadAsync(bool isReadonly = true, bool withProfile = true, bool withUser = false, bool withState = true, int? usrId = null, string? username = null, int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(isReadonly: isReadonly, withProfile: withProfile, withUser: withUser, withState: withState, usrId: usrId, username: username, profileId: profileId, objId: objId, profileActive: profileActive)
.FirstOrDefaultAsync();
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

@ -1,9 +1,12 @@
using Microsoft.EntityFrameworkCore;
using DigitalData.UserManager.Domain.Entities;
using DigitalData.UserManager.Infrastructure;
using DigitalData.UserManager.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure
{
public class WFDBContext(DbContextOptions options) : DbContext(options)
public class WFDBContext(DbContextOptions options) : DbContext(options), IUserManagerDbContext
{
public DbSet<Config> Configs { get; set; }
@ -14,5 +17,25 @@ namespace WorkFlow.Infrastructure
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

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.0.0" />
<PackageReference Include="UserManager.Infrastructure" Version="2.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlow.Infrastructure", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Application", "WorkFlow.Application\WorkFlow.Application.csproj", "{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.API", "WorkFlow.API\WorkFlow.API.csproj", "{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.Build.0 = Release|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE