mirror of https://github.com/abpframework/abp.git
32 changed files with 590 additions and 74 deletions
@ -0,0 +1,30 @@ |
|||
using System.Linq; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
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>(); |
|||
var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); |
|||
|
|||
await MapCommonClaimsAsync(identity, dynamicClaims); |
|||
await AddDynamicClaims(identity, dynamicClaims); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
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.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<List<AbpClaimCacheItem>> Cache { get; } |
|||
protected IHttpClientFactory HttpClientFactory { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } |
|||
protected IOptions<RemoteDynamicClaimsPrincipalContributorCacheOptions> CacheOptions { get; } |
|||
|
|||
public RemoteDynamicClaimsPrincipalContributorCache( |
|||
IDistributedCache<List<AbpClaimCacheItem>> cache, |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, |
|||
IJsonSerializer jsonSerializer, |
|||
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, |
|||
IOptions<RemoteDynamicClaimsPrincipalContributorCacheOptions> cacheOptions) |
|||
{ |
|||
Cache = cache; |
|||
HttpClientFactory = httpClientFactory; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
JsonSerializer = jsonSerializer; |
|||
HttpClientAuthenticator = httpClientAuthenticator; |
|||
CacheOptions = cacheOptions; |
|||
|
|||
Logger = NullLogger<RemoteDynamicClaimsPrincipalContributorCache>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<List<AbpClaimCacheItem>> GetAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); |
|||
//The UI may use the same cache as AuthServer in the tiered application.
|
|||
var claims = await Cache.GetAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
if (!claims.IsNullOrEmpty()) |
|||
{ |
|||
return claims!; |
|||
} |
|||
|
|||
Logger.LogDebug($"Get dynamic claims cache for user: {userId} from remote cache."); |
|||
// Use independent cache for remote claims.
|
|||
return (await Cache.GetOrAddAsync($"{nameof(RemoteDynamicClaimsPrincipalContributorCache)}{AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)}", async () => |
|||
{ |
|||
var dynamicClaims = new List<AbpClaimCacheItem>(); |
|||
Logger.LogDebug($"Get dynamic claims for user: {userId} from remote service."); |
|||
try |
|||
{ |
|||
var client = HttpClientFactory.CreateClient(HttpClientName); |
|||
var requestMessage = new HttpRequestMessage(HttpMethod.Get, AbpClaimsPrincipalFactoryOptions.Value.RemoteUrl); |
|||
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration("/"), string.Empty)); |
|||
var response = await client.SendAsync(requestMessage); |
|||
dynamicClaims = JsonSerializer.Deserialize<List<AbpClaimCacheItem>>(await response.Content.ReadAsStringAsync()); |
|||
Logger.LogDebug($"Successfully got {dynamicClaims.Count} remote claims for user: {userId}"); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogWarning(e, $"Failed to get remote claims for user: {userId}"); |
|||
} |
|||
return dynamicClaims; |
|||
}, () => new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration |
|||
}))!; |
|||
} |
|||
|
|||
public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null) |
|||
{ |
|||
Logger.LogDebug($"Clear dynamic claims cache for user: {userId}"); |
|||
Logger.LogDebug($"Clear dynamic claims cache from remote cache for user: {userId}"); |
|||
await Cache.RemoveAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)); |
|||
await Cache.RemoveAsync($"{nameof(RemoteDynamicClaimsPrincipalContributorCache)}{AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)}"); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Client; |
|||
|
|||
public class RemoteDynamicClaimsPrincipalContributorCacheOptions |
|||
{ |
|||
public TimeSpan CacheAbsoluteExpiration { get; set; } |
|||
|
|||
public RemoteDynamicClaimsPrincipalContributorCacheOptions() |
|||
{ |
|||
CacheAbsoluteExpiration = TimeSpan.FromSeconds(5); |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Security.Principal; |
|||
using System.Threading.Tasks; |
|||
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 MapCommonClaimsAsync(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims) |
|||
{ |
|||
await MapClaimsAsync(identity, dynamicClaims, identity.NameClaimType, "preferred_username"); |
|||
await MapClaimsAsync(identity, dynamicClaims, identity.NameClaimType, ClaimTypes.Name); |
|||
await MapClaimsAsync(identity, dynamicClaims, identity.RoleClaimType, "role"); |
|||
await MapClaimsAsync(identity, dynamicClaims, identity.RoleClaimType, ClaimTypes.Role); |
|||
await MapClaimsAsync(identity, dynamicClaims, "email", ClaimTypes.Email); |
|||
await MapClaimsAsync(identity, dynamicClaims, "family_name", ClaimTypes.Surname); |
|||
await MapClaimsAsync(identity, dynamicClaims, "given_name", ClaimTypes.GivenName); |
|||
} |
|||
|
|||
protected virtual Task MapClaimsAsync(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims, string sourceType, string targetType) |
|||
{ |
|||
if (sourceType == targetType) |
|||
{ |
|||
return Task.CompletedTask;; |
|||
} |
|||
|
|||
if (identity.Claims.Any(c => c.Type == sourceType) && dynamicClaims.All(c => c.Type != sourceType)) |
|||
{ |
|||
var claims = dynamicClaims.Where(c => c.Type == targetType).ToList(); |
|||
if (!claims.IsNullOrEmpty()) |
|||
{ |
|||
identity.RemoveAll(sourceType); |
|||
identity.AddClaims(claims.Select(c => new Claim(sourceType, c.Value))); |
|||
dynamicClaims.RemoveAll(c => c.Type == targetType); |
|||
} |
|||
} |
|||
|
|||
if (identity.Claims.Any(c => c.Type == targetType) && dynamicClaims.All(c => c.Type != targetType)) |
|||
{ |
|||
var claims = dynamicClaims.Where(c => c.Type == sourceType).ToList(); |
|||
if (!claims.IsNullOrEmpty()) |
|||
{ |
|||
identity.RemoveAll(targetType); |
|||
identity.AddClaims(claims.Select(c => new Claim(targetType, c.Value))); |
|||
dynamicClaims.RemoveAll(c => c.Type == sourceType); |
|||
} |
|||
} |
|||
|
|||
return Task.CompletedTask;; |
|||
} |
|||
|
|||
protected virtual Task AddDynamicClaims(ClaimsIdentity identity, List<AbpClaimCacheItem> dynamicClaims) |
|||
{ |
|||
foreach (var claims in dynamicClaims.GroupBy(x => x.Type)) |
|||
{ |
|||
if (claims.Count() > 1) |
|||
{ |
|||
identity.RemoveAll(claims.First().Type); |
|||
identity.AddClaims(claims.Select(c => new Claim(claims.First().Type, c.Value))); |
|||
} |
|||
else |
|||
{ |
|||
identity.AddOrReplace(new Claim(claims.First().Type, claims.First().Value)); |
|||
} |
|||
} |
|||
|
|||
return Task.CompletedTask;; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.Security.Claims; |
|||
|
|||
public interface IAbpDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.Account; |
|||
|
|||
public class DynamicClaimDto |
|||
{ |
|||
public string Type { get; set; } |
|||
|
|||
public string Value { get; set; } |
|||
} |
|||
@ -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<List<DynamicClaimDto>> GetAsync(); |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace Volo.Abp.Account; |
|||
|
|||
[Authorize] |
|||
public class DynamicClaimsAppService : IdentityAppServiceBase, IDynamicClaimsAppService |
|||
{ |
|||
protected IAbpClaimsPrincipalFactory AbpClaimsPrincipalFactory { get; } |
|||
protected ICurrentPrincipalAccessor PrincipalAccessor { get; } |
|||
protected IOptions<AbpClaimsPrincipalFactoryOptions> AbpClaimsPrincipalFactoryOptions { get; } |
|||
|
|||
public DynamicClaimsAppService( |
|||
IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, |
|||
ICurrentPrincipalAccessor principalAccessor, |
|||
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions) |
|||
{ |
|||
AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory; |
|||
PrincipalAccessor = principalAccessor; |
|||
AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; |
|||
} |
|||
|
|||
public virtual async Task<List<DynamicClaimDto>> GetAsync() |
|||
{ |
|||
var principal = await AbpClaimsPrincipalFactory.CreateAsync(PrincipalAccessor.Principal); |
|||
|
|||
var dynamicClaims = principal.Claims |
|||
.Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type)) |
|||
.Select(c => new DynamicClaimDto |
|||
{ |
|||
Type = c.Type, |
|||
Value = c.Value |
|||
}); |
|||
|
|||
return dynamicClaims.ToList(); |
|||
} |
|||
} |
|||
@ -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<List<DynamicClaimDto>> GetAsync() |
|||
{ |
|||
return await RequestAsync<List<DynamicClaimDto>>(nameof(GetAsync)); |
|||
} |
|||
} |
|||
@ -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,26 @@ |
|||
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; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<List<DynamicClaimDto>> GetAsync() |
|||
{ |
|||
return DynamicClaimsAppService.GetAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityDynamicClaimsPrincipalContributorCacheOptions |
|||
{ |
|||
public TimeSpan CacheAbsoluteExpiration { get; set; } |
|||
|
|||
public IdentityDynamicClaimsPrincipalContributorCacheOptions() |
|||
{ |
|||
CacheAbsoluteExpiration = TimeSpan.FromHours(1); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue