-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(%release-note: - Added DfE.CoreLibs.Security, including Authorization extension which faciliates creation of Policies, Roles and Claims %)
- Loading branch information
Farshad DASHTI
authored and
Farshad DASHTI
committed
Nov 6, 2024
1 parent
904e6c0
commit cebb793
Showing
15 changed files
with
761 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: Build DfE.CoreLibs.Security | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- 'src/DfE.CoreLibs.Security/**' | ||
pull_request: | ||
branches: | ||
- main | ||
paths: | ||
- 'src/DfE.CoreLibs.Security/**' | ||
|
||
jobs: | ||
build-and-test: | ||
uses: ./.github/workflows/build-test-template.yml | ||
with: | ||
project_name: DfE.CoreLibs.Security | ||
project_path: src/DfE.CoreLibs.Security | ||
secrets: | ||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
name: Pack DfE.CoreLibs.Security | ||
|
||
on: | ||
workflow_run: | ||
workflows: ["Build DfE.CoreLibs.Security"] | ||
types: | ||
- completed | ||
|
||
jobs: | ||
build-and-package: | ||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.event != 'pull_request' }} | ||
uses: ./.github/workflows/nuget-package-template.yml | ||
with: | ||
project_name: DfE.CoreLibs.Security | ||
project_path: src/DfE.CoreLibs.Security | ||
nuget_package_name: DfE.CoreLibs.Security |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
src/DfE.CoreLibs.Security/Authorization/AuthorizationExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using DfE.CoreLibs.Security.Configurations; | ||
using DfE.CoreLibs.Security.Interfaces; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace DfE.CoreLibs.Security.Authorization | ||
{ | ||
/// <summary> | ||
/// Extension methods for configuring application authorization and custom claim providers. | ||
/// </summary> | ||
public static class AuthorizationExtensions | ||
{ | ||
/// <summary> | ||
/// Configures authorization policies based on roles, claims, and custom requirements. | ||
/// Loads policies from the provided configuration. | ||
/// </summary> | ||
/// <param name="services">The service collection to add authorization services to.</param> | ||
/// <param name="configuration">The configuration object containing policy definitions.</param> | ||
/// <param name="policyCustomizations">The the customizations such as Requirements which can be added to the policy after it is created.</param> | ||
/// <returns>The modified service collection.</returns> | ||
public static IServiceCollection AddApplicationAuthorization( | ||
Check warning on line 23 in src/DfE.CoreLibs.Security/Authorization/AuthorizationExtensions.cs GitHub Actions / build-and-test / build-and-test
Check warning on line 23 in src/DfE.CoreLibs.Security/Authorization/AuthorizationExtensions.cs GitHub Actions / build-and-test / build-and-test
|
||
this IServiceCollection services, | ||
IConfiguration configuration, | ||
Dictionary<string, Action<AuthorizationPolicyBuilder>>? policyCustomizations = null) | ||
{ | ||
services.AddAuthorization(options => | ||
{ | ||
var policies = configuration.GetSection("Authorization:Policies").Get<List<PolicyDefinition>>(); | ||
|
||
foreach (var policyConfig in policies ?? []) | ||
{ | ||
options.AddPolicy(policyConfig.Name, policyBuilder => | ||
{ | ||
policyBuilder.RequireAuthenticatedUser(); | ||
|
||
if (policyConfig.Roles.Any()) | ||
{ | ||
if (string.Equals(policyConfig.Operator, "AND", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
// Use AND logic: require each role individually | ||
foreach (var role in policyConfig.Roles) | ||
{ | ||
policyBuilder.RequireRole(role); | ||
} | ||
} | ||
else | ||
{ | ||
// Use OR logic: require any of the roles | ||
policyBuilder.RequireRole(policyConfig.Roles.ToArray()); | ||
} | ||
} | ||
|
||
if (policyConfig.Claims != null && policyConfig.Claims.Any()) | ||
{ | ||
foreach (var claim in policyConfig.Claims) | ||
{ | ||
policyBuilder.RequireClaim(claim.Type, claim.Values.ToArray()); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
if (policyCustomizations != null) | ||
{ | ||
foreach (var (policyName, customization) in policyCustomizations) | ||
{ | ||
if (options.GetPolicy(policyName) is not null) | ||
{ | ||
// If the policy already exists, modify it | ||
UpdateExistingPolicy(options, policyName, customization); | ||
} | ||
else | ||
{ | ||
// If the policy does not exist, create a new one | ||
options.AddPolicy(policyName, customization); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
services.AddTransient<IClaimsTransformation, CustomClaimsTransformation>(); | ||
|
||
return services; | ||
} | ||
|
||
/// <summary> | ||
/// Registers a custom claim provider to retrieve claims dynamically. | ||
/// </summary> | ||
/// <typeparam name="TProvider">The custom claim provider implementing ICustomClaimProvider.</typeparam> | ||
/// <param name="services">The service collection to add the claim provider to.</param> | ||
/// <returns>The modified service collection.</returns> | ||
public static IServiceCollection AddCustomClaimProvider<TProvider>(this IServiceCollection services) | ||
where TProvider : class, ICustomClaimProvider | ||
{ | ||
services.AddTransient<ICustomClaimProvider, TProvider>(); | ||
return services; | ||
} | ||
|
||
/// <summary> | ||
/// Updates an existing policy with additional requirements from a customization action. | ||
/// </summary> | ||
private static void UpdateExistingPolicy(AuthorizationOptions options, string policyName, Action<AuthorizationPolicyBuilder> customization) | ||
{ | ||
var existingPolicyBuilder = new AuthorizationPolicyBuilder(); | ||
|
||
// Copy existing policy requirements | ||
var existingPolicy = options.GetPolicy(policyName)!; | ||
foreach (var requirement in existingPolicy.Requirements) | ||
{ | ||
existingPolicyBuilder.Requirements.Add(requirement); | ||
} | ||
|
||
// Apply the new customization | ||
customization(existingPolicyBuilder); | ||
|
||
// Replace the policy with the updated one | ||
options.AddPolicy(policyName, existingPolicyBuilder.Build()); | ||
} | ||
|
||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/DfE.CoreLibs.Security/Authorization/CustomClaimsTransformation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using DfE.CoreLibs.Security.Interfaces; | ||
using Microsoft.AspNetCore.Authentication; | ||
using System.Security.Claims; | ||
|
||
namespace DfE.CoreLibs.Security.Authorization | ||
{ | ||
/// <summary> | ||
/// Transforms the claims of the current user by adding custom claims from registered claim providers. | ||
/// </summary> | ||
public class CustomClaimsTransformation(IEnumerable<ICustomClaimProvider> claimProviders) : IClaimsTransformation | ||
{ | ||
/// <summary> | ||
/// Transforms the user's claims by adding custom claims retrieved from each registered claim provider. | ||
/// </summary> | ||
/// <param name="principal">The current user's ClaimsPrincipal.</param> | ||
/// <returns>The modified ClaimsPrincipal with additional claims.</returns> | ||
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) | ||
{ | ||
var identity = (ClaimsIdentity)principal.Identity!; | ||
|
||
foreach (var provider in claimProviders) | ||
{ | ||
var claims = await provider.GetClaimsAsync(principal); | ||
|
||
foreach (var claim in claims) | ||
{ | ||
if (!identity.HasClaim(c => c.Type == claim.Type)) | ||
{ | ||
identity.AddClaim(claim); | ||
} | ||
} | ||
} | ||
|
||
return principal; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace DfE.DomainDrivenDesignTemplate.Infrastructure.Security.Configurations | ||
{ | ||
public class ClaimDefinition | ||
{ | ||
public required string Type { get; set; } | ||
public required List<string> Values { get; set; } | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/DfE.CoreLibs.Security/Configurations/PolicyDefinition.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using DfE.DomainDrivenDesignTemplate.Infrastructure.Security.Configurations; | ||
|
||
namespace DfE.CoreLibs.Security.Configurations | ||
{ | ||
/// <summary> | ||
/// Represents a policy definition, including roles, claims, and custom requirements. | ||
/// </summary> | ||
public class PolicyDefinition | ||
{ | ||
public required string Name { get; set; } | ||
public required string Operator { get; set; } = "OR"; // "AND" or "OR" | ||
public required List<string> Roles { get; set; } | ||
public List<ClaimDefinition>? Claims { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<PackageReadmeFile>readme.md</PackageReadmeFile> | ||
<Title>DfE.CoreLibs.Security</Title> | ||
<Description>A library providing flexible foundation for managing security in .NET projects, including role-based and claim-based policies, custom requirements, and dynamic claims. It enables consistent, configurable security across applications. | ||
</Description> | ||
<Authors>DFE-Digital</Authors> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<None Include="readme.md" Pack="true" PackagePath="\" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.10" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" /> | ||
<PackageReference Include="Microsoft.Identity.Web" Version="3.3.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="readme.md"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
12 changes: 12 additions & 0 deletions
12
src/DfE.CoreLibs.Security/Interfaces/ICustomAuthorizationRequirement.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
|
||
namespace DfE.CoreLibs.Security.Interfaces | ||
{ | ||
/// <summary> | ||
/// Represents a custom authorization requirement with a unique type. | ||
/// </summary> | ||
public interface ICustomAuthorizationRequirement : IAuthorizationRequirement | ||
{ | ||
string Type { get; } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/DfE.CoreLibs.Security/Interfaces/ICustomClaimProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.Security.Claims; | ||
|
||
namespace DfE.CoreLibs.Security.Interfaces | ||
{ | ||
/// <summary> | ||
/// Interface for custom claim providers that retrieve claims for a user. | ||
/// </summary> | ||
public interface ICustomClaimProvider | ||
{ | ||
/// <summary> | ||
/// Asynchronously retrieves claims for the specified user. | ||
/// </summary> | ||
/// <param name="principal">The current user's ClaimsPrincipal.</param> | ||
/// <returns>A list of claims to add to the user's identity.</returns> | ||
Task<IEnumerable<Claim>> GetClaimsAsync(ClaimsPrincipal principal); | ||
} | ||
} |
Oops, something went wrong.