From 7f760d073badfb3c0d05e995b029c10cc14feb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 2 Nov 2023 18:17:13 +0300 Subject: [PATCH 01/26] Created draft services --- .../Claims/AbpDynamicClaimsOptions.cs | 31 ++++++++++ .../Security/Claims/DynamicClaimService.cs | 57 +++++++++++++++++++ .../Security/Claims/IDynamicClaimService.cs | 9 +++ 3 files changed, 97 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs create mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs create mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs new file mode 100644 index 0000000000..11345e5b0e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.AspNetCore.Security.Claims; + +public class AbpDynamicClaimsOptions +{ + /// + /// List of the claims that will be dynamically added/overriden to the current principal. + /// Default values are: + /// - + /// - + /// - + /// - + /// - + /// - + /// + public List 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); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs new file mode 100644 index 0000000000..2c7df05c8e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs @@ -0,0 +1,57 @@ +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 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) + */ + } +} + diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs new file mode 100644 index 0000000000..9160961bb2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs @@ -0,0 +1,9 @@ +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Security.Claims; + +public interface IDynamicClaimService +{ + Task GetClaimsAsync(); +} \ No newline at end of file From 706e36a0d563641056ca12a3d31e8c5f28b57275 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 6 Nov 2023 14:40:50 +0800 Subject: [PATCH 02/26] Add `DynamicClaims` infrastructure. --- .../AbpApplicationBuilderExtensions.cs | 5 ++ .../Claims/AbpDynamicClaimsMiddleware.cs | 23 +++++++ .../Claims/AbpDynamicClaimsOptions.cs | 31 ---------- .../Security/Claims/DynamicClaimService.cs | 57 ----------------- .../Security/Claims/IDynamicClaimService.cs | 9 --- .../Principal/AbpClaimsIdentityExtensions.cs | 12 ++++ .../Volo/Abp/Security/AbpSecurityModule.cs | 25 -------- .../Claims/AbpClaimsPrincipalFactory.cs | 13 +++- .../AbpClaimsPrincipalFactoryOptions.cs | 21 ++++++- .../Claims/IAbpClaimsPrincipalFactory.cs | 2 + .../Claims/AbpClaimsPrincipalFactory_Test.cs | 61 +++++++++++++++++++ 11 files changed, 135 insertions(+), 124 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs delete mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs delete mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs delete mode 100644 framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index 8628c2c803..593992aed4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -111,4 +111,9 @@ public static class AbpApplicationBuilderExtensions { return app.UseMiddleware(); } + + public static IApplicationBuilder UseDynamicClaims(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs new file mode 100644 index 0000000000..6e2a55ab39 --- /dev/null +++ b/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(); + if (currentUser.IsAuthenticated) + { + var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService(); + await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); + } + + await next(context); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs deleted file mode 100644 index 11345e5b0e..0000000000 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.Security.Claims; - -namespace Volo.Abp.AspNetCore.Security.Claims; - -public class AbpDynamicClaimsOptions -{ - /// - /// List of the claims that will be dynamically added/overriden to the current principal. - /// Default values are: - /// - - /// - - /// - - /// - - /// - - /// - - /// - public List 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); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs deleted file mode 100644 index 2c7df05c8e..0000000000 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/DynamicClaimService.cs +++ /dev/null @@ -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 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) - */ - } -} - diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs deleted file mode 100644 index 9160961bb2..0000000000 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/IDynamicClaimService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Volo.Abp.AspNetCore.Security.Claims; - -public interface IDynamicClaimService -{ - Task GetClaimsAsync(); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs index 6c12decd19..d8141a31ed 100644 --- a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs +++ b/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)); diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs index 69107c6d1d..5f3bf47997 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs +++ b/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(); - - services.OnRegistered(context => - { - if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) - { - contributorTypes.Add(context.ImplementationType); - } - }); - - services.Configure(options => - { - options.Contributors.AddIfNotContains(contributorTypes); - }); - } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs index ae9d947fb0..a55be7a82b 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs +++ b/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 CreateAsync(ClaimsPrincipal? existsClaimsPrincipal = null) + { + return await InternalCreateAsync(Options.Contributors, existsClaimsPrincipal); + } + + public virtual async Task CreateDynamicAsync(ClaimsPrincipal? existsClaimsPrincipal = null) + { + return await InternalCreateAsync(Options.DynamicContributors, existsClaimsPrincipal); + } + + public virtual async Task InternalCreateAsync(ITypeList 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); diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index 90e411e2a1..3d96a32205 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/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 Contributors { get; } + public ITypeList DynamicContributors { get; } + + public List DynamicClaims { get; } + + public string RemoteRefreshUrl { get; set; } + public AbpClaimsPrincipalFactoryOptions() { Contributors = new TypeList(); + DynamicContributors = new TypeList(); + + DynamicClaims = new List + { + AbpClaimTypes.UserName, + AbpClaimTypes.Role, + AbpClaimTypes.Email, + AbpClaimTypes.EmailVerified, + AbpClaimTypes.PhoneNumber, + AbpClaimTypes.PhoneNumberVerified + }; + RemoteRefreshUrl = "/api/account/refresh-dynamic-claims"; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs index b4f3046c9d..75da993f4d 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpClaimsPrincipalFactory.cs @@ -6,4 +6,6 @@ namespace Volo.Abp.Security.Claims; public interface IAbpClaimsPrincipalFactory { Task CreateAsync(ClaimsPrincipal? existsClaimsPrincipal = null); + + Task CreateDynamicAsync(ClaimsPrincipal? existsClaimsPrincipal = null); } diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs index b46694f9ed..b444fa3ded 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs @@ -27,9 +27,22 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest(options => + { + options.Contributors.Add(); + options.Contributors.Add(); + options.Contributors.Add(); + + options.DynamicContributors.Add(); + options.DynamicContributors.Add(); + options.DynamicContributors.Add(); + options.DynamicContributors.Add(); + }); + services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } [Fact] @@ -56,6 +69,36 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest 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 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; + } + } } From 44ef56c2d83226cae203604c152aefa617345c61 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 7 Nov 2023 09:27:08 +0800 Subject: [PATCH 03/26] Add `IdentityDynamicClaimsPrincipalContributor`. --- .../Abp/Security/Claims/AbpClaimCacheIteam.cs | 17 ++++++ .../AbpClaimsPrincipalContributorContext.cs | 8 +++ .../Abp/Identity/AbpIdentityDomainModule.cs | 5 ++ .../Identity/AbpUserClaimsPrincipalFactory.cs | 2 +- ...entityDynamicClaimsPrincipalContributor.cs | 28 +++++++++ ...yDynamicClaimsPrincipalContributorCache.cs | 60 +++++++++++++++++++ .../Abp/Identity/UserUpdatedEventHandler.cs | 30 ++++++++++ 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCache.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserUpdatedEventHandler.cs 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); + } +} From 9a1b4c2004174f6ed096e5ef70153f911e397b14 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 7 Nov 2023 10:11:53 +0800 Subject: [PATCH 04/26] Create IdentityDynamicClaimsPrincipalContributor_Tests.cs --- ...DynamicClaimsPrincipalContributor_Tests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs new file mode 100644 index 0000000000..9ac0a2d36b --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor_Tests.cs @@ -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(); + _abpClaimsPrincipalFactory = GetRequiredService(); + _abpUserClaimsPrincipalFactory = GetRequiredService(); + _testData = GetRequiredService(); + } + + [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 + } +} From 02bd699c4d08644ca9b88b3dcfb70325b7461a28 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 8 Nov 2023 20:49:15 +0800 Subject: [PATCH 05/26] Add `RemoteDynamicClaimsPrincipalContributor`. --- .../Web/Security/AbpComponentsClaimsCache.cs | 7 +- ...RemoteDynamicClaimsPrincipalContributor.cs | 30 ++++++ ...eDynamicClaimsPrincipalContributorCache.cs | 91 +++++++++++++++++++ ...cClaimsPrincipalContributorCacheOptions.cs | 13 +++ .../Volo/Abp/Security/AbpSecurityModule.cs | 38 ++++++++ .../Abp/Security/Claims/AbpClaimCacheIteam.cs | 7 ++ .../AbpClaimsPrincipalFactoryOptions.cs | 7 +- ...bpDynamicClaimsPrincipalContributorBase.cs | 74 +++++++++++++++ .../IAbpDynamicClaimsPrincipalContributor.cs | 6 ++ .../Claims/AbpClaimsPrincipalFactory_Test.cs | 75 ++++++++++----- .../Volo/Abp/Account/DynamicClaimDto.cs | 8 ++ .../Abp/Account/IDynamicClaimsAppService.cs | 10 ++ .../Abp/Account/DynamicClaimsAppService.cs | 42 +++++++++ .../DynamicClaimsClientProxy.Generated.cs | 24 +++++ .../Abp/Account/DynamicClaimsClientProxy.cs | 7 ++ .../ClientProxies/account-generate-proxy.json | 41 +++++++++ .../Abp/Account/DynamicClaimsController.cs | 26 ++++++ .../wwwroot/client-proxies/account-proxy.js | 15 +++ .../Abp/Identity/AbpIdentityDomainModule.cs | 5 - ...entityDynamicClaimsPrincipalContributor.cs | 17 ++-- ...yDynamicClaimsPrincipalContributorCache.cs | 65 ++++++++----- ...cClaimsPrincipalContributorCacheOptions.cs | 13 +++ .../AbpUserClaimsPrincipalFactory_Tests.cs | 8 +- .../MyProjectNameAuthServerModule.cs | 1 + .../MyProjectNameBlazorModule.cs | 10 +- .../MyProjectNameBlazorModule.cs | 2 +- .../MyProjectNameBlazorModule.cs | 6 ++ .../MyProjectNameHttpApiHostModule.cs | 3 +- .../MyProjectNameHttpApiHostModule.cs | 2 +- ...yCompanyName.MyProjectName.Web.Host.csproj | 1 + .../MyProjectNameWebModule.cs | 9 +- .../MyProjectNameWebModule.cs | 1 + 32 files changed, 590 insertions(+), 74 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs create mode 100644 modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs create mode 100644 modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs create mode 100644 modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs create mode 100644 modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs create mode 100644 modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.cs create mode 100644 modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCacheOptions.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpComponentsClaimsCache.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpComponentsClaimsCache.cs index 74cbd37e29..8df1824e81 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpComponentsClaimsCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpComponentsClaimsCache.cs @@ -1,9 +1,9 @@ using System.Security.Claims; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; namespace Volo.Abp.AspNetCore.Components.Web.Security; @@ -12,16 +12,18 @@ public class AbpComponentsClaimsCache : IScopedDependency public ClaimsPrincipal Principal { get; private set; } = default!; private readonly AuthenticationStateProvider? _authenticationStateProvider; + private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; public AbpComponentsClaimsCache( IClientScopeServiceProviderAccessor serviceProviderAccessor) { _authenticationStateProvider = serviceProviderAccessor.ServiceProvider.GetService(); + _abpClaimsPrincipalFactory = serviceProviderAccessor.ServiceProvider.GetRequiredService(); if (_authenticationStateProvider != null) { _authenticationStateProvider.AuthenticationStateChanged += async (task) => { - Principal = (await task).User; + Principal = await _abpClaimsPrincipalFactory.CreateDynamicAsync((await task).User); }; } } @@ -32,6 +34,7 @@ public class AbpComponentsClaimsCache : IScopedDependency { var authenticationState = await _authenticationStateProvider.GetAuthenticationStateAsync(); Principal = authenticationState.User; + await _abpClaimsPrincipalFactory.CreateDynamicAsync(Principal); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs new file mode 100644 index 0000000000..38d3b7840a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs @@ -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(); + var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); + + await MapCommonClaimsAsync(identity, dynamicClaims); + await AddDynamicClaims(identity, dynamicClaims); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs new file mode 100644 index 0000000000..33a1dfd5f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs @@ -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 Logger { get; set; } + protected IDistributedCache> Cache { get; } + protected IHttpClientFactory HttpClientFactory { get; } + protected IOptions AbpClaimsPrincipalFactoryOptions { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } + protected IOptions CacheOptions { get; } + + public RemoteDynamicClaimsPrincipalContributorCache( + IDistributedCache> cache, + IHttpClientFactory httpClientFactory, + IOptions abpClaimsPrincipalFactoryOptions, + IJsonSerializer jsonSerializer, + IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, + IOptions cacheOptions) + { + Cache = cache; + HttpClientFactory = httpClientFactory; + AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; + JsonSerializer = jsonSerializer; + HttpClientAuthenticator = httpClientAuthenticator; + CacheOptions = cacheOptions; + + Logger = NullLogger.Instance; + } + + public virtual async Task> 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(); + 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>(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)}"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs new file mode 100644 index 0000000000..c8e31b1269 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs @@ -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); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs index 5f3bf47997..9705c1e2e6 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs @@ -1,7 +1,9 @@ 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; @@ -9,6 +11,11 @@ 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(); @@ -51,4 +58,35 @@ public class AbpSecurityModule : AbpModule } }); } + + + private static void AutoAddClaimsPrincipalContributors(IServiceCollection services) + { + var contributorTypes = new List(); + + services.OnRegistered(context => + { + if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType) && + !typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) + { + contributorTypes.Add(context.ImplementationType); + } + }); + + var dynamicContributorTypes = new List(); + + services.OnRegistered(context => + { + if (typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) + { + dynamicContributorTypes.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.Contributors.AddIfNotContains(contributorTypes); + options.DynamicContributors.AddIfNotContains(dynamicContributorTypes); + }); + } } 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 index fa4850cb96..02f15e2478 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs @@ -14,4 +14,11 @@ public class AbpClaimCacheItem Type = type; Value = value; } + + + public static string CalculateCacheKey(Guid userId, Guid? tenantId) + { + return $"{tenantId}-{userId}"; + } + } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index 3d96a32205..5f027f6862 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs @@ -11,7 +11,7 @@ public class AbpClaimsPrincipalFactoryOptions public List DynamicClaims { get; } - public string RemoteRefreshUrl { get; set; } + public string RemoteUrl { get; set; } public AbpClaimsPrincipalFactoryOptions() { @@ -21,12 +21,15 @@ public class AbpClaimsPrincipalFactoryOptions DynamicClaims = new List { AbpClaimTypes.UserName, + AbpClaimTypes.Name, + AbpClaimTypes.SurName, AbpClaimTypes.Role, AbpClaimTypes.Email, AbpClaimTypes.EmailVerified, AbpClaimTypes.PhoneNumber, AbpClaimTypes.PhoneNumberVerified }; - RemoteRefreshUrl = "/api/account/refresh-dynamic-claims"; + + RemoteUrl = "/api/account/dynamic-claims"; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs new file mode 100644 index 0000000000..55f2dc96a6 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs @@ -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 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 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 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;; + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs new file mode 100644 index 0000000000..dde236c363 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Security.Claims; + +public interface IAbpDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor +{ + +} diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs index b444fa3ded..7726c2fe12 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs @@ -2,8 +2,8 @@ using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Shouldly; +using Volo.Abp.DependencyInjection; using Volo.Abp.Testing; using Xunit; @@ -25,26 +25,6 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest(options => - { - options.Contributors.Add(); - options.Contributors.Add(); - options.Contributors.Add(); - - options.DynamicContributors.Add(); - options.DynamicContributors.Add(); - options.DynamicContributors.Add(); - options.DynamicContributors.Add(); - }); - - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - } - [Fact] public async Task CreateAsync() { @@ -99,7 +79,52 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest x.Type == ClaimTypes.Version && x.Value == "2.0"); } - class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor + 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) { @@ -114,7 +139,7 @@ public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest> GetAsync(); +} diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs new file mode 100644 index 0000000000..ebe9e06cb7 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs @@ -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 { get; } + + public DynamicClaimsAppService( + IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, + ICurrentPrincipalAccessor principalAccessor, + IOptions abpClaimsPrincipalFactoryOptions) + { + AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory; + PrincipalAccessor = principalAccessor; + AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; + } + + public virtual async Task> 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(); + } +} diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs new file mode 100644 index 0000000000..d460483a8d --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs @@ -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 +{ + public virtual async Task> GetAsync() + { + return await RequestAsync>(nameof(GetAsync)); + } +} diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.cs b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.cs new file mode 100644 index 0000000000..da398d8951 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.cs @@ -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 +{ +} diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json index 24bd9c6079..857c27a3cd 100644 --- a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json +++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json @@ -338,6 +338,47 @@ } } }, + "Volo.Abp.Account.DynamicClaimsController": { + "controllerName": "DynamicClaims", + "controllerGroupName": "DynamicClaims", + "isRemoteService": true, + "isIntegrationService": false, + "apiVersion": null, + "type": "Volo.Abp.Account.DynamicClaimsController", + "interfaces": [ + { + "type": "Volo.Abp.Account.IDynamicClaimsAppService", + "name": "IDynamicClaimsAppService", + "methods": [ + { + "name": "GetAsync", + "parametersOnMethod": [], + "returnValue": { + "type": "System.Collections.Generic.List", + "typeSimple": "[Volo.Abp.Account.DynamicClaimDto]" + } + } + ] + } + ], + "actions": { + "GetAsync": { + "uniqueName": "GetAsync", + "name": "GetAsync", + "httpMethod": "GET", + "url": "api/account/dynamic-claims", + "supportedVersions": [], + "parametersOnMethod": [], + "parameters": [], + "returnValue": { + "type": "System.Collections.Generic.List", + "typeSimple": "[Volo.Abp.Account.DynamicClaimDto]" + }, + "allowAnonymous": null, + "implementFrom": "Volo.Abp.Account.IDynamicClaimsAppService" + } + } + }, "Volo.Abp.Account.ProfileController": { "controllerName": "Profile", "controllerGroupName": "Profile", diff --git a/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs b/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs new file mode 100644 index 0000000000..85587478ed --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs @@ -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> GetAsync() + { + return DynamicClaimsAppService.GetAsync(); + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/wwwroot/client-proxies/account-proxy.js b/modules/account/src/Volo.Abp.Account.Web/wwwroot/client-proxies/account-proxy.js index 2e291710d5..d3c29354ef 100644 --- a/modules/account/src/Volo.Abp.Account.Web/wwwroot/client-proxies/account-proxy.js +++ b/modules/account/src/Volo.Abp.Account.Web/wwwroot/client-proxies/account-proxy.js @@ -79,6 +79,21 @@ })(); + // controller volo.abp.account.dynamicClaims + + (function(){ + + abp.utils.createNamespace(window, 'volo.abp.account.dynamicClaims'); + + volo.abp.account.dynamicClaims.get = function(ajaxParams) { + return abp.ajax($.extend(true, { + url: abp.appPath + 'api/account/dynamic-claims', + type: 'GET' + }, ajaxParams)); + }; + + })(); + // controller volo.abp.account.profile (function(){ 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 536171837a..354e7e4586 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,11 +62,6 @@ 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/IdentityDynamicClaimsPrincipalContributor.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs index 4556723fe9..6572c08b7a 100644 --- 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 @@ -1,15 +1,13 @@ 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 class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContributorBase { - public virtual async Task ContributeAsync(AbpClaimsPrincipalContributorContext context) + public async override Task ContributeAsync(AbpClaimsPrincipalContributorContext context) { var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); var userId = identity?.FindUserId(); @@ -18,11 +16,10 @@ public class IdentityDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalCont 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)); - } + var dynamicClaimsCache = context.GetRequiredService(); + var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); + + await MapCommonClaimsAsync(identity, dynamicClaims); + await AddDynamicClaims(identity, dynamicClaims); } } 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 index 9102be79c5..7006a97176 100644 --- 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 @@ -1,11 +1,11 @@ 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.Caching.Distributed; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; @@ -16,45 +16,64 @@ namespace Volo.Abp.Identity; public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependency { - protected IServiceProvider ServiceProvider { get; } + public ILogger Logger { get; set; } - public IdentityDynamicClaimsPrincipalContributorCache(IServiceProvider serviceProvider) + protected IDistributedCache> Cache { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IdentityUserManager UserManager { get; } + protected IUserClaimsPrincipalFactory UserClaimsPrincipalFactory { get; } + protected IOptions AbpClaimsPrincipalFactoryOptions { get; } + protected IOptions CacheOptions { get; } + + public IdentityDynamicClaimsPrincipalContributorCache( + IDistributedCache> cache, + ICurrentTenant currentTenant, + IdentityUserManager userManager, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory, + IOptions abpClaimsPrincipalFactoryOptions, + IOptions cacheOptions) { - this.ServiceProvider = serviceProvider; + Cache = cache; + CurrentTenant = currentTenant; + UserManager = userManager; + UserClaimsPrincipalFactory = userClaimsPrincipalFactory; + AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; + CacheOptions = cacheOptions; + + Logger = NullLogger.Instance; } 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>>(); + Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); - return await cache.GetOrAddAsync($"{nameof(IdentityDynamicClaimsPrincipalContributorCache)}_{tenantId}_{userId}", async () => + return await Cache.GetOrAddAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => { - using (ServiceProvider.GetRequiredService().Change(tenantId)) + using (CurrentTenant.Change(tenantId)) { - logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); - var userManager = ServiceProvider.GetRequiredService(); - var user = await userManager.FindByIdAsync(userId.ToString()); + Logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); + + var user = await UserManager.FindByIdAsync(userId.ToString()); if (user == null) { - logger.LogWarning($"User not found: {userId}"); + 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(); + + var principal = await UserClaimsPrincipalFactory.CreateAsync(user); + return principal.Identities.FirstOrDefault()?.Claims + .Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type)) + .Select(c => new AbpClaimCacheItem(c.Type, c.Value)).ToList(); } + }, () => new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration }); } 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}"); + Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); + await Cache.RemoveAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCacheOptions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCacheOptions.cs new file mode 100644 index 0000000000..3387f6cda5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDynamicClaimsPrincipalContributorCacheOptions.cs @@ -0,0 +1,13 @@ +using System; + +namespace Volo.Abp.Identity; + +public class IdentityDynamicClaimsPrincipalContributorCacheOptions +{ + public TimeSpan CacheAbsoluteExpiration { get; set; } + + public IdentityDynamicClaimsPrincipalContributorCacheOptions() + { + CacheAbsoluteExpiration = TimeSpan.FromHours(1); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory_Tests.cs index 60a62cda1e..cf9b339cc1 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory_Tests.cs @@ -2,6 +2,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.DependencyInjection; using Volo.Abp.Security.Claims; @@ -22,6 +23,11 @@ public class AbpUserClaimsPrincipalFactory_Tests : AbpIdentityDomainTestBase _testData = GetRequiredService(); } + protected override void AfterAddApplication(IServiceCollection services) + { + services.AddTransient(); + } + [Fact] public async Task Add_And_Replace_Claims_Test() { @@ -42,7 +48,7 @@ public class AbpUserClaimsPrincipalFactory_Tests : AbpIdentityDomainTestBase }); } - class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency + class TestAbpClaimsPrincipalContributor : IAbpClaimsPrincipalContributor { //https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79 private static string IdentityAuthenticationType => "Identity.Application"; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs index 2a5a34c7e5..95b2563438 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs @@ -211,6 +211,7 @@ public class MyProjectNameAuthServerModule : AbpModule app.UseMultiTenancy(); } + app.UseDynamicClaims(); app.UseUnitOfWork(); app.UseAuthorization(); app.UseAuditing(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index 04ae40fea8..dd890d4dc6 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -44,6 +44,7 @@ using Volo.Abp.Identity.Blazor.Server; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; +using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Blazor.Server; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement.Blazor.Server; @@ -188,7 +189,7 @@ public class MyProjectNameBlazorModule : AbpModule * This configuration is used when the AuthServer is running on the internal network such as docker or k8s. * Configuring the redirecting URLs for internal network and the web * The login and the logout URLs are configured to redirect to the AuthServer real DNS for browser. - * The token acquired and validated from the the internal network AuthServer URL. + * The token acquired and validated from the the internal network AuthServer URL. */ if (configuration.GetValue("AuthServer:IsContainerized")) { @@ -227,6 +228,11 @@ public class MyProjectNameBlazorModule : AbpModule }; }); } + + context.Services.Configure(options => + { + options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl; + }); } private void ConfigureVirtualFileSystem(IWebHostEnvironment hostingEnvironment) @@ -351,7 +357,7 @@ public class MyProjectNameBlazorModule : AbpModule { app.UseMultiTenancy(); } - + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); app.UseAbpSwaggerUI(options => diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index e13c32cbf4..0bb20fa36e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -265,7 +265,7 @@ public class MyProjectNameBlazorModule : AbpModule { app.UseMultiTenancy(); } - + app.UseDynamicClaims(); app.UseUnitOfWork(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index 84abc220c8..a323ff8dcd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -15,6 +15,7 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Blazor.WebAssembly; using Volo.Abp.TenantManagement.Blazor.WebAssembly; @@ -80,6 +81,11 @@ public class MyProjectNameBlazorModule : AbpModule options.ProviderOptions.DefaultScopes.Add("email"); options.ProviderOptions.DefaultScopes.Add("phone"); }); + + builder.Services.Configure(options => + { + options.RemoteUrl = builder.Configuration["AuthServer:Authority"] + options.RemoteUrl; + }); } private static void ConfigureUI(WebAssemblyHostBuilder builder) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 48a250aa4f..d277aa4039 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -24,6 +24,7 @@ using Volo.Abp.Autofac; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; +using Volo.Abp.Identity; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Swashbuckle; @@ -188,7 +189,7 @@ public class MyProjectNameHttpApiHostModule : AbpModule { app.UseMultiTenancy(); } - + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index fe71a42e9a..238339b1cf 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -198,7 +198,7 @@ public class MyProjectNameHttpApiHostModule : AbpModule { app.UseMultiTenancy(); } - + app.UseDynamicClaims(); app.UseUnitOfWork(); app.UseAuthorization(); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index b59c0bcf08..55a5a4cfdb 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -20,6 +20,7 @@ + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index fd20023627..01ed6034f8 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -38,6 +38,7 @@ using Volo.Abp.Identity.Web; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement.Web; +using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Web; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement.Web; @@ -170,7 +171,7 @@ public class MyProjectNameWebModule : AbpModule * This configuration is used when the AuthServer is running on the internal network such as docker or k8s. * Configuring the redirecting URLs for internal network and the web * The login and the logout URLs are configured to redirect to the AuthServer real DNS for browser. - * The token acquired and validated from the the internal network AuthServer URL. + * The token acquired and validated from the the internal network AuthServer URL. */ if (configuration.GetValue("AuthServer:IsContainerized")) { @@ -209,6 +210,11 @@ public class MyProjectNameWebModule : AbpModule }; }); } + + context.Services.Configure(options => + { + options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl; + }); } private void ConfigureAutoMapper() @@ -316,6 +322,7 @@ public class MyProjectNameWebModule : AbpModule app.UseMultiTenancy(); } + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); app.UseAbpSwaggerUI(options => diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index f9c8d6a1c2..a259540829 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -230,6 +230,7 @@ public class MyProjectNameWebModule : AbpModule app.UseMultiTenancy(); } + app.UseDynamicClaims(); app.UseUnitOfWork(); app.UseAuthorization(); app.UseSwagger(); From f6e844851e9edba5d1698b2338887f1a88b8593a Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 12:53:48 +0800 Subject: [PATCH 06/26] Update `UserEntityUpdatedEventHandler `. --- ...yDynamicClaimsPrincipalContributorCache.cs | 8 +------- .../Abp/Identity/UserUpdatedEventHandler.cs | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) 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 index 7006a97176..51b16aa88f 100644 --- 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 @@ -53,13 +53,7 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen { Logger.LogDebug($"Filling dynamic claims cache for user: {userId}"); - var user = await UserManager.FindByIdAsync(userId.ToString()); - if (user == null) - { - Logger.LogWarning($"User not found: {userId}"); - return new List(); - } - + var user = await UserManager.GetByIdAsync(userId); var principal = await UserClaimsPrincipalFactory.CreateAsync(user); return principal.Identities.FirstOrDefault()?.Claims .Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type)) 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 index 5a0838e281..d4a84c341a 100644 --- 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 @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -8,7 +9,10 @@ using Volo.Abp.Uow; namespace Volo.Abp.Identity; -public class UserEntityUpdatedEventHandler : ILocalEventHandler>, ITransientDependency +public class UserEntityUpdatedEventHandler : + ILocalEventHandler>, + ILocalEventHandler>, + ITransientDependency { public ILogger Logger { get; set; } @@ -23,8 +27,18 @@ public class UserEntityUpdatedEventHandler : ILocalEventHandler eventData) { - var userId = eventData.Entity.Id; + await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); + } + + [UnitOfWork] + public virtual async Task HandleEventAsync(EntityDeletedEventData eventData) + { + await ClearAsync(eventData.Entity.Id, eventData.Entity.TenantId); + } + + protected virtual async Task ClearAsync(Guid userId, Guid? tenantId) + { Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); - await _cache.ClearAsync(userId, eventData.Entity.TenantId); + await _cache.ClearAsync(userId, tenantId); } } From abb8cfc9634ebfa07c7218364aabb359df28bbd0 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 13:46:38 +0800 Subject: [PATCH 07/26] Force to clear the claims principal when user is not found. --- .../Claims/AbpDynamicClaimsMiddleware.cs | 2 +- .../Principal/AbpClaimsIdentityExtensions.cs | 2 +- .../AbpClaimsPrincipalContributorContext.cs | 2 +- .../Claims/AbpClaimsPrincipalFactory.cs | 2 +- ...entityDynamicClaimsPrincipalContributor.cs | 19 ++++++++++++++++++- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs index 6e2a55ab39..33b7b3f16e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs @@ -15,7 +15,7 @@ public class AbpDynamicClaimsMiddleware : IMiddleware, ITransientDependency if (currentUser.IsAuthenticated) { var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService(); - await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); + context.User = await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); } await next(context); diff --git a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs index d8141a31ed..2661298405 100644 --- a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs +++ b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs @@ -250,6 +250,7 @@ public static class AbpClaimsIdentityExtensions return claimsIdentity; } + public static ClaimsIdentity AddOrReplace(this ClaimsIdentity claimsIdentity, Claim claim) { Check.NotNull(claimsIdentity, nameof(claimsIdentity)); @@ -275,5 +276,4 @@ public static class AbpClaimsIdentityExtensions return principal; } - } 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 442bcf867e..6b9aff1a66 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 @@ -8,7 +8,7 @@ namespace Volo.Abp.Security.Claims; public class AbpClaimsPrincipalContributorContext { [NotNull] - public ClaimsPrincipal ClaimsPrincipal { get; } + public ClaimsPrincipal ClaimsPrincipal { get; set; } [NotNull] public IServiceProvider ServiceProvider { get; } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs index a55be7a82b..77635d72f4 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs @@ -49,7 +49,7 @@ public class AbpClaimsPrincipalFactory : IAbpClaimsPrincipalFactory, ITransientD await contributor.ContributeAsync(context); } - return claimsPrincipal; + return context.ClaimsPrincipal; } } } 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 index 6572c08b7a..82f5874d63 100644 --- 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 @@ -1,6 +1,11 @@ +using System; +using System.Collections.Generic; 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; @@ -17,7 +22,19 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip } var dynamicClaimsCache = context.GetRequiredService(); - var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); + List 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>(); + logger.LogWarning(e, $"User not found: {userId.Value}"); + return; + } await MapCommonClaimsAsync(identity, dynamicClaims); await AddDynamicClaims(identity, dynamicClaims); From a0415c26d7af2f8402cf9d0df33538853ad08a35 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 9 Nov 2023 21:00:25 +0800 Subject: [PATCH 08/26] Update IdentityDynamicClaimsPrincipalContributor.cs --- .../Abp/Identity/IdentityDynamicClaimsPrincipalContributor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 82f5874d63..94002c15cf 100644 --- 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 @@ -31,7 +31,7 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip { // In case if user not found, We force to clear the claims principal. context.ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); - var logger = context.GetRequiredService>(); + var logger = context.GetRequiredService>(); logger.LogWarning(e, $"User not found: {userId.Value}"); return; } From 59a2227ffc9d5bef305509f697778eea3f481774 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 10 Nov 2023 11:43:51 +0800 Subject: [PATCH 09/26] Update `AbpDynamicClaimsPrincipalContributorBase`. --- ...eDynamicClaimsPrincipalContributorCache.cs | 9 ++- .../Abp/Security/Claims/AbpClaimCacheIteam.cs | 4 +- ...bpDynamicClaimsPrincipalContributorBase.cs | 68 +++++++++++-------- .../Volo/Abp/Account/DynamicClaimDto.cs | 6 ++ .../Abp/Account/DynamicClaimsAppService.cs | 20 ++++-- ...yDynamicClaimsPrincipalContributorCache.cs | 19 +++++- 6 files changed, 82 insertions(+), 44 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs index 33a1dfd5f6..400d42349c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -59,7 +60,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency // Use independent cache for remote claims. return (await Cache.GetOrAddAsync($"{nameof(RemoteDynamicClaimsPrincipalContributorCache)}{AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)}", async () => { - var dynamicClaims = new List(); + var dynamicClaims = AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Select(claimType => new AbpClaimCacheItem(claimType, null)).ToList(); Logger.LogDebug($"Get dynamic claims for user: {userId} from remote service."); try { @@ -67,7 +68,11 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency 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>(await response.Content.ReadAsStringAsync()); + var remoteClaims = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + foreach (var claim in dynamicClaims) + { + claim.Value = remoteClaims.FirstOrDefault(c => c.Type == claim.Type)?.Value; + } Logger.LogDebug($"Successfully got {dynamicClaims.Count} remote claims for user: {userId}"); } catch (Exception e) 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 index 02f15e2478..94241284fe 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs @@ -7,9 +7,9 @@ public class AbpClaimCacheItem { public string Type { get; set; } - public string Value { get; set; } + public string? Value { get; set; } - public AbpClaimCacheItem(string type, string value) + public AbpClaimCacheItem(string type, string? value) { Type = type; Value = value; diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs index 55f2dc96a6..71ec0ae7c2 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs @@ -13,41 +13,41 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai protected virtual async Task MapCommonClaimsAsync(ClaimsIdentity identity, List 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); + await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.UserName, "preferred_username", "unique_name", ClaimTypes.Name); + await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Role, "role", "roles", ClaimTypes.Role); + await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Email, "email", ClaimTypes.Email); + await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.SurName, "family_name", ClaimTypes.Surname); + await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Name, "given_name", ClaimTypes.GivenName); } - protected virtual Task MapClaimsAsync(ClaimsIdentity identity, List dynamicClaims, string sourceType, string targetType) + protected virtual Task MapClaimAsync(ClaimsIdentity identity, List dynamicClaims, string abpClaimType, params string[] dynamicClaimTypes) { - if (sourceType == targetType) + var claims = dynamicClaims.Where(c => dynamicClaimTypes.Contains(c.Type)).ToList(); + if (claims.IsNullOrEmpty()) { - return Task.CompletedTask;; + return Task.CompletedTask; } - if (identity.Claims.Any(c => c.Type == sourceType) && dynamicClaims.All(c => c.Type != sourceType)) + foreach (var claimGroup in claims.GroupBy(x => x.Type)) { - var claims = dynamicClaims.Where(c => c.Type == targetType).ToList(); - if (!claims.IsNullOrEmpty()) + var claim = claimGroup.First(); + if (claimGroup.Count() > 1) { - identity.RemoveAll(sourceType); - identity.AddClaims(claims.Select(c => new Claim(sourceType, c.Value))); - dynamicClaims.RemoveAll(c => c.Type == targetType); + dynamicClaims.RemoveAll(c => c.Type == claim.Type && identity.Claims.All(x => x.Type != claim.Type)); + identity.RemoveAll(abpClaimType); + identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(abpClaimType, c.Value!))); } - } - - 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()) + else { - identity.RemoveAll(targetType); - identity.AddClaims(claims.Select(c => new Claim(targetType, c.Value))); - dynamicClaims.RemoveAll(c => c.Type == sourceType); + dynamicClaims.RemoveAll(c => c.Type == claim.Type && identity.Claims.All(x => x.Type != claim.Type)); + if (claim.Value != null) + { + identity.AddOrReplace(new Claim(abpClaimType, claim.Value)); + } + else + { + identity.RemoveAll(abpClaimType); + } } } @@ -56,16 +56,24 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai protected virtual Task AddDynamicClaims(ClaimsIdentity identity, List dynamicClaims) { - foreach (var claims in dynamicClaims.GroupBy(x => x.Type)) + foreach (var claimGroup in dynamicClaims.GroupBy(x => x.Type)) { - if (claims.Count() > 1) + if (claimGroup.Count() > 1) { - identity.RemoveAll(claims.First().Type); - identity.AddClaims(claims.Select(c => new Claim(claims.First().Type, c.Value))); + identity.RemoveAll(claimGroup.First().Type); + identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(claimGroup.First().Type, c.Value!))); } else { - identity.AddOrReplace(new Claim(claims.First().Type, claims.First().Value)); + var claim = claimGroup.First(); + if (claim.Value != null) + { + identity.AddOrReplace(new Claim(claimGroup.First().Type, claim.Value)); + } + else + { + identity.RemoveAll(claim.Type); + } } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs index 896263e08f..ad5ac82678 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs @@ -5,4 +5,10 @@ public class DynamicClaimDto public string Type { get; set; } public string Value { get; set; } + + public DynamicClaimDto(string type, string value) + { + Type = type; + Value = value; + } } diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs index ebe9e06cb7..d32d265d4a 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs @@ -29,14 +29,20 @@ public class DynamicClaimsAppService : IdentityAppServiceBase, IDynamicClaimsApp { var principal = await AbpClaimsPrincipalFactory.CreateAsync(PrincipalAccessor.Principal); - var dynamicClaims = principal.Claims - .Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type)) - .Select(c => new DynamicClaimDto + var dynamicClaims = new List(); + foreach (var claimType in AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims) + { + var claims = principal.Claims.Where(x => x.Type == claimType).ToList(); + if (claims.Any()) { - Type = c.Type, - Value = c.Value - }); + dynamicClaims.AddRange(claims.Select(claim => new DynamicClaimDto(claimType, claim.Value))); + } + else + { + dynamicClaims.Add(new DynamicClaimDto(claimType, null)); + } + } - return dynamicClaims.ToList(); + return dynamicClaims; } } 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 index 51b16aa88f..f3014adfce 100644 --- 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 @@ -55,9 +55,22 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen var user = await UserManager.GetByIdAsync(userId); var principal = await UserClaimsPrincipalFactory.CreateAsync(user); - return principal.Identities.FirstOrDefault()?.Claims - .Where(c => AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Contains(c.Type)) - .Select(c => new AbpClaimCacheItem(c.Type, c.Value)).ToList(); + + var dynamicClaims = new List(); + foreach (var claimType in AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims) + { + var claims = principal.Claims.Where(x => x.Type == claimType).ToList(); + if (claims.Any()) + { + dynamicClaims.AddRange(claims.Select(claim => new AbpClaimCacheItem(claimType, claim.Value))); + } + else + { + dynamicClaims.Add(new AbpClaimCacheItem(claimType, null)); + } + } + + return dynamicClaims; } }, () => new DistributedCacheEntryOptions { From 7a52c95434d76c81186649bee8eac3018777aa33 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 10 Nov 2023 14:59:04 +0800 Subject: [PATCH 10/26] Set `CacheAbsoluteExpiration ` to 60 seconds. --- .../RemoteDynamicClaimsPrincipalContributorCacheOptions.cs | 2 +- .../Volo/Abp/Identity/UserUpdatedEventHandler.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs index c8e31b1269..ea7afb5dcb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs @@ -8,6 +8,6 @@ public class RemoteDynamicClaimsPrincipalContributorCacheOptions public RemoteDynamicClaimsPrincipalContributorCacheOptions() { - CacheAbsoluteExpiration = TimeSpan.FromSeconds(5); + CacheAbsoluteExpiration = TimeSpan.FromSeconds(60); } } 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 index d4a84c341a..9ac8ad4746 100644 --- 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 @@ -14,14 +14,11 @@ public class UserEntityUpdatedEventHandler : ILocalEventHandler>, ITransientDependency { - public ILogger Logger { get; set; } - private readonly IdentityDynamicClaimsPrincipalContributorCache _cache; public UserEntityUpdatedEventHandler(IdentityDynamicClaimsPrincipalContributorCache cache) { _cache = cache; - Logger = NullLogger.Instance; } [UnitOfWork] @@ -38,7 +35,6 @@ public class UserEntityUpdatedEventHandler : protected virtual async Task ClearAsync(Guid userId, Guid? tenantId) { - Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); await _cache.ClearAsync(userId, tenantId); } } From 21fa81a55070d8a1e514012a7dfb48d9aa20c632 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 10 Nov 2023 19:24:18 +0800 Subject: [PATCH 11/26] Add `AbpDynamicClaimsOpenIddictClaimsPrincipalHandler`. --- .../AbpOpenIddictAspNetCoreModule.cs | 1 + ...icClaimsOpenIddictClaimsPrincipalHandler.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDynamicClaimsOpenIddictClaimsPrincipalHandler.cs diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 23f41900b5..71963e43f5 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -25,6 +25,7 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule Configure(options => { options.ClaimsPrincipalHandlers.Add(); + options.ClaimsPrincipalHandlers.Add(); }); Configure(options => diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDynamicClaimsOpenIddictClaimsPrincipalHandler.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDynamicClaimsOpenIddictClaimsPrincipalHandler.cs new file mode 100644 index 0000000000..ec2d92c870 --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDynamicClaimsOpenIddictClaimsPrincipalHandler.cs @@ -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(); + + await abpClaimsPrincipalFactory.CreateDynamicAsync(context.Principal); + } +} From bb36aec36371c947eff7b044f24c0e56e49baef1 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 10 Nov 2023 21:57:44 +0800 Subject: [PATCH 12/26] Add `AbpDynamicClaimsPrincipalContributorBase_Tests`. --- ...bpDynamicClaimsPrincipalContributorBase.cs | 25 +----- ....cs => AbpClaimsPrincipalFactory_Tests.cs} | 4 +- ...micClaimsPrincipalContributorBase_Tests.cs | 77 +++++++++++++++++++ ...t.cs => CurrentPrincipalAccessor_Tests.cs} | 4 +- 4 files changed, 84 insertions(+), 26 deletions(-) rename framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/{AbpClaimsPrincipalFactory_Test.cs => AbpClaimsPrincipalFactory_Tests.cs} (98%) create mode 100644 framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs rename framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/{CurrentPrincipalAccessor_Test.cs => CurrentPrincipalAccessor_Tests.cs} (91%) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs index 71ec0ae7c2..407dc8fca8 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs @@ -28,28 +28,9 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai return Task.CompletedTask; } - foreach (var claimGroup in claims.GroupBy(x => x.Type)) - { - var claim = claimGroup.First(); - if (claimGroup.Count() > 1) - { - dynamicClaims.RemoveAll(c => c.Type == claim.Type && identity.Claims.All(x => x.Type != claim.Type)); - identity.RemoveAll(abpClaimType); - identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(abpClaimType, c.Value!))); - } - else - { - dynamicClaims.RemoveAll(c => c.Type == claim.Type && identity.Claims.All(x => x.Type != claim.Type)); - if (claim.Value != null) - { - identity.AddOrReplace(new Claim(abpClaimType, claim.Value)); - } - else - { - identity.RemoveAll(abpClaimType); - } - } - } + dynamicClaims.RemoveAll(claims); + identity.RemoveAll(abpClaimType); + identity.AddClaims(claims.Where(c => c.Value != null).Select(c => new Claim(abpClaimType, c.Value!))); return Task.CompletedTask;; } diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Tests.cs similarity index 98% rename from framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs rename to framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Tests.cs index 7726c2fe12..b8abaff000 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Test.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory_Tests.cs @@ -9,12 +9,12 @@ using Xunit; namespace Volo.Abp.Security.Claims; -public class AbpClaimsPrincipalFactory_Test : AbpIntegratedTest +public class AbpClaimsPrincipalFactory_Tests : AbpIntegratedTest { private readonly IAbpClaimsPrincipalFactory _abpClaimsPrincipalFactory; private static string TestAuthenticationType => "Identity.Application"; - public AbpClaimsPrincipalFactory_Test() + public AbpClaimsPrincipalFactory_Tests() { _abpClaimsPrincipalFactory = GetRequiredService(); diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs new file mode 100644 index 0000000000..1e03008498 --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs @@ -0,0 +1,77 @@ +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 MapCommonClaimsAsync(identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); + await AddDynamicClaims(identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); + } +} + +public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest +{ + private readonly TestAbpDynamicClaimsPrincipalContributor _dynamicClaimsPrincipalContributorBase = new TestAbpDynamicClaimsPrincipalContributor(); + + public readonly static List DynamicClaims = new List(); + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public async Task CreateAsync() + { + 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.AddRange(new [] + { + new AbpClaimCacheItem("preferred_username", "test-preferred_username"), + new AbpClaimCacheItem(ClaimTypes.GivenName, "test-given_name"), + new AbpClaimCacheItem("family_name", "test-family_name"), + new AbpClaimCacheItem("role", "test-role1"), + new AbpClaimCacheItem("roles", "test-role2"), + new AbpClaimCacheItem(ClaimTypes.Role, "test-role3"), + new AbpClaimCacheItem("email", "test-email"), + new AbpClaimCacheItem(AbpClaimTypes.EmailVerified, "test-email-verified"), + new AbpClaimCacheItem(AbpClaimTypes.PhoneNumberVerified, null), + }); + + await _dynamicClaimsPrincipalContributorBase.ContributeAsync(new AbpClaimsPrincipalContributorContext(claimsPrincipal, GetRequiredService())); + + 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.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"); + } +} diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Test.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Tests.cs similarity index 91% rename from framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Test.cs rename to framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Tests.cs index d7c87148f4..bbc18089be 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Test.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/CurrentPrincipalAccessor_Tests.cs @@ -6,11 +6,11 @@ using Xunit; namespace Volo.Abp.Security.Claims; -public class CurrentPrincipalAccessor_Test : AbpIntegratedTest +public class CurrentPrincipalAccessor_Tests : AbpIntegratedTest { private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; - public CurrentPrincipalAccessor_Test() + public CurrentPrincipalAccessor_Tests() { _currentPrincipalAccessor = GetRequiredService(); } From fe4129d3e38934a37c3012f23cd461580eaa5bf4 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 11 Nov 2023 10:52:06 +0800 Subject: [PATCH 13/26] Add `ClaimsMap` to `AbpClaimsPrincipalFactoryOptions`. --- ...RemoteDynamicClaimsPrincipalContributor.cs | 3 +- .../AbpClaimsPrincipalFactoryOptions.cs | 12 +++++ ...bpDynamicClaimsPrincipalContributorBase.cs | 53 ++++++------------- ...micClaimsPrincipalContributorBase_Tests.cs | 7 ++- ...entityDynamicClaimsPrincipalContributor.cs | 3 +- 5 files changed, 34 insertions(+), 44 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs index 38d3b7840a..e1f545a995 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs @@ -24,7 +24,6 @@ public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipal var dynamicClaimsCache = context.GetRequiredService(); var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); - await MapCommonClaimsAsync(identity, dynamicClaims); - await AddDynamicClaims(identity, dynamicClaims); + await AddDynamicClaimsAsync(context, identity, dynamicClaims); } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index 5f027f6862..f2f422b2a3 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Security.Claims; using Volo.Abp.Collections; namespace Volo.Abp.Security.Claims; @@ -13,6 +14,8 @@ public class AbpClaimsPrincipalFactoryOptions public string RemoteUrl { get; set; } + public Dictionary> ClaimsMap { get; set; } + public AbpClaimsPrincipalFactoryOptions() { Contributors = new TypeList(); @@ -31,5 +34,14 @@ public class AbpClaimsPrincipalFactoryOptions }; RemoteUrl = "/api/account/dynamic-claims"; + + ClaimsMap = new Dictionary>() + { + { AbpClaimTypes.UserName, new List { "preferred_username", "unique_name", ClaimTypes.Name }}, + { AbpClaimTypes.Name, new List { "given_name", ClaimTypes.GivenName }}, + { AbpClaimTypes.SurName, new List { "family_name", ClaimTypes.Surname }}, + { AbpClaimTypes.Role, new List { "role", "roles", ClaimTypes.Role }}, + { AbpClaimTypes.Email, new List { "email", ClaimTypes.Email }}, + }; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs index 407dc8fca8..2982018650 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs @@ -3,6 +3,7 @@ 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; @@ -11,52 +12,32 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai { public abstract Task ContributeAsync(AbpClaimsPrincipalContributorContext context); - protected virtual async Task MapCommonClaimsAsync(ClaimsIdentity identity, List dynamicClaims) + protected virtual async Task AddDynamicClaimsAsync(AbpClaimsPrincipalContributorContext context, ClaimsIdentity identity, List dynamicClaims) { - await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.UserName, "preferred_username", "unique_name", ClaimTypes.Name); - await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Role, "role", "roles", ClaimTypes.Role); - await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Email, "email", ClaimTypes.Email); - await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.SurName, "family_name", ClaimTypes.Surname); - await MapClaimAsync(identity, dynamicClaims, AbpClaimTypes.Name, "given_name", ClaimTypes.GivenName); + var options = context.GetRequiredService>().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 dynamicClaims, string abpClaimType, params string[] dynamicClaimTypes) + protected virtual Task MapClaimAsync(ClaimsIdentity identity, List dynamicClaims, string targetClaimType, params string[] sourceClaimTypes) { - var claims = dynamicClaims.Where(c => dynamicClaimTypes.Contains(c.Type)).ToList(); + var claims = dynamicClaims.Where(c => sourceClaimTypes.Contains(c.Type)).ToList(); if (claims.IsNullOrEmpty()) { return Task.CompletedTask; } dynamicClaims.RemoveAll(claims); - identity.RemoveAll(abpClaimType); - identity.AddClaims(claims.Where(c => c.Value != null).Select(c => new Claim(abpClaimType, c.Value!))); - - return Task.CompletedTask;; - } - - protected virtual Task AddDynamicClaims(ClaimsIdentity identity, List dynamicClaims) - { - foreach (var claimGroup in dynamicClaims.GroupBy(x => x.Type)) - { - if (claimGroup.Count() > 1) - { - identity.RemoveAll(claimGroup.First().Type); - identity.AddClaims(claimGroup.Where(c => c.Value != null).Select(c => new Claim(claimGroup.First().Type, c.Value!))); - } - else - { - var claim = claimGroup.First(); - if (claim.Value != null) - { - identity.AddOrReplace(new Claim(claimGroup.First().Type, claim.Value)); - } - else - { - identity.RemoveAll(claim.Type); - } - } - } + identity.RemoveAll(targetClaimType); + identity.AddClaims(claims.Where(c => c.Value != null).Select(c => new Claim(targetClaimType, c.Value!))); return Task.CompletedTask;; } diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs index 1e03008498..d82ae2803a 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs @@ -16,8 +16,7 @@ class TestAbpDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContri var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); Check.NotNull(identity, nameof(identity)); - await MapCommonClaimsAsync(identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); - await AddDynamicClaims(identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); + await AddDynamicClaimsAsync(context, identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); } } @@ -33,7 +32,7 @@ public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest< } [Fact] - public async Task CreateAsync() + public async Task AddDynamicClaimsAsync() { var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.UserName, "test-source-userName")); @@ -45,7 +44,6 @@ public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest< 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.AddRange(new [] @@ -68,6 +66,7 @@ public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest< 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"); 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 index 94002c15cf..45093591b6 100644 --- 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 @@ -36,7 +36,6 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip return; } - await MapCommonClaimsAsync(identity, dynamicClaims); - await AddDynamicClaims(identity, dynamicClaims); + await AddDynamicClaimsAsync(context, identity, dynamicClaims); } } From 80b6060f2a93c5e92adcaa57fefda00caf3509e9 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 11 Nov 2023 10:55:41 +0800 Subject: [PATCH 14/26] Update AbpClaimCacheIteam.cs --- .../Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs | 2 -- 1 file changed, 2 deletions(-) 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 index 94241284fe..d87e8634b2 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs @@ -15,10 +15,8 @@ public class AbpClaimCacheItem Value = value; } - public static string CalculateCacheKey(Guid userId, Guid? tenantId) { return $"{tenantId}-{userId}"; } - } From 45ea3fa6f60f05409c7cd9c6aac68f66c90f32d0 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 11 Nov 2023 10:57:56 +0800 Subject: [PATCH 15/26] Update IDynamicClaimsAppService.cs --- .../Volo/Abp/Account/IDynamicClaimsAppService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs index 3d67e44ac3..8702ef5fcd 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs @@ -4,7 +4,7 @@ using Volo.Abp.Application.Services; namespace Volo.Abp.Account; -public interface IDynamicClaimsAppService: IApplicationService +public interface IDynamicClaimsAppService : IApplicationService { Task> GetAsync(); } From 758bc77fd783e1e27fca68ae1b65a365ff70d825 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 11 Nov 2023 16:08:12 +0800 Subject: [PATCH 16/26] Change the order of `ClaimsPrincipalHandlers`. --- .../Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 71963e43f5..2b890c6067 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -24,8 +24,8 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule Configure(options => { - options.ClaimsPrincipalHandlers.Add(); options.ClaimsPrincipalHandlers.Add(); + options.ClaimsPrincipalHandlers.Add(); }); Configure(options => From 3b09f26e68174a8c197dee2990d519a3a87ee535 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 12 Nov 2023 11:35:58 +0800 Subject: [PATCH 17/26] Update `AbpClaimsPrincipalFactory` system. --- .../Volo/Abp/Security/AbpSecurityModule.cs | 9 ++----- .../Claims/AbpClaimsPrincipalFactory.cs | 24 +++++++++++++------ .../AbpClaimsPrincipalFactoryOptions.cs | 4 ++-- .../IAbpDynamicClaimsPrincipalContributor.cs | 6 +++-- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs index 9705c1e2e6..5abddbd7a0 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/AbpSecurityModule.cs @@ -63,20 +63,15 @@ public class AbpSecurityModule : AbpModule private static void AutoAddClaimsPrincipalContributors(IServiceCollection services) { var contributorTypes = new List(); + var dynamicContributorTypes = new List(); services.OnRegistered(context => { - if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType) && - !typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) + if (typeof(IAbpClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) { contributorTypes.Add(context.ImplementationType); } - }); - - var dynamicContributorTypes = new List(); - services.OnRegistered(context => - { if (typeof(IAbpDynamicClaimsPrincipalContributor).IsAssignableFrom(context.ImplementationType)) { dynamicContributorTypes.Add(context.ImplementationType); diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs index 77635d72f4..b0e9fea653 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactory.cs @@ -2,7 +2,6 @@ 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; @@ -24,15 +23,15 @@ public class AbpClaimsPrincipalFactory : IAbpClaimsPrincipalFactory, ITransientD public virtual async Task CreateAsync(ClaimsPrincipal? existsClaimsPrincipal = null) { - return await InternalCreateAsync(Options.Contributors, existsClaimsPrincipal); + return await InternalCreateAsync(Options, existsClaimsPrincipal, false); } public virtual async Task CreateDynamicAsync(ClaimsPrincipal? existsClaimsPrincipal = null) { - return await InternalCreateAsync(Options.DynamicContributors, existsClaimsPrincipal); + return await InternalCreateAsync(Options, existsClaimsPrincipal, true); } - public virtual async Task InternalCreateAsync(ITypeList contributorTypes, ClaimsPrincipal? existsClaimsPrincipal = null) + public virtual async Task InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal? existsClaimsPrincipal = null, bool isDynamic = false) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -43,10 +42,21 @@ public class AbpClaimsPrincipalFactory : IAbpClaimsPrincipalFactory, ITransientD var context = new AbpClaimsPrincipalContributorContext(claimsPrincipal, scope.ServiceProvider); - foreach (var contributorType in contributorTypes) + if (!isDynamic) { - var contributor = (IAbpClaimsPrincipalContributor)scope.ServiceProvider.GetRequiredService(contributorType); - await contributor.ContributeAsync(context); + foreach (var contributorType in options.Contributors) + { + var contributor = (IAbpClaimsPrincipalContributor)scope.ServiceProvider.GetRequiredService(contributorType); + await contributor.ContributeAsync(context); + } + } + else + { + foreach (var contributorType in options.DynamicContributors) + { + var contributor = (IAbpDynamicClaimsPrincipalContributor)scope.ServiceProvider.GetRequiredService(contributorType); + await contributor.ContributeAsync(context); + } } return context.ClaimsPrincipal; diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index f2f422b2a3..0f5b5cdbd6 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs @@ -8,7 +8,7 @@ public class AbpClaimsPrincipalFactoryOptions { public ITypeList Contributors { get; } - public ITypeList DynamicContributors { get; } + public ITypeList DynamicContributors { get; } public List DynamicClaims { get; } @@ -19,7 +19,7 @@ public class AbpClaimsPrincipalFactoryOptions public AbpClaimsPrincipalFactoryOptions() { Contributors = new TypeList(); - DynamicContributors = new TypeList(); + DynamicContributors = new TypeList(); DynamicClaims = new List { diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs index dde236c363..43f73374d7 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/IAbpDynamicClaimsPrincipalContributor.cs @@ -1,6 +1,8 @@ +using System.Threading.Tasks; + namespace Volo.Abp.Security.Claims; -public interface IAbpDynamicClaimsPrincipalContributor : IAbpClaimsPrincipalContributor +public interface IAbpDynamicClaimsPrincipalContributor { - + Task ContributeAsync(AbpClaimsPrincipalContributorContext context); } From 0a3be97a28da606170f625a8179bad9634000c43 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 12 Nov 2023 18:53:58 +0800 Subject: [PATCH 18/26] Clear the dynamic cache when user logs in. --- .../IdentityServerSupportedLoginModel.cs | 14 ++++++------ .../Account/OpenIddictSupportedLoginModel.cs | 5 ++++- .../Account/Controllers/AccountController.cs | 15 ++++++++++++- .../Pages/Account/Login.cshtml.cs | 22 ++++++++++++++++--- .../Pages/Account/Register.cshtml.cs | 11 +++++++++- .../Controllers/TokenController.Password.cs | 4 ++++ 6 files changed, 58 insertions(+), 13 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index d04abee6d9..d60c90ce60 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -10,7 +10,6 @@ using System; using System.Diagnostics; using System.Linq; using System.Security.Claims; -using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Volo.Abp.Account.Settings; @@ -32,14 +31,12 @@ public class IdentityServerSupportedLoginModel : LoginModel public IdentityServerSupportedLoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions, + IOptions identityOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, IIdentityServerInteractionService interaction, IClientStore clientStore, - IEventService identityServerEvents, - IOptions identityOptions) - : base( - schemeProvider, - accountOptions, - identityOptions) + IEventService identityServerEvents) + : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache) { Interaction = interaction; ClientStore = clientStore; @@ -177,6 +174,9 @@ public class IdentityServerSupportedLoginModel : LoginModel Debug.Assert(user != null, nameof(user) + " != null"); await IdentityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + return RedirectSafely(ReturnUrl, ReturnUrlHash); } diff --git a/modules/account/src/Volo.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictSupportedLoginModel.cs index 10231a2607..d7998eb826 100644 --- a/modules/account/src/Volo.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictSupportedLoginModel.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using OpenIddict.Server; using OpenIddict.Server.AspNetCore; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.OpenIddict; @@ -17,12 +18,14 @@ namespace Volo.Abp.Account.Web.Pages.Account; public class OpenIddictSupportedLoginModel : LoginModel { protected AbpOpenIddictRequestHelper OpenIddictRequestHelper { get; } + public OpenIddictSupportedLoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions, IOptions identityOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, AbpOpenIddictRequestHelper openIddictRequestHelper) - : base(schemeProvider, accountOptions, identityOptions) + : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache) { OpenIddictRequestHelper = openIddictRequestHelper; } diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index 8e93913176..9caff2c187 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -29,13 +29,15 @@ public class AccountController : AbpControllerBase protected ISettingProvider SettingProvider { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } protected IOptions IdentityOptions { get; } + protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } public AccountController( SignInManager signInManager, IdentityUserManager userManager, ISettingProvider settingProvider, IdentitySecurityLogManager identitySecurityLogManager, - IOptions identityOptions) + IOptions identityOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache) { LocalizationResource = typeof(AccountResource); @@ -44,6 +46,7 @@ public class AccountController : AbpControllerBase SettingProvider = settingProvider; IdentitySecurityLogManager = identitySecurityLogManager; IdentityOptions = identityOptions; + IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; } [HttpPost] @@ -69,6 +72,16 @@ public class AccountController : AbpControllerBase UserName = login.UserNameOrEmailAddress }); + if (signInResult.Succeeded) + { + var user = await UserManager.FindByNameAsync(login.UserNameOrEmailAddress); + if (user != null) + { + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + } + } + return GetAbpLoginResult(signInResult); } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index d4bb0dbc5e..7b7d4a2b94 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -52,17 +52,19 @@ public class LoginModel : AccountPageModel protected IAuthenticationSchemeProvider SchemeProvider { get; } protected AbpAccountOptions AccountOptions { get; } protected IOptions IdentityOptions { get; } - + protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } public bool ShowCancelButton { get; set; } public LoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions, - IOptions identityOptions) + IOptions identityOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache) { SchemeProvider = schemeProvider; IdentityOptions = identityOptions; AccountOptions = accountOptions.Value; + IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; } public virtual async Task OnGetAsync() @@ -138,6 +140,9 @@ public class LoginModel : AccountPageModel Debug.Assert(user != null, nameof(user) + " != null"); + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + return RedirectSafely(ReturnUrl, ReturnUrlHash); } @@ -222,8 +227,16 @@ public class LoginModel : AccountPageModel throw new UserFriendlyException("Cannot proceed because user is not allowed!"); } + IdentityUser user; if (result.Succeeded) { + user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (user != null) + { + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + } + return RedirectSafely(returnUrl, returnUrlHash); } @@ -239,7 +252,7 @@ public class LoginModel : AccountPageModel }); } - var user = await UserManager.FindByEmailAsync(email); + user = await UserManager.FindByEmailAsync(email); if (user == null) { return RedirectToPage("./Register", new { @@ -263,6 +276,9 @@ public class LoginModel : AccountPageModel UserName = user.Name }); + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + return RedirectSafely(returnUrl, returnUrlHash); } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 322a17e82f..9e0a2c191b 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -46,13 +46,16 @@ public class RegisterModel : AccountPageModel protected IAuthenticationSchemeProvider SchemeProvider { get; } protected AbpAccountOptions AccountOptions { get; } + protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } public RegisterModel( IAccountAppService accountAppService, IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions) + IOptions accountOptions, + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache) { SchemeProvider = schemeProvider; + IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; AccountAppService = accountAppService; AccountOptions = accountOptions.Value; } @@ -159,6 +162,9 @@ public class RegisterModel : AccountPageModel var user = await UserManager.GetByIdAsync(userDto.Id); await SignInManager.SignInAsync(user, isPersistent: true); + + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); } protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string userName, string emailAddress) @@ -185,6 +191,9 @@ public class RegisterModel : AccountPageModel } await SignInManager.SignInAsync(user, isPersistent: true, ExternalLoginAuthSchema); + + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); } protected virtual async Task CheckSelfRegistrationAsync() diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs index 9617d4f409..26f826da7a 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs @@ -31,6 +31,7 @@ public partial class TokenController protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService(); protected ISettingProvider SettingProvider => LazyServiceProvider.LazyGetRequiredService(); + protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache => LazyServiceProvider.LazyGetRequiredService(); [UnitOfWork] protected virtual async Task HandlePasswordAsync(OpenIddictRequest request) @@ -334,6 +335,9 @@ public partial class TokenController protected virtual async Task SetSuccessResultAsync(OpenIddictRequest request, IdentityUser user) { + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + // Create a new ClaimsPrincipal containing the claims that // will be used to create an id_token, a token or a code. var principal = await SignInManager.CreateUserPrincipalAsync(user); From 3d7009ab2ccc1db4c2edf669527020f3d1fdc430 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 14 Nov 2023 14:35:38 +0800 Subject: [PATCH 19/26] Rename AbpClaimCacheIteam to AbpClaimCacheItem --- .../Claims/{AbpClaimCacheIteam.cs => AbpClaimCacheItem.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/{AbpClaimCacheIteam.cs => AbpClaimCacheItem.cs} (100%) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs similarity index 100% rename from framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheIteam.cs rename to framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs From e40775428000f0957b1de9fba7e0f57a510ce186 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 14 Nov 2023 18:05:10 +0800 Subject: [PATCH 20/26] Refresh instead of returning dynamic claims. --- ...RemoteDynamicClaimsPrincipalContributor.cs | 19 ++++- ...eDynamicClaimsPrincipalContributorCache.cs | 72 +++++++------------ ...cClaimsPrincipalContributorCacheOptions.cs | 13 ---- .../Claims/AbpDynamicClaimsMiddleware.cs | 12 ++-- .../Abp/Security/Claims/AbpClaimCacheItem.cs | 22 ------ .../AbpClaimsPrincipalFactoryOptions.cs | 8 ++- .../Abp/Security/Claims/AbpDynamicClaim.cs | 17 +++++ .../Claims/AbpDynamicClaimCacheItem.cs | 25 +++++++ ...bpDynamicClaimsPrincipalContributorBase.cs | 4 +- ...micClaimsPrincipalContributorBase_Tests.cs | 24 +++---- .../Abp/Account/IDynamicClaimsAppService.cs | 2 +- .../Abp/Account/DynamicClaimsAppService.cs | 33 +++------ .../DynamicClaimsClientProxy.Generated.cs | 4 +- .../ClientProxies/account-generate-proxy.json | 20 +++--- .../Abp/Account/DynamicClaimsController.cs | 7 +- ...entityDynamicClaimsPrincipalContributor.cs | 6 +- ...yDynamicClaimsPrincipalContributorCache.cs | 16 ++--- .../MyProjectNameAuthServerModule.cs | 6 ++ .../MyProjectNameBlazorModule.cs | 3 +- .../MyProjectNameBlazorModule.cs | 5 ++ .../MyProjectNameBlazorModule.cs | 3 +- .../MyProjectNameHttpApiHostModule.cs | 6 ++ .../MyProjectNameHttpApiHostModule.cs | 5 ++ .../MyProjectNameWebModule.cs | 9 +-- .../MyProjectNameWebModule.cs | 5 ++ 25 files changed, 183 insertions(+), 163 deletions(-) delete mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs delete mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaim.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimCacheItem.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs index e1f545a995..d9339b57df 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributor.cs @@ -1,6 +1,9 @@ +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; @@ -22,8 +25,20 @@ public class RemoteDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipal } var dynamicClaimsCache = context.GetRequiredService(); - var dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); + 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>(); + logger.LogWarning(e, $"Failed to refresh remote dynamic claims cache for user: {userId.Value}"); + return; + } - await AddDynamicClaimsAsync(context, identity, dynamicClaims); + await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs index 400d42349c..eb9618e6b1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; 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; @@ -21,76 +19,58 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency public const string HttpClientName = nameof(RemoteDynamicClaimsPrincipalContributorCache); public ILogger Logger { get; set; } - protected IDistributedCache> Cache { get; } + protected IDistributedCache Cache { get; } protected IHttpClientFactory HttpClientFactory { get; } protected IOptions AbpClaimsPrincipalFactoryOptions { get; } protected IJsonSerializer JsonSerializer { get; } protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } - protected IOptions CacheOptions { get; } public RemoteDynamicClaimsPrincipalContributorCache( - IDistributedCache> cache, + IDistributedCache cache, IHttpClientFactory httpClientFactory, IOptions abpClaimsPrincipalFactoryOptions, IJsonSerializer jsonSerializer, - IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, - IOptions cacheOptions) + IRemoteServiceHttpClientAuthenticator httpClientAuthenticator) { Cache = cache; HttpClientFactory = httpClientFactory; AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; JsonSerializer = jsonSerializer; HttpClientAuthenticator = httpClientAuthenticator; - CacheOptions = cacheOptions; Logger = NullLogger.Instance; } - public virtual async Task> GetAsync(Guid userId, Guid? tenantId = null) + public virtual async Task 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()) + var claims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); + if (claims != null && !claims.Claims.IsNullOrEmpty()) { - return claims!; + 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 () => + Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); + try { - var dynamicClaims = AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims.Select(claimType => new AbpClaimCacheItem(claimType, null)).ToList(); - 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); - var remoteClaims = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); - foreach (var claim in dynamicClaims) - { - claim.Value = remoteClaims.FirstOrDefault(c => c.Type == claim.Type)?.Value; - } - 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 + 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) { - AbsoluteExpirationRelativeToNow = CacheOptions.Value.CacheAbsoluteExpiration - }))!; - } + Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); + throw; + } - 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)}"); + claims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); + if (claims == null || claims.Claims.IsNullOrEmpty()) + { + throw new AbpException($"Failed to refresh remote claims for user: {userId}"); + } + + return claims!; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs deleted file mode 100644 index ea7afb5dcb..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCacheOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Volo.Abp.AspNetCore.Mvc.Client; - -public class RemoteDynamicClaimsPrincipalContributorCacheOptions -{ - public TimeSpan CacheAbsoluteExpiration { get; set; } - - public RemoteDynamicClaimsPrincipalContributorCacheOptions() - { - CacheAbsoluteExpiration = TimeSpan.FromSeconds(60); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs index 33b7b3f16e..777ca7744a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/AbpDynamicClaimsMiddleware.cs @@ -1,9 +1,9 @@ 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; -using Volo.Abp.Users; namespace Volo.Abp.AspNetCore.Security.Claims; @@ -11,11 +11,13 @@ public class AbpDynamicClaimsMiddleware : IMiddleware, ITransientDependency { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var currentUser = context.RequestServices.GetRequiredService(); - if (currentUser.IsAuthenticated) + if (context.User.Identity?.IsAuthenticated == true) { - var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService(); - context.User = await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); + if (context.RequestServices.GetRequiredService>().Value.IsDynamicClaimsEnabled) + { + var abpClaimsPrincipalFactory = context.RequestServices.GetRequiredService(); + context.User = await abpClaimsPrincipalFactory.CreateDynamicAsync(context.User); + } } await next(context); diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs deleted file mode 100644 index d87e8634b2..0000000000 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimCacheItem.cs +++ /dev/null @@ -1,22 +0,0 @@ -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; - } - - public static string CalculateCacheKey(Guid userId, Guid? tenantId) - { - return $"{tenantId}-{userId}"; - } -} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs index 0f5b5cdbd6..75bf67bee6 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimsPrincipalFactoryOptions.cs @@ -12,10 +12,12 @@ public class AbpClaimsPrincipalFactoryOptions public List DynamicClaims { get; } - public string RemoteUrl { get; set; } + public string RemoteRefreshUrl { get; set; } public Dictionary> ClaimsMap { get; set; } + public bool IsDynamicClaimsEnabled { get; set; } + public AbpClaimsPrincipalFactoryOptions() { Contributors = new TypeList(); @@ -33,7 +35,7 @@ public class AbpClaimsPrincipalFactoryOptions AbpClaimTypes.PhoneNumberVerified }; - RemoteUrl = "/api/account/dynamic-claims"; + RemoteRefreshUrl = "/api/account/dynamic-claims/refresh"; ClaimsMap = new Dictionary>() { @@ -43,5 +45,7 @@ public class AbpClaimsPrincipalFactoryOptions { AbpClaimTypes.Role, new List { "role", "roles", ClaimTypes.Role }}, { AbpClaimTypes.Email, new List { "email", ClaimTypes.Email }}, }; + + IsDynamicClaimsEnabled = false; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaim.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaim.cs new file mode 100644 index 0000000000..ab8fc13f17 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaim.cs @@ -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; + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimCacheItem.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimCacheItem.cs new file mode 100644 index 0000000000..8c25cb19e7 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimCacheItem.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Security.Claims; + +[Serializable] +public class AbpDynamicClaimCacheItem +{ + public List Claims { get; set; } + + public AbpDynamicClaimCacheItem() + { + Claims = new List(); + } + + public AbpDynamicClaimCacheItem(List claims) + { + Claims = claims; + } + + public static string CalculateCacheKey(Guid userId, Guid? tenantId) + { + return $"{tenantId}-{userId}"; + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs index 2982018650..2e607ace93 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase.cs @@ -12,7 +12,7 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai { public abstract Task ContributeAsync(AbpClaimsPrincipalContributorContext context); - protected virtual async Task AddDynamicClaimsAsync(AbpClaimsPrincipalContributorContext context, ClaimsIdentity identity, List dynamicClaims) + protected virtual async Task AddDynamicClaimsAsync(AbpClaimsPrincipalContributorContext context, ClaimsIdentity identity, List dynamicClaims) { var options = context.GetRequiredService>().Value; foreach (var map in options.ClaimsMap) @@ -27,7 +27,7 @@ public abstract class AbpDynamicClaimsPrincipalContributorBase : IAbpDynamicClai } } - protected virtual Task MapClaimAsync(ClaimsIdentity identity, List dynamicClaims, string targetClaimType, params string[] sourceClaimTypes) + protected virtual Task MapClaimAsync(ClaimsIdentity identity, List dynamicClaims, string targetClaimType, params string[] sourceClaimTypes) { var claims = dynamicClaims.Where(c => sourceClaimTypes.Contains(c.Type)).ToList(); if (claims.IsNullOrEmpty()) diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs index d82ae2803a..7054319dfa 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Claims/AbpDynamicClaimsPrincipalContributorBase_Tests.cs @@ -16,7 +16,7 @@ class TestAbpDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincipalContri var identity = context.ClaimsPrincipal.Identities.FirstOrDefault(); Check.NotNull(identity, nameof(identity)); - await AddDynamicClaimsAsync(context, identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims); + await AddDynamicClaimsAsync(context, identity, AbpDynamicClaimsPrincipalContributorBase_Tests.DynamicClaims.Claims); } } @@ -24,7 +24,7 @@ public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest< { private readonly TestAbpDynamicClaimsPrincipalContributor _dynamicClaimsPrincipalContributorBase = new TestAbpDynamicClaimsPrincipalContributor(); - public readonly static List DynamicClaims = new List(); + public readonly static AbpDynamicClaimCacheItem DynamicClaims = new AbpDynamicClaimCacheItem(); protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { @@ -46,17 +46,17 @@ public class AbpDynamicClaimsPrincipalContributorBase_Tests : AbpIntegratedTest< claimsPrincipal.Identities.First().AddClaim(new Claim(AbpClaimTypes.PhoneNumberVerified, "test-source-phoneNumberVerified")); claimsPrincipal.Identities.First().AddClaim(new Claim("my-claim", "test-source-my-claim")); - DynamicClaims.AddRange(new [] + DynamicClaims.Claims.AddRange(new [] { - new AbpClaimCacheItem("preferred_username", "test-preferred_username"), - new AbpClaimCacheItem(ClaimTypes.GivenName, "test-given_name"), - new AbpClaimCacheItem("family_name", "test-family_name"), - new AbpClaimCacheItem("role", "test-role1"), - new AbpClaimCacheItem("roles", "test-role2"), - new AbpClaimCacheItem(ClaimTypes.Role, "test-role3"), - new AbpClaimCacheItem("email", "test-email"), - new AbpClaimCacheItem(AbpClaimTypes.EmailVerified, "test-email-verified"), - new AbpClaimCacheItem(AbpClaimTypes.PhoneNumberVerified, null), + 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())); diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs index 8702ef5fcd..36a91b2155 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/IDynamicClaimsAppService.cs @@ -6,5 +6,5 @@ namespace Volo.Abp.Account; public interface IDynamicClaimsAppService : IApplicationService { - Task> GetAsync(); + Task RefreshAsync(); } diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs index d32d265d4a..e26dbfc979 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/DynamicClaimsAppService.cs @@ -1,48 +1,31 @@ -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; +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; } - protected IOptions AbpClaimsPrincipalFactoryOptions { get; } public DynamicClaimsAppService( + IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, - ICurrentPrincipalAccessor principalAccessor, - IOptions abpClaimsPrincipalFactoryOptions) + ICurrentPrincipalAccessor principalAccessor) { + IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; AbpClaimsPrincipalFactory = abpClaimsPrincipalFactory; PrincipalAccessor = principalAccessor; - AbpClaimsPrincipalFactoryOptions = abpClaimsPrincipalFactoryOptions; } - public virtual async Task> GetAsync() + public virtual async Task RefreshAsync() { - var principal = await AbpClaimsPrincipalFactory.CreateAsync(PrincipalAccessor.Principal); - - var dynamicClaims = new List(); - foreach (var claimType in AbpClaimsPrincipalFactoryOptions.Value.DynamicClaims) - { - var claims = principal.Claims.Where(x => x.Type == claimType).ToList(); - if (claims.Any()) - { - dynamicClaims.AddRange(claims.Select(claim => new DynamicClaimDto(claimType, claim.Value))); - } - else - { - dynamicClaims.Add(new DynamicClaimDto(claimType, null)); - } - } - - return dynamicClaims; + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(CurrentUser.GetId(), CurrentUser.TenantId); + await AbpClaimsPrincipalFactory.CreateDynamicAsync(PrincipalAccessor.Principal); } } diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs index d460483a8d..7bb80a95cb 100644 --- a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs +++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/Volo/Abp/Account/DynamicClaimsClientProxy.Generated.cs @@ -17,8 +17,8 @@ namespace Volo.Abp.Account; [ExposeServices(typeof(IDynamicClaimsAppService), typeof(DynamicClaimsClientProxy))] public partial class DynamicClaimsClientProxy : ClientProxyBase, IDynamicClaimsAppService { - public virtual async Task> GetAsync() + public virtual async Task RefreshAsync() { - return await RequestAsync>(nameof(GetAsync)); + await RequestAsync(nameof(RefreshAsync)); } } diff --git a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json index 857c27a3cd..9857f97c9a 100644 --- a/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json +++ b/modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/account-generate-proxy.json @@ -351,28 +351,28 @@ "name": "IDynamicClaimsAppService", "methods": [ { - "name": "GetAsync", + "name": "RefreshAsync", "parametersOnMethod": [], "returnValue": { - "type": "System.Collections.Generic.List", - "typeSimple": "[Volo.Abp.Account.DynamicClaimDto]" + "type": "System.Void", + "typeSimple": "System.Void" } } ] } ], "actions": { - "GetAsync": { - "uniqueName": "GetAsync", - "name": "GetAsync", - "httpMethod": "GET", - "url": "api/account/dynamic-claims", + "RefreshAsync": { + "uniqueName": "RefreshAsync", + "name": "RefreshAsync", + "httpMethod": "POST", + "url": "api/account/dynamic-claims/refresh", "supportedVersions": [], "parametersOnMethod": [], "parameters": [], "returnValue": { - "type": "System.Collections.Generic.List", - "typeSimple": "[Volo.Abp.Account.DynamicClaimDto]" + "type": "System.Void", + "typeSimple": "System.Void" }, "allowAnonymous": null, "implementFrom": "Volo.Abp.Account.IDynamicClaimsAppService" diff --git a/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs b/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs index 85587478ed..f3546e6607 100644 --- a/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs +++ b/modules/account/src/Volo.Abp.Account.HttpApi/Volo/Abp/Account/DynamicClaimsController.cs @@ -18,9 +18,10 @@ public class DynamicClaimsController : AbpControllerBase, IDynamicClaimsAppServi DynamicClaimsAppService = dynamicClaimsAppService; } - [HttpGet] - public virtual Task> GetAsync() + [HttpPost] + [Route("refresh")] + public virtual Task RefreshAsync() { - return DynamicClaimsAppService.GetAsync(); + return DynamicClaimsAppService.RefreshAsync(); } } 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 index 45093591b6..7d56a3b184 100644 --- 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 @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Security.Principal; @@ -22,7 +20,7 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip } var dynamicClaimsCache = context.GetRequiredService(); - List dynamicClaims; + AbpDynamicClaimCacheItem dynamicClaims; try { dynamicClaims = await dynamicClaimsCache.GetAsync(userId.Value, identity.FindTenantId()); @@ -36,6 +34,6 @@ public class IdentityDynamicClaimsPrincipalContributor : AbpDynamicClaimsPrincip return; } - await AddDynamicClaimsAsync(context, identity, dynamicClaims); + await AddDynamicClaimsAsync(context, identity, dynamicClaims.Claims); } } 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 index f3014adfce..b0ab885e03 100644 --- 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 @@ -18,7 +18,7 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen { public ILogger Logger { get; set; } - protected IDistributedCache> Cache { get; } + protected IDistributedCache Cache { get; } protected ICurrentTenant CurrentTenant { get; } protected IdentityUserManager UserManager { get; } protected IUserClaimsPrincipalFactory UserClaimsPrincipalFactory { get; } @@ -26,7 +26,7 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen protected IOptions CacheOptions { get; } public IdentityDynamicClaimsPrincipalContributorCache( - IDistributedCache> cache, + IDistributedCache cache, ICurrentTenant currentTenant, IdentityUserManager userManager, IUserClaimsPrincipalFactory userClaimsPrincipalFactory, @@ -43,11 +43,11 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen Logger = NullLogger.Instance; } - public virtual async Task> GetAsync(Guid userId, Guid? tenantId = null) + public virtual async Task GetAsync(Guid userId, Guid? tenantId = null) { Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); - return await Cache.GetOrAddAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => + return await Cache.GetOrAddAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId), async () => { using (CurrentTenant.Change(tenantId)) { @@ -56,17 +56,17 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen var user = await UserManager.GetByIdAsync(userId); var principal = await UserClaimsPrincipalFactory.CreateAsync(user); - var dynamicClaims = new List(); + 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.AddRange(claims.Select(claim => new AbpClaimCacheItem(claimType, claim.Value))); + dynamicClaims.Claims.AddRange(claims.Select(claim => new AbpDynamicClaim(claimType, claim.Value))); } else { - dynamicClaims.Add(new AbpClaimCacheItem(claimType, null)); + dynamicClaims.Claims.Add(new AbpDynamicClaim(claimType, null)); } } @@ -81,6 +81,6 @@ public class IdentityDynamicClaimsPrincipalContributorCache : ITransientDependen public virtual async Task ClearAsync(Guid userId, Guid? tenantId = null) { Logger.LogDebug($"Clearing dynamic claims cache for user: {userId}"); - await Cache.RemoveAsync(AbpClaimCacheItem.CalculateCacheKey(userId, tenantId)); + await Cache.RemoveAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs index 95b2563438..4c191579f8 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs @@ -32,6 +32,7 @@ using Volo.Abp.DistributedLocking; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.UI; using Volo.Abp.VirtualFileSystem; @@ -180,6 +181,11 @@ public class MyProjectNameAuthServerModule : AbpModule .AllowCredentials(); }); }); + + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index dd890d4dc6..c41ed749be 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -231,7 +231,8 @@ public class MyProjectNameBlazorModule : AbpModule context.Services.Configure(options => { - options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl; + options.IsDynamicClaimsEnabled = true; + options.RemoteRefreshUrl = configuration["AuthServer:Authority"] + options.RemoteRefreshUrl; }); } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index 0bb20fa36e..8c93eeca33 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -34,6 +34,7 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Identity.Blazor.Server; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Blazor.Server; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement.Blazor.Server; @@ -122,6 +123,10 @@ public class MyProjectNameBlazorModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureUrls(IConfiguration configuration) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index a323ff8dcd..833ae909f5 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -84,7 +84,8 @@ public class MyProjectNameBlazorModule : AbpModule builder.Services.Configure(options => { - options.RemoteUrl = builder.Configuration["AuthServer:Authority"] + options.RemoteUrl; + options.IsDynamicClaimsEnabled = true; + options.RemoteRefreshUrl = builder.Configuration["AuthServer:Authority"] + options.RemoteRefreshUrl; }); } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index d277aa4039..e785db843a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -27,6 +27,7 @@ using Volo.Abp.DistributedLocking; using Volo.Abp.Identity; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.Swashbuckle; using Volo.Abp.VirtualFileSystem; @@ -106,6 +107,11 @@ public class MyProjectNameHttpApiHostModule : AbpModule options.RequireHttpsMetadata = configuration.GetValue("AuthServer:RequireHttpsMetadata"); options.Audience = "MyProjectName"; }); + + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index 238339b1cf..45d3c605e7 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -25,6 +25,7 @@ using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.Swashbuckle; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; @@ -74,6 +75,10 @@ public class MyProjectNameHttpApiHostModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureBundles() diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index 01ed6034f8..70e98a64eb 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -211,10 +211,11 @@ public class MyProjectNameWebModule : AbpModule }); } - context.Services.Configure(options => - { - options.RemoteUrl = configuration["AuthServer:Authority"] + options.RemoteUrl; - }); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + options.RemoteRefreshUrl = configuration["AuthServer:Authority"] + options.RemoteRefreshUrl; + }); } private void ConfigureAutoMapper() diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index a259540829..1b61db27c0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -30,6 +30,7 @@ using Volo.Abp.Identity.Web; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.Web; +using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.Web; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement.Web; @@ -115,6 +116,10 @@ public class MyProjectNameWebModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureUrls(IConfiguration configuration) From 2a1e416a4ee0f21f3876ac2a3c9ed046f68c6ed3 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 14 Nov 2023 19:37:25 +0800 Subject: [PATCH 21/26] Redirect to home page when access denied. --- .../Mvc/Authentication/ChallengeAccountController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs index 8a5a8c2073..36b941cd4a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -61,12 +61,12 @@ public abstract class ChallengeAccountController : AbpController } [HttpGet] - public virtual Task AccessDenied(string returnUrl = "", string returnUrlHash = "") + public virtual Task AccessDenied() { return Task.FromResult(Challenge( new AuthenticationProperties { - RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash) + RedirectUri = "/" }, (ForbidSchemes.IsNullOrEmpty() ? new[] From d5629f14cc6e655004acfa6ce06df1960f1f5eb1 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 14 Nov 2023 20:42:58 +0800 Subject: [PATCH 22/26] Add `Dynamic-Claims.md` --- docs/en/Dynamic-Claims.md | 82 +++++++++++++++++++++++++++++++++++++++ docs/en/docs-nav.json | 8 +++- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 docs/en/Dynamic-Claims.md diff --git a/docs/en/Dynamic-Claims.md b/docs/en/Dynamic-Claims.md new file mode 100644 index 0000000000..e56be09be8 --- /dev/null +++ b/docs/en/Dynamic-Claims.md @@ -0,0 +1,82 @@ +# 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(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(options => + { + options.IsDynamicClaimsEnabled = true; + options.RemoteRefreshUrl = configuration["AuthServerUrl"] + options.RemoteRefreshUrl; + }); +} +```` + +Then add the `DynamicClaims` middleware. + +````csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + //... + app.UseAuthentication(); + + // Add this line after UseAuthentication + 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. + +## 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) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index fa0a98915a..80ff5aa501 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -178,7 +178,13 @@ }, { "text": "Authorization", - "path": "Authorization.md" + "path": "Authorization.md", + "items": [ + { + "text": "Dynamic Claims", + "path": "Dynamic-Claims.md" + } + ] }, { "text": "Caching", From a5fd934358447962c495ae01e690ae30a36275eb Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 14 Nov 2023 21:16:04 +0800 Subject: [PATCH 23/26] Rename claims to dynamicClaims --- .../RemoteDynamicClaimsPrincipalContributorCache.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs index eb9618e6b1..3a68f51568 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs @@ -44,10 +44,10 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency public virtual async Task GetAsync(Guid userId, Guid? tenantId = null) { Logger.LogDebug($"Get dynamic claims cache for user: {userId}"); - var claims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); - if (claims != null && !claims.Claims.IsNullOrEmpty()) + var dynamicClaims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); + if (dynamicClaims != null && !dynamicClaims.Claims.IsNullOrEmpty()) { - return claims; + return dynamicClaims; } Logger.LogDebug($"Refresh dynamic claims for user: {userId} from remote service."); @@ -65,12 +65,12 @@ public class RemoteDynamicClaimsPrincipalContributorCache : ITransientDependency throw; } - claims = await Cache.GetAsync(AbpDynamicClaimCacheItem.CalculateCacheKey(userId, tenantId)); - if (claims == null || claims.Claims.IsNullOrEmpty()) + 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 claims!; + return dynamicClaims!; } } From 6e20cd025889b389b39924814f74955d12a0f827 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 15 Nov 2023 11:36:30 +0800 Subject: [PATCH 24/26] Set dynamic claims on OpenIddict's controllers. --- .../Controllers/AbpOpenIdDictControllerBase.cs | 2 ++ .../OpenIddict/Controllers/AuthorizeController.cs | 14 ++++++++++++-- .../TokenController.AuthorizationCode.cs | 1 + .../Controllers/TokenController.DeviceCode.cs | 1 + .../Controllers/TokenController.RefreshToken.cs | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AbpOpenIdDictControllerBase.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AbpOpenIdDictControllerBase.cs index 7e8f6386b9..edf7c5d6ec 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AbpOpenIdDictControllerBase.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AbpOpenIdDictControllerBase.cs @@ -13,6 +13,7 @@ using OpenIddict.Abstractions; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Identity; using Volo.Abp.OpenIddict.Localization; +using Volo.Abp.Security.Claims; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Volo.Abp.OpenIddict.Controllers; @@ -26,6 +27,7 @@ public abstract class AbpOpenIdDictControllerBase : AbpController protected IOpenIddictScopeManager ScopeManager => LazyServiceProvider.LazyGetRequiredService(); protected IOpenIddictTokenManager TokenManager => LazyServiceProvider.LazyGetRequiredService(); protected AbpOpenIddictClaimsPrincipalManager OpenIddictClaimsPrincipalManager => LazyServiceProvider.LazyGetRequiredService(); + protected IAbpClaimsPrincipalFactory AbpClaimsPrincipalFactory => LazyServiceProvider.LazyGetRequiredService(); protected AbpOpenIdDictControllerBase() { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs index f4d7008ef5..140b0418d5 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs @@ -78,8 +78,18 @@ public class AuthorizeController : AbpOpenIdDictControllerBase } // Retrieve the profile of the logged in user. - var user = await UserManager.GetUserAsync(result.Principal) ?? - throw new InvalidOperationException(L["TheUserDetailsCannotBbeRetrieved"]); + var dynamicPrincipal = await AbpClaimsPrincipalFactory.CreateDynamicAsync(result.Principal); + var user = await UserManager.GetUserAsync(dynamicPrincipal); + if (user == null) + { + return Challenge( + authenticationSchemes: IdentityConstants.ApplicationScheme, + properties: new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create( + Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) + }); + } // Retrieve the application details from the database. var application = await ApplicationManager.FindByClientIdAsync(request.ClientId) ?? diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.AuthorizationCode.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.AuthorizationCode.cs index 58812f8111..4da4808201 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.AuthorizationCode.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.AuthorizationCode.cs @@ -14,6 +14,7 @@ public partial class TokenController { // Retrieve the claims principal stored in the authorization code/device code/refresh token. var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; + principal = await AbpClaimsPrincipalFactory.CreateDynamicAsync(principal); using (CurrentTenant.Change(principal.FindTenantId())) { // Retrieve the user profile corresponding to the authorization code/refresh token. diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.DeviceCode.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.DeviceCode.cs index a581dbcf7b..9559199bc9 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.DeviceCode.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.DeviceCode.cs @@ -14,6 +14,7 @@ public partial class TokenController { // Retrieve the claims principal stored in the authorization code/device code/refresh token. var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; + principal = await AbpClaimsPrincipalFactory.CreateDynamicAsync(principal); using (CurrentTenant.Change(principal.FindTenantId())) { // Retrieve the user profile corresponding to the authorization code/refresh token. diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.RefreshToken.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.RefreshToken.cs index e13cd3c595..f7f0cf0433 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.RefreshToken.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.RefreshToken.cs @@ -14,6 +14,7 @@ public partial class TokenController { // Retrieve the claims principal stored in the authorization code/device code/refresh token. var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; + principal = await AbpClaimsPrincipalFactory.CreateDynamicAsync(principal); using (CurrentTenant.Change(principal.FindTenantId())) { // Retrieve the user profile corresponding to the authorization code/refresh token. From 462cf9c17acc3df75c1678b32e447b0905bb5a49 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 15 Nov 2023 13:09:09 +0800 Subject: [PATCH 25/26] Delete DynamicClaimDto.cs --- .../Volo/Abp/Account/DynamicClaimDto.cs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs deleted file mode 100644 index ad5ac82678..0000000000 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/DynamicClaimDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Volo.Abp.Account; - -public class DynamicClaimDto -{ - public string Type { get; set; } - - public string Value { get; set; } - - public DynamicClaimDto(string type, string value) - { - Type = type; - Value = value; - } -} From 39240f117054e55d61aec7664d2b4bfcf809e62b Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 15 Nov 2023 14:17:52 +0800 Subject: [PATCH 26/26] Enable dynamic calims in `app nolayers`. --- docs/en/Dynamic-Claims.md | 8 +++----- .../MyProjectNameModule.cs | 6 ++++++ .../MyProjectNameModule.cs | 6 ++++++ .../Server.Mongo/MyProjectNameHostModule.cs | 6 ++++++ .../Server/MyProjectNameHostModule.cs | 6 ++++++ .../MyProjectNameModule.cs | 6 ++++++ .../MyProjectNameModule.cs | 6 ++++++ .../MyProjectNameModule.cs | 6 ++++++ .../MyProjectNameModule.cs | 6 ++++++ 9 files changed, 51 insertions(+), 5 deletions(-) diff --git a/docs/en/Dynamic-Claims.md b/docs/en/Dynamic-Claims.md index e56be09be8..3a2ef3ee58 100644 --- a/docs/en/Dynamic-Claims.md +++ b/docs/en/Dynamic-Claims.md @@ -38,12 +38,8 @@ Then add the `DynamicClaims` middleware. ````csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) { - //... - app.UseAuthentication(); - - // Add this line after UseAuthentication + // Add this line before UseAuthorization. app.UseDynamicClaims(); - app.UseAuthorization(); //... } @@ -68,6 +64,8 @@ This implementation is used for the `Tiered` solution. It will get the dynamic c 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. diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs index faf7121dfc..dc30b3568d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs @@ -47,6 +47,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.Blazor.Server; using Volo.Abp.TenantManagement.MongoDB; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Uow; @@ -174,6 +175,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureUrls(IConfiguration configuration) @@ -358,6 +363,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs index d73ba01a49..742f061fa4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs @@ -49,6 +49,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.Blazor.Server; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Validation.Localization; @@ -176,6 +177,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureUrls(IConfiguration configuration) @@ -374,6 +379,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs index 2f625c6d4e..f9043aae71 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs @@ -41,6 +41,7 @@ using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.MongoDB; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Uow; using Volo.Abp.VirtualFileSystem; @@ -160,6 +161,10 @@ public class MyProjectNameHostModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureBundles() @@ -312,6 +317,7 @@ public class MyProjectNameHostModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs index e3e7afe999..65b09d8f09 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs @@ -43,6 +43,7 @@ using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Uow; using Volo.Abp.VirtualFileSystem; @@ -163,6 +164,10 @@ public class MyProjectNameHostModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureBundles() @@ -329,6 +334,7 @@ public class MyProjectNameHostModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs index 7a99e10635..69f550112c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs @@ -41,6 +41,7 @@ using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.MongoDB; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Uow; using Volo.Abp.Validation.Localization; @@ -162,6 +163,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureBundles() @@ -354,6 +359,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs index 6705902ed4..91218e4f0f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs @@ -44,6 +44,7 @@ using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Validation.Localization; using Volo.Abp.VirtualFileSystem; @@ -165,6 +166,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureBundles() @@ -371,6 +376,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs index 6e702fd36c..6117aafd5d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs @@ -43,6 +43,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.MongoDB; using Volo.Abp.TenantManagement.Web; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Uow; @@ -167,6 +168,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureMultiTenancy() @@ -335,6 +340,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger(); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs index 43c60fc6a7..90f20f4325 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs @@ -45,6 +45,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.TenantManagement.Web; using Volo.Abp.OpenIddict; +using Volo.Abp.Security.Claims; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.Validation.Localization; @@ -169,6 +170,10 @@ public class MyProjectNameModule : AbpModule private void ConfigureAuthentication(ServiceConfigurationContext context) { context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + context.Services.Configure(options => + { + options.IsDynamicClaimsEnabled = true; + }); } private void ConfigureMultiTenancy() @@ -351,6 +356,7 @@ public class MyProjectNameModule : AbpModule } app.UseUnitOfWork(); + app.UseDynamicClaims(); app.UseAuthorization(); app.UseSwagger();