From b063162d241dec03c38c6ed36eb30816742ec7aa Mon Sep 17 00:00:00 2001
From: cKey <35512826+colinin@users.noreply.github.com>
Date: Sat, 19 Feb 2022 08:25:40 +0800
Subject: [PATCH] feat: add link user login support
---
aspnet-core/LINGYUN.MicroService.All.sln | 7 +
...LINGYUN.Abp.IdentityServer.LinkUser.csproj | 23 +++
.../AbpIdentityServerLinkUserModule.cs | 37 +++++
.../LinkUser/LinkUserGrantValidator.cs | 148 ++++++++++++++++++
.../LinkUser/Localization/en.json | 8 +
.../LinkUser/Localization/zh-Hans.json | 8 +
6 files changed, 231 insertions(+)
create mode 100644 aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN.Abp.IdentityServer.LinkUser.csproj
create mode 100644 aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/AbpIdentityServerLinkUserModule.cs
create mode 100644 aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/LinkUserGrantValidator.cs
create mode 100644 aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/en.json
create mode 100644 aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.LinkUser/LINGYUN/Abp/IdentityServer/LinkUser/Localization/zh-Hans.json
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