diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index cd76d0186..bef2efb85 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -380,6 +380,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BlobStoring.Ten EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.Tencent", "modules\oss-management\LINGYUN.Abp.OssManagement.Tencent\LINGYUN.Abp.OssManagement.Tencent.csproj", "{31E60E23-FD98-4D5E-A137-2B3F2968BA09}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.IdentityServer.LinkUser", "modules\identityServer\LINGYUN.Abp.IdentityServer.LinkUser\LINGYUN.Abp.IdentityServer.LinkUser.csproj", "{25378F9D-2A66-4568-AAC6-E9282ACA3DD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -982,6 +984,10 @@ Global {31E60E23-FD98-4D5E-A137-2B3F2968BA09}.Debug|Any CPU.Build.0 = Debug|Any CPU {31E60E23-FD98-4D5E-A137-2B3F2968BA09}.Release|Any CPU.ActiveCfg = Release|Any CPU {31E60E23-FD98-4D5E-A137-2B3F2968BA09}.Release|Any CPU.Build.0 = Release|Any CPU + {25378F9D-2A66-4568-AAC6-E9282ACA3DD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25378F9D-2A66-4568-AAC6-E9282ACA3DD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25378F9D-2A66-4568-AAC6-E9282ACA3DD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25378F9D-2A66-4568-AAC6-E9282ACA3DD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1167,6 +1173,7 @@ Global {8FE2725C-6829-4778-93BA-A53260697AFB} = {3B96F4D8-4993-419B-BCEB-AFE4ED39449F} {A4B972EC-9F0B-4405-9965-766FABC9B07E} = {3B96F4D8-4993-419B-BCEB-AFE4ED39449F} {31E60E23-FD98-4D5E-A137-2B3F2968BA09} = {B05CB08F-C088-4D6D-97EE-A94A5D1AE4A6} + {25378F9D-2A66-4568-AAC6-E9282ACA3DD3} = {0439B173-F41E-4CE0-A44A-CCB70328F272} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN.Abp.IdentityServer.LinkUser.csproj b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN.Abp.IdentityServer.LinkUser.csproj new file mode 100644 index 000000000..f9cbcd2cc --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN.Abp.IdentityServer.LinkUser.csproj @@ -0,0 +1,23 @@ + + + + + + + net6.0 + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/AbpIdentityServerLinkUserModule.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/AbpIdentityServerLinkUserModule.cs new file mode 100644 index 000000000..b7b38faa3 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/AbpIdentityServerLinkUserModule.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.IdentityServer; +using Volo.Abp.IdentityServer.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.IdentityServer.LinkUser; + +[DependsOn(typeof(AbpIdentityServerDomainModule))] +public class AbpIdentityServerLinkUserModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + PreConfigure(builder => + { + builder.AddExtensionGrantValidator(); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/IdentityServer/LinkUser/Localization"); + }); + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/LinkUserGrantValidator.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/LinkUserGrantValidator.cs new file mode 100644 index 000000000..5a2d34b83 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/LinkUserGrantValidator.cs @@ -0,0 +1,148 @@ +using IdentityServer4.Validation; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Volo.Abp.Identity; +using Volo.Abp.IdentityServer.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Security.Claims; +using Volo.Abp.Users; + +namespace LINGYUN.Abp.IdentityServer.LinkUser; + +public class LinkUserGrantValidator : IExtensionGrantValidator +{ + public const string ExtensionGrantType = "link_user"; + + public string GrantType => ExtensionGrantType; + + protected ITokenValidator TokenValidator { get; } + protected IdentityLinkUserManager IdentityLinkUserManager { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ICurrentUser CurrentUser { get; } + protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; } + protected IdentityUserManager UserManager { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } + protected ILogger Logger { get; } + protected IStringLocalizer Localizer { get; } + + public LinkUserGrantValidator( + ITokenValidator tokenValidator, + IdentityLinkUserManager identityLinkUserManager, + ICurrentTenant currentTenant, + ICurrentUser currentUser, + IdentityUserManager userManager, + ICurrentPrincipalAccessor currentPrincipalAccessor, + IdentitySecurityLogManager identitySecurityLogManager, + ILogger logger, + IStringLocalizer localizer) + { + TokenValidator = tokenValidator; + IdentityLinkUserManager = identityLinkUserManager; + CurrentTenant = currentTenant; + CurrentUser = currentUser; + UserManager = userManager; + CurrentPrincipalAccessor = currentPrincipalAccessor; + IdentitySecurityLogManager = identitySecurityLogManager; + Logger = logger; + Localizer = localizer; + } + + public virtual async Task ValidateAsync(ExtensionGrantValidationContext context) + { + var accessToken = context.Request.Raw["access_token"]; + if (accessToken.IsNullOrWhiteSpace()) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = Localizer["InvalidAccessToken"] + }; + return; + } + + var result = await TokenValidator.ValidateAccessTokenAsync(accessToken); + if (result.IsError) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = result.Error, + ErrorDescription = result.ErrorDescription + }; + return; + } + + using (CurrentPrincipalAccessor.Change(result.Claims)) + { + if (!Guid.TryParse(context.Request.Raw["LinkUserId"], out var linkUserId)) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = Localizer["InvalidLinkUserId"] + }; + return; + } + + Guid? linkTenantId = null; + if (!context.Request.Raw["LinkTenantId"].IsNullOrWhiteSpace()) + { + if (!Guid.TryParse(context.Request.Raw["LinkTenantId"], out var parsedGuid)) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = Localizer["InvalidLinkTenantId"] + }; + return; + } + + linkTenantId = parsedGuid; + } + + var isLinked = await IdentityLinkUserManager.IsLinkedAsync( + new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id), + new IdentityLinkUserInfo(linkUserId, linkTenantId)); + + if (isLinked) + { + using (CurrentTenant.Change(linkTenantId)) + { + var user = await UserManager.GetByIdAsync(linkUserId); + var sub = await UserManager.GetUserIdAsync(user); + + var additionalClaims = new List(); + await AddCustomClaimsAsync(additionalClaims, user, context); + + context.Result = new GrantValidationResult( + sub, + GrantType, + additionalClaims.ToArray() + ); + } + } + else + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = Localizer["TheTargetUserIsNotLinkedToYou"] + }; + } + } + } + + protected virtual Task AddCustomClaimsAsync(List customClaims, IdentityUser user, ExtensionGrantValidationContext context) + { + if (user.TenantId.HasValue) + { + customClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString())); + } + + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/en.json b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/en.json new file mode 100644 index 000000000..ca74b7c0b --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/en.json @@ -0,0 +1,8 @@ +{ + "culture": "en", + "texts": { + "InvalidAccessToken": "Invalid access token.", + "InvalidLinkUserId": "Invalid link user id.", + "InvalidLinkTenantId": "Invalid link tenant id." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/zh-Hans.json b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/zh-Hans.json new file mode 100644 index 000000000..ad8e07adc --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "culture": "zh-Hans", + "texts": { + "InvalidAccessToken": "无效的访问令牌.", + "InvalidLinkUserId": "无效的链接用户标识.", + "InvalidLinkTenantId": "无效的链接租户标识." + } +} \ No newline at end of file