diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 9e36576e56..69a9cfabb9 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -9,6 +9,7 @@ using Volo.Abp.Caching; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Identity; using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.AspNetIdentity; using Volo.Abp.IdentityServer.Clients; using Volo.Abp.IdentityServer.Devices; using Volo.Abp.IdentityServer.IdentityResources; @@ -94,6 +95,8 @@ namespace Volo.Abp.IdentityServer identityServerBuilder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources")); identityServerBuilder.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources")); } + + identityServerBuilder.AddExtensionGrantValidator(); } public override void PostConfigureServices(ServiceConfigurationContext context) diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs new file mode 100644 index 0000000000..3f4ff7dcf2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer4.Validation; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Volo.Abp.Identity; +using Volo.Abp.IdentityServer.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Security.Claims; +using Volo.Abp.Users; +using IdentityUser = Volo.Abp.Identity.IdentityUser; + +namespace Volo.Abp.IdentityServer.AspNetIdentity +{ + public class LinkLoginExtensionGrantValidator : IExtensionGrantValidator + { + public const string ExtensionGrantType = "LinkUserLogin"; + + 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 LinkLoginExtensionGrantValidator( + 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 = "invalid_access_token" + }; + 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)) + { + var linkUserId = Guid.Empty;; + if (!StringToGuid(context.Request.Raw["LinkUserId"], ref linkUserId)) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = "invalid_link_user_id" + }; + return; + } + + Guid? linkTenantId = null; + if (!context.Request.Raw["LinkTenantId"].IsNullOrWhiteSpace()) + { + if (!StringToGuid(context.Request.Raw["LinkTenantId"], ref linkTenantId)) + { + context.Result = new GrantValidationResult + { + IsError = true, + Error = "invalid_link_tenant_id" + }; + return; + } + } + + 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, + //TODO: Localizer error message. + Error = "The_Target_User_Is_Not_Linked_ToYou" + }; + } + } + } + + 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; + } + + protected virtual bool StringToGuid(string str, ref Guid guid) + { + if (str.IsNullOrWhiteSpace()) + { + return false; + } + + if (Guid.TryParse(str, out var g)) + { + guid = g; + return true; + } + + return false; + } + + protected virtual bool StringToGuid(string str, ref Guid? guid) + { + var g = Guid.Empty; + if (StringToGuid(str, ref g)) + { + guid = g; + return true; + } + + return false; + } + } +}