Add project files.

This commit is contained in:
Sergio Matias Urquin 2025-04-29 18:57:20 -06:00
parent 62e9799537
commit d5925a6476
44 changed files with 2716 additions and 0 deletions

362
.gitignore vendored Normal file
View File

@ -0,0 +1,362 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

59
.pipelines/GitVersion.yml Normal file
View File

@ -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: []

View File

@ -0,0 +1,46 @@
pool:
vmImage: 'ubuntu-latest'
variables:
project: 'Core.Cerberos.Adapters/Core.Cerberos.Adapters.csproj'
solution: 'Core.Cerberos.Adapters.sln'
buildConfiguration: 'Release'
snykConnectionEndpoint: 'SnykConnection'
feed: '1b3770f1-17db-4bf2-a43d-49f305aa7a22'
artifactName: 'Core.Cerberos'
projectFileName: 'Core.Cerberos.Adapters.csproj'
projectPath: 'Core.Cerberos.Adapters/'
resources:
repositories:
- repository: templates
name: "Template.DevOps.Pipelines"
type: "git"
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: 'Core.Cerberos'
projectKeyOnSonar: 'heathpbu_Core.Cerberos'
feed: '$(feed)'
- template: templates/dotnet/v1/step4_sonar_analysis.yml@templates
- template: templates/dotnet/v1/step5_snyk_analysis.yml@templates
parameters:
snykConnectionEndpoint: '$(snykConnectionEndpoint)'
solutionToScan: '$(solution)'
- template: templates/dotnet/v1/step6_release_nuget.yml@templates
parameters:
artifactName: '$(artifactName)'
project: '$(project)'

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Cerberos.Adapters", "Core.Cerberos.Adapters\Core.Cerberos.Adapters.csproj", "{C902AB37-E6D1-4CE7-B271-0E3969C989F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C902AB37-E6D1-4CE7-B271-0E3969C989F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5BADECD6-CE7F-4167-A29F-AC3D7E4FE4D0}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Core.Cerberos.Adapters
{
public class BaseAdapterResponse
{
public bool HasError { get; set; } = false;
public bool IsSuccess { get; private set; } = false;
public string Message { get; set; }
public void SetResult(string message)
{
HasError = false;
IsSuccess = true;
Message = message;
}
public string SetErrorMessage(string message)
{
var _message = new
{
Content = JsonSerializer.Serialize(message),
HasError = true
};
return JsonSerializer.Serialize(_message);
}
}
}

View File

@ -0,0 +1,118 @@
// ***********************************************************************
// <copyright file="ModuleAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Enums;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters
{
/// <summary>
/// Adapter for representing a module.
/// </summary>
public class ModuleAdapter
{
/// <summary>
/// Gets or sets the ID of the module.
/// </summary>
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the name of the module.
/// </summary>
[BsonElement("name")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets the description of the module.
/// </summary>
[BsonElement("description")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Gets or sets the description of the module.
/// </summary>
[BsonElement("icon")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("icon")]
public string? Icon { get; set; }
/// <summary>
/// Gets or sets the description of the module.
/// </summary>
[BsonElement("route")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("route")]
public string Route { get; set; } = null!;
/// <summary>
/// Gets or sets the order of the module.
/// </summary>
[BsonElement("order")]
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("order")]
public int? Order { get; set; }
/// <summary>
/// Gets or sets the application of the module.
/// </summary>
[BsonElement("application")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("application")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public ApplicationsEnum? Application { get; set; } = null!;
/// <summary>
/// Gets or sets the date and time when the module was created.
/// </summary>
[BsonElement("createdAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Gets or sets the user who created the module.
/// </summary>
[BsonElement("createdBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("createdBy")]
public string? CreatedBy { get; set; }
/// <summary>
/// Gets or sets the date and time when the module was last updated.
/// </summary>
[BsonElement("updatedAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("updatedAt")]
public DateTime? UpdatedAt { get; set; } = null;
/// <summary>
/// Gets or sets the user who last updated the module.
/// </summary>
[BsonElement("updatedBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("updatedBy")]
public string? UpdatedBy { get; set; } = null;
/// <summary>
/// Gets or sets the status of the module.
/// </summary>
[BsonElement("status")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public StatusEnum Status { get; set; } = StatusEnum.Active;
}
}

View File

@ -0,0 +1,95 @@
// ***********************************************************************
// <copyright file="PermissionAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Constants;
using Core.Cerberos.Adapters.Common.Enums;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters
{
/// <summary>
/// Adapter for representing a permission.
/// </summary>
public class PermissionAdapter
{
/// <summary>
/// Gets or sets the ID of the entity.
/// </summary>
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the name of the entity.
/// </summary>
[BsonElement("name")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets the description of the entity.
/// </summary>
[BsonElement("description")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Gets or sets the status of the entity object.
/// </summary>
[BsonElement("accessLevel")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("accessLevel")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AccessLevelEnum? AccessLevel { get; set; } = null!;
/// <summary>
/// Gets or sets the date and time when the entity was created.
/// </summary>
[BsonElement("createdAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Gets or sets the user who created the entity.
/// </summary>
[BsonElement("createdBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("createdBy")]
public string? CreatedBy { get; set; }
/// <summary>
/// Gets or sets the date and time when the entity was last updated.
/// </summary>
[BsonElement("updatedAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("updatedAt")]
public DateTime? UpdatedAt { get; set; } = null;
/// <summary>
/// Gets or sets the user who last updated the entity.
/// </summary>
[BsonElement("updatedBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("updatedBy")]
public string? UpdatedBy { get; set; } = null;
/// <summary>
/// Gets or sets the status of the entity.
/// </summary>
[BsonElement("status")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public StatusEnum Status { get; set; } = StatusEnum.Active;
}
}

View File

@ -0,0 +1,107 @@
// ***********************************************************************
// <copyright file="RoleAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Enums;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters
{
/// <summary>
/// Adapter representing a role.
/// </summary>
public class RoleAdapter
{
/// <summary>
/// Gets or sets the unique identifier of the role.
/// </summary>
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the name of the role.
/// </summary>
[BsonElement("name")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets the description of the role.
/// </summary>
[BsonElement("description")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Gets or sets the status of the entity.
/// </summary>
[BsonElement("applications")]
[JsonPropertyName("applications")]
[JsonConverter(typeof(EnumArrayJsonConverter<ApplicationsEnum>))]
public ApplicationsEnum[]? Applications { get; set; }
/// <summary>
/// Gets or sets the modules of the role.
/// </summary>
[BsonElement("modules")]
[JsonPropertyName("modules")]
public string[] Modules { get; set; } = null!;
/// <summary>
/// Gets or sets the permissions of the role.
/// </summary>
[BsonElement("permissions")]
[JsonPropertyName("permissions")]
public string[] Permissions { get; set; } = null!;
/// <summary>
/// Gets or sets the date and time when the entity was created.
/// </summary>
[BsonElement("createdAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Gets or sets the user who created the entity.
/// </summary>
[BsonElement("createdBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("createdBy")]
public string? CreatedBy { get; set; }
/// <summary>
/// Gets or sets the date and time when the entity was last updated.
/// </summary>
[BsonElement("updatedAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("updatedAt")]
public DateTime? UpdatedAt { get; set; } = null;
/// <summary>
/// Gets or sets the user who last updated the entity.
/// </summary>
[BsonElement("updatedBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("updatedBy")]
public string? UpdatedBy { get; set; } = null;
/// <summary>
/// Gets or sets the status of the entity.
/// </summary>
[BsonElement("status")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public StatusEnum Status { get; set; } = StatusEnum.Active;
}
}

View File

@ -0,0 +1,18 @@
// ***********************************************************************
// <copyright file="TokenAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters
{
public class TokenAdapter
{
public UserAdapter? User { get; set; }
public RoleAdapter? Role { get; set; }
public IEnumerable<PermissionAdapter>? Permissions { get; set; }
public IEnumerable<ModuleAdapter>? Modules { get; set; }
}
}

View File

@ -0,0 +1,171 @@
// ***********************************************************************
// <copyright file="UserAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Enums;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters
{
/// <summary>
/// Adapter representing a user.
/// </summary>
public class UserAdapter : BaseAdapterResponse
{
/// <summary>
/// Gets or sets the unique identifier of the user.
/// </summary>
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the guid of the user.
/// </summary>
[BsonElement("guid")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("guid")]
public string? Guid { get; set; }
/// <summary>
/// Gets or sets the email address of the user.
/// </summary>
[BsonElement("email")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("email")]
public string Email { get; set; } = null!;
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
[BsonElement("name")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets the middlename of the user.
/// </summary>
[BsonElement("middleName")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("middleName")]
public string? MiddleName { get; set; }
/// <summary>
/// Gets or sets the last name of the user.
/// </summary>
[BsonElement("lastName")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("lastName")]
public string LastName { get; set; } = null!;
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
[BsonElement("displayName")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("displayName")]
public string? DisplayName { get; set; }
/// <summary>
/// Gets or sets the role ID of the user.
/// </summary>
[BsonElement("roleId")]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("roleId")]
public string RoleId { get; set; } = null!;
/// <summary>
/// Gets or sets the array of companies associated with the user.
/// </summary>
[BsonElement("companies")]
[JsonPropertyName("companies")]
public string[] Companies { get; set; } = null!;
/// <summary>
/// Gets or sets the array of projects associated with the user.
/// </summary>
[BsonElement("projects")]
[JsonPropertyName("projects")]
public string[]? Projects { get; set; }
/// <summary>
/// Gets or sets the boolean of the consent form accepted by the user.
/// </summary>
[BsonElement("consentFormAccepted")]
[JsonPropertyName("consentFormAccepted")]
[BsonIgnoreIfNull]
public bool ConsentFormAccepted { get; set; }
/// <summary>
/// Gets or sets the timestamp of the last login of the user.
/// </summary>
[BsonElement("lastLogIn")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("lastLogIn")]
public DateTime? LastLogIn { get; set; }
/// <summary>
/// Gets or sets the timestamp of the last logout of the user.
/// </summary>
[BsonElement("lastLogOut")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("lastLogOut")]
public DateTime? LastLogOut { get; set; }
/// <summary>
/// Gets or sets the token associated with the user.
/// </summary>
[BsonElement("token")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("token")]
public string? Token { get; set; } = null;
/// <summary>
/// Gets or sets the date and time when the entity was created.
/// </summary>
[BsonElement("createdAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Gets or sets the user who created the entity.
/// </summary>
[BsonElement("createdBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("createdBy")]
public string? CreatedBy { get; set; }
/// <summary>
/// Gets or sets the date and time when the entity was last updated.
/// </summary>
[BsonElement("updatedAt")]
[BsonRepresentation(BsonType.DateTime)]
[JsonPropertyName("updatedAt")]
public DateTime? UpdatedAt { get; set; } = null;
/// <summary>
/// Gets or sets the user who last updated the entity.
/// </summary>
[BsonElement("updatedBy")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("updatedBy")]
public string? UpdatedBy { get; set; } = null;
/// <summary>
/// Gets or sets the status of the entity.
/// </summary>
[BsonElement("status")]
[BsonRepresentation(BsonType.String)]
[JsonPropertyName("status")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public StatusEnum Status { get; set; } = StatusEnum.Active;
}
}

View File

@ -0,0 +1,9 @@
namespace Core.Cerberos.Adapters
{
public class Permission
{
public string Name { get; set; }
public string AccessLevel { get; set; }
}
}

View File

@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Core.Cerberos.Adapters.Attributes
{
/// <summary>
/// Custom authorization attribute that checks if the user has any of the required permissions.
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class PermissionAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private readonly string _requiredPermissions;
/// <summary>
/// Initializes a new instance of the <see cref="PermissionAttribute"/> class.
/// </summary>
/// <param name="requiredPermissions">The array of permissions required to access the resource.</param>
public PermissionAttribute(string requiredPermissions)
{
_requiredPermissions = requiredPermissions;
}
/// <summary>
/// Called during the authorization process to determine if the user has any of the required permissions.
/// </summary>
/// <param name="context">The context in which the authorization filter operates.</param>
public void OnAuthorization(AuthorizationFilterContext context)
{
try
{
var hasPermission = false;
var servicePermissionsList = _requiredPermissions.Replace(" ", "").Split(',').ToList();
var servicePermissions = servicePermissionsList.Select(s => new Permission
{
Name = s.Substring(0, s.IndexOf('.')),
AccessLevel = s.Substring(s.IndexOf('.') + 1),
});
var userPermissionsList = context.HttpContext.User.Claims
.Where(c => c.Type == "permissions")
.Select(c => c.Value)
.ToList();
var userPermissions = userPermissionsList.Select(s => new Permission
{
Name = s.Substring(0, s.IndexOf('.')),
AccessLevel = s.Substring(s.IndexOf('.') + 1),
});
foreach (var servicePermission in servicePermissions)
{
hasPermission = userPermissions
.Where(up => up.Name == servicePermission.Name && up.AccessLevel == "All"
|| up.Name == servicePermission.Name && up.AccessLevel == servicePermission.AccessLevel)
.Count() > 0 ? true : false;
if (hasPermission) break;
}
if (!hasPermission)
context.Result = new UnauthorizedResult();
}
catch (Exception ex)
{
context.Result = new UnauthorizedResult();
}
}
}
}

View File

@ -0,0 +1,32 @@
// ***********************************************************************
// <copyright file="AccessLevelEnum.cs">
// Heath
// </copyright>
// ***********************************************************************
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Specifies different access level for a permission.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum AccessLevelEnum
{
/// <summary>
/// The object is accessible for reading.
/// </summary>
Read = 0,
/// <summary>
/// The object is accessible for writing.
/// </summary>
Write = 1,
/// <summary>
/// The object is accessible for all operations.
/// </summary>
All = 2
}
}

View File

@ -0,0 +1,43 @@
// ***********************************************************************
// <copyright file="AzureAd.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for Azure Active Directory.
/// </summary>
public class AzureAd
{
/// <summary>
/// The ClientId parameter.
/// </summary>
public const string ClientId = "AzureAdB2C:ClientId";
/// <summary>
/// The TenantId parameter.
/// </summary>
public const string TenantId = "AzureAdB2C:TenantId";
/// <summary>
/// The ClientSecret parameter.
/// </summary>
public const string ClientSecret = "AzureAdB2C:ClientSecret";
/// <summary>
/// The MicrosoftOnlineUri parameter.
/// </summary>
public const string MicrosoftOnlineUri = "https://login.microsoftonline.com/";
/// <summary>
/// The GraphUri parameter.
/// </summary>
public const string GraphUri = "https://graph.microsoft.com/.default";
/// <summary>
/// The Instance parameter.
/// </summary>
public const string Instance = "AzureAdB2C:Instance";
}
}

View File

@ -0,0 +1,73 @@
// ***********************************************************************
// <copyright file="Claims.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for claims used in JWT tokens.
/// </summary>
public class Claims
{
/// <summary>
/// Claim name for user's name.
/// </summary>
public const string Name = "name";
/// <summary>
/// Claim name for user's guid.
/// </summary>
public const string GUID = "guid";
/// <summary>
/// Claim name for user's ID.
/// </summary>
public const string Id = "id";
/// <summary>
/// Claim name for user's role ID.
/// </summary>
public const string Role = "role";
/// <summary>
/// Claim name for user's role Iidentifier.
/// </summary>
public const string RoleId = "roleId";
/// <summary>
/// Claim name for user's companies.
/// </summary>
public const string Companies = "companies";
/// <summary>
/// Claim name for user's projects.
/// </summary>
public const string Projects = "projects";
/// <summary>
/// Claim name for user's applications.
/// </summary>
public const string Applications = "applications";
/// <summary>
/// Claim name for application's modules.
/// </summary>
public const string Modules = "modules";
/// <summary>
/// Claim name for user's permissions.
/// </summary>
public const string Permissions = "permissions";
/// <summary>
/// Claim name for user's ID.
/// </summary>
public const string Email = "email";
/// <summary>
/// Claim name for user's role.
/// </summary>
public const string LSARoleId = "LSARoleId";
}
}

View File

@ -0,0 +1,25 @@
namespace Core.Cerberos.Adapters.Common.Constants
{
public static class CollectionNames
{
/// <summary>
/// The User collection name.
/// </summary>
public const string User = "Users";
/// <summary>
/// The Role collection name.
/// </summary>
public const string Role = "Roles";
/// <summary>
/// The Permission collection name.
/// </summary>
public const string Permission = "Permissions";
/// <summary>
/// The Module collection name.
/// </summary>
public const string Module = "Modules";
}
}

View File

@ -0,0 +1,21 @@
// ***********************************************************************
// <copyright file="EnvironmentVariables.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants of the environment variables for this service.
/// </summary>
public static class EnvironmentVariables
{
/// <summary>
/// The stage environment vriable.
/// </summary>
public const string Stage = "ASPNETCORE_ENVIRONMENT";
}
}

View File

@ -0,0 +1,33 @@
// ***********************************************************************
// <copyright file="KeyVaultConfiguration.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for Key Vault configuration.
/// </summary>
public class KeyVaultConfiguration
{
/// <summary>
/// The KeyVaultUrl parameter.
/// </summary>
public const string KeyVaultUrl = "KeyVaultConfiguration:KeyVaultUrl";
/// <summary>
/// The TenantId parameter.
/// </summary>
public const string TenantId = "KeyVaultConfiguration:TenantId";
/// <summary>
/// The ClientId parameter.
/// </summary>
public const string ClientId = "KeyVaultConfiguration:ClientId";
/// <summary>
/// The ClientSecretId parameter.
/// </summary>
public const string ClientSecretId = "KeyVaultConfiguration:ClientSecretId";
}
}

View File

@ -0,0 +1,153 @@
// ***********************************************************************
// <copyright file="MimeTypes.cs">
// Axen IT
// </copyright>
// ***********************************************************************
using System.Globalization;
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for the mime types.
/// </summary>
public static class MimeTypes
{
/// <summary>
/// The application version.
/// </summary>
public const string ApplicationVersion = "1.0";
/// <summary>
/// The service application/json mime type.
/// </summary>
public const string ApplicationJson = "application/json";
/// <summary>
/// The application/pdf mime type.
/// </summary>
public const string ApplicationPdf = "application/pdf";
/// <summary>
/// The end index.
/// </summary>
public const int EndIndex = 5;
/// <summary>
/// The JPEG extension.
/// </summary>
public const string ExtensionGif = "gif";
/// <summary>
/// The JPEG extension.
/// </summary>
public const string ExtensionJpeg = "jpeg";
/// <summary>
/// The PNG extension.
/// </summary>
public const string ExtensionPng = "png";
/// <summary>
/// The SVG extension.
/// </summary>
public const string ExtensionSvg = "svg";
/// <summary>
/// The image/gif mime type.
/// </summary>
public const string ImageGif = "image/gif";
/// <summary>
/// The image/jpeg mime type.
/// </summary>
public const string ImageJpeg = "image/jpeg";
/// <summary>
/// The image/png mime type.
/// </summary>
public const string ImagePng = "image/png";
/// <summary>
/// The image/svg+xml mime type.
/// </summary>
public const string ImageSvg = "image/svg+xml";
/// <summary>
/// The identifier GIF.
/// </summary>
public const string IdentifierGif = "R0LGO";
/// <summary>
/// The identifier PNG.
/// </summary>
public const string IdentifierJpeg = "/9J/4";
/// <summary>
/// The identifier PDF.
/// </summary>
public const string IdentifierPdf = "JVBER";
/// <summary>
/// The identifier PNG.
/// </summary>
public const string IdentifierPng = "IVBOR";
/// <summary>
/// The identifier SVG.
/// </summary>
public const string IdentifierSvg = "PHN2Z";
/// <summary>
/// The parameter name.
/// </summary>
public const string ParameterName = "MimeType";
/// <summary>
/// The start index.
/// </summary>
public const int StartIndex = 0;
/// <summary>
/// The mime type dictionary.
/// </summary>
public static readonly Dictionary<string, string> Dictionary = new Dictionary<string, string>
{
{ IdentifierJpeg, ImageJpeg },
{ IdentifierPng, ImagePng },
{ IdentifierGif, ImageGif },
{ IdentifierSvg, ImageSvg },
};
/// <summary>
/// The mime type dictionary.
/// </summary>
public static readonly Dictionary<string, string> DictionaryExtension = new Dictionary<string, string>
{
{ IdentifierJpeg, ExtensionJpeg },
{ IdentifierPng, ExtensionPng },
{ IdentifierGif, ExtensionGif },
{ IdentifierSvg, ExtensionSvg },
};
/// <summary>
/// Gets the mime type.
/// </summary>
/// <param name="content">The content with mime type identifier, substring 0, 5 from content.</param>
/// <returns>A <see cref="string"/> representing the value.</returns>
public static string GetMimeType(this string content)
{
return Dictionary.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
}
/// <summary>
/// Gets the extension.
/// </summary>
/// <param name="content">The mime type identifier, substring 0, 5 from content.</param>
/// <returns>A <see cref="string"/> representing the value.</returns>
public static string GetExtension(this string content)
{
return DictionaryExtension.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
}
}
}

View File

@ -0,0 +1,114 @@
// ***********************************************************************
// <copyright file="Routes.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants of the routes of this service.
/// </summary>
public static class Routes
{
/// <summary>
/// The User route.
/// </summary>
public const string User = "users";
/// <summary>
/// The Register User route.
/// </summary>
public const string Register = "{sendInvitation}/send-invitation/register";
/// <summary>
/// The identifier route.
/// </summary>
public const string Id = "{id}";
/// <summary>
/// The Authentication route.
/// </summary>
public const string Authentication = "api/v1/authentication";
/// <summary>
/// The LogIn route.
/// </summary>
public const string LogIn = "{email}/login";
/// <summary>
/// The LogOut route.
/// </summary>
public const string LogOut = "{email}/logout";
/// <summary>
/// The Generate Token route.
/// </summary>
public const string GenerateToken = "GenerateToken";
/// <summary>
/// The refresh token route.
/// </summary>
public const string RefreshToken = "RefreshToken";
/// <summary>
/// The InviteUser route.
/// </summary>
public const string InviteUser = "invite-user";
/// <summary>
/// The role identifier route.
/// </summary>
public const string RoleId = "role/{roleId}";
/// <summary>
/// The GetPermissionList route.
/// </summary>
public const string GetPermissionList = "GetPermissionList";
/// <summary>
/// The GetModuleList route.
/// </summary>
public const string GetModuleList = "GetModuleList";
/// <summary>
/// The ChangeStatus route.
/// </summary>
public const string ChangeStatus = "{id}/{newStatus}/ChangeStatus";
/// <summary>
/// The AddCompany route.
/// </summary>
public const string AddCompany = "{userId}/Companies/{companyId}/Add";
/// <summary>
/// The RemoveCompany route.
/// </summary>
public const string RemoveCompany = "{userId}/Companies/{companyId}/Remove";
/// <summary>
/// The AddProject route.
/// </summary>
public const string AddProject = "{userId}/Projects/{projectId}/Add";
/// <summary>
/// The RemoveProject route.
/// </summary>
public const string RemoveProject = "{userId}/Projects/{projectId}/Remove";
/// <summary>
/// The AddApplication route.
/// </summary>
public const string AddApplication = "{roleId}/{application}/AddApplication";
/// <summary>
/// The RemoveApplication route.
/// </summary>
public const string RemoveApplication = "{roleId}/{application}/RemoveApplication";
/// <summary>
/// The email route.
/// </summary>
public const string Email = "{email}/GetByEmail";
}
}

View File

@ -0,0 +1,18 @@
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for schemes.
/// </summary>
public class Schemes
{
/// <summary>
/// The heath scheme.
/// </summary>
public const string HeathScheme = "HeathScheme";
/// <summary>
/// The azure scheme.
/// </summary>
public const string AzureScheme = "AzureScheme";
}
}

View File

@ -0,0 +1,59 @@
// ***********************************************************************
// <copyright file="AppSettings.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Common.Constants
{
/// <summary>
/// Constants for secrets in azure key vault.
/// </summary>
public class Secrets
{
/// <summary>
/// The MongoDBName parameter.
/// </summary>
public const string MongoDBName = "MongoDBName";
/// <summary>
/// The MongoDBConnection parameter.
/// </summary>
public const string MongoDBConnection = "MongoDBConnection";
/// <summary>
/// The Issuer parameter for JWT settings.
/// </summary>
public const string Issuer = "Issuer";
/// <summary>
/// The Audience parameter for JWT settings.
/// </summary>
public const string Audience = "Audience";
/// <summary>
/// The TokenExpirationInMinutes parameter for JWT settings.
/// </summary>
public const string TokenExpirationInMinutes = "TokenExpirationInMinutes";
/// <summary>
/// The TokenExpirationInHours parameter for JWT settings.
/// </summary>
public const string TokenExpirationInHours = "TokenExpirationInHours";
/// <summary>
/// The IssuerSigningKey parameter for JWT settings.
/// </summary>
public const string IssuerSigningKey = "IssuerSigningKey";
public const string AzureADInstance = "B2C:InstanceUri";
public const string AzureADTenantId = "B2C:TenantId";
public const string AzureADClientId = "B2C:ClientId";
public const string AzureADClientSecret = "B2C:ClientSecret";
public const string HeathCerberosAppAuthorizationUrl = "Swagger:AuthorizationUri";
public const string HeathCerberosAppTokenUrl = "Swagger:TokenUri";
public const string HeathCerberosAppClientId = "Swagger:ClientId";
public const string HeathCerberosAppScope = "Swagger:Scope";
public const string PrivateKey = "B2C:JwtIssuerOptions:TokenPrivateKey";
public const string PublicKey = "B2C:JwtIssuerOptions:TokenPublicKey";
}
}

View File

@ -0,0 +1,42 @@
// ***********************************************************************
// <copyright file="ApplicationsEnum.cs">
// Heath
// </copyright>
// ***********************************************************************
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters.Common.Enums
{
/// <summary>
/// Defines the applications associated with the role.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ApplicationsEnum
{
/// <summary>
/// LSA Web Portal application.
/// </summary>
LSAWebPortal = 0,
/// <summary>
/// Customer DashBoard application.
/// </summary>
CustomerDashboard = 1,
/// <summary>
/// Discover application.
/// </summary>
Discover = 2,
/// <summary>
/// LSA Mobile application.
/// </summary>
LSAMobile = 3,
/// <summary>
/// BluePrint application.
/// </summary>
BluePrint = 4,
}
}

View File

@ -0,0 +1,32 @@
// ***********************************************************************
// <copyright file="StatusEnum.cs">
// Heath
// </copyright>
// ***********************************************************************
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters.Common.Enums
{
/// <summary>
/// Defines the status of an entity.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum StatusEnum
{
/// <summary>
/// Indicates the entity is active.
/// </summary>
Active = 0,
/// <summary>
/// Indicates the entity is inactive.
/// </summary>
Inactive = 1,
/// <summary>
/// Indicates the entity is deleted.
/// </summary>
Deleted = 2
}
}

View File

@ -0,0 +1,17 @@
using System.Text.Json;
using System.Text.Json.Serialization;
public class EnumArrayJsonConverter<T> : JsonConverter<T[]> where T : Enum
{
public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = JsonSerializer.Deserialize<string[]>(ref reader, options);
return Array.ConvertAll(values, value => (T)Enum.Parse(typeof(T), value));
}
public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
{
var stringValues = Array.ConvertAll(value, v => v.ToString());
JsonSerializer.Serialize(writer, stringValues, options);
}
}

View File

@ -0,0 +1,31 @@
// ***********************************************************************
// <copyright file="EnumSchemaFilter.cs">
// Heath
// </copyright>
// ***********************************************************************
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
/// <summary>
/// Applies enumeration schema to OpenAPI schema definitions.
/// </summary>
public class EnumSchemaFilter : ISchemaFilter
{
/// <summary>
/// Applies the schema filter to an OpenAPI schema.
/// </summary>
/// <param name="schema">The OpenAPI schema to apply the filter to.</param>
/// <param name="context">The context information for the schema filter.</param>
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
schema.Enum.Clear();
Enum.GetNames(context.Type)
.ToList()
.ForEach(name => schema.Enum.Add(new OpenApiString(name)));
}
}
}

View File

@ -0,0 +1,19 @@
// ***********************************************************************
// <copyright file="ITokenProvider.cs">
// Heath
// </copyright>
// ***********************************************************************
namespace Core.Cerberos.Adapters.Contracts
{
/// <summary>
/// Interface for token provider.
/// </summary>
public interface ITokenProvider
{
/// <summary>
/// Get token from headers.
/// </summary>
string GetToken();
}
}

View File

@ -0,0 +1,32 @@
// ***********************************************************************
// <copyright file="ITokenService.cs">
// Heath
// </copyright>
// ***********************************************************************
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Core.Cerberos.Adapters.Contracts
{
/// <summary>
/// Interface for authenticacion service.
/// </summary>
public interface ITokenService
{
/// <summary>
/// Refreshes the access token.
/// </summary>
string GenerateAccessToken(TokenAdapter adapter);
/// <summary>
/// Refreshes the access token.
/// </summary>
IActionResult RefreshAccessToken(HttpContext context, TokenAdapter adapter);
/// <summary>
/// Extracts the user email claim from the http context.
/// </summary>
string GetEmailClaim(HttpContext httpContext);
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="8.0.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.2.2" />
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="3.2.2" />
<PackageReference Include="MongoDB.Bson" Version="3.0.0" />
<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,97 @@
// ***********************************************************************
// <copyright file="AuthExtension.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Constants;
using Core.Cerberos.Adapters.Contracts;
using Core.Cerberos.Adapters.Handlers;
using Core.Cerberos.Adapters.Options;
using Core.Cerberos.Adapters.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace Core.Cerberos.Adapters.Extensions
{
/// <summary>
/// Extension methods for configuring authentication with various Azure AD setups.
/// </summary>
public static class AuthenticationExtension
{
/// <summary>
/// Configures authentication using Azure AD for an API that requires downstream API access.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> containing Azure AD configuration settings.</param>
public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration, AuthSettings authSettings)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
var azureAdInMemorySettings = new Dictionary<string, string?>
{
{ "AzureAdB2C:Instance", authSettings.AzureADInstance ?? string.Empty },
{ "AzureAdB2C:TenantId", authSettings.AzureADTenantId ?? string.Empty },
{ "AzureAdB2C:ClientId", authSettings.AzureADClientId ?? string.Empty },
{ "AzureAdB2C:ClientSecret", authSettings.AzureADClientSecret ?? string.Empty }
};
var configurationBuilder = new ConfigurationBuilder()
.AddConfiguration(configuration)
.AddInMemoryCollection(azureAdInMemorySettings);
var combinedConfiguration = configurationBuilder.Build();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(combinedConfiguration.GetSection("AzureAdB2C"), Schemes.AzureScheme)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
var rsa = RSA.Create();
rsa.ImportFromPem(authSettings.PrivateKey?.ToCharArray());
var rsaPrivateKey = new RsaSecurityKey(rsa);
var rsaPublic = RSA.Create();
rsaPublic.ImportFromPem(authSettings.PublicKey?.ToCharArray());
var rsaPublicKey = new RsaSecurityKey(rsaPublic);
var jwtAppSettingOptions = configuration.GetSection("B2C:JwtIssuerOptions");
var jwtIssuerOptions = jwtAppSettingOptions.Get<JwtIssuerOptions>();
if (string.IsNullOrEmpty(jwtIssuerOptions?.Issuer) || string.IsNullOrEmpty(jwtIssuerOptions.Audience))
throw new InvalidOperationException("JwtIssuerOptions are not configured correctly.");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(Schemes.HeathScheme, x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtIssuerOptions?.Issuer,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = jwtIssuerOptions?.Audience,
IssuerSigningKey = rsaPublicKey
};
});
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtIssuerOptions?.Issuer;
options.Audience = jwtIssuerOptions?.Audience;
options.SigningCredentials = new SigningCredentials(rsaPrivateKey, SecurityAlgorithms.RsaSha256);
});
services.AddSingleton(jwtAppSettingOptions);
services.AddTransient<IAuthorizationHandler, PermissionsAuthorizationHandler>();
services.AddTransient<ITokenService, TokenService>();
}
}
}

View File

@ -0,0 +1,193 @@
// ***********************************************************************
// <copyright file="SwaggerExtensions.cs">
// Heath
// </copyright>
// ***********************************************************************
using Asp.Versioning.ApiExplorer;
using Core.Cerberos.Adapters.Common.Constants;
using Core.Cerberos.Adapters.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
namespace Core.Cerberos.Adapters.Extensions
{
/// <summary>
/// Extension methods for configuring Swagger documentation and UI.
/// </summary>
public static class SwaggerExtensions
{
private static readonly string? environment = Environment.GetEnvironmentVariable(EnvironmentVariables.Stage);
/// <summary>
/// Adds Swagger services to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
public static void AddSwagger(this IServiceCollection services, IConfiguration configuration, string DocumentationFile, AuthSettings authSettings)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(configuration, DocumentationFile, authSettings);
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
}
/// <summary>
/// Configures Swagger generation with OAuth2 security and XML comments.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> containing Swagger and OAuth2 configuration settings.</param>
public static void AddSwaggerGen(this IServiceCollection services, IConfiguration configuration, string DocumentationFile, AuthSettings authSettings)
{
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "OAuth2.0 Authorization Code flow",
Name = "oauth2.0",
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(authSettings.HeathCerberosAppAuthorizationUrl ?? string.Empty),
TokenUrl = new Uri(authSettings.HeathCerberosAppTokenUrl ?? string.Empty),
Scopes = new Dictionary<string, string>
{
{ authSettings.HeathCerberosAppScope ?? string.Empty, "Access API as User" }
}
}
}
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { authSettings.HeathCerberosAppScope }
}
});
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
var filePath = Path.Combine(AppContext.BaseDirectory, DocumentationFile);
c.IncludeXmlComments(filePath);
c.SchemaFilter<EnumSchemaFilter>();
});
}
/// <summary>
/// Configures Swagger and Swagger UI for the application.
/// </summary>
/// <param name="app">The <see cref="WebApplication"/> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> containing Swagger configuration settings.</param>
public static void ConfigureSwagger(this WebApplication app, IConfiguration configuration)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (var version in app.DescribeApiVersions().Select(version => version.GroupName))
options.SwaggerEndpoint($"/swagger/{version}/swagger.json", version);
options.DisplayRequestDuration();
options.EnableTryItOutByDefault();
options.DocExpansion(DocExpansion.None);
});
}
/// <summary>
/// Configures Swagger UI for the application with OAuth2 settings.
/// </summary>
/// <param name="app">The <see cref="WebApplication"/> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> containing Swagger UI and OAuth2 configuration settings.</param>
public static void UseSwaggerUI(this WebApplication app, IConfiguration configuration, AuthSettings authSettings)
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Custom Auth API with Azure AD v1");
options.OAuthClientId(authSettings.HeathCerberosAppClientId);
options.OAuthUsePkce();
options.OAuthScopeSeparator(" ");
});
}
/// <summary>
/// Adds API versioning and API explorer to the application.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The modified <see cref="IServiceCollection"/> instance.</returns>
public static IServiceCollection AddVersioning(this IServiceCollection services, IConfiguration configuration)
{
services.AddApiVersioning(options => options.ReportApiVersions = true)
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
return services;
}
}
/// <summary>
/// Configures Swagger generation options.
/// </summary>
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
/// <summary>
/// Configures SwaggerGen options.
/// </summary>
/// <param name="options">The SwaggerGen options to configure.</param>
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, new OpenApiInfo
{
Title = AppDomain.CurrentDomain.FriendlyName,
Version = description.ApiVersion.ToString()
});
}
// Map DateOnly type to Swagger schema
options.MapType<DateOnly>(() => new OpenApiSchema
{
Type = "string",
Format = "date",
Example = new OpenApiString(DateOnly.MinValue.ToString())
});
// Customize schema IDs for Swagger
options.CustomSchemaIds(type => type.ToString().Replace("+", "."));
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace Core.Cerberos.Adapters.Extensions
{
public static class TelemetryExtensions
{
public static void AddTelemetry(this IServiceCollection services)
{
// Add OpenTelemetry Tracing
services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("lsa.dashboard.bff.api"))
.WithTracing(tracing => tracing.AddAspNetCoreInstrumentation().AddConsoleExporter())
.WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation().AddConsoleExporter()).
WithLogging(logs => logs.AddConsoleExporter());
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http;
namespace Core.Cerberos.Adapters.Extensions
{
public sealed class TrackingMechanismExtension : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TrackingMechanismExtension(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_httpContextAccessor.HttpContext.Items.TryGetValue("TrackingId", out var trackingId))
{
request.Headers.Add("TrackingId", trackingId.ToString());
}
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Authorization;
namespace Core.Cerberos.Adapters.Handlers.Adapters
{
public class PermissionsAuthorizationAdapter : IAuthorizationRequirement
{
public PermissionsAuthorizationAdapter(string[] permission)
{
Permission = permission;
}
public string[] Permission { get; set; }
}
}

View File

@ -0,0 +1,29 @@
// ***********************************************************************
// <copyright file="AuthenticatedHttpClientHandler.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Contracts;
namespace Core.Cerberos.Adapters.Handlers
{
/// <summary>
/// Class to inject the token in all requests.
/// </summary>
public class AuthenticatedHttpClientHandler(ITokenProvider tokenProvider) : DelegatingHandler
{
private readonly ITokenProvider _tokenProvider = tokenProvider;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = _tokenProvider.GetToken();
if (!string.IsNullOrEmpty(token))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,18 @@
using Core.Cerberos.Adapters.Handlers.Adapters;
using Microsoft.AspNetCore.Authorization;
namespace Core.Cerberos.Adapters.Handlers
{
public class PermissionsAuthorizationHandler : AuthorizationHandler<PermissionsAuthorizationAdapter>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionsAuthorizationAdapter requirement)
{
if (context.User.Claims.Any(x => x.Type == "LSARoleId" && requirement.Permission.Contains(x.Value)))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,52 @@
using Azure.Identity;
using Core.Cerberos.Adapters.Common.Constants;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.Logging;
namespace Core.Cerberos.Adapters.Helpers
{
public static class AuthHelper
{
private static readonly ILogger logger = LoggerFactory.Create(builder =>
{
builder.AddConsole();
}).CreateLogger("AuthHelper");
public static AuthSettings GetAuthSettings(WebApplicationBuilder builder, string appConfigLabel)
{
builder.Configuration.AddAzureAppConfiguration(options =>
{
var endpoint = builder.Configuration.GetSection("Endpoints:AppConfigurationURI").Value;
if (string.IsNullOrEmpty(endpoint))
throw new ArgumentException("The app configuration is missing");
options.Connect(new Uri(endpoint), new DefaultAzureCredential())
.Select(KeyFilter.Any, "cerberos_common")
.Select(KeyFilter.Any, appConfigLabel);
options.ConfigureKeyVault(keyVaultOptions =>
{
keyVaultOptions.SetCredential(new DefaultAzureCredential());
});
});
return new AuthSettings
{
AzureADInstance = builder.Configuration.GetSection(Secrets.AzureADInstance).Value,
AzureADTenantId = builder.Configuration.GetSection(Secrets.AzureADTenantId).Value,
AzureADClientId = builder.Configuration.GetSection(Secrets.AzureADClientId).Value,
AzureADClientSecret = builder.Configuration.GetSection(Secrets.AzureADClientSecret).Value,
HeathCerberosAppAuthorizationUrl = builder.Configuration.GetSection(Secrets.HeathCerberosAppAuthorizationUrl).Value,
HeathCerberosAppTokenUrl = builder.Configuration.GetSection(Secrets.HeathCerberosAppTokenUrl).Value,
HeathCerberosAppClientId = builder.Configuration.GetSection(Secrets.HeathCerberosAppClientId).Value,
HeathCerberosAppScope = builder.Configuration.GetSection(Secrets.HeathCerberosAppScope).Value,
PrivateKey = builder.Configuration.GetSection(Secrets.PrivateKey).Value,
PublicKey = builder.Configuration.GetSection(Secrets.PublicKey).Value,
};
}
}
}

View File

@ -0,0 +1,94 @@
// ***********************************************************************
// <copyright file="RsaHelper.cs">
// Heath
// </copyright>
// ***********************************************************************
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;
using System.Text;
namespace Core.Cerberos.Adapters.Helpers
{
/// <summary>
/// Handles all methods related to RSA encryption"/>.
/// </summary>
public class RsaHelper
{
private readonly RSACryptoServiceProvider _privateKey;
private readonly RSACryptoServiceProvider _publicKey;
private readonly string keysFolder = "Keys\\";
private readonly string exeDirectory = AppContext.BaseDirectory;
/// <summary>
/// Initializes a new instance of <see cref="RsaHelper"/>.
/// </summary>
public RsaHelper()
{
exeDirectory = exeDirectory + keysFolder;
_publicKey = GetPublicKeyFromPemFile();
_privateKey = GetPrivateKeyFromPemFile();
}
/// <summary>
/// Encrypts a text using RSA algorithm.
/// </summary>
/// <param name="text">The text to be encrypted.</param>
/// <returns>The encrypted text.</returns>
public string Encrypt(string text)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(text);
var encryptedBytes = _publicKey.Encrypt(Encoding.UTF8.GetBytes(text), true);
return Convert.ToBase64String(encryptedBytes);
}
/// <summary>
/// Decrypts a text using RSA algorithm.
/// </summary>
/// <param name="text">The encrypted text to be decrypted.</param>
/// <returns>The decrypted text.</returns>
public string Decrypt(string encrypted)
{
var decryptedBytes = _privateKey.Decrypt(Convert.FromBase64String(encrypted), true);
return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
}
/// <summary>
///Obtains the private key from a file.
/// </summary>
/// <returns>The private key.</returns>
private RSACryptoServiceProvider GetPrivateKeyFromPemFile()
{
using (TextReader privateKeyTextReader = new StringReader(File.ReadAllText(Path.Combine(exeDirectory, "HeathPrivateKey.pem"))))
{
AsymmetricCipherKeyPair readKeyPair = (AsymmetricCipherKeyPair)new PemReader(privateKeyTextReader).ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)readKeyPair.Private);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaParams);
return csp;
}
}
/// <summary>
///Obtains the public key from a file.
/// </summary>
/// <returns>The public key.</returns>
public RSACryptoServiceProvider GetPublicKeyFromPemFile()
{
using (TextReader publicKeyTextReader = new StringReader(File.ReadAllText(Path.Combine(exeDirectory, "HeathPublicKey.pem"))))
{
RsaKeyParameters publicKeyParam = (RsaKeyParameters)new PemReader(publicKeyTextReader).ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKeyParam);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaParams);
return csp;
}
}
}
}

View File

@ -0,0 +1,60 @@
using Microsoft.IdentityModel.Tokens;
namespace Core.Cerberos.Adapters.Options
{
/// <summary>
/// JWT token Issuer options (used for JWT Factory)
/// </summary>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class JwtIssuerOptions
{
/// <summary>
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
/// </summary>
public string? Issuer { get; set; }
/// <summary>
/// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT.
/// </summary>
public string? Subject { get; set; }
/// <summary>
/// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for.
/// </summary>
public string? Audience { get; set; }
/// <summary>
/// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime Expiration => IssuedAt.Add(ValidFor);
/// <summary>
/// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime NotBefore => DateTime.UtcNow;
/// <summary>
/// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued.
/// </summary>
public DateTime IssuedAt => DateTime.UtcNow;
/// <summary>
/// Set the timespan the token will be valid for (default is 120 min)
/// </summary>
public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(525601);
/// <summary>
/// "jti" (JWT ID) Claim (default ID is a GUID)
/// </summary>
public Func<Task<string>> JtiGenerator =>
() => Task.FromResult(Guid.NewGuid().ToString());
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials? SigningCredentials { get; set; }
}
}

View File

@ -0,0 +1,146 @@
// ***********************************************************************
// <copyright file="T5okenService.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Constants;
using Core.Cerberos.Adapters.Contracts;
using Core.Cerberos.Adapters.Options;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
namespace Core.Cerberos.Adapters.Services
{
/// <summary>
/// Service responsible for manage authenticacion.
/// </summary>
public class TokenService : ITokenService
{
private readonly JwtSecurityTokenHandler tokenHandler;
private readonly IConfiguration configuration;
private readonly JwtIssuerOptions jwtOptions;
private readonly JsonSerializerOptions jsonOptions;
/// <summary>
/// Initializes a new instance of the <see cref="TokenService"/> class.
/// </summary>
public TokenService(
IConfiguration configuration,
IOptions<JwtIssuerOptions> jwtOptions
)
{
tokenHandler = new JwtSecurityTokenHandler();
this.configuration = configuration;
this.jwtOptions = jwtOptions.Value;
jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
}
/// <summary>
/// Refreshes the token.
/// </summary>
public IActionResult RefreshAccessToken(HttpContext httpContext, TokenAdapter tokenAdapter)
{
var tokenString = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (tokenString is not null)
{
var oldToken = tokenHandler.ReadJwtToken(tokenString);
var tokenExpiration = oldToken.Claims.FirstOrDefault(c => c.Type == "exp")?.Value;
var difference = ValidateTokenExpiration(tokenExpiration ?? "");
if (difference.Value.TotalMinutes <= 5)
return new OkObjectResult(GenerateAccessToken(tokenAdapter));
}
return new BadRequestObjectResult("The token could not be refreshed");
}
/// <summary>
/// Generates a JWT token for the provided user data.
/// </summary>
/// <param name="user">The user data.</param>
/// <returns>The user DTO with the generated token.</returns>
public string GenerateAccessToken(TokenAdapter adapter)
{
var hours = 1;
var minutes = 0;
var expires = DateTime.UtcNow
.AddHours(hours)
.AddMinutes(minutes);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(Claims.Name, adapter?.User?.DisplayName ?? string.Empty),
new Claim(Claims.GUID, adapter?.User?.Guid ?? string.Empty),
new Claim(Claims.Email, adapter?.User?.Email ?? string.Empty),
new Claim(Claims.Role, adapter?.Role?.Name ?? string.Empty),
new Claim(Claims.RoleId, adapter?.Role?.Id ?? string.Empty),
new Claim(Claims.Applications, JsonSerializer.Serialize(adapter?.Role?.Applications), JsonClaimValueTypes.JsonArray),
new Claim(Claims.Modules, JsonSerializer.Serialize(adapter?.Modules?.Select(m => new { m.Name, m.Application, m.Route, m.Icon, m.Order }), jsonOptions), JsonClaimValueTypes.JsonArray),
new Claim(Claims.Companies, JsonSerializer.Serialize(adapter?.User?.Companies), JsonClaimValueTypes.JsonArray),
new Claim(Claims.Projects, JsonSerializer.Serialize(adapter?.User?.Projects), JsonClaimValueTypes.JsonArray),
new Claim(Claims.Permissions, JsonSerializer.Serialize(adapter?.Permissions?.Select(p => $"{p.Name}.{p.AccessLevel}".Replace(" ", "")).ToArray()), JsonClaimValueTypes.JsonArray),
}),
Expires = expires,
Issuer = jwtOptions.Issuer,
Audience = jwtOptions.Audience,
SigningCredentials = jwtOptions.SigningCredentials
};
var token = tokenHandler.CreateEncodedJwt(tokenDescriptor);
return token;
}
public ActionResult<TimeSpan> ValidateTokenExpiration(string tokenExpiration)
{
long unixTimestamp = long.Parse(tokenExpiration ?? "0");
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
DateTime dateTimeExpiration = dateTimeOffset.UtcDateTime;
var difference = dateTimeExpiration - DateTime.UtcNow;
if (difference.TotalMinutes <= 0)
return new BadRequestObjectResult("Expired token");
else return difference;
}
/// <summary>
/// Extracts the user email claim from the http context.
/// </summary>
public string GetEmailClaim(HttpContext httpContext)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenString = httpContext.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last();
var token = tokenHandler.ReadJwtToken(tokenString);
var email = !string.IsNullOrEmpty(token.Claims.FirstOrDefault(c => c.Type == "email")?.Value)
? token.Claims.FirstOrDefault(c => c.Type == "email")?.Value
: token.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value;
return (email is not null) ? email : "";
}
}
}

View File

@ -0,0 +1,25 @@
// ***********************************************************************
// <copyright file="AuthSettings.cs">
// Heath
// </copyright>
// ***********************************************************************
public class AuthSettings
{
// Azure AD Settings
public string? AzureADInstance { get; set; }
public string? AzureADTenantId { get; set; }
public string? AzureADClientId { get; set; }
public string? AzureADClientSecret { get; set; }
// Heath Cerberos App Settings
public string? HeathCerberosAppAuthorizationUrl { get; set; }
public string? HeathCerberosAppTokenUrl { get; set; }
public string? HeathCerberosAppClientId { get; set; }
public string? HeathCerberosAppScope { get; set; }
// Token Keys
public string? PrivateKey { get; set; }
public string? PublicKey { get; set; }
}

View File

@ -0,0 +1,32 @@
// ***********************************************************************
// <copyright file="HttpContextTokenProvider.cs">
// Heath
// </copyright>
// ***********************************************************************
using Core.Cerberos.Adapters.Contracts;
using Microsoft.AspNetCore.Http;
namespace Core.Cerberos.Adapters.TokenProvider
{
/// <summary>
/// Class to return the access token to controllers.
/// </summary>
public class HttpContextTokenProvider : ITokenProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextTokenProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Get token from headers.
/// </summary>
public string GetToken()
{
return _httpContextAccessor.HttpContext?.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
}
}
}

View File

@ -0,0 +1,22 @@
// ***********************************************************************
// <copyright file="UserExistenceAdapter.cs">
// Heath
// </copyright>
// ***********************************************************************
using System.Text.Json.Serialization;
namespace Core.Cerberos.Adapters
{
/// <summary>
/// Adapter representing a user.
/// </summary>
public class UserExistenceAdapter
{
/// <summary>
/// user existence.
/// </summary>
[JsonPropertyName("existence")]
public bool Existence { get; set; }
}
}

1
README.md Normal file
View File

@ -0,0 +1 @@
SharedLibs