Browse Source

Add `DynamicClaims` infrastructure.

pull/18064/head
maliming 3 years ago
parent
commit
706e36a0d5
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 5
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs
  2. 23
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs
  3. 31
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs
  4. 57
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs
  5. 9
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs
  6. 12
      framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
  7. 25
      framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs
  8. 13
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs
  9. 21
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs
  10. 2
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs
  11. 61
      framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs

5
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs

@ -111,4 +111,9 @@ public static class AbpApplicationBuilderExtensions
{
return app.UseMiddleware<AbpSecurityHeadersMiddleware>();
}
public static IApplicationBuilder UseDynamicClaims(this IApplicationBuilder app)
{
return app.UseMiddleware<AbpDynamicClaimsMiddleware>();
}
}

23
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Security.Claims;
public class AbpDynamicClaimsMiddleware : IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var currentUser = context.RequestServices.GetRequiredService<ICurrentUser>();
if (currentUser.IsAuthenticated)
{
var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService<IAbpClaimsPrincipalFactory>();
await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User);
}
await next(context);
}
}

31
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs

@ -1,31 +0,0 @@
using System.Collections.Generic;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.AspNetCore.Security.Claims;
public class AbpDynamicClaimsOptions
{
/// <summary>
/// List of the claims that will be dynamically added/overriden to the current principal.
/// Default values are:
/// - <see cref="AbpClaimTypes.UserName"/>
/// - <see cref="AbpClaimTypes.Role"/>
/// - <see cref="AbpClaimTypes.Email"/>
/// - <see cref="AbpClaimTypes.EmailVerified"/>
/// - <see cref="AbpClaimTypes.PhoneNumber"/>
/// - <see cref="AbpClaimTypes.PhoneNumberVerified"/>
/// </summary>
public List<string> DynamicClaims { get; } = new();
public string RemoteRefreshUrl { get; set; } = "/api/account/refresh-dynamic-claims";
public AbpDynamicClaimsOptions()
{
DynamicClaims.Add(AbpClaimTypes.UserName);
DynamicClaims.Add(AbpClaimTypes.Role);
DynamicClaims.Add(AbpClaimTypes.Email);
DynamicClaims.Add(AbpClaimTypes.EmailVerified);
DynamicClaims.Add(AbpClaimTypes.PhoneNumber);
DynamicClaims.Add(AbpClaimTypes.PhoneNumberVerified);
}
}

57
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs

@ -1,57 +0,0 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Security.Claims;
/* This will be used by DynamicClaimsMiddleware to get dynamic claims
* and override the claims in the current principle.
*/
public class DynamicClaimService : IDynamicClaimService, ITransientDependency
{
public Task<Claim[]> GetClaimsAsync()
{
/* TODO:
* - If current user is not authenticated, return empty array.
* - Try to get from distributed cache for the current user, if available
* - If not available, call IDynamicClaimRefreshService.RefreshAsync()
* Then check from distributed cache again. If still not found, ignore it.
*/
throw new NotImplementedException();
}
}
public interface IDynamicClaimRefreshService
{
Task RefreshAsync();
}
[Dependency(TryRegister = true)]
public class NullDynamicClaimRefreshService : IDynamicClaimRefreshService, ISingletonDependency
{
public Task RefreshAsync()
{
/* TODO: Set distributed cache with an empty claim list
*/
throw new NotImplementedException();
/* TODO:
* IDynamicClaimRefreshService will have more implementations:
* - AccountDynamicClaimRefreshService is the real implementation
* that constructs claims using UserManager.
* It will be effective in a monolith application, where the account
* module is installed in the application
* - RemoteDynamicClaimRefreshService is used in a system (like a microservice solution)
* where the account module is located remotely. In that case, we make a REST call
* to the account service.
* RemoteDynamicClaimRefreshService should be registered only if
* NullDynamicClaimRefreshService is used. I mean AccountDynamicClaimRefreshService
* (in-process implementation) should work by default if available.
* RemoteDynamicClaimRefreshService uses AbpDynamicClaimsOptions.RemoteRefreshUrl (and issuer)
*/
}
}

9
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs

@ -1,9 +0,0 @@
using System.Security.Claims;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Security.Claims;
public interface IDynamicClaimService
{
Task<Claim[]> GetClaimsAsync();
}

12
framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs

@ -238,6 +238,18 @@ public static class AbpClaimsIdentityExtensions
return claimsIdentity;
}
public static ClaimsIdentity RemoveAll(this ClaimsIdentity claimsIdentity, string claimType)
{
Check.NotNull(claimsIdentity, nameof(claimsIdentity));
foreach (var x in claimsIdentity.FindAll(claimType).ToList())
{
claimsIdentity.RemoveClaim(x);
}
return claimsIdentity;
}
public static ClaimsIdentity AddOrReplace(this ClaimsIdentity claimsIdentity, Claim claim)
{
Check.NotNull(claimsIdentity, nameof(claimsIdentity));

25
framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Security.Encryption;
using Volo.Abp.SecurityLog;
@ -11,11 +9,6 @@ namespace Volo.Abp.Security;
public class AbpSecurityModule : AbpModule
{
public override void PostConfigureServices(ServiceConfigurationContext context)
{
AutoAddClaimsPrincipalContributors(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var applicationName = context.Services.GetApplicationName();
@ -58,22 +51,4 @@ public class AbpSecurityModule : AbpModule
}
});
}
private static void AutoAddClaimsPrincipalContributors(IServiceCollection services)
{
var contributorTypes = new List<Type>();
services.OnRegistered(context =>
{
if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType))
{
contributorTypes.Add(context.ImplementationType);
}
});
services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.Contributors.AddIfNotContains(contributorTypes);
});
}
}

13
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Collections;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Security.Claims;
@ -22,6 +23,16 @@ public class AbpClaimsPrincipalFactory : IAbpClaimsPrincipalFactory, ITransientD
}
public virtual async Task<ClaimsPrincipal> CreateAsync(ClaimsPrincipal? existsClaimsPrincipal = null)
{
return await InternalCreateAsync(Options.Contributors, existsClaimsPrincipal);
}
public virtual async Task<ClaimsPrincipal> CreateDynamicAsync(ClaimsPrincipal? existsClaimsPrincipal = null)
{
return await InternalCreateAsync(Options.DynamicContributors, existsClaimsPrincipal);
}
public virtual async Task<ClaimsPrincipal> InternalCreateAsync(ITypeList<IAbpClaimsPrincipalContributor> contributorTypes, ClaimsPrincipal? existsClaimsPrincipal = null)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
@ -32,7 +43,7 @@ public class AbpClaimsPrincipalFactory : IAbpClaimsPrincipalFactory, ITransientD
var context = new AbpClaimsPrincipalContributorContext(claimsPrincipal, scope.ServiceProvider);
foreach (var contributorType in Options.Contributors)
foreach (var contributorType in contributorTypes)
{
var contributor = (IAbpClaimsPrincipalContributor)scope.ServiceProvider.GetRequiredService(contributorType);
await contributor.ContributeAsync(context);

21
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs

@ -1,4 +1,5 @@
using Volo.Abp.Collections;
using System.Collections.Generic;
using Volo.Abp.Collections;
namespace Volo.Abp.Security.Claims;
@ -6,8 +7,26 @@ public class AbpClaimsPrincipalFactoryOptions
{
public ITypeList<IAbpClaimsPrincipalContributor> Contributors { get; }
public ITypeList<IAbpClaimsPrincipalContributor> DynamicContributors { get; }
public List<string> DynamicClaims { get; }
public string RemoteRefreshUrl { get; set; }
public AbpClaimsPrincipalFactoryOptions()
{
Contributors = new TypeList<IAbpClaimsPrincipalContributor>();
DynamicContributors = new TypeList<IAbpClaimsPrincipalContributor>();
DynamicClaims = new List<string>
{
AbpClaimTypes.UserName,
AbpClaimTypes.Role,
AbpClaimTypes.Email,
AbpClaimTypes.EmailVerified,
AbpClaimTypes.PhoneNumber,
AbpClaimTypes.PhoneNumberVerified
};
RemoteRefreshUrl = "/api/account/refresh-dynamic-claims";
}
}

2
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs

@ -6,4 +6,6 @@ namespace Volo.Abp.Security.Claims;
public interface IAbpClaimsPrincipalFactory
{
Task<ClaimsPrincipal> CreateAsync(ClaimsPrincipal? existsClaimsPrincipal = null);
Task<ClaimsPrincipal> CreateDynamicAsync(ClaimsPrincipal? existsClaimsPrincipal = null);
}

61
framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs

@ -27,9 +27,22 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
protected override void AfterAddApplication(IServiceCollection services)
{
services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.Contributors.Add<TestAbpClaimsPrincipalContributor>();
options.Contributors.Add<Test2AbpClaimsPrincipalContributor>();
options.Contributors.Add<Test3AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<TestAbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test2AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test3AbpClaimsPrincipalContributor>();
options.DynamicContributors.Add<Test4AbpClaimsPrincipalContributor>();
});
services.AddTransient<TestAbpClaimsPrincipalContributor>();
services.AddTransient<Test2AbpClaimsPrincipalContributor>();
services.AddTransient<Test3AbpClaimsPrincipalContributor>();
services.AddTransient<Test4AbpClaimsPrincipalContributor>();
}
[Fact]
@ -56,6 +69,36 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0");
}
[Fact]
public async Task DynamicCreateAsync()
{
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync();
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "admin");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io");
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0");
}
[Fact]
public async Task DynamicCreate_With_Exists_ClaimsPrincipal()
{
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(TestAuthenticationType, ClaimTypes.Name, ClaimTypes.Role));
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Name, "123"));
claimsPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.Role, "123"));
await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal);
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Name && x.Value == "123");
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Role && x.Value == "123");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "admin");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "manager");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "admin2@abp.io");
claimsPrincipal.Claims.ShouldNotContain(x => x.Type == ClaimTypes.Email && x.Value == "admin@abp.io");
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Version && x.Value == "2.0");
}
class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
@ -100,4 +143,22 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestM
return Task.CompletedTask;
}
}
class Test4AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor
{
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType)
?? new ClaimsIdentity(TestAuthenticationType);
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Name, "admin"));
claimsIdentity.RemoveAll(ClaimTypes.Role);
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "manager"));
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity);
return Task.CompletedTask;
}
}
}

Loading…
Cancel
Save