diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs new file mode 100644 index 0000000000..fa4850cb96 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs @@ -0,0 +1,17 @@ +using System; + +namespace Volo.Abp.Security.Claims; + +[Serializable] +public class AbpClaimCacheItem +{ + public string Type { get; set; } + + public string Value { get; set; } + + public AbpClaimCacheItem(string type, string value) + { + Type = type; + Value = value; + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalContributorContext.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalContributorContext.cs index 274f109f93..442bcf867e 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalContributorContext.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalContributorContext.cs @@ -1,6 +1,7 @@ using System; using System.Security.Claims; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.Security.Claims; @@ -19,4 +20,11 @@ public class AbpClaimsPrincipalContributorContext ClaimsPrincipal = claimsIdentity; ServiceProvider = serviceProvider; } + + public virtual T GetRequiredService() + where T : notnull + { + Check.NotNull(ServiceProvider, nameof(ServiceProvider)); + return ServiceProvider.GetRequiredService(); + } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index 354e7e4586..536171837a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -62,6 +62,11 @@ public class AbpIdentityDomainModule : AbpModule }); context.Services.AddAbpDynamicOptions(); + + Configure(options => + { + options.DynamicContributors.Add(); + }); } public override void PostConfigureServices(ServiceConfigurationContext context) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs index d6043a1624..8fb3260beb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs @@ -33,7 +33,7 @@ public class AbpUserClaimsPrincipalFactory : UserClaimsPrincipalFactory CreateAsync(IdentityUser user) + public async override Task CreateAsync(IdentityUser user) { var principal = await base.CreateAsync(user); var identity = principal.Identities.First(); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs new file mode 100644 index 0000000000..4556723fe9 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs @@ -0,0 +1,28 @@ +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.Identity; + +public class IdentityDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency +{ + public virtual async Task ContributeAsync(AbpClaimsPrincipalContributorContext context) + { + var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); + var userId = identity?.FindUserId(); + if (userId == null) + { + return; + } + + var cache = context.GetRequiredService(); + var dynamicClaims = await cache.GetAsync(userId.Value, identity.FindTenantId()); + foreach (var claim in dynamicClaims) + { + identity.AddOrReplace(new Claim(claim.Type, claim.Value)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs new file mode 100644 index 0000000000..9102be79c5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +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 +{ + protected IServiceProvider ServiceProvider { get; } + + public IdentityDynamicClaimsPrincipalContributorCache(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public virtual async Task> GetAsync(Guid userId, Guid? tenantId = null) + { + var logger = ServiceProvider.GetRequiredService>(); + logger.LogDebug($"Get dynamic claims cache for user: {userId}"); + + var cache = ServiceProvider.GetRequiredService>>(); + + return await cache.GetOrAddAsync($"{nameof(IdentityDynamicClaimsPrincipalContributorCache)}_{tenantId}_{userId}", async () => + { + using (ServiceProvider.GetRequiredService().Change(tenantId)) + { + logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); + var userManager = ServiceProvider.GetRequiredService(); + var user = await userManager.FindByIdAsync(userId.ToString()); + if (user == null) + { + logger.LogWarning($"User not found: {userId}"); + return new List(); + } + var factory = ServiceProvider.GetRequiredService>(); + var principal = await factory.CreateAsync(user); + var options = ServiceProvider.GetRequiredService>().Value; + return principal.Identities.FirstOrDefault()?.Claims.Where(c => options.DynamicClaims.Contains(c.Type)).Select(c => new AbpClaimCacheItem(c.Type, c.Value)).ToList(); + } + }); + } + + public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null) + { + var cache = ServiceProvider.GetRequiredService>>(); + var logger = ServiceProvider.GetRequiredService>(); + logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); + await cache.RemoveAsync($"{nameof(IdentityDynamicClaimsPrincipalContributorCache)}_{tenantId}_{userId}"); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserUpdatedEventHandler.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserUpdatedEventHandler.cs new file mode 100644 index 0000000000..5a0838e281 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserUpdatedEventHandler.cs @@ -0,0 +1,30 @@ +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>, ITransientDependency +{ + public ILogger Logger { get; set; } + + private readonly IdentityDynamicClaimsPrincipalContributorCache _cache; + + public UserEntityUpdatedEventHandler(IdentityDynamicClaimsPrincipalContributorCache cache) + { + _cache = cache; + Logger = NullLogger.Instance; + } + + [UnitOfWork] + public virtual async Task HandleEventAsync(EntityUpdatedEventData eventData) + { + var userId = eventData.Entity.Id; + Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); + await _cache.ClearAsync(userId, eventData.Entity.TenantId); + } +}