Skip to content

Commit 2d4a3f2

Browse files
author
Jakab László
committed
AnyPolicies
1 parent 09cb9a8 commit 2d4a3f2

File tree

7 files changed

+206
-1
lines changed

7 files changed

+206
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace AutSoft.Common.AnyPolicies;
5+
6+
/// <summary>
7+
/// Evulate the policies in the <see cref="AnyPoliciesRequirement"/> with OR relationship,
8+
/// so it is enough for a policy to pass the test
9+
/// </summary>
10+
public class AnyPoliciesAuthorizationHandler : AuthorizationHandler<AnyPoliciesRequirement>
11+
{
12+
private readonly IServiceProvider _serviceProvider;
13+
14+
/// <summary>
15+
/// Initialize a new instance of the <see cref="AnyPoliciesAuthorizationHandler"/> class.
16+
/// </summary>
17+
/// <param name="serviceProvider">An instance of <see cref="IServiceProvider"/></param>
18+
/// <remarks>
19+
/// Not possible to inject <see cref="IAuthorizationService"/>, because it would be a circular reference
20+
/// </remarks>
21+
public AnyPoliciesAuthorizationHandler(IServiceProvider serviceProvider)
22+
{
23+
_serviceProvider = serviceProvider;
24+
}
25+
26+
/// <summary>
27+
/// Evulate the policies in the <see cref="AnyPoliciesRequirement"/> with OR relationship,
28+
/// with the help of <see cref="IAuthorizationService"/>
29+
/// </summary>
30+
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AnyPoliciesRequirement requirement)
31+
{
32+
var authorizationService = _serviceProvider.GetRequiredService<IAuthorizationService>();
33+
34+
foreach (var policy in requirement.Policies)
35+
{
36+
var result = await authorizationService.AuthorizeAsync(context.User, context.Resource, policy);
37+
if (result.Succeeded)
38+
{
39+
context.Succeed(requirement);
40+
return;
41+
}
42+
}
43+
}
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
3+
namespace AutSoft.Common.AnyPolicies;
4+
5+
/// <summary>
6+
/// The policies, which are specified in the constructor or <see cref="Policies"/> property
7+
/// will be evulated with OR condition with help of <see cref="AnyPoliciesAuthorizationHandler"/>
8+
/// </summary>
9+
/// <remarks>
10+
/// The policies will be evulated in a new dinamically generated policy,
11+
/// what the <see cref="AnyPoliciesPolicyProvider"/> class generate.
12+
/// </remarks>
13+
public class AnyPoliciesAuthorizeAttribute : AuthorizeAttribute
14+
{
15+
private string[] _policies = Array.Empty<string>();
16+
17+
/// <summary>
18+
/// The policies in OR relationship
19+
/// </summary>
20+
public string[] Policies
21+
{
22+
get => _policies;
23+
24+
set
25+
{
26+
_policies = value;
27+
Policy = AnyPoliciesPolicyProvider.GenerateDynamicPolicy(_policies);
28+
}
29+
}
30+
31+
/// <summary>
32+
/// Initialize a new instance of the <see cref="AnyPoliciesAuthorizeAttribute"/> class.
33+
/// </summary>
34+
/// <param name="policies">The policies in OR relationship</param>
35+
public AnyPoliciesAuthorizeAttribute(params string[] policies)
36+
{
37+
Policies = policies;
38+
}
39+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using Microsoft.AspNetCore.Authentication.JwtBearer;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.Extensions.Options;
4+
5+
namespace AutSoft.Common.AnyPolicies;
6+
7+
/// <summary>
8+
/// Dynamically generate policies based on <see cref="AnyPoliciesAuthorizeAttribute"/>,
9+
/// that will contain a <see cref="AnyPoliciesRequirement"/> requirement.
10+
/// For anything not <see cref="AnyPoliciesAuthorizeAttribute"/>,
11+
/// the default policy provider (<see cref="DefaultAuthorizationPolicyProvider"/>) will return the policy.
12+
/// </summary>
13+
public class AnyPoliciesPolicyProvider : IAuthorizationPolicyProvider
14+
{
15+
/// <summary>
16+
/// Prefix of dinamically generated policy's name
17+
/// </summary>
18+
public const string PolicyPrefix = "AnyPolicies_";
19+
20+
private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
21+
22+
/// <summary>
23+
/// Initialize a new instance of <see cref="AnyPoliciesPolicyProvider"/> class.
24+
/// </summary>
25+
/// <param name="options">Options of provider</param>
26+
public AnyPoliciesPolicyProvider(IOptions<AuthorizationOptions> options)
27+
{
28+
_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
29+
}
30+
31+
/// <summary>
32+
/// Return with the name of policies from the name of dynamic policy
33+
/// </summary>
34+
/// <param name="dynamicPolicyName">The name of dinamic policy</param>
35+
/// <returns>The name of policies</returns>
36+
public static IEnumerable<string> GetPolicyNamesFromDynamicPolicy(string dynamicPolicyName)
37+
{
38+
return dynamicPolicyName[PolicyPrefix.Length..].Split(',');
39+
}
40+
41+
/// <summary>
42+
/// Generate a dynamic policy with the OR combine of policies
43+
/// </summary>
44+
/// <param name="policies">The policies to be combine</param>
45+
/// <returns>The dynamic policy</returns>
46+
public static string GenerateDynamicPolicy(IEnumerable<string> policies)
47+
{
48+
return PolicyPrefix + string.Join(",", policies);
49+
}
50+
51+
/// <summary>
52+
/// Return with the default authorization policy
53+
/// </summary>
54+
/// <returns>The default authorization policy</returns>
55+
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
56+
{
57+
return _fallbackPolicyProvider.GetDefaultPolicyAsync();
58+
}
59+
60+
/// <summary>
61+
/// Return with the authorization policy with the specified policy name
62+
/// </summary>
63+
/// <param name="policyName">The specified policy name</param>
64+
/// <returns>The authorization policy with the specified name</returns>
65+
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
66+
{
67+
if (policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
68+
{
69+
var policies = GetPolicyNamesFromDynamicPolicy(policyName).ToArray();
70+
var policy = new AuthorizationPolicyBuilder().AddRequirements(new AnyPoliciesRequirement(policies));
71+
72+
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
73+
74+
return Task.FromResult((AuthorizationPolicy?)policy.Build());
75+
}
76+
77+
return _fallbackPolicyProvider.GetPolicyAsync(policyName);
78+
}
79+
80+
/// <summary>
81+
/// Return with the fallback policy
82+
/// </summary>
83+
/// <returns>The fallback policy</returns>
84+
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
85+
{
86+
return _fallbackPolicyProvider.GetFallbackPolicyAsync();
87+
}
88+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
3+
namespace AutSoft.Common.AnyPolicies;
4+
5+
/// <summary>
6+
/// Requirement of wanted to combine OR authorizations
7+
/// </summary>
8+
public class AnyPoliciesRequirement : IAuthorizationRequirement
9+
{
10+
/// <summary>
11+
/// Initialize a new instance of <see cref="AnyPoliciesRequirement"/> class.
12+
/// </summary>
13+
/// <param name="policies">Array of policies to be combined</param>
14+
public AnyPoliciesRequirement(params string[] policies)
15+
{
16+
Policies = policies;
17+
}
18+
19+
/// <summary>
20+
/// Policies to be combine OR
21+
/// </summary>
22+
public IEnumerable<string> Policies { get; }
23+
}

src/AutSoft.Core/AutSoft.Common.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
11+
<PackageReference Include="Microsoft.AspNetCore.Authorization" />
1012
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
1113
<PackageReference Include="Microsoft.EntityFrameworkCore" />
1214
<PackageReference Include="Stateless" />

src/AutSoft.Core/ServiceCollectionExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
using AutSoft.Common.AnyPolicies;
12
using AutSoft.Common.Time;
23

4+
using Microsoft.AspNetCore.Authorization;
35
using Microsoft.Extensions.DependencyInjection;
46

57
namespace AutSoft.Common;
@@ -15,6 +17,11 @@ public static class ServiceCollectionExtensions
1517
/// <returns>Expanded service collection</returns>
1618
public static IServiceCollection AddAutSoftCommon(this IServiceCollection services)
1719
{
18-
return services.AddSingleton<ITimeProvider, TimeProvider>();
20+
services.AddSingleton<ITimeProvider, TimeProvider>();
21+
22+
services.AddSingleton<IAuthorizationPolicyProvider, AnyPoliciesPolicyProvider>();
23+
services.AddSingleton<IAuthorizationHandler, AnyPoliciesAuthorizationHandler>();
24+
25+
return services;
1926
}
2027
}

src/Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<ItemGroup>
88
<PackageVersion Include="AutoMapper" Version="11.0.1" />
99
<PackageVersion Include="EntityFrameworkCore.Scaffolding.Handlebars" Version="6.0.3" />
10+
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.8" />
11+
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="6.0.8" />
1012
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
1113
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
1214
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8" />

0 commit comments

Comments
 (0)