commit c34987797a073c3f3f382d306eabb4acfb5d777a Author: Sergio Matias Urquin Date: Tue Apr 29 18:55:44 2025 -0600 Add project files. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.pipelines/GitVersion.yml b/.pipelines/GitVersion.yml new file mode 100644 index 0000000..f5867a9 --- /dev/null +++ b/.pipelines/GitVersion.yml @@ -0,0 +1,59 @@ +mode: mainline +assembly-versioning-scheme: MajorMinorPatch +tag-prefix: '[vV]' +major-version-bump-message: '\+semver:\s?(breaking|major)' +minor-version-bump-message: '\+semver:\s?(feature|minor)' +patch-version-bump-message: '\+semver:\s?(fix|patch)' +no-bump-message: '\+semver:\s?(none|skip)' +legacy-semver-padding: 4 +build-metadata-padding: 4 +commits-since-version-source-padding: 4 +commit-message-incrementing: Enabled +branches: + main: + regex: ^master$|^main$ + tag: '' + increment: Patch + prevent-increment-of-merged-branch-version: true + track-merge-target: false + source-branches: [ 'develop', 'release' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: true + pre-release-weight: 55000 + feature: + regex: ^features?[/-] + tag: useBranchName + increment: Inherit + prevent-increment-of-merged-branch-version: false + track-merge-target: false + source-branches: [ 'develop', 'main', 'release', 'feature', 'support', 'hotfix' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: false + pre-release-weight: 30000 + release: + regex: ^releases?[/-] + tag: beta + increment: None + is-mainline: true + regex: ^releases?[/-] + prevent-increment-of-merged-branch-version: true + track-merge-target: false + source-branches: [ 'develop', 'main', 'support', 'release' ] + tracks-release-branches: false + is-release-branch: true + is-mainline: false + pre-release-weight: 30000 + hotfix: + regex: ^hotfix(es)?[/-] + increment: Patch + prevent-increment-of-merged-branch-version: false + track-merge-target: false + source-branches: [ 'develop', 'main', 'support' ] + tracks-release-branches: false + is-release-branch: false + is-mainline: false + pre-release-weight: 30000 +ignore: + sha: [] \ No newline at end of file diff --git a/.pipelines/templated-pipeline.yml b/.pipelines/templated-pipeline.yml new file mode 100644 index 0000000..132e4c7 --- /dev/null +++ b/.pipelines/templated-pipeline.yml @@ -0,0 +1,57 @@ +pool: + vmImage: 'windows-latest' + +trigger: + branches: + include: + - release/* + - feature/* + - hotfix/* + - bugfix/* + - development + +variables: + project: 'Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj' + solution: 'Core.Cerberos.DAL.API.sln' + buildConfiguration: 'Release' + artifactName: 'drop' + snykConnectionEndpoint: 'SnykConnection' + projectNameOnSonar: 'Core.Cerberos.DAL.API' + projectKeyOnSonar: 'heathpbu_Core.Cerberos.DAL.API' + feed: '1b3770f1-17db-4bf2-a43d-49f305aa7a22' + projectFileName: 'Core.Cerberos.DAL.API.csproj' + projectPath: 'Core.Cerberos.DAL.API/' + +resources: + repositories: + - repository: templates + name: "Template.DevOps.Pipelines" + type: "git" + project: "SharedLibs" + +jobs: +- job: CI + steps: + - template: templates/dotnet/v1/step1_setup.yml@templates + - template: templates/dotnet/v1/step2_versioning.yml@templates + parameters: + projectFileName: '$(projectFileName)' + path: '$(projectPath)' + - template: templates/dotnet/v1/step3_restore_and_build.yml@templates + parameters: + project: '$(project)' + solution: '$(solution)' + buildConfiguration: '$(buildConfiguration)' + projectNameOnSonar: '$(projectNameOnSonar)' + projectKeyOnSonar: '$(projectKeyOnSonar)' + feed: '$(feed)' + + - template: templates/dotnet/v1/step5_snyk_analysis.yml@templates + parameters: + snykConnectionEndpoint: '$(snykConnectionEndpoint)' + solutionToScan: '$(solution)' + + - template: templates/dotnet/v1/step6_release.yml@templates + parameters: + artifactName: '$(artifactName)' + project: '$(project)' \ No newline at end of file diff --git a/Core.Cerberos.DAL.API.sln b/Core.Cerberos.DAL.API.sln new file mode 100644 index 0000000..f8a648f --- /dev/null +++ b/Core.Cerberos.DAL.API.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35027.167 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.DAL.API", "Core.Cerberos.DAL.API\Core.Cerberos.DAL.API.csproj", "{F00B4683-03B3-487A-9608-4B30675AA278}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{2E7D918E-AB9F-44BF-A334-FD675C9B626E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.Domain", "Core.Cerberos.Domain\Core.Cerberos.Domain.csproj", "{BE8E05D6-05B2-4317-B619-21853B7D21DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.Infrastructure", "Core.Cerberos.Infraestructure\Core.Cerberos.Infrastructure.csproj", "{43BD5F47-132F-4E78-83F1-A1FEED01A502}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.Provider", "Core.Cerberos.Provider\Core.Cerberos.Provider.csproj", "{8CAE8380-475F-46B8-AF90-C495AAC58606}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{36839283-6407-476A-BB33-F0EE90383E2B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F00B4683-03B3-487A-9608-4B30675AA278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F00B4683-03B3-487A-9608-4B30675AA278}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F00B4683-03B3-487A-9608-4B30675AA278}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F00B4683-03B3-487A-9608-4B30675AA278}.Release|Any CPU.Build.0 = Release|Any CPU + {BE8E05D6-05B2-4317-B619-21853B7D21DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE8E05D6-05B2-4317-B619-21853B7D21DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE8E05D6-05B2-4317-B619-21853B7D21DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE8E05D6-05B2-4317-B619-21853B7D21DB}.Release|Any CPU.Build.0 = Release|Any CPU + {43BD5F47-132F-4E78-83F1-A1FEED01A502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43BD5F47-132F-4E78-83F1-A1FEED01A502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43BD5F47-132F-4E78-83F1-A1FEED01A502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43BD5F47-132F-4E78-83F1-A1FEED01A502}.Release|Any CPU.Build.0 = Release|Any CPU + {8CAE8380-475F-46B8-AF90-C495AAC58606}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CAE8380-475F-46B8-AF90-C495AAC58606}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CAE8380-475F-46B8-AF90-C495AAC58606}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CAE8380-475F-46B8-AF90-C495AAC58606}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F00B4683-03B3-487A-9608-4B30675AA278} = {36839283-6407-476A-BB33-F0EE90383E2B} + {BE8E05D6-05B2-4317-B619-21853B7D21DB} = {2E7D918E-AB9F-44BF-A334-FD675C9B626E} + {43BD5F47-132F-4E78-83F1-A1FEED01A502} = {2E7D918E-AB9F-44BF-A334-FD675C9B626E} + {8CAE8380-475F-46B8-AF90-C495AAC58606} = {2E7D918E-AB9F-44BF-A334-FD675C9B626E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6800B16F-366B-476B-9BD1-FA841D443A6B} + EndGlobalSection +EndGlobal diff --git a/Core.Cerberos.DAL.API/Controllers/ModuleController.cs b/Core.Cerberos.DAL.API/Controllers/ModuleController.cs new file mode 100644 index 0000000..0ecb4ea --- /dev/null +++ b/Core.Cerberos.DAL.API/Controllers/ModuleController.cs @@ -0,0 +1,220 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Asp.Versioning; +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Attributes; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Provider.Contracts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace LSA.Core.Kerberos.API.Controllers +{ + /// + /// Handles all requests for module authentication. + /// + [ApiVersion(MimeTypes.ApplicationVersion)] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Produces(MimeTypes.ApplicationJson)] + [Consumes(MimeTypes.ApplicationJson)] + [ApiController] + public class ModuleController(IModuleService service, ILogger logger) : ControllerBase + { + /// + /// Gets all the modules. + /// + /// The found entities. + /// The roles found. + /// The roles not found error. + /// The service internal error. + [HttpGet] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Read, RoleManagement.Read")] + public async Task GetAllModulesAsync() + { + try + { + var result = await service.GetAllModulesService(); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllModulesAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets all the modules by module identifiers. + /// + /// The list of module identifiers. + /// The found entities. + /// The modules found. + /// The modules not found error. + /// The service internal error. + [HttpPost] + [Route(Routes.GetModuleList)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Read")] + public async Task GetAllModulesByList([FromBody] string[] modules) + { + if (modules == null || !modules.Any()) + { + return BadRequest("Module identifiers are required."); + } + + try + { + var result = await service.GetAllModulesByListService(modules); + + if (result == null || !result.Any()) + { + return NotFound("No modules found for the given identifiers."); + } + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllModulesByList"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + + /// + /// Gets the module by identifier. + /// + /// The module identifier. + /// The found entity. + /// The module found. + /// The module not found error. + /// The service internal error. + [HttpGet] + [Route(Routes.Id)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(ModuleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Read")] + public async Task GetModuleByIdAsync([FromRoute] string id) + { + try + { + var result = await service.GetModuleByIdService(id); + + if (result is null) return NotFound($"module with id: '{id}' not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetModuleByIdAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Creates a new module. + /// + /// The module to be added. + /// The created entity. + /// The module created. + /// The module could not be created. + /// The service internal e|ror. + [HttpPost] + [ProducesResponseType(typeof(ModuleAdapter), StatusCodes.Status201Created)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Write")] + public async Task CreateModuleAsync([FromBody] ModuleRequest newModule) + { + try + { + var result = await service.CreateModuleService(newModule).ConfigureAwait(false); + return Created("CreatedWithIdService", result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in CreateModuleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Updates a full module by identifier. + /// + /// The module to update. + /// The module identifier. + /// The updated entity. + /// The module updated. + /// The module not found. + /// The module could not be updated. + /// The service internal error. + [HttpPut] + [Route(Routes.Id)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(ModuleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Write")] + public async Task UpdateModuleAsync(ModuleAdapter entity, string id) + { + try + { + var result = await service.UpdateModuleService(entity, id); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in UpdateModuleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Changes the status of the module. + /// + /// The module identifier. + /// The new status of the module. + /// The updated entity. + /// The module updates. + /// The module not found. + /// The module could not be deleted. + /// The service internal error. + [HttpPatch] + [Route(Routes.ChangeStatus)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(ModuleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("ModuleManagement.Write")] + public async Task ChangeModuleStatus([FromRoute] string id, [FromRoute] StatusEnum newStatus) + { + try + { + var result = await service.ChangeModuleStatusService(id, newStatus); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in ChangeModuleStatus"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + } +} diff --git a/Core.Cerberos.DAL.API/Controllers/PermissionController.cs b/Core.Cerberos.DAL.API/Controllers/PermissionController.cs new file mode 100644 index 0000000..d9876d0 --- /dev/null +++ b/Core.Cerberos.DAL.API/Controllers/PermissionController.cs @@ -0,0 +1,219 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Asp.Versioning; +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Attributes; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Provider.Contracts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace LSA.Core.Kerberos.API.Controllers +{ + /// + /// Handles all requests for permission authentication. + /// + [ApiVersion(MimeTypes.ApplicationVersion)] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Produces(MimeTypes.ApplicationJson)] + [Consumes(MimeTypes.ApplicationJson)] + [ApiController] + public class PermissionController(IPermissionService service, ILogger logger) : ControllerBase + { + /// + /// Gets all the permissions. + /// + /// The found entities. + /// The roles found. + /// The roles not found error. + /// The service internal error. + [HttpGet] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Read, RoleManagement.Read")] + public async Task GetAllPermissionsAsync() + { + try + { + var result = await service.GetAllPermissionsService(); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllPermissionsAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets all the permissions by permission identifiers. + /// + /// The list of permission identifiers. + /// The found entities. + /// The permissions found. + /// The permissions not found error. + /// The service internal error. + [HttpPost] + [Route(Routes.GetPermissionList)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Read")] + public async Task GetAllPermissionsByList([FromBody] string[] permissions) + { + if (permissions == null || !permissions.Any()) + { + return BadRequest("Permission identifiers are required."); + } + + try + { + var result = await service.GetAllPermissionsByListService(permissions); + + if (result == null || !result.Any()) + { + return NotFound("No permissions found for the given identifiers."); + } + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllPermissionsByList"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets the permission by identifier. + /// + /// The permission identifier. + /// The found entity. + /// The permission found. + /// The permission not found error. + /// The service internal error. + [HttpGet] + [Route(Routes.Id)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(PermissionAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Read")] + public async Task GetPermissionByIdAsync([FromRoute] string id) + { + try + { + var result = await service.GetPermissionByIdService(id); + + if (result is null) return NotFound($"permission with id: '{id}' not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetPermissionByIdAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Creates a new permission. + /// + /// The permission to be added. + /// The created entity. + /// The permission created. + /// The permission could not be created. + /// The service internal e|ror. + [HttpPost] + [ProducesResponseType(typeof(PermissionAdapter), StatusCodes.Status201Created)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Write")] + public async Task CreatePermissionAsync([FromBody] PermissionRequest newPermission) + { + try + { + var result = await service.CreatePermissionService(newPermission).ConfigureAwait(false); + return Created("CreatedWithIdService", result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in CreatePermissionAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Updates a full permission by identifier. + /// + /// The permission to update. + /// The permission identifier. + /// The updated entity. + /// The permission updated. + /// The permission not found. + /// The permission could not be updated. + /// The service internal error. + [HttpPut] + [Route(Routes.Id)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(PermissionAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Write")] + public async Task UpdatePermissionAsync(PermissionAdapter entity, string id) + { + try + { + var result = await service.UpdatePermissionService(entity, id); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in UpdatePermissionAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Changes the status of the permission. + /// + /// The permission identifier. + /// The new status of the permission. + /// The updated entity. + /// The permission updates. + /// The permission not found. + /// The permission could not be deleted. + /// The service internal error. + [HttpPatch] + [Route(Routes.ChangeStatus)] + [Consumes(MimeTypes.ApplicationJson)] + [Produces(MimeTypes.ApplicationJson)] + [ProducesResponseType(typeof(PermissionAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("PermissionManagement.Write")] + public async Task ChangePermissionStatus([FromRoute] string id, [FromRoute] StatusEnum newStatus) + { + try + { + var result = await service.ChangePermissionStatusService(id, newStatus); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in ChangePermissionStatus"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + } +} diff --git a/Core.Cerberos.DAL.API/Controllers/RoleController.cs b/Core.Cerberos.DAL.API/Controllers/RoleController.cs new file mode 100644 index 0000000..95e4fed --- /dev/null +++ b/Core.Cerberos.DAL.API/Controllers/RoleController.cs @@ -0,0 +1,231 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Asp.Versioning; +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Attributes; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Provider.Contracts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace LSA.Core.Kerberos.API.Controllers +{ + /// + /// Handles all requests for role authentication. + /// + [ApiVersion(MimeTypes.ApplicationVersion)] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Produces(MimeTypes.ApplicationJson)] + [Consumes(MimeTypes.ApplicationJson)] + [ApiController] + public class RoleController(IRoleService service, ILogger logger) : ControllerBase + { + /// + /// Gets all the roles. + /// + /// The rol found entities. + /// The roles found. + /// The roles not found error. + /// The service internal error. + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Read")] + public async Task GetAllRolesAsync() + { + try + { + var result = await service.GetAllRolesService(); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllRolesAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets the role by identifier. + /// + /// The role identifier. + /// The found entity. + /// The role found. + /// The role not found error. + /// The service internal error. + [HttpGet] + [Route(Routes.Id)] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Read")] + public async Task GetRoleByIdAsync([FromRoute] string id) + { + try + { + var result = await service.GetRoleByIdService(id); + + if (result is null) return NotFound($"role with id: '{id}' not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetRoleByIdAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Creates a new role. + /// + /// The role to be added. + /// The created entity. + /// The role created. + /// The role could not be created. + /// The service internal error. + [HttpPost] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status201Created)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Write")] + public async Task CreateRoleAsync([FromBody] RoleRequest newRole) + { + try + { + var result = await service.CreateRoleService(newRole).ConfigureAwait(false); + + return Created("CreatedWithIdService", result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in CreateRoleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Updates a full role by identifier. + /// + /// The role to update. + /// The role identifier. + /// The updated entity. + /// The role updated. + /// The role not found. + /// The role could not be updated. + /// The service internal error. + [HttpPut] + [Route(Routes.Id)] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Write")] + public async Task UpdateRoleAsync([FromBody] RoleAdapter entity, [FromRoute] string id) + { + try + { + var result = await service.UpdateRoleService(entity, id); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in UpdateRoleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Changes the status of the role. + /// + /// The role identifier. + /// The new status of the role. + /// The updated entity. + /// The role updates. + /// The role not found. + /// The role could not be deleted. + /// The service internal error. + [HttpPatch] + [Route(Routes.ChangeStatus)] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Write")] + public async Task ChangeRoleStatus([FromRoute] string id, [FromRoute] StatusEnum newStatus) + { + try + { + var result = await service.ChangeRoleStatusService(id, newStatus); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in ChangeRoleStatus"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + + } + + /// + /// Adds an application to the role's list of applications. + /// + /// The identifier of the role to which the application will be added. + /// The application enum value to add. + /// A representing the asynchronous operation, with the updated role object. + /// The role updates. + /// The role not found. + /// The role could not be deleted. + /// The service internal error. + [HttpPost(Routes.AddApplication)] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Write")] + public async Task AddApplicationToRoleAsync([FromRoute] string roleId, + [FromRoute] ApplicationsEnum application) + { + try + { + var updatedRole = await service.AddApplicationToRoleService(roleId, application); + + return Ok(updatedRole); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in AddApplicationToRoleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Removes an application from the role's list of applications. + /// + /// The identifier of the role from which the application will be removed. + /// The application enum value to remove. + /// A representing the asynchronous operation, with the updated role object. + /// The role updates. + /// The role not found. + /// The role could not be deleted. + /// The service internal error. + [HttpDelete(Routes.RemoveApplication)] + [ProducesResponseType(typeof(RoleAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("RoleManagement.Write")] + public async Task RemoveApplicationFromRoleAsync([FromRoute] string roleId, + [FromRoute] ApplicationsEnum application) + { + try + { + var updatedRole = await service.RemoveApplicationFromRoleService(roleId, application); + return Ok(updatedRole); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in RemoveApplicationFromRoleAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + } +} diff --git a/Core.Cerberos.DAL.API/Controllers/UserController.cs b/Core.Cerberos.DAL.API/Controllers/UserController.cs new file mode 100644 index 0000000..fd8eeef --- /dev/null +++ b/Core.Cerberos.DAL.API/Controllers/UserController.cs @@ -0,0 +1,495 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Asp.Versioning; +using Core.Blueprint.Storage.Adapters; +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Attributes; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Provider.Contracts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Graph; +using UserRequest = Core.Cerberos.Domain.Contexts.Onboarding.Request.UserRequest; + +namespace LSA.Core.Kerberos.API.Controllers +{ + /// + /// Handles all requests for user authentication. + /// + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Produces(MimeTypes.ApplicationJson)] + [Consumes(MimeTypes.ApplicationJson)] + [ApiController] + public class UserController(IUserService service, ILogger logger) : ControllerBase + { + /// + /// Gets all the users. + /// + /// The found entity. + /// The users found. + /// The users not found error. + /// The service internal error. + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Read")] + public async Task GetAllUsersService() + { + try + { + var result = await service.GetAllUsersService(); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetAllUsersService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets the user by identifier. + /// + /// The user identifier. + /// The found entity. + /// The user found. + /// The user not found error. + /// The service internal error. + [HttpGet] + [Route(Routes.Id)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Read")] + public async Task GetUserByIdService([FromRoute] string id) + { + try + { + var result = await service.GetUserByIdService(id); + + if (result is null) return NotFound($"user with id: '{id}' not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetUserByIdService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets the user by email. + /// + /// The user's email. + /// The found entity. + /// The user found. + /// The user not found error. + /// The service internal error. + [HttpGet] + [Route(Routes.Email)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = $"{Schemes.HeathScheme}, {Schemes.AzureScheme}")] + public async Task GetUserByEmail([FromRoute] string email) + { + try + { + var result = await service.GetUserByEmailService(email); + + if (result is null) return NotFound($"user with email: '{email}' not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetUserByIdEmail"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Validates if a user exists on the database. + /// + /// The user's email. + /// The found entity. + /// The user found. + /// The user not found error. + /// The service internal error. + [HttpGet] + [Route("{email}/ValidateExistence")] + [ProducesResponseType(typeof(UserExistenceAdapter), StatusCodes.Status200OK)] + [AllowAnonymous] + public async Task ValidateUserExistence([FromRoute] string email) + { + try + { + var result = await service.ValidateUserExistenceService(email); + + var existence = new UserExistenceAdapter + { + Existence = (result is not null) ? true : false + }; + + return Ok(existence); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in ValidateUserExistance"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Creates a new user. + /// + /// The user to be added. + /// Sends an invitation in case of third party access. + /// The created entity. + /// The user created. + /// The user could not be created. + /// The service internal error. + [HttpPost(Routes.Register)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status201Created)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task CreateUserAsync([FromBody] UserRequest newUser, [FromRoute] bool sendInvitation) + { + try + { + var user = await service.GetUserByEmailService(newUser.Email).ConfigureAwait(false); + + if (user is not null) + return UnprocessableEntity("There is a user with the same email registered in the database"); + + var result = await service.CreateUserService(newUser).ConfigureAwait(false); + + return Created("CreatedWithIdService", result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in CreateUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Updates a full user by identifier. + /// + /// The user to update. + /// The user identifier. + /// The updated entity. + /// The user updated. + /// The user not found. + /// The user could not be updated. + /// The service internal error. + [HttpPut] + [Route(Routes.Id)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task UpdateUserAsync([FromBody] UserAdapter entity, [FromRoute] string id) + { + try + { + var result = await service.UpdateUserService(entity, id); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in UpdateUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Logs in the user. + /// + /// The User's email. + /// A representing + /// the asynchronous execution of the service. + /// The User found. + /// The User not found. + /// The service internal error. + [HttpPatch(Routes.LogIn)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = $"{Schemes.HeathScheme}, {Schemes.AzureScheme}")] + public async Task LoginUserAsync([FromRoute] string email) + { + try + { + var result = await service.LogInUserService(email).ConfigureAwait(false); + + if (result is null) + return new NotFoundObjectResult($"The user with email: '{email}' was not found"); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in LogInUserService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Logs out the user. + /// + /// The User's email. + /// A representing + /// the asynchronous execution of the service. + /// The User updated. + /// The service internal error. + [HttpPatch(Routes.LogOut)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = $"{Schemes.HeathScheme}, {Schemes.AzureScheme}")] + public async Task LogOutUserSessionAsync([FromRoute] string email) + { + try + { + var result = await service.LogOutUserSessionService(email).ConfigureAwait(false); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in LogOutUserSessionService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + + } + + /// + /// Changes the status of the user. + /// + /// The user identifier. + /// The new status of the user. + /// The updated entity. + /// The user updates. + /// The user not found. + /// The user could not be deleted. + /// The service internal error. + [HttpPatch] + [Route(Routes.ChangeStatus)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task ChangeUserStatus([FromRoute] string id, [FromRoute] StatusEnum newStatus) + { + try + { + var result = await service.ChangeUserStatusService(id, newStatus); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in ChangeUserStatus"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Adds a company to the user's list of companies. + /// + /// The user identifier. + /// The company identifier to add. + /// The updated entity. + /// The user with the updated companies. + /// The user or company not found. + /// The service internal error. + [HttpPost] + [Route(Routes.AddCompany)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task AddCompanyToUserAsync([FromRoute] string userId, [FromRoute] string companyId) + { + try + { + var result = await service.AddCompanyToUserService(userId, companyId); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in AddCompanyToUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Removes a company from the user's list of companies. + /// + /// The user identifier. + /// The company identifier to remove. + /// The updated entity. + /// The user with the updated companies. + /// The user or company not found. + /// The service internal error. + [HttpDelete] + [Route(Routes.RemoveCompany)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task RemoveCompanyFromUserAsync([FromRoute] string userId, [FromRoute] string companyId) + { + try + { + var result = await service.RemoveCompanyFromUserService(userId, companyId); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in RemoveCompanyFromUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Adds a project to the user's list of projects. + /// + /// The user identifier. + /// The project identifier to add. + /// The updated entity. + /// The user with the updated projects. + /// The user or project not found. + /// The service internal error. + [HttpPost] + [Route(Routes.AddProject)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task AddProjectToUserAsync([FromRoute] string userId, [FromRoute] string projectId) + { + try + { + var result = await service.AddProjectToUserService(userId, projectId); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in AddProjectToUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Removes a project from the user's list of projects. + /// + /// The user identifier. + /// The project identifier to remove. + /// The updated entity. + /// The user with the updated projects. + /// The user or project not found. + /// The service internal error. + [HttpDelete] + [Route(Routes.RemoveProject)] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Write")] + public async Task RemoveProjectFromUserAsync([FromRoute] string userId, [FromRoute] string projectId) + { + try + { + var result = await service.RemoveProjectFromUserService(userId, projectId); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in RemoveProjectFromUserAsync"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Gets a token for the user, including roles, permissions, and modules. + /// + /// The user's email. + /// The token adapter with user details, role, permissions, and modules. + /// The token adapter with user details. + /// The user not found. + /// The service internal error. + [HttpGet] + [Route("{email}/GetTokenAdapter")] + [ProducesResponseType(typeof(TokenAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = $"{Schemes.HeathScheme}, {Schemes.AzureScheme}")] + public async Task GetTokenAdapter([FromRoute] string email) + { + try + { + var tokenAdapter = await service.GetTokenAdapter(email); + + if (tokenAdapter == null) return NotFound($"User with email: {email} not found"); + + return Ok(tokenAdapter); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetTokenAdapter"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Get Consent Form PDF. + /// + /// The found pdf. + /// The pdf found. + /// The pdf not found error. + /// The service internal error. + [HttpGet] + [Route("GetConsentFormPDF")] + [ProducesResponseType(typeof(BlobDownloadUriAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = Schemes.HeathScheme)] + [Permission("UserManagement.Read")] + public async Task GetConsentFormPDFService() + { + try + { + var result = await service.GetConsentFormPDFService(); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in GetConsentFormPDFService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + + /// + /// Accept user consent form. + /// + /// A representing + /// the asynchronous execution of the service. + /// The User found. + /// The User not found. + /// The service internal error. + [HttpPatch("AcceptUserConsentForm")] + [ProducesResponseType(typeof(UserAdapter), StatusCodes.Status200OK)] + [Authorize(AuthenticationSchemes = $"{Schemes.HeathScheme}, {Schemes.AzureScheme}")] + public async Task AcceptUserConsentFormAsync() + { + try + { + var result = await service.AcceptUserConsentFormService().ConfigureAwait(false); + + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Error in AcceptUserConsentFormService"); + return StatusCode(500, $"Internal server error, ErrorMessage: {ex.Message}"); + } + } + } +} diff --git a/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj new file mode 100644 index 0000000..16684f3 --- /dev/null +++ b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + True + + + + + + + + + + + + + + + + + + + diff --git a/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.http b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.http new file mode 100644 index 0000000..fd753cc --- /dev/null +++ b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.http @@ -0,0 +1,6 @@ +@Core.Cerberos.DAL.API_HostAddress = http://localhost:5211 + +GET {{Core.Cerberos.DAL.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Core.Cerberos.DAL.API/Program.cs b/Core.Cerberos.DAL.API/Program.cs new file mode 100644 index 0000000..029857b --- /dev/null +++ b/Core.Cerberos.DAL.API/Program.cs @@ -0,0 +1,86 @@ +using Core.Cerberos.Adapters.Extensions; +using Core.Cerberos.Adapters.Helpers; +using Core.Cerberos.Provider; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.AspNetCore.ResponseCompression; +using System.IO.Compression; +using System.Reflection; +using System.Threading.RateLimiting; + +var builder = WebApplication.CreateBuilder(args); + +var authSettings = AuthHelper.GetAuthSettings(builder, "cerberos_dal"); + +builder.Services.ConfigureAuthentication(builder.Configuration, authSettings); + +builder.Configuration.AddUserSecrets(Assembly.GetExecutingAssembly()).AddEnvironmentVariables(); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", policyBuilder => + policyBuilder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); +}); +builder.Services.AddMvc().AddJsonOptions(options => +{ + options.JsonSerializerOptions.WriteIndented = true; + options.JsonSerializerOptions.MaxDepth = 20; + options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals; +}); +builder.Services.Configure(options => +{ + options.Level = CompressionLevel.Fastest; +}); +builder.Services.Configure(options => +{ + options.Level = CompressionLevel.SmallestSize; +}); +builder.Services.AddResponseCompression(options => +{ + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); +}); + +builder.Services.AddRateLimiter(_ => _ + .AddFixedWindowLimiter("fixed", options => + { + options.PermitLimit = 5; + options.Window = TimeSpan.FromSeconds(10); + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 2; + }) + .AddSlidingWindowLimiter("sliding", options => + { + options.PermitLimit = 5; + options.Window = TimeSpan.FromSeconds(10); + options.SegmentsPerWindow = 5; + options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; + options.QueueLimit = 2; + })); + +builder.Services.AddResponseCaching(); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwagger(builder.Configuration, "Core.Cerberos.DAL.API.xml", authSettings); +builder.Services.AddVersioning(builder.Configuration); +builder.Services.AddLogging(); +builder.Services.AddProblemDetails(); + + +builder.Services.AddDALLayer(builder.Configuration); + +var app = builder.Build(); + +app.UseSwaggerUI(builder.Configuration, authSettings); +app.ConfigureSwagger(builder.Configuration); +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/Core.Cerberos.DAL.API/Properties/launchSettings.json b/Core.Cerberos.DAL.API/Properties/launchSettings.json new file mode 100644 index 0000000..2deec93 --- /dev/null +++ b/Core.Cerberos.DAL.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59551", + "sslPort": 44359 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5211", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7031;http://localhost:5211", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + } + } +} diff --git a/Core.Cerberos.DAL.API/appsettings.Development.json b/Core.Cerberos.DAL.API/appsettings.Development.json new file mode 100644 index 0000000..a339ca1 --- /dev/null +++ b/Core.Cerberos.DAL.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, +} diff --git a/Core.Cerberos.DAL.API/appsettings.json b/Core.Cerberos.DAL.API/appsettings.json new file mode 100644 index 0000000..d321977 --- /dev/null +++ b/Core.Cerberos.DAL.API/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Endpoints": { + "AppConfigurationURI": "https://sandbox-hci-usc-appcg.azconfig.io" + } +} diff --git a/Core.Cerberos.DAL.API/sample.settings.json b/Core.Cerberos.DAL.API/sample.settings.json new file mode 100644 index 0000000..da0eb8c --- /dev/null +++ b/Core.Cerberos.DAL.API/sample.settings.json @@ -0,0 +1,35 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "", // Sandbox MongoDB connection string + "Redis": "", // New Redis connection string + "KeyVault": "" //KeyVault Uri + }, + "MongoDb": { + "DatabaseName": "Cerberos" + }, + "CacheSettings": { + "DefaultCacheDurationInMinutes": 3 // Default cache duration set to 3 minutes + }, + "JwtIssuerOptions": { + "Audience": "", // Audience for token creation, specifies intended recipients + "Issuer": "" // Issuer for token creation, identifies the issuer of the token + }, + "AzureAdB2C": { + "Instance": "", // Azure AD instance URL (STORED IN KEY VAULT) + "TenantId": "", // Azure AD tenant ID (STORED IN KEY VAULT) + "ClientId": "", // Azure AD application client ID (STORED IN KEY VAULT) + "ClientSecret": "", // Azure AD application client secret (STORED IN KEY VAULT) + "CallbackPath": "", // Path for redirect after authentication + "Scopes": "" // Access scopes for user permissions + }, + "HeathCerberosApp": { + "AuthorizationUrl": "", // URL for authorization endpoint (STORED IN KEY VAULT) + "TokenUrl": "", // URL for token endpoint (STORED IN KEY VAULT) + "Scope": "", // Scope for application permissions (STORED IN KEY VAULT) + "ClientId": "" // Client ID for Kerberos application (STORED IN KEY VAULT) + }, + "MicrosoftGraph": { + "Scopes": "", // Scopes for Microsoft Graph API access + "BaseUrl": "" // Base URL for Microsoft Graph API + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/ModuleMapper.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/ModuleMapper.cs new file mode 100644 index 0000000..68e25c6 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/ModuleMapper.cs @@ -0,0 +1,42 @@ +// *********************************************************************** +// +// HEATH +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Microsoft.AspNetCore.Http; +using MongoDB.Bson; +using System.Security.Claims; +namespace Core.Cerberos.Domain.Contexts.Onboarding.Mappers +{ + /// + /// Handles mappings between + /// , + /// and + /// + public static class ModuleMapper + { + /// + /// Maps the permissionRequest to ModuleAdapter. + /// + /// The Module to be mapped. + /// A representing + /// the asynchronous execution of the service. + public static ModuleAdapter ToAdapter(this ModuleRequest newModule, IHttpContextAccessor httpContextAccessor) + { + return new ModuleAdapter + { + Id = ObjectId.GenerateNewId().ToString(), + Name = newModule.Name, + Description = newModule.Description, + Icon = newModule.Icon, + Route = newModule.Route, + Order = newModule.Order, + Application = newModule.Application, + CreatedAt = DateTime.UtcNow, + CreatedBy = httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty, + }; + } + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/PermissionMapper.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/PermissionMapper.cs new file mode 100644 index 0000000..aee6861 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/PermissionMapper.cs @@ -0,0 +1,39 @@ +// *********************************************************************** +// +// HEATH +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Microsoft.AspNetCore.Http; +using MongoDB.Bson; +using System.Security.Claims; +namespace Core.Cerberos.Domain.Contexts.Onboarding.Mappers +{ + /// + /// Handles mappings between + /// , + /// and + /// + public static class PermissionMapper + { + /// + /// Maps the permissionRequest to PermissionAdapter. + /// + /// The Permission to be mapped. + /// A representing + /// the asynchronous execution of the service. + public static PermissionAdapter ToAdapter(this PermissionRequest newPermission, IHttpContextAccessor httpContextAccessor) + { + return new PermissionAdapter + { + Id = ObjectId.GenerateNewId().ToString(), + Name = newPermission.Name, + Description = newPermission.Description, + CreatedAt = DateTime.UtcNow, + CreatedBy = httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty, + AccessLevel = newPermission.AccessLevel + }; + } + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/RoleMapper.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/RoleMapper.cs new file mode 100644 index 0000000..17619b9 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/RoleMapper.cs @@ -0,0 +1,42 @@ +// *********************************************************************** +// +// HEATH +// +// *********************************************************************** + +using Core.Cerberos.Adapters; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Microsoft.AspNetCore.Http; +using MongoDB.Bson; +using System.Security.Claims; +namespace Core.Cerberos.Domain.Contexts.Onboarding.Mappers +{ + /// + /// Handles mappings between + /// , + /// and + /// + public static class RoleMapper + { + /// + /// Maps the RoleRequest to RoleAdapter. + /// + /// The Role to be mapped. + /// A representing + /// the asynchronous execution of the service. + public static RoleAdapter ToAdapter(this RoleRequest newRole, IHttpContextAccessor httpContextAccessor) + { + return new RoleAdapter + { + Id = ObjectId.GenerateNewId().ToString(), + Name = newRole.Name, + Description = newRole.Description, + Applications = newRole.Applications, + Modules = newRole.Modules, + Permissions = newRole.Permissions, + CreatedAt = DateTime.UtcNow, + CreatedBy = httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty + }; + } + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/UserMapper.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/UserMapper.cs new file mode 100644 index 0000000..fd055ef --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Mappers/UserMapper.cs @@ -0,0 +1,46 @@ +// *********************************************************************** +// +// HEATH +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Microsoft.AspNetCore.Http; +using MongoDB.Bson; +using System.Security.Claims; + +namespace Core.Cerberos.Domain.Contexts.Onboarding.Mappers +{ + /// + /// Handles mappings between + /// , + /// and + /// + public static class UserMapper + { + /// + /// Maps the UserRequest to UserAdapter. + /// + /// The User to be mapped. + /// A representing + /// the asynchronous execution of the service. + public static UserAdapter ToAdapter(this UserRequest newUser, IHttpContextAccessor httpContextAccessor) + { + return new UserAdapter + { + Id = ObjectId.GenerateNewId().ToString(), + Guid = Guid.NewGuid().ToString(), + Email = newUser.Email, + Name = newUser.Name, + MiddleName = newUser.MiddleName, + LastName = newUser.LastName, + DisplayName = $"{newUser.Name} {newUser.MiddleName} {newUser.LastName}", + RoleId = newUser.RoleId, + Companies = newUser.Companies, + Projects = newUser.Projects, + CreatedAt = DateTime.UtcNow, + CreatedBy = httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty + }; + } + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Request/ModuleRequest.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Request/ModuleRequest.cs new file mode 100644 index 0000000..aed86ac --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Request/ModuleRequest.cs @@ -0,0 +1,68 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Core.Cerberos.Adapters.Common.Enums; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System.Text.Json.Serialization; + +namespace Core.Cerberos.Domain.Contexts.Onboarding.Request +{ + /// + /// Data transfer object (DTO) for adding modules. + /// + public class ModuleRequest + { + /// + /// Gets or sets the name of the module. + /// + [BsonElement("name")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the description of the module. + /// + [BsonElement("description")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// + /// Gets or sets the icon of the module. + /// + [BsonElement("icon")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("icon")] + public string? Icon { get; set; } + + /// + /// Gets or sets the route of the module. + /// + [BsonElement("route")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("route")] + public string Route { get; set; } = null!; + + /// + /// Gets or sets the order of the module. + /// + [BsonElement("order")] + [BsonRepresentation(BsonType.Int32)] + [JsonPropertyName("order")] + public int? Order { get; set; } + + /// + /// Gets or sets the application of the module. + /// + [BsonElement("application")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("application")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public ApplicationsEnum? Application { get; set; } = null!; + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Request/PermissionRequest.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Request/PermissionRequest.cs new file mode 100644 index 0000000..c265619 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Request/PermissionRequest.cs @@ -0,0 +1,44 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Core.Cerberos.Adapters.Common.Constants; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System.Text.Json.Serialization; + +namespace Core.Cerberos.Domain.Contexts.Onboarding.Request +{ + /// + /// Data transfer object (DTO) for adding permissions. + /// + public class PermissionRequest + { + /// + /// Gets or sets the name of the permission. + /// + [BsonElement("name")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the description of the permission. + /// + [BsonElement("description")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// + /// Gets or sets the access level of the permission. + /// + [BsonElement("accessLevel")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("accessLevel")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public AccessLevelEnum? AccessLevel { get; set; } = null!; + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Request/RoleRequest.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Request/RoleRequest.cs new file mode 100644 index 0000000..a51fb84 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Request/RoleRequest.cs @@ -0,0 +1,57 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Core.Cerberos.Adapters.Common.Enums; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System.Text.Json.Serialization; + +namespace Core.Cerberos.Domain.Contexts.Onboarding.Request +{ + /// + /// Data transfer object (DTO) for adding a role. + /// + public class RoleRequest + { + /// + /// Gets or sets the name of the role. + /// + [BsonElement("name")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the description of the role. + /// + [BsonElement("description")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// + /// Gets or sets the status of the entity. + /// + [BsonElement("applications")] + [JsonPropertyName("applications")] + [JsonConverter(typeof(EnumArrayJsonConverter))] + public ApplicationsEnum[]? Applications { get; set; } + + /// + /// Gets or sets the modules of the role. + /// + [BsonElement("modules")] + [JsonPropertyName("modules")] + public string[] Modules { get; set; } = null!; + + /// + /// Gets or sets the permissions of the role. + /// + [BsonElement("permissions")] + [JsonPropertyName("permissions")] + public string[] Permissions { get; set; } = null!; + } +} diff --git a/Core.Cerberos.Domain/Contexts/Onboarding/Request/UserRequest.cs b/Core.Cerberos.Domain/Contexts/Onboarding/Request/UserRequest.cs new file mode 100644 index 0000000..048d793 --- /dev/null +++ b/Core.Cerberos.Domain/Contexts/Onboarding/Request/UserRequest.cs @@ -0,0 +1,72 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System.Text.Json.Serialization; + +namespace Core.Cerberos.Domain.Contexts.Onboarding.Request +{ + /// + /// Data transfer object (DTO) for adding a user. + /// + public class UserRequest + { + /// + /// Gets or sets the email address of the user. + /// + [BsonElement("email")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("email")] + public string Email { get; set; } = null!; + + /// + /// Gets or sets the name of the user. + /// + [BsonElement("name")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the middlename of the user. + /// + [BsonElement("middleName")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("middleName")] + public string MiddleName { get; set; } = null!; + + /// + /// Gets or sets the last name of the user. + /// + [BsonElement("lastName")] + [BsonRepresentation(BsonType.String)] + [JsonPropertyName("lastName")] + public string LastName { get; set; } = null!; + + /// + /// Gets or sets the role ID of the user. + /// + [BsonElement("roleId")] + [BsonRepresentation(BsonType.ObjectId)] + [JsonPropertyName("roleId")] + public string RoleId { get; set; } = null!; + + /// + /// Gets or sets the array of companies associated with the user. + /// + [BsonElement("companies")] + [JsonPropertyName("companies")] + public string[] Companies { get; set; } = null!; + + /// + /// Gets or sets the array of projects associated with the user. + /// + [BsonElement("projects")] + [JsonPropertyName("projects")] + public string[]? Projects { get; set; } + } +} diff --git a/Core.Cerberos.Domain/Core.Cerberos.Domain.csproj b/Core.Cerberos.Domain/Core.Cerberos.Domain.csproj new file mode 100644 index 0000000..f89ca8a --- /dev/null +++ b/Core.Cerberos.Domain/Core.Cerberos.Domain.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Core.Cerberos.Infraestructure/Caching/CacheHelper.cs b/Core.Cerberos.Infraestructure/Caching/CacheHelper.cs new file mode 100644 index 0000000..7da86a8 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Caching/CacheHelper.cs @@ -0,0 +1,30 @@ +using Core.Cerberos.Infraestructure.Caching.Configs; + +namespace LSA.Core.Dapper.Service.Caching +{ + public static class CacheHelper + { + /// + /// Determines the cache duration based on specific duration, settings, or a default value. + /// + /// Specific cache duration in minutes, if provided. + /// General cache settings containing default duration values. + /// The cache duration as a TimeSpan. + public static TimeSpan GetCacheDuration(CacheSettings cacheSettings, int? specificCacheDuration = 0) + { + var defaultCacheDuration = TimeSpan.FromMinutes(.5); + + if (specificCacheDuration.HasValue && specificCacheDuration.Value > 0) + { + return TimeSpan.FromMinutes(specificCacheDuration.Value); + } + + if (cacheSettings.DefaultCacheDurationInMinutes > 0) + { + return TimeSpan.FromMinutes(cacheSettings.DefaultCacheDurationInMinutes); + } + + return defaultCacheDuration; + } + } +} diff --git a/Core.Cerberos.Infraestructure/Caching/CacheKeyHelper.cs b/Core.Cerberos.Infraestructure/Caching/CacheKeyHelper.cs new file mode 100644 index 0000000..d1db226 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Caching/CacheKeyHelper.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace LSA.Core.Dapper.Service.Caching +{ + public static class CacheKeyHelper + { + public static string GenerateCacheKey(object instance, string methodName, params object[] parameters) + { + var className = instance.GetType().Name; + var keyBuilder = new StringBuilder($"{className}.{methodName}"); + + foreach (var param in parameters) + { + string normalizedParam = NormalizeParameter(param); + keyBuilder.Append($".{normalizedParam}"); + } + + return keyBuilder.ToString(); + } + + private static string NormalizeParameter(object param) + { + if (param == null) + { + return "null"; + } + + string paramString; + + if (param is DateTime dateTime) + { + paramString = dateTime.ToString("yyyyMMdd"); + } + else + { + paramString = param.ToString(); + } + + // Replace special characters with an underscore + string normalizedParam = Regex.Replace(paramString, @"[^a-zA-Z0-9]", "_"); + + return normalizedParam; + } + } +} diff --git a/Core.Cerberos.Infraestructure/Caching/CacheService.cs b/Core.Cerberos.Infraestructure/Caching/CacheService.cs new file mode 100644 index 0000000..6e7ac4f --- /dev/null +++ b/Core.Cerberos.Infraestructure/Caching/CacheService.cs @@ -0,0 +1,133 @@ +using Azure.Identity; +using Core.Cerberos.Infraestructure.Caching.Contracts; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; +using System.Text.Json; + +namespace LSA.Core.Dapper.Service.Caching +{ + public class CacheService : ICacheService + { + private IDatabase _cacheDatabase = null!; + private readonly ILogger _logger; + + + public CacheService(string connectionString, ILogger logger) + { + _logger = logger; + Task.Run(async () => + { + _cacheDatabase = await GetRedisDatabase(connectionString); + }).Wait(); + } + + private async Task GetRedisDatabase(string connectionString) + { + try + { + var configurationOptions = await ConfigurationOptions.Parse($"{connectionString}") + .ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); + + configurationOptions.AbortOnConnectFail = false; + var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions); + + _logger.LogInformation("Successfully connected to Redis."); + + return connectionMultiplexer.GetDatabase(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error establishing Redis connection."); + throw; + } + } + + + public async Task GetAsync(string key) + { + try + { + var value = await _cacheDatabase.StringGetAsync(key); + if (value.IsNullOrEmpty) + { + _logger.LogInformation($"Cache miss for key: {key}"); + return default; + } + + _logger.LogInformation($"Cache hit for key: {key}"); + return JsonSerializer.Deserialize(value); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error getting cache item with key {key}"); + throw; + } + } + + public async Task SetAsync(string key, T value, TimeSpan? expiry = null) + { + try + { + var json = JsonSerializer.Serialize(value); + await _cacheDatabase.StringSetAsync(key, json, expiry); + _logger.LogInformation($"Cache item set with key: {key}"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error setting cache item with key {key}"); + throw; + } + } + + public async Task RemoveAsync(string key) + { + try + { + await _cacheDatabase.KeyDeleteAsync(key); + _logger.LogInformation($"Cache item removed with key: {key}"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error removing cache item with key {key}"); + throw; + } + } + + public async Task ExistsAsync(string key) + { + try + { + var exists = await _cacheDatabase.KeyExistsAsync(key); + _logger.LogInformation($"Cache item exists check for key: {key} - {exists}"); + return exists; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error checking existence of cache item with key {key}"); + throw; + } + } + + public async Task RefreshAsync(string key, TimeSpan? expiry = null) + { + try + { + var value = await _cacheDatabase.StringGetAsync(key); + if (!value.IsNullOrEmpty) + { + await _cacheDatabase.StringSetAsync(key, value, expiry); + _logger.LogInformation($"Cache item refreshed with key: {key}"); + } + else + { + _logger.LogWarning($"Cache item with key: {key} does not exist, cannot refresh"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error refreshing cache item with key {key}"); + throw; + } + } + } +} diff --git a/Core.Cerberos.Infraestructure/Caching/Configs/CacheSettings.cs b/Core.Cerberos.Infraestructure/Caching/Configs/CacheSettings.cs new file mode 100644 index 0000000..cf35292 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Caching/Configs/CacheSettings.cs @@ -0,0 +1,7 @@ +namespace Core.Cerberos.Infraestructure.Caching.Configs +{ + public class CacheSettings + { + public int DefaultCacheDurationInMinutes { get; set; } + } +} \ No newline at end of file diff --git a/Core.Cerberos.Infraestructure/Caching/Contracts/ICacheService.cs b/Core.Cerberos.Infraestructure/Caching/Contracts/ICacheService.cs new file mode 100644 index 0000000..3c70ff1 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Caching/Contracts/ICacheService.cs @@ -0,0 +1,11 @@ +namespace Core.Cerberos.Infraestructure.Caching.Contracts +{ + public interface ICacheService + { + Task GetAsync(string key); + Task SetAsync(string key, T value, TimeSpan? expiry = null); + Task RemoveAsync(string key); + Task ExistsAsync(string key); + Task RefreshAsync(string key, TimeSpan? expiry = null); + } +} diff --git a/Core.Cerberos.Infraestructure/Contexts/Mongo/ConnectionStringProvider.cs b/Core.Cerberos.Infraestructure/Contexts/Mongo/ConnectionStringProvider.cs new file mode 100644 index 0000000..c4b2995 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Contexts/Mongo/ConnectionStringProvider.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Configuration; + +namespace Core.Cerberos.Infraestructure.Contexts.Mongo +{ + public class ConnectionStringProvider(IConfiguration configuration) : IConnectionStringProvider + { + public string ConnectionString { get; set; } = string.Empty; + public string Databasename { get; set; } = string.Empty; + public string Audience { get; set; } = string.Empty; + + public string GetConnectionString() + { + return configuration?.GetConnectionString("DefaultConnection")?.ToString() ?? string.Empty; + } + public string GetDatabasename() + { + return configuration.GetSection("MongoDb:DatabaseName").Value ?? string.Empty; + } + + public string GetAudience() + { + return configuration.GetSection("MongoDb:Audience").Value ?? string.Empty; + } + } + public interface IConnectionStringProvider + { + string GetConnectionString(); + string GetDatabasename(); + string ConnectionString { get; set; } + string Databasename { get; set; } + string Audience { get; set; } + } +} diff --git a/Core.Cerberos.Infraestructure/Contexts/Mongo/MongoConnSettings.cs b/Core.Cerberos.Infraestructure/Contexts/Mongo/MongoConnSettings.cs new file mode 100644 index 0000000..652bea1 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Contexts/Mongo/MongoConnSettings.cs @@ -0,0 +1,14 @@ +namespace Core.Cerberos.Infraestructure.Contexts.Mongo; + +public interface IMongoConnSettings +{ + string ConnectionString { get; set; } + string Databasename { get; set; } + string Audience { get; set; } +} +public class MongoConnSettings : IMongoConnSettings +{ + public string ConnectionString { get; set; } + public string Databasename { get; set; } + public string Audience { get; set; } +} diff --git a/Core.Cerberos.Infraestructure/Core.Cerberos.Infrastructure.csproj b/Core.Cerberos.Infraestructure/Core.Cerberos.Infrastructure.csproj new file mode 100644 index 0000000..fb84c78 --- /dev/null +++ b/Core.Cerberos.Infraestructure/Core.Cerberos.Infrastructure.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Core.Cerberos.Infraestructure/PerformanceCacheService/CacheAttribute.cs b/Core.Cerberos.Infraestructure/PerformanceCacheService/CacheAttribute.cs new file mode 100644 index 0000000..d1bfbec --- /dev/null +++ b/Core.Cerberos.Infraestructure/PerformanceCacheService/CacheAttribute.cs @@ -0,0 +1,15 @@ +namespace Core.Cerberos.Infraestructure.PerformanceCacheService +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] + public class CacheAttribute : Attribute + { + public int CacheDurationInMinutes { get; set; } + public bool EnableCaching { get; set; } + + public CacheAttribute(int cacheDurationInMinutes, bool enableCaching = true) + { + CacheDurationInMinutes = cacheDurationInMinutes; + EnableCaching = enableCaching; + } + } +} diff --git a/Core.Cerberos.Provider/Contracts/IModuleService.cs b/Core.Cerberos.Provider/Contracts/IModuleService.cs new file mode 100644 index 0000000..0fa7138 --- /dev/null +++ b/Core.Cerberos.Provider/Contracts/IModuleService.cs @@ -0,0 +1,64 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; + +namespace Core.Cerberos.Provider.Contracts +{ + public interface IModuleService + { + /// + /// Creates a new Module. + /// + /// The Module to be created. + /// A representing + /// the asynchronous execution of the service. + Task CreateModuleService(ModuleRequest newModule); + + /// + /// Gets an Module by identifier. + /// + /// The Module identifier. + /// A representing + /// the asynchronous execution of the service. + Task GetModuleByIdService(string id); + + /// + /// Gets all the roles. + /// + /// A representing + /// the asynchronous execution of the service. + Task> GetAllModulesService(); + + /// + /// Gets all the permissions by permissions identifier list. + /// + /// The list of permissions identifiers. + /// A representing + /// the asynchronous execution of the service. + Task> GetAllModulesByListService(string[] permissions); + + /// + /// Changes the status of the permission. + /// + /// The permission identifier. + /// The new status of the permission. + /// The updated entity. + /// A representing + /// the asynchronous execution of the service. + Task ChangeModuleStatusService(string id, StatusEnum newStatus); + + /// + /// Updates a Module by id. + /// + /// The Module to be updated. + /// The Module identifier. + /// A representing + /// the asynchronous execution of the service. + Task UpdateModuleService(ModuleAdapter entity, string id); + } +} diff --git a/Core.Cerberos.Provider/Contracts/IPermissionService.cs b/Core.Cerberos.Provider/Contracts/IPermissionService.cs new file mode 100644 index 0000000..d701b45 --- /dev/null +++ b/Core.Cerberos.Provider/Contracts/IPermissionService.cs @@ -0,0 +1,64 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; + +namespace Core.Cerberos.Provider.Contracts +{ + public interface IPermissionService + { + /// + /// Creates a new Permission. + /// + /// The Permission to be created. + /// A representing + /// the asynchronous execution of the service. + Task CreatePermissionService(PermissionRequest newPermission); + + /// + /// Gets an Permission by identifier. + /// + /// The Permission identifier. + /// A representing + /// the asynchronous execution of the service. + Task GetPermissionByIdService(string id); + + /// + /// Gets all the roles. + /// + /// A representing + /// the asynchronous execution of the service. + Task> GetAllPermissionsService(); + + /// + /// Gets all the permissions by permissions identifier list. + /// + /// The list of permissions identifiers. + /// A representing + /// the asynchronous execution of the service. + Task> GetAllPermissionsByListService(string[] permissions); + + /// + /// Changes the status of the permission. + /// + /// The permission identifier. + /// The new status of the permission. + /// The updated entity. + /// A representing + /// the asynchronous execution of the service. + Task ChangePermissionStatusService(string id, StatusEnum newStatus); + + /// + /// Updates a Permission by id. + /// + /// The Permission to be updated. + /// The Permission identifier. + /// A representing + /// the asynchronous execution of the service. + Task UpdatePermissionService(PermissionAdapter entity, string id); + } +} diff --git a/Core.Cerberos.Provider/Contracts/IRoleService.cs b/Core.Cerberos.Provider/Contracts/IRoleService.cs new file mode 100644 index 0000000..7386146 --- /dev/null +++ b/Core.Cerberos.Provider/Contracts/IRoleService.cs @@ -0,0 +1,72 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; + +namespace Core.Cerberos.Provider.Contracts +{ + public interface IRoleService + { + /// + /// Creates a new Role. + /// + /// The Role to be created. + /// A representing + /// the asynchronous execution of the service. + Task CreateRoleService(RoleRequest newRole); + + /// + /// Gets an Role by identifier. + /// + /// The Role identifier. + /// A representing + /// the asynchronous execution of the service. + Task GetRoleByIdService(string id); + + /// + /// Gets all the roles. + /// + /// A representing + /// the asynchronous execution of the service. + Task> GetAllRolesService(); + + /// + /// Changes the status of the role. + /// + /// The role identifier. + /// The new status of the role. + /// The updated entity. + /// A representing + /// the asynchronous execution of the service. + Task ChangeRoleStatusService(string id, StatusEnum newStatus); + + /// + /// Updates a Role by id. + /// + /// The Role to be updated. + /// The Role identifier. + /// A representing + /// the asynchronous execution of the service. + Task UpdateRoleService(RoleAdapter entity, string id); + + /// + /// Adds an application to the role's list of applications. + /// + /// The identifier of the role to which the application will be added. + /// The application enum value to add. + /// A representing the asynchronous operation, with the updated role object. + Task AddApplicationToRoleService(string roleId, ApplicationsEnum application); + + /// + /// Removes an application from the role's list of applications. + /// + /// The identifier of the role from which the application will be removed. + /// The application enum value to remove. + /// A representing the asynchronous operation, with the updated role object. + Task RemoveApplicationFromRoleService(string roleId, ApplicationsEnum application); + } +} diff --git a/Core.Cerberos.Provider/Contracts/IUserService.cs b/Core.Cerberos.Provider/Contracts/IUserService.cs new file mode 100644 index 0000000..7b2e9dd --- /dev/null +++ b/Core.Cerberos.Provider/Contracts/IUserService.cs @@ -0,0 +1,143 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Blueprint.Storage.Adapters; +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; + +namespace Core.Cerberos.Provider.Contracts +{ + public interface IUserService + { + /// + /// Creates a new User. + /// + /// The User to be created. + /// A representing + /// the asynchronous execution of the service. + Task CreateUserService(UserRequest newUser); + + /// + /// Gets an User by identifier. + /// + /// The User identifier. + /// A representing + /// the asynchronous execution of the service. + Task GetUserByIdService(string id); + + + /// + /// Gets all the users. + /// + /// A representing + /// the asynchronous execution of the service. + Task> GetAllUsersService(); + + /// + /// Gets an User by email. + /// + /// The User email. + /// A representing + /// the asynchronous execution of the service. + Task GetUserByEmailService(string? email); + + /// + /// Validates if a users exists by email. + /// + /// The User email. + /// A representing + /// the asynchronous execution of the service. + Task ValidateUserExistenceService(string? email); + + /// + /// Changes the status of the user. + /// + /// The user identifier. + /// The new status of the user. + /// A representing + /// the asynchronous execution of the service. + Task ChangeUserStatusService(string id, StatusEnum newStatus); + + /// + /// Updates a User by id. + /// + /// The User to be updated. + /// The User identifier. + /// A representing + /// the asynchronous execution of the service. + Task UpdateUserService(UserAdapter entity, string id); + + /// + /// Logs in the user. + /// + /// The User's email. + /// A representing + /// the asynchronous execution of the service. + Task LogInUserService(string email); + + /// + /// Logs out the user's session. + /// + /// The User's email. + /// A representing + /// the asynchronous execution of the service. + Task LogOutUserSessionService(string email); + + /// + /// Adds a company to the user's list of companies. + /// + /// The identifier of the user to whom the company will be added. + /// The identifier of the company to add. + /// A representing the asynchronous operation, with the updated user object. + Task AddCompanyToUserService(string userId, string companyId); + + /// + /// Removes a company from the user's list of companies. + /// + /// The identifier of the user from whom the company will be removed. + /// The identifier of the company to remove. + /// A representing the asynchronous operation, with the updated user object. + Task RemoveCompanyFromUserService(string userId, string companyId); + + /// + /// Adds a project to the user's list of projects. + /// + /// The identifier of the user to whom the project will be added. + /// The identifier of the project to add. + /// A representing the asynchronous operation, with the updated user object. + Task AddProjectToUserService(string userId, string projectId); + + + /// + /// Removes a project from the user's list of projects. + /// + /// The identifier of the user from whom the project will be removed. + /// The identifier of the project to remove. + /// A representing the asynchronous operation, with the updated user object. + Task RemoveProjectFromUserService(string userId, string projectId); + + /// + /// Gets the token adapter for a user. + /// + /// The user's email. + /// A representing the asynchronous execution of the service. + Task GetTokenAdapter(string email); + + /// + /// Get Consent Form PDF. + /// + /// A representing + /// the asynchronous execution of the service. + Task GetConsentFormPDFService(); + + /// + /// Accept user consent form. + /// + /// A representing + /// the asynchronous execution of the service. + Task AcceptUserConsentFormService(); + } +} diff --git a/Core.Cerberos.Provider/Core.Cerberos.Provider.csproj b/Core.Cerberos.Provider/Core.Cerberos.Provider.csproj new file mode 100644 index 0000000..aab3e6c --- /dev/null +++ b/Core.Cerberos.Provider/Core.Cerberos.Provider.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/Core.Cerberos.Provider/Providers/BaseProvider.cs b/Core.Cerberos.Provider/Providers/BaseProvider.cs new file mode 100644 index 0000000..c28925e --- /dev/null +++ b/Core.Cerberos.Provider/Providers/BaseProvider.cs @@ -0,0 +1,16 @@ +using MongoDB.Driver; + +namespace Core.Cerberos.Provider.Providers +{ + public class BaseProvider + { + private readonly IMongoDatabase _database; + + public BaseProvider(IMongoDatabase database) + { + _database = database ?? throw new ArgumentNullException(nameof(database)); + } + + protected IMongoDatabase Database => _database; + } +} \ No newline at end of file diff --git a/Core.Cerberos.Provider/Providers/HeathOidcCallback.cs b/Core.Cerberos.Provider/Providers/HeathOidcCallback.cs new file mode 100644 index 0000000..d69ed2d --- /dev/null +++ b/Core.Cerberos.Provider/Providers/HeathOidcCallback.cs @@ -0,0 +1,99 @@ +using Azure.Core; +using Azure.Identity; +using Core.Cerberos.Adapters.Common.Constants; +using MongoDB.Driver.Authentication.Oidc; + +namespace Core.Cerberos.Provider.Providers +{ + public class HeathOidcCallback : IOidcCallback + { + private readonly string _audience; + private readonly string _environment; + public HeathOidcCallback(string audience) + { + _audience = audience; + _environment = Environment.GetEnvironmentVariable(EnvironmentVariables.Stage) ?? string.Empty; + } + + public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken) + { + try + { + AccessToken token; + + TokenRequestContext tokenRequestContext = + new TokenRequestContext( + new[] { _audience } + ); + + if (_environment == "Local") + { + token = + new ChainedTokenCredential( + new ManagedIdentityCredential(), + new VisualStudioCredential(), + new VisualStudioCodeCredential(), + new SharedTokenCacheCredential() + ) + .GetToken( + tokenRequestContext + ); + } + else + { + token = + new ManagedIdentityCredential() + .GetToken( + tokenRequestContext + ); + } + + return new(token.Token, expiresIn: null); + } + catch (Exception ex) + { + + throw new Exception($"An error ocurred while trying to get the OIDC token to connect to the database, ERROR: {ex.Message}"); + } + } + + public async Task GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken) + { + try + { + TokenRequestContext tokenRequestContext = + new TokenRequestContext( + new[] { _audience } + ); + + AccessToken token; + + if (_environment == "Local") + { + token = await new ChainedTokenCredential( + new ManagedIdentityCredential(), + new VisualStudioCredential(), + new VisualStudioCodeCredential(), + new SharedTokenCacheCredential() + ) + .GetTokenAsync( + tokenRequestContext, cancellationToken + ).ConfigureAwait(false); + } + else + { + token = await new ManagedIdentityCredential() + .GetTokenAsync( + tokenRequestContext, cancellationToken + ).ConfigureAwait(false); + } + + return new(token.Token, expiresIn: null); + } + catch (Exception ex) + { + throw new Exception($"An error ocurred while trying to get the OIDC token to connect to the database, ERROR: {ex.Message}"); + } + } + } +} diff --git a/Core.Cerberos.Provider/Providers/Helper.cs b/Core.Cerberos.Provider/Providers/Helper.cs new file mode 100644 index 0000000..0b9f1e7 --- /dev/null +++ b/Core.Cerberos.Provider/Providers/Helper.cs @@ -0,0 +1,27 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** + +using Microsoft.AspNetCore.Http; +using System.Security.Claims; + +namespace Core.Cerberos.Provider.Providers +{ + /// + /// Provides helper methods for common operations. + /// + public static class Helper + { + /// + /// Retrieves the email address of the authenticated user from the HTTP context. + /// + /// The IHttpContextAccessor instance to access the current HTTP context. + /// The email address of the authenticated user, or an empty string if not available. + public static string GetEmail(IHttpContextAccessor httpContextAccessor) + { + return httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.Email)?.Value ?? ""; + } + } +} diff --git a/Core.Cerberos.Provider/Providers/Onboarding/ModuleService.cs b/Core.Cerberos.Provider/Providers/Onboarding/ModuleService.cs new file mode 100644 index 0000000..2a3acad --- /dev/null +++ b/Core.Cerberos.Provider/Providers/Onboarding/ModuleService.cs @@ -0,0 +1,256 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Mappers; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Infraestructure.Caching.Configs; +using Core.Cerberos.Infraestructure.Caching.Contracts; +using Core.Cerberos.Provider.Contracts; +using LSA.Core.Dapper.Service.Caching; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Core.Cerberos.Provider.Providers.Onboarding +{ + /// + /// Handles all services and business rules related to . + /// + public class ModuleService(ILogger logger, IHttpContextAccessor httpContextAccessor, ICacheService cacheService, + IOptions cacheSettings, IMongoDatabase database) : IModuleService + { + private readonly CacheSettings _cacheSettings = cacheSettings.Value; + + /// + /// Creates a new Module. + /// + /// The Module to be created. + /// A representing + /// the asynchronous execution of the service. + public async Task CreateModuleService(ModuleRequest newModule) + { + try + { + var entity = newModule.ToAdapter(httpContextAccessor); + entity.Order = (entity.Order is not null) ? entity.Order : await GetLastOrderModule(newModule); + await database.GetCollection(CollectionNames.Module).InsertOneAsync(entity); + entity.Id = (entity as dynamic ?? "").Id.ToString(); + + return entity; + } + catch (Exception ex) + { + logger.LogError(ex, $"CreateModuleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets an Module by identifier. + /// + /// The Module identifier. + /// A representing + /// the asynchronous execution of the service.0 + public async Task GetModuleByIdService(string id) + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetModuleByIdService", id); + var cachedData = await cacheService.GetAsync(cacheKey); + + if (cachedData is not null) { return cachedData; } + + try + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("_id", ObjectId.Parse(id)), + Builders.Filter.Eq("status", StatusEnum.Active.ToString()) + ); + + var module = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .FirstOrDefaultAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, module, cacheDuration); + + return module; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetModuleByIdService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets all the modules. + /// + /// A representing + /// the asynchronous execution of the service. + public async Task> GetAllModulesService() + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllModulesService"); + var cachedData = await cacheService.GetAsync>(cacheKey) ?? []; + + if (cachedData.Any()) return cachedData; + + try + { + var filter = Builders.Filter.Eq("status", StatusEnum.Active.ToString()); + + var roles = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .SortBy(m => m.Application) + .ThenBy(m => m.Order) + .ToListAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, roles, cacheDuration); + + return roles; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetAllModulesService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets all the modules by modules identifier list. + /// + /// The list of modules identifiers. + /// A representing + /// the asynchronous execution of the service. + public async Task> GetAllModulesByListService(string[] modules) + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllModulesByListService", modules); + + var cachedData = await cacheService.GetAsync>(cacheKey); + + if (cachedData != null && cachedData.Any()) return cachedData; + + try + { + var objectIds = modules.Select(id => ObjectId.Parse(id)).ToArray(); + + var filter = Builders.Filter.In("_id", objectIds) + & Builders.Filter.Eq("status", StatusEnum.Active.ToString()); + + var roles = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .SortBy(m => m.Application) + .ThenBy(m => m.Order) + .ToListAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, roles, cacheDuration); + + return roles; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetAllModulesByListService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + + /// + /// Changes the status of the module. + /// + /// The module identifier. + /// The new status of the module. + /// A representing + /// the asynchronous execution of the service. + public async Task ChangeModuleStatusService(string id, StatusEnum newStatus) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Status, newStatus) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Module).UpdateOneAsync(filter, update); + + var updatedModule = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedModule; + } + catch (Exception ex) + { + logger.LogError(ex, $"ChangeModuleStatusService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Updates a Module by id. + /// + /// The Module to be updated. + /// The Module identifier. + /// A representing + /// the asynchronous execution of the service. + public async Task UpdateModuleService(ModuleAdapter entity, string id) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Name, entity.Name) + .Set(v => v.Description, entity.Description) + .Set(v => v.Icon, entity.Icon) + .Set(v => v.Route, entity.Route) + .Set(v => v.Order, entity.Order) + .Set(v => v.Application, entity.Application) + .Set(v => v.Status, entity.Status) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Module).UpdateOneAsync(filter, update); + + var updatedModule = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedModule; + } + catch (Exception ex) + { + logger.LogError(ex, $"UpdateModuleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + private async Task GetLastOrderModule(ModuleRequest newModule) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("status", StatusEnum.Active.ToString()), + Builders.Filter.Eq("application", newModule.Application.ToString())); + + var maxOrderModule = await database.GetCollection(CollectionNames.Module) + .Find(filter) + .SortByDescending(m => m.Order) + .FirstOrDefaultAsync(); + + return (maxOrderModule is not null && maxOrderModule.Order is not null) ? maxOrderModule.Order : 0; + } + } +} diff --git a/Core.Cerberos.Provider/Providers/Onboarding/PermissionService.cs b/Core.Cerberos.Provider/Providers/Onboarding/PermissionService.cs new file mode 100644 index 0000000..d1da585 --- /dev/null +++ b/Core.Cerberos.Provider/Providers/Onboarding/PermissionService.cs @@ -0,0 +1,234 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Mappers; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Infraestructure.Caching.Configs; +using Core.Cerberos.Infraestructure.Caching.Contracts; +using Core.Cerberos.Provider.Contracts; +using LSA.Core.Dapper.Service.Caching; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Core.Cerberos.Provider.Providers.Onboarding +{ + /// + /// Handles all services and business rules related to . + /// + public class PermissionService(ILogger logger, IHttpContextAccessor httpContextAccessor, ICacheService cacheService, + IOptions cacheSettings, IMongoDatabase database) : IPermissionService + { + private readonly CacheSettings _cacheSettings = cacheSettings.Value; + + /// + /// Creates a new Permission. + /// + /// The Permission to be created. + /// A representing + /// the asynchronous execution of the service. + public async Task CreatePermissionService(PermissionRequest newPermission) + { + try + { + var entity = newPermission.ToAdapter(httpContextAccessor); + await database.GetCollection(CollectionNames.Permission).InsertOneAsync(entity); + entity.Id = (entity as dynamic ?? "").Id.ToString(); + + return entity; + } + catch (Exception ex) + { + logger.LogError(ex, $"CreatePermissionService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets an Permission by identifier. + /// + /// The Permission identifier. + /// A representing + /// the asynchronous execution of the service.0 + public async Task GetPermissionByIdService(string id) + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetPermissionByIdService", id); + var cachedData = await cacheService.GetAsync(cacheKey); + + if (cachedData is not null) { return cachedData; } + + try + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("_id", ObjectId.Parse(id)), + Builders.Filter.Eq("status", StatusEnum.Active.ToString()) + ); + + var permission = await database.GetCollection(CollectionNames.Permission) + .Find(filter) + .FirstOrDefaultAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, permission, cacheDuration); + + return permission; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetPermissionByIdService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets all the permissions. + /// + /// A representing + /// the asynchronous execution of the service. + public async Task> GetAllPermissionsService() + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllPermissionsService"); + var cachedData = await cacheService.GetAsync>(cacheKey) ?? []; + + if (cachedData.Any()) return cachedData; + + try + { + var filter = Builders.Filter.Eq("status", StatusEnum.Active.ToString()); + + var roles = await database.GetCollection(CollectionNames.Permission) + .Find(filter) + .ToListAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, roles, cacheDuration); + + return roles; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetAllPermissionsService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets all the permissions by permissions identifier list. + /// + /// The list of permissions identifiers. + /// A representing + /// the asynchronous execution of the service. + public async Task> GetAllPermissionsByListService(string[] permissions) + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllPermissionsByListService", permissions); + + var cachedData = await cacheService.GetAsync>(cacheKey); + + if (cachedData != null && cachedData.Any()) return cachedData; + + try + { + var objectIds = permissions.Select(id => ObjectId.Parse(id)).ToArray(); + + var filter = Builders.Filter.In("_id", objectIds) + & Builders.Filter.Eq("status", StatusEnum.Active.ToString()); + + var roles = await database.GetCollection(CollectionNames.Permission) + .Find(filter) + .ToListAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, roles, cacheDuration); + + return roles; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetAllPermissionsByListService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + + /// + /// Changes the status of the permission. + /// + /// The permission identifier. + /// The new status of the permission. + /// A representing + /// the asynchronous execution of the service. + public async Task ChangePermissionStatusService(string id, StatusEnum newStatus) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Status, newStatus) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Permission).UpdateOneAsync(filter, update); + + var updatedPermission = await database.GetCollection(CollectionNames.Permission) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedPermission; + } + catch (Exception ex) + { + logger.LogError(ex, $"ChangePermissionStatusService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Updates a Permission by id. + /// + /// The Permission to be updated. + /// The Permission identifier. + /// A representing + /// the asynchronous execution of the service. + public async Task UpdatePermissionService(PermissionAdapter entity, string id) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Name, entity.Name) + .Set(v => v.Description, entity.Description) + .Set(v => v.AccessLevel, entity.AccessLevel) + .Set(v => v.Status, entity.Status) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Permission).UpdateOneAsync(filter, update); + + var updatedPermission = await database.GetCollection(CollectionNames.Permission) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedPermission; + } + catch (Exception ex) + { + logger.LogError(ex, $"UpdatePermissionService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + } +} diff --git a/Core.Cerberos.Provider/Providers/Onboarding/RoleService.cs b/Core.Cerberos.Provider/Providers/Onboarding/RoleService.cs new file mode 100644 index 0000000..66b90ee --- /dev/null +++ b/Core.Cerberos.Provider/Providers/Onboarding/RoleService.cs @@ -0,0 +1,251 @@ +// *********************************************************************** +// +// Heath +// +// *********************************************************************** +using Core.Cerberos.Adapters; +using Core.Cerberos.Adapters.Common.Constants; +using Core.Cerberos.Adapters.Common.Enums; +using Core.Cerberos.Domain.Contexts.Onboarding.Mappers; +using Core.Cerberos.Domain.Contexts.Onboarding.Request; +using Core.Cerberos.Infraestructure.Caching.Configs; +using Core.Cerberos.Infraestructure.Caching.Contracts; +using Core.Cerberos.Provider.Contracts; +using LSA.Core.Dapper.Service.Caching; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Core.Cerberos.Provider.Providers.Onboarding +{ + /// + /// Handles all services and business rules related to . + /// + public class RoleService(ILogger logger, IHttpContextAccessor httpContextAccessor, ICacheService cacheService, + IOptions cacheSettings, IMongoDatabase database) : IRoleService + { + private readonly CacheSettings _cacheSettings = cacheSettings.Value; + + /// + /// Creates a new Role. + /// + /// The Role to be created. + /// A representing + /// the asynchronous execution of the service. + public async Task CreateRoleService(RoleRequest newRole) + { + try + { + var entity = newRole.ToAdapter(httpContextAccessor); + await database.GetCollection(CollectionNames.Role).InsertOneAsync(entity); + entity.Id = (entity as dynamic ?? "").Id.ToString(); + + return entity; + } + catch (Exception ex) + { + logger.LogError(ex, $"CreateRoleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets an Role by identifier. + /// + /// The Role identifier. + /// A representing + /// the asynchronous execution of the service. + public async Task GetRoleByIdService(string id) + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetRoleByIdService", id); + var cachedData = await cacheService.GetAsync(cacheKey); + + if (cachedData is not null) { return cachedData; } + try + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("_id", ObjectId.Parse(id)), + Builders.Filter.Eq("status", StatusEnum.Active.ToString()) + ); + + var role = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .FirstOrDefaultAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, role, cacheDuration); + + return role; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetRoleByIdService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Gets all the roles. + /// + /// A representing + /// the asynchronous execution of the service. + public async Task> GetAllRolesService() + { + var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllRolesService"); + var cachedData = await cacheService.GetAsync>(cacheKey) ?? []; + + if (cachedData.Any()) return cachedData; + try + { + var filter = Builders.Filter.Eq("status", StatusEnum.Active.ToString()); + + var roles = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .ToListAsync(); + + var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); + + await cacheService.SetAsync(cacheKey, roles, cacheDuration); + + return roles; + } + catch (Exception ex) + { + logger.LogError(ex, $"GetAllRolesService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Changes the status of the role. + /// + /// The role identifier. + /// The new status of the role. + /// A representing + /// the asynchronous execution of the service. + public async Task ChangeRoleStatusService(string id, StatusEnum newStatus) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Status, newStatus) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Role).UpdateOneAsync(filter, update); + + var updatedRole = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedRole; + } + catch (Exception ex) + { + logger.LogError(ex, $"ChangeRoleStatusService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + + /// + /// Updates a Role by id. + /// + /// The Role to be updated. + /// The Role identifier. + /// A representing + /// the asynchronous execution of the service. + public async Task UpdateRoleService(RoleAdapter entity, string id) + { + try + { + var filter = Builders.Filter + .Eq("_id", ObjectId.Parse(id)); + + var update = Builders.Update + .Set(v => v.Name, entity.Name) + .Set(v => v.Description, entity.Description) + .Set(v => v.Applications, entity.Applications) + .Set(v => v.Modules, entity.Modules) + .Set(v => v.Permissions, entity.Permissions) + .Set(v => v.Status, entity.Status) + .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) + .Set(v => v.UpdatedAt, DateTime.UtcNow); + + await database.GetCollection(CollectionNames.Role).UpdateOneAsync(filter, update); + + var updatedRole = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .FirstOrDefaultAsync(); + + return updatedRole; + } + catch (Exception ex) + { + logger.LogError(ex, $"UpdateRoleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + + } + + /// + /// Adds an application to the role's list of applications. + /// + /// The identifier of the role to which the application will be added. + /// The application enum value to add. + /// A representing the asynchronous operation, with the updated role object. + public async Task AddApplicationToRoleService(string roleId, ApplicationsEnum application) + { + try + { + var filter = Builders.Filter.Eq("_id", ObjectId.Parse(roleId)); + var update = Builders.Update.AddToSet(r => r.Applications, application); + + await database.GetCollection(CollectionNames.Role).UpdateOneAsync(filter, update); + + var updatedRole = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .FirstOrDefaultAsync(); + return updatedRole; + } + catch (Exception ex) + { + logger.LogError(ex, $"AddApplicationToRoleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + + } + + /// + /// Removes an application from the role's list of applications. + /// + /// The identifier of the role from which the application will be removed. + /// The application enum value to remove. + /// A representing the asynchronous operation, with the updated role object. + public async Task RemoveApplicationFromRoleService(string roleId, ApplicationsEnum application) + { + try + { + var filter = Builders.Filter.Eq("_id", ObjectId.Parse(roleId)); + var update = Builders.Update.Pull(r => r.Applications, application); + + await database.GetCollection(CollectionNames.Role).UpdateOneAsync(filter, update); + + var updatedRole = await database.GetCollection(CollectionNames.Role) + .Find(filter) + .FirstOrDefaultAsync(); + return updatedRole; + } + catch (Exception ex) + { + logger.LogError(ex, $"RemoveApplicationFromRoleService: Error in getting data - {ex.Message}"); + throw new Exception(ex.Message, ex); + } + } + } +} diff --git a/Core.Cerberos.Provider/ServiceCollectionExtensions.cs b/Core.Cerberos.Provider/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..a053d4d --- /dev/null +++ b/Core.Cerberos.Provider/ServiceCollectionExtensions.cs @@ -0,0 +1,104 @@ +using Core.Blueprint.Storage.Configuration; +using Core.Cerberos.Infraestructure.Caching.Contracts; +using Core.Cerberos.Infraestructure.Contexts.Mongo; +using Core.Cerberos.Provider.Contracts; +using Core.Cerberos.Provider.Providers; +using Core.Cerberos.Provider.Providers.Onboarding; +using LSA.Core.Dapper.Service.Caching; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace Core.Cerberos.Provider +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddDALLayer(this IServiceCollection services, IConfiguration configuration) + { + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty; + + var connectionString = configuration.GetSection("ConnectionStrings:MongoDB").Value ?? string.Empty; + var databaseName = configuration.GetSection("MongoDB:DatabaseName").Value ?? string.Empty; + var audience = (environment == "Local") + ? configuration.GetSection("MongoDB:LocalAudience").Value + : configuration.GetSection("MongoDB:Audience").Value; + + if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(databaseName) || string.IsNullOrEmpty(audience)) + { + throw new InvalidOperationException("Mongo connection is not configured correctly."); + } + + services.Configure(options => + { + options.ConnectionString = connectionString; + options.Databasename = databaseName; + options.Audience = audience ?? string.Empty; + }); + + services.AddSingleton(serviceProvider => + { + var settings = serviceProvider.GetRequiredService>().Value; + var mongoClientSettings = MongoClientSettings.FromConnectionString(settings.ConnectionString); + mongoClientSettings.Credential = MongoCredential.CreateOidcCredential(new HeathOidcCallback(settings.Audience)); + return new MongoClient(mongoClientSettings); + }); + + services.AddSingleton(serviceProvider => + { + var settings = serviceProvider.GetRequiredService>().Value; + var client = serviceProvider.GetRequiredService(); + return client.GetDatabase(settings.Databasename); + }); + + services.AddDALConfigurationLayer(); + services.AddLogs(); + services.AddRedisCacheService(configuration); + services.AddBlobStorage(configuration); + + return services; + } + + + private static IServiceCollection AddDALConfigurationLayer(this IServiceCollection services) + { + services.AddHttpContextAccessor(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; + } + + private static IServiceCollection AddLogs(this IServiceCollection services) + { + services.AddLogging(); + var serviceProvider = services.BuildServiceProvider(); + //var logger = serviceProvider.GetService>(); //Add for Markup class later TODO + + //services.AddSingleton(typeof(ILogger), logger); + + return services; + } + + private static IServiceCollection AddRedisCacheService(this IServiceCollection services, IConfiguration configuration) + { + var source = configuration.GetSection("ConnectionStrings"); + + var redisConnectionString = source["Redis"]?.ToString(); + + if (string.IsNullOrEmpty(redisConnectionString)) + { + throw new InvalidOperationException("Redis connection string is not configured."); + } + + services.AddSingleton(provider => + new CacheService(redisConnectionString, provider.GetRequiredService>())); + + return services; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e074419 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +SharedApplications \ No newline at end of file