mirror of https://github.com/abpframework/abp.git
committed by
GitHub
66 changed files with 1303 additions and 141 deletions
@ -0,0 +1,80 @@ |
|||
# Dynamic Claims |
|||
|
|||
## What is Dynamic Claims and Why do we need it |
|||
|
|||
We use claims-based authentication in ASP.NET Core, It will be store the claims in the cookie or token. But the claims are static, it will be not change after the user re-login. If the user changed its username or role, we still get the old claims. |
|||
|
|||
The `Dynamic Claims` feature is used to dynamically generate claims for the user in each request. You can always get the latest user claims. |
|||
|
|||
## How to use it |
|||
|
|||
This feature is disabled by default. You can enable it by following code: |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options => |
|||
{ |
|||
options.IsDynamicClaimsEnabled = true; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
If you are using the tiered solution you need to set the `RemoteRefreshUrl` to the Auth Server url in the UI project. |
|||
|
|||
````csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options => |
|||
{ |
|||
options.IsDynamicClaimsEnabled = true; |
|||
options.RemoteRefreshUrl = configuration["AuthServerUrl"] + options.RemoteRefreshUrl; |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
Then add the `DynamicClaims` middleware. |
|||
|
|||
````csharp |
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
// Add this line before UseAuthorization. |
|||
app.UseDynamicClaims(); |
|||
app.UseAuthorization(); |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
## How it works |
|||
|
|||
The `DynamicClaims` middleware will use `IAbpClaimsPrincipalFactory` to dynamically generate claims for the current user(`HttpContext.User`) in each request. |
|||
|
|||
There are two implementations of `IAbpDynamicClaimsPrincipalContributor` for different scenarios. |
|||
|
|||
### IdentityDynamicClaimsPrincipalContributor |
|||
|
|||
This implementation is used for the `Monolithic` solution. It will get the dynamic claims from the `IUserClaimsPrincipalFactory` and add/replace the current user claims. |
|||
It uses cache to improve performance. the cache will be invalidated when the user entity changed. |
|||
|
|||
### RemoteDynamicClaimsPrincipalContributor |
|||
|
|||
This implementation is used for the `Tiered` solution. It will get the dynamic claims from the cache of the Auth Server. It will call the `RemoteRefreshUrl` of the Auth Server to refresh the cache when the cache is invalid. |
|||
|
|||
## IAbpDynamicClaimsPrincipalContributor |
|||
|
|||
If you want to add your own dynamic claims contributor, you can a class that implement the `IAbpDynamicClaimsPrincipalContributor` interface. The framework will call the `ContributeAsync` method when get the dynamic claims. |
|||
|
|||
> It better to use cache to improve performance. |
|||
|
|||
## AbpClaimsPrincipalFactoryOptions |
|||
|
|||
* `IsDynamicClaimsEnabled`: Enable or disable the dynamic claims feature. |
|||
* `RemoteRefreshUrl`: The url of the Auth Server to refresh the cache. It will be used by the `RemoteDynamicClaimsPrincipalContributor`. The default value is `/api/account/dynamic-claims/refresh`. |
|||
* `DynamicClaims`: A list of dynamic claim types, `DynamicClaims contributor`` will only handle the claim type in this list. |
|||
* `ClaimsMap`: A dictionary to map the claim types. This is used when the claim types are different between the Auth Server and the client. Already set up for common claim types by default |
|||
|
|||
## See Also |
|||
|
|||
* [Authorization](Authorization.md) |
|||
* [Claims-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims) |
|||
* [Mapping, customizing, and transforming claims in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims) |
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
|||
|
|||
public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
if (identity == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var userId = identity.FindUserId(); |
|||
if (userId == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var dynamicClaimsCache = context.GetRequiredService<RemoteDynamicClaimsPrincipalContributorCache>(); |
|||
AbpDynamicClaimCacheItem dynamicClaims; |
|||
try |
|||
{ |
|||
dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// In case if failed refresh remote dynamic cache, We force to clear the claims principal.
|
|||
context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
var logger = context.GetRequiredService<ILogger<RemoteDynamicClaimsPrincipalContributor>>(); |
|||
logger.LogWarning(e, $"Failed to refresh remote dynamic claims cache for user: {userId.Value}"); |
|||
return; |
|||
} |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.Authentication; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
|||
|
|||
public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency |
|||
{ |
|||
public const string HttpClientName = nameof(RemoteDynamicClaimsPrincipalContributorCache); |
|||
|
|||
public ILogger<RemoteDynamicClaimsPrincipalContributorCache> Logger { get; set; } |
|||
protected IDistributedCache<AbpDynamicClaimCacheItem> Cache { get; } |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } |
|||
|
|||
public RemoteDynamicClaimsPrincipalContributorCache( |
|||
IDistributedCache<AbpDynamicClaimCacheItem> cache, |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
|||
IJsonSerializer jsonSerializer, |
|||
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator) |
|||
{ |
|||
Cache = cache; |
|||
HttpClientFactory = httpClientFactory; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
JsonSerializer = jsonSerializer; |
|||
HttpClientAuthenticator = httpClientAuthenticator; |
|||
|
|||
Logger = NullLogger<RemoteDynamicClaimsPrincipalContributorCache>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<AbpDynamicClaimCacheItem> GetAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); |
|||
var dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
if (dynamicClaims != null && !dynamicClaims.Claims.IsNullOrEmpty()) |
|||
{ |
|||
return dynamicClaims; |
|||
} |
|||
|
|||
Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); |
|||
try |
|||
{ |
|||
var client = HttpClientFactory.CreateClient(HttpClientName); |
|||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, AbpClaimsPrincipalFactoryOptions.Value.RemoteRefreshUrl); |
|||
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration("/"), string.Empty)); |
|||
var response = await client.SendAsync(requestMessage); |
|||
response.EnsureSuccessStatusCode(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); |
|||
throw; |
|||
} |
|||
|
|||
dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
if (dynamicClaims == null || dynamicClaims.Claims.IsNullOrEmpty()) |
|||
{ |
|||
throw new AbpException($"Failed to refresh remote claims for user: {userId}"); |
|||
} |
|||
|
|||
return dynamicClaims!; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Security.Claims; |
|||
|
|||
public class AbpDynamicClaimsMiddleware : IMiddleware, ITransientDependency |
|||
{ |
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
if (context.User.Identity?.IsAuthenticated == true) |
|||
{ |
|||
if (context.RequestServices.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value.IsDynamicClaimsEnabled) |
|||
{ |
|||
var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
context.User = await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); |
|||
} |
|||
} |
|||
|
|||
await next(context); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
[Serializable] |
|||
public class AbpDynamicClaim |
|||
{ |
|||
public string Type { get; set; } |
|||
|
|||
public string? Value { get; set; } |
|||
|
|||
public AbpDynamicClaim(string type, string? value) |
|||
{ |
|||
Type = type; |
|||
Value = value; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
[Serializable] |
|||
public class AbpDynamicClaimCacheItem |
|||
{ |
|||
public List<AbpDynamicClaim> Claims { get; set; } |
|||
|
|||
public AbpDynamicClaimCacheItem() |
|||
{ |
|||
Claims = new List<AbpDynamicClaim>(); |
|||
} |
|||
|
|||
public AbpDynamicClaimCacheItem(List<AbpDynamicClaim> claims) |
|||
{ |
|||
Claims = claims; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(Guid userId, Guid? tenantId) |
|||
{ |
|||
return $"{tenantId}-{userId}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public abstract Task ContributeAsync(AbpClaimsPrincipalContributorContext context); |
|||
|
|||
protected virtual async Task AddDynamicClaimsAsync(AbpClaimsPrincipalContributorContext context, ClaimsIdentity identity, List<AbpDynamicClaim> dynamicClaims) |
|||
{ |
|||
var options = context.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value; |
|||
foreach (var map in options.ClaimsMap) |
|||
{ |
|||
await MapClaimAsync(identity, dynamicClaims, map.Key, map.Value.ToArray()); |
|||
} |
|||
|
|||
foreach (var claimGroup in dynamicClaims.GroupBy(x => x.Type)) |
|||
{ |
|||
identity.RemoveAll(claimGroup.First().Type); |
|||
identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(claimGroup.First().Type, c.Value!))); |
|||
} |
|||
} |
|||
|
|||
protected virtual Task MapClaimAsync(ClaimsIdentity identity, List<AbpDynamicClaim> dynamicClaims, string targetClaimType, params string[] sourceClaimTypes) |
|||
{ |
|||
var claims = dynamicClaims.Where(c => sourceClaimTypes.Contains(c.Type)).ToList(); |
|||
if (claims.IsNullOrEmpty()) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
dynamicClaims.RemoveAll(claims); |
|||
identity.RemoveAll(targetClaimType); |
|||
identity.AddClaims(claims.Where(c => c.Value != null).Select(c => new Claim(targetClaimType, c.Value!))); |
|||
|
|||
return Task.CompletedTask;; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public interface IAbpDynamicClaimsPrincipalContributor |
|||
{ |
|||
Task ContributeAsync(AbpClaimsPrincipalContributorContext context); |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; |
|||
private static string TestAuthenticationType => "Identity.Application"; |
|||
|
|||
public AbpClaimsPrincipalFactory_Test() |
|||
{ |
|||
_abpClaimsPrincipalFactory = GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
|
|||
} |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
services.AddTransient<TestAbpClaimsPrincipalContributor>(); |
|||
services.AddTransient<Test2AbpClaimsPrincipalContributor>(); |
|||
services.AddTransient<Test3AbpClaimsPrincipalContributor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateAsync(); |
|||
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 Create_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, "admin")); |
|||
|
|||
await _abpClaimsPrincipalFactory.CreateAsync(claimsPrincipal); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
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) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public class AbpClaimsPrincipalFactory_Tests : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; |
|||
private static string TestAuthenticationType => "Identity.Application"; |
|||
|
|||
public AbpClaimsPrincipalFactory_Tests() |
|||
{ |
|||
_abpClaimsPrincipalFactory = GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
|
|||
} |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task CreateAsync() |
|||
{ |
|||
var claimsPrincipal = await _abpClaimsPrincipalFactory.CreateAsync(); |
|||
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 Create_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, "admin")); |
|||
|
|||
await _abpClaimsPrincipalFactory.CreateAsync(claimsPrincipal); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == "123"); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Role && x.Value == "admin"); |
|||
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 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, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class TestAbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test2AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Email, "admin2@abp.io")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test3AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
public Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var claimsIdentity = context.ClaimsPrincipal.Identities.FirstOrDefault(x => x.AuthenticationType == TestAuthenticationType) |
|||
?? new ClaimsIdentity(TestAuthenticationType); |
|||
|
|||
claimsIdentity.AddOrReplace(new Claim(ClaimTypes.Version, "2.0")); |
|||
|
|||
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
|
|||
class Test4AbpDynamicClaimsPrincipalContributor : IAbpDynamicClaimsPrincipalContributor, ITransientDependency |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
class TestAbpDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
Check.NotNull(identity, nameof(identity)); |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims.Claims); |
|||
} |
|||
} |
|||
|
|||
public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest<AbpSecurityTestModule> |
|||
{ |
|||
private readonly TestAbpDynamicClaimsPrincipalContributor _dynamicClaimsPrincipalContributorBase = new TestAbpDynamicClaimsPrincipalContributor(); |
|||
|
|||
public readonly static AbpDynamicClaimCacheItem DynamicClaims = new AbpDynamicClaimCacheItem(); |
|||
|
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task AddDynamicClaimsAsync() |
|||
{ |
|||
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.UserName, "test-source-userName")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Name, "test-source-name")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.SurName, "test-source-surname")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Role, "test-source-role1")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Role, "test-source-role2")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.Email, "test-source-email")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.EmailVerified, "test-source-emailVerified")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.PhoneNumber, "test-source-phoneNumber")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.PhoneNumberVerified, "test-source-phoneNumberVerified")); |
|||
claimsPrincipal.Identities.First().AddClaim(new Claim("my-claim", "test-source-my-claim")); |
|||
|
|||
DynamicClaims.Claims.AddRange(new [] |
|||
{ |
|||
new AbpDynamicClaim("preferred_username", "test-preferred_username"), |
|||
new AbpDynamicClaim(ClaimTypes.GivenName, "test-given_name"), |
|||
new AbpDynamicClaim("family_name", "test-family_name"), |
|||
new AbpDynamicClaim("role", "test-role1"), |
|||
new AbpDynamicClaim("roles", "test-role2"), |
|||
new AbpDynamicClaim(ClaimTypes.Role, "test-role3"), |
|||
new AbpDynamicClaim("email", "test-email"), |
|||
new AbpDynamicClaim(AbpClaimTypes.EmailVerified, "test-email-verified"), |
|||
new AbpDynamicClaim(AbpClaimTypes.PhoneNumberVerified, null), |
|||
}); |
|||
|
|||
await _dynamicClaimsPrincipalContributorBase.ContributeAsync(new AbpClaimsPrincipalContributorContext(claimsPrincipal, GetRequiredService<IServiceProvider>())); |
|||
|
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.UserName && c.Value == "test-preferred_username"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.SurName && c.Value == "test-family_name"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Name && c.Value == "test-given_name"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role1"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role2"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Role && c.Value == "test-role3"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.Email && c.Value == "test-email"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.EmailVerified && c.Value == "test-email-verified"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == AbpClaimTypes.PhoneNumber && c.Value == "test-source-phoneNumber"); |
|||
claimsPrincipal.Identities.First().Claims.ShouldNotContain(c => c.Type == AbpClaimTypes.PhoneNumberVerified); |
|||
claimsPrincipal.Identities.First().Claims.ShouldContain(c => c.Type == "my-claim" && c.Value == "test-source-my-claim"); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
public interface IDynamicClaimsAppService : IApplicationService |
|||
{ |
|||
Task RefreshAsync(); |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Security.Claims; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[Authorize] |
|||
public class DynamicClaimsAppService : IdentityAppServiceBase, IDynamicClaimsAppService |
|||
{ |
|||
protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } |
|||
protected IAbpClaimsPrincipalFactory AbpClaimsPrincipalFactory { get; } |
|||
protected ICurrentPrincipalAccessor PrincipalAccessor { get; } |
|||
|
|||
public DynamicClaimsAppService( |
|||
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, |
|||
IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, |
|||
ICurrentPrincipalAccessor principalAccessor) |
|||
{ |
|||
IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; |
|||
AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory; |
|||
PrincipalAccessor = principalAccessor; |
|||
} |
|||
|
|||
public virtual async Task RefreshAsync() |
|||
{ |
|||
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(CurrentUser.GetId(), CurrentUser.TenantId); |
|||
await AbpClaimsPrincipalFactory.CreateDynamicAsync(PrincipalAccessor.Principal); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Account; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IDynamicClaimsAppService), typeof(DynamicClaimsClientProxy))] |
|||
public partial class DynamicClaimsClientProxy : ClientProxyBase<IDynamicClaimsAppService>, IDynamicClaimsAppService |
|||
{ |
|||
public virtual async Task RefreshAsync() |
|||
{ |
|||
await RequestAsync(nameof(RefreshAsync)); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
// This file is part of DynamicClaimsClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
public partial class DynamicClaimsClientProxy |
|||
{ |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(AccountRemoteServiceConsts.ModuleName)] |
|||
[ControllerName("DynamicClaims")] |
|||
[Route("/api/account/dynamic-claims")] |
|||
public class DynamicClaimsController : AbpControllerBase, IDynamicClaimsAppService |
|||
{ |
|||
protected IDynamicClaimsAppService DynamicClaimsAppService { get; } |
|||
|
|||
public DynamicClaimsController(IDynamicClaimsAppService dynamicClaimsAppService) |
|||
{ |
|||
DynamicClaimsAppService = dynamicClaimsAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("refresh")] |
|||
public virtual Task RefreshAsync() |
|||
{ |
|||
return DynamicClaimsAppService.RefreshAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase |
|||
{ |
|||
public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
|||
{ |
|||
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); |
|||
var userId = identity?.FindUserId(); |
|||
if (userId == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var dynamicClaimsCache = context.GetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>(); |
|||
AbpDynamicClaimCacheItem dynamicClaims; |
|||
try |
|||
{ |
|||
dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); |
|||
} |
|||
catch (EntityNotFoundException e) |
|||
{ |
|||
// In case if user not found, We force to clear the claims principal.
|
|||
context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); |
|||
var logger = context.GetRequiredService<ILogger<IdentityDynamicClaimsPrincipalContributor>>(); |
|||
logger.LogWarning(e, $"User not found: {userId.Value}"); |
|||
return; |
|||
} |
|||
|
|||
await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependency |
|||
{ |
|||
public ILogger<IdentityDynamicClaimsPrincipalContributorCache> Logger { get; set; } |
|||
|
|||
protected IDistributedCache<AbpDynamicClaimCacheItem> Cache { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IdentityUserManager UserManager { get; } |
|||
protected IUserClaimsPrincipalFactory<IdentityUser> UserClaimsPrincipalFactory { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
protected IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> CacheOptions { get; } |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributorCache( |
|||
IDistributedCache<AbpDynamicClaimCacheItem> cache, |
|||
ICurrentTenant currentTenant, |
|||
IdentityUserManager userManager, |
|||
IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
|||
IOptions<IdentityDynamicClaimsPrincipalContributorCacheOptions> cacheOptions) |
|||
{ |
|||
Cache = cache; |
|||
CurrentTenant = currentTenant; |
|||
UserManager = userManager; |
|||
UserClaimsPrincipalFactory = userClaimsPrincipalFactory; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
CacheOptions = cacheOptions; |
|||
|
|||
Logger = NullLogger<IdentityDynamicClaimsPrincipalContributorCache>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<AbpDynamicClaimCacheItem> GetAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); |
|||
|
|||
return await Cache.GetOrAddAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => |
|||
{ |
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
Logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); |
|||
|
|||
var user = await UserManager.GetByIdAsync(userId); |
|||
var principal = await UserClaimsPrincipalFactory.CreateAsync(user); |
|||
|
|||
var dynamicClaims = new AbpDynamicClaimCacheItem(); |
|||
foreach (var claimType in AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims) |
|||
{ |
|||
var claims = principal.Claims.Where(x => x.Type == claimType).ToList(); |
|||
if (claims.Any()) |
|||
{ |
|||
dynamicClaims.Claims.AddRange(claims.Select(claim => new AbpDynamicClaim(claimType, claim.Value))); |
|||
} |
|||
else |
|||
{ |
|||
dynamicClaims.Claims.Add(new AbpDynamicClaim(claimType, null)); |
|||
} |
|||
} |
|||
|
|||
return dynamicClaims; |
|||
} |
|||
}, () => new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration |
|||
}); |
|||
} |
|||
|
|||
public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); |
|||
await Cache.RemoveAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributorCacheOptions |
|||
{ |
|||
public TimeSpan CacheAbsoluteExpiration { get; set; } |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributorCacheOptions() |
|||
{ |
|||
CacheAbsoluteExpiration = TimeSpan.FromHours(1); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class UserEntityUpdatedEventHandler : |
|||
ILocalEventHandler<EntityUpdatedEventData<IdentityUser>>, |
|||
ILocalEventHandler<EntityDeletedEventData<IdentityUser>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IdentityDynamicClaimsPrincipalContributorCache _cache; |
|||
|
|||
public UserEntityUpdatedEventHandler(IdentityDynamicClaimsPrincipalContributorCache cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(EntityUpdatedEventData<IdentityUser> eventData) |
|||
{ |
|||
await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(EntityDeletedEventData<IdentityUser> eventData) |
|||
{ |
|||
await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); |
|||
} |
|||
|
|||
protected virtual async Task ClearAsync(Guid userId, Guid? tenantId) |
|||
{ |
|||
await _cache.ClearAsync(userId, tenantId); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Security.Claims; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributor_Tests : AbpIdentityDomainTestBase |
|||
{ |
|||
private readonly IdentityUserManager _identityUserManager; |
|||
private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; |
|||
private readonly AbpUserClaimsPrincipalFactory _abpUserClaimsPrincipalFactory; |
|||
private readonly IdentityTestData _testData; |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributor_Tests() |
|||
{ |
|||
_identityUserManager = GetRequiredService<IdentityUserManager>(); |
|||
_abpClaimsPrincipalFactory = GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
_abpUserClaimsPrincipalFactory = GetRequiredService<AbpUserClaimsPrincipalFactory>(); |
|||
_testData = GetRequiredService<IdentityTestData>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Correct_Claims_After_User_Updating() |
|||
{ |
|||
IdentityUser user = null; |
|||
ClaimsPrincipal claimsPrincipal = null; |
|||
string securityStamp = null; |
|||
await UsingUowAsync(async () => |
|||
{ |
|||
user = await _identityUserManager.GetByIdAsync(_testData.UserJohnId); |
|||
user.ShouldNotBeNull(); |
|||
securityStamp = user.SecurityStamp; |
|||
claimsPrincipal = await _abpUserClaimsPrincipalFactory.CreateAsync(user); |
|||
|
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.NameIdentifier && x.Value == user.Id.ToString()); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == user.UserName); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == user.Email); |
|||
claimsPrincipal.Claims.ShouldContain(x => x.Type == "AspNet.Identity.SecurityStamp" && x.Value == securityStamp); |
|||
|
|||
var dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.NameIdentifier && x.Value == user.Id.ToString()); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value == user.UserName); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == user.Email); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == "AspNet.Identity.SecurityStamp" && x.Value == securityStamp);//SecurityStamp is not dynamic claim
|
|||
|
|||
await _identityUserManager.SetUserNameAsync(user, "newUserName"); |
|||
await _identityUserManager.SetEmailAsync(user, "newUserEmail@abp.io"); |
|||
await _identityUserManager.UpdateSecurityStampAsync(user); |
|||
}); |
|||
|
|||
var dynamicClaimsPrincipal = await _abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.NameIdentifier && x.Value == user.Id.ToString()); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Name && x.Value =="newUserName"); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == ClaimTypes.Email && x.Value == "newUserEmail@abp.io"); |
|||
dynamicClaimsPrincipal.Claims.ShouldContain(x => x.Type == "AspNet.Identity.SecurityStamp" && x.Value == securityStamp);//SecurityStamp is not dynamic claim
|
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.OpenIddict; |
|||
|
|||
public class AbpDynamicClaimsOpenIddictClaimsPrincipalHandler: IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency |
|||
{ |
|||
public virtual async Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context) |
|||
{ |
|||
var abpClaimsPrincipalFactory = context |
|||
.ScopeServiceProvider |
|||
.GetRequiredService<IAbpClaimsPrincipalFactory>(); |
|||
|
|||
await abpClaimsPrincipalFactory.CreateDynamicAsync(context.Principal); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue