diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
index 59b8439fd..8ab17a456 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
@@ -1,11 +1,15 @@
+using LINGYUN.Abp.Account.Dto;
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MyCSharp.HttpUserAgentParser.Providers;
@@ -15,6 +19,7 @@ using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account.Settings;
@@ -66,8 +71,29 @@ public class LoginModel : AccountPageModel
protected IIdentityUserRepository UserRepository => LazyServiceProvider.LazyGetRequiredService
();
protected IQrCodeLoginProvider QrCodeLoginProvider => LazyServiceProvider.LazyGetRequiredService();
+ protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyServiceProvider.LazyGetRequiredService();
protected IHttpUserAgentParserProvider HttpUserAgentParserProvider => LazyServiceProvider.LazyGetRequiredService();
+ #region LinkUser
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkUserId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkTenantId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public string LinkToken { get; set; }
+
+ public bool IsLinkLogin { get; set; }
+
+ public IIdentityLinkUserAppService IdentityLinkUserAppService => LazyServiceProvider.LazyGetRequiredService();
+
+ #endregion
+
protected IExternalProviderService ExternalProviderService { get; }
protected IAuthenticationSchemeProvider SchemeProvider { get; }
protected AbpAccountOptions AccountOptions { get; }
@@ -89,21 +115,9 @@ public class LoginModel : AccountPageModel
public virtual async Task OnGetAsync()
{
LoginType = LoginType.Password;
- PhoneLoginInput = new PhoneLoginInputModel
- {
- ReturnUrl = ReturnUrl,
- ReturnUrlHash = ReturnUrlHash,
- };
- QrCodeLoginInput = new QrCodeLoginInputModel
- {
- ReturnUrl = ReturnUrl,
- ReturnUrlHash = ReturnUrlHash,
- };
- PasswordLoginInput = new PasswordLoginInputModel
- {
- ReturnUrl = ReturnUrl,
- ReturnUrlHash = ReturnUrlHash,
- };
+ PhoneLoginInput = new PhoneLoginInputModel();
+ QrCodeLoginInput = new QrCodeLoginInputModel();
+ PasswordLoginInput = new PasswordLoginInputModel();
AllowQrCodeLoginIfNotMobileDevice();
@@ -116,6 +130,20 @@ public class LoginModel : AccountPageModel
return await OnPostExternalLogin(ExternalProviders.First().AuthenticationScheme);
}
+ IsLinkLogin = await VerifyLinkTokenAsync();
+ if (IsLinkLogin && CurrentUser.IsAuthenticated)
+ {
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ Action = IdentitySecurityLogActionConsts.Logout
+ });
+
+ await SignInManager.SignOutAsync();
+
+ return Redirect(HttpContext.Request.GetDisplayUrl());
+ }
+
return Page();
}
@@ -140,6 +168,8 @@ public class LoginModel : AccountPageModel
await IdentityOptions.SetAsync();
+ IsLinkLogin = await VerifyLinkTokenAsync();
+
var result = await SignInManager.PasswordSignInAsync(
PasswordLoginInput.UserNameOrEmailAddress,
PasswordLoginInput.Password,
@@ -179,10 +209,15 @@ public class LoginModel : AccountPageModel
Debug.Assert(user != null, nameof(user) + " != null");
+ if (IsLinkLogin)
+ {
+ return await HandleLinkUserLogin(user);
+ }
+
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(PasswordLoginInput.ReturnUrl, PasswordLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public async virtual Task OnPostPhoneNumberLogin(string action)
@@ -217,6 +252,11 @@ public class LoginModel : AccountPageModel
return Page();
}
+ if (IsLinkLogin)
+ {
+ return await HandleLinkUserLogin(user);
+ }
+
await SignInManager.SignInAsync(user, PhoneLoginInput.RememberMe);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
@@ -229,7 +269,7 @@ public class LoginModel : AccountPageModel
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(PhoneLoginInput.ReturnUrl, PhoneLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
protected virtual void SetTenantCookies(Guid? tenantId = null)
@@ -296,6 +336,11 @@ public class LoginModel : AccountPageModel
return Page();
}
+ if (IsLinkLogin)
+ {
+ return await HandleLinkUserLogin(user);
+ }
+
// TODO: ¼ÇסµÇ¼
await SignInManager.SignInAsync(user, true);
@@ -311,7 +356,7 @@ public class LoginModel : AccountPageModel
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
- return await RedirectSafelyAsync(QrCodeLoginInput.ReturnUrl, QrCodeLoginInput.ReturnUrlHash);
+ return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
}
@@ -398,7 +443,10 @@ public class LoginModel : AccountPageModel
{
IsExternalLogin = true,
ExternalLoginAuthSchema = loginInfo.LoginProvider,
- ReturnUrl = returnUrl
+ ReturnUrl = returnUrl,
+ LinkUserId,
+ LinkTenantId,
+ LinkToken
});
}
@@ -409,7 +457,10 @@ public class LoginModel : AccountPageModel
{
IsExternalLogin = true,
ExternalLoginAuthSchema = loginInfo.LoginProvider,
- ReturnUrl = returnUrl
+ ReturnUrl = returnUrl,
+ LinkUserId,
+ LinkTenantId,
+ LinkToken
});
}
@@ -438,9 +489,12 @@ public class LoginModel : AccountPageModel
// Öض¨Ïò˫ÒòËØÈÏ֤ҳÃæ
return Task.FromResult(RedirectToPage("SendCode", new
{
- returnUrl = PasswordLoginInput.ReturnUrl,
- returnUrlHash = PasswordLoginInput.ReturnUrlHash,
- rememberMe = PasswordLoginInput.RememberMe
+ returnUrl = ReturnUrl,
+ returnUrlHash = ReturnUrlHash,
+ rememberMe = PasswordLoginInput.RememberMe,
+ linkUserId = LinkUserId,
+ linkTenantId = LinkTenantId,
+ linkToken = LinkToken,
}));
}
@@ -535,9 +589,9 @@ public class LoginModel : AccountPageModel
return RedirectToPage("ChangePassword", new
{
- returnUrl = PasswordLoginInput.ReturnUrl,
- returnUrlHash = PasswordLoginInput.ReturnUrlHash,
- rememberMe = PasswordLoginInput.RememberMe
+ returnUrl = ReturnUrl,
+ returnUrlHash = ReturnUrlHash,
+ rememberMe = PasswordLoginInput.RememberMe,
});
}
}
@@ -562,7 +616,7 @@ public class LoginModel : AccountPageModel
return RedirectToPage("./ChangePassword", new
{
ReturnUrl,
- ReturnUrlHash
+ ReturnUrlHash,
});
}
@@ -612,17 +666,118 @@ public class LoginModel : AccountPageModel
}
}
}
+
+ #region LinkUser
+
+ public async virtual Task GetWithoutLinkReturnUrlAsync(string returnUrl, string returnUrlHash)
+ {
+ var redirectUrl = await base.GetRedirectUrlAsync(returnUrl, returnUrlHash);
+
+ // ʹÓÃÕýÔò±í´ïʽÒƳý LinkUser ²ÎÊý
+ redirectUrl = Regex.Replace(redirectUrl, @"[&?]LinkToken=[^&]*", "");
+ redirectUrl = Regex.Replace(redirectUrl, @"[&?]LinkUserId=[^&]*", "");
+ redirectUrl = Regex.Replace(redirectUrl, @"[&?]LinkTenantId=[^&]*", "");
+ redirectUrl = Regex.Replace(redirectUrl, @"[&?]linkUserId=[^&]*", "");
+ redirectUrl = Regex.Replace(redirectUrl, @"[&?]linkToken=[^&]*", "");
+
+ return redirectUrl;
+ }
+
+ protected async virtual Task VerifyLinkTokenAsync()
+ {
+ if (LinkToken.IsNullOrWhiteSpace() || !LinkUserId.HasValue)
+ {
+ if (string.IsNullOrWhiteSpace(ReturnUrl))
+ {
+ return false;
+ }
+ var queryString = QueryHelpers.ParseQuery(ReturnUrl);
+ queryString.TryGetValue("LinkToken", out var linkTokenVal);
+ queryString.TryGetValue("LinkUserId", out var linkUserIdVal);
+ queryString.TryGetValue("LinkTenantId", out var linkTenantIdVal);
+ if (!linkTokenVal.IsNullOrEmpty() && !linkUserIdVal.IsNullOrEmpty() &&
+ Guid.TryParse(linkUserIdVal.ToString(), out var linkUserId))
+ {
+ LinkToken = linkTokenVal.ToString();
+ LinkUserId = linkUserId;
+ if (Guid.TryParse(linkTenantIdVal.ToString(), out var linkTenantId))
+ {
+ LinkTenantId = linkTenantId;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ // TODO: Ì滻¿ò¼ܶÔÓڿոñ·ûºŵıàÂë´íÎó
+ LinkToken = LinkToken.Replace(" ", "+");
+ return await IdentityLinkUserAppService.VerifyLinkTokenAsync(new VerifyLinkTokenInput()
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+ }
+
+
+ protected async virtual Task HandleLinkUserLogin(IdentityUser user)
+ {
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
+ {
+ await IdentityLinkUserAppService.LinkAsync(new LinkUserInput
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = user.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+
+ using (CurrentTenant.Change(LinkTenantId))
+ {
+ user = await UserManager.GetByIdAsync(LinkUserId.Value);
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
+ {
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = user.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+ }
+ }
+
+ return RedirectToPage("./LinkLogged", new
+ {
+ ReturnUrl,
+ ReturnUrlHash,
+ LinkUserId,
+ LinkTenantId
+ });
+ }
+ }
+
+ #endregion
}
public abstract class LoginInputModel
{
- [HiddenInput]
- [BindProperty(SupportsGet = true)]
- public string ReturnUrl { get; set; }
-
- [HiddenInput]
- [BindProperty(SupportsGet = true)]
- public string ReturnUrlHash { get; set; }
}
public class PhoneLoginInputModel : LoginInputModel
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs
index 70a1e12e5..36ae6f5dc 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs
@@ -1,3 +1,4 @@
+using LINGYUN.Abp.Account.Dto;
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Authentication;
@@ -46,6 +47,25 @@ public class RegisterModel : AccountPageModel
[BindProperty(SupportsGet = true)]
public string ExternalLoginAuthSchema { get; set; }
+ #region LinkUser
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkUserId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkTenantId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public string LinkToken { get; set; }
+
+ protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyServiceProvider.LazyGetRequiredService();
+
+ public IIdentityLinkUserAppService IdentityLinkUserAppService => LazyServiceProvider.LazyGetRequiredService();
+
+ #endregion
+
public IEnumerable ExternalProviders { get; set; }
public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName));
public bool EnableLocalRegister { get; set; }
@@ -146,7 +166,17 @@ public class RegisterModel : AccountPageModel
}
else
{
- await RegisterLocalUserAsync();
+ var user = await RegisterLocalUserAsync();
+
+ if (await VerifyLinkTokenAsync())
+ {
+ await HandleLinkUserLogin(user);
+ }
+
+ await SignInManager.SignInAsync(user, isPersistent: true);
+
+ // Clear the dynamic claims cache.
+ await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
}
return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
@@ -158,7 +188,7 @@ public class RegisterModel : AccountPageModel
}
}
- protected virtual async Task RegisterLocalUserAsync()
+ protected virtual async Task RegisterLocalUserAsync()
{
ValidateModel();
@@ -172,11 +202,7 @@ 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);
+ return await UserManager.GetByIdAsync(userDto.Id);
}
protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string userName, string emailAddress)
@@ -271,6 +297,63 @@ public class RegisterModel : AccountPageModel
return await Task.FromResult(Challenge(properties, provider));
}
+ #region LinkUser
+ protected async virtual Task HandleLinkUserLogin(IdentityUser user)
+ {
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
+ {
+ await IdentityLinkUserAppService.LinkAsync(new LinkUserInput
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = user.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+
+ using (CurrentTenant.Change(LinkTenantId))
+ {
+ var targetUser = await UserManager.GetByIdAsync(LinkUserId.Value);
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(targetUser)))
+ {
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = targetUser.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+ }
+ }
+ }
+ }
+
+ protected virtual async Task VerifyLinkTokenAsync()
+ {
+ return !LinkToken.IsNullOrWhiteSpace() && LinkUserId != null
+ && await IdentityLinkUserAppService.VerifyLinkTokenAsync(new VerifyLinkTokenInput
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+ }
+ #endregion
+
public class PostInput
{
[Required]
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs
index cc8fe74b9..5acd7c01d 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/SendCode.cshtml.cs
@@ -2,6 +2,7 @@ using LINGYUN.Abp.Account.Security;
using LINGYUN.Abp.Identity.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -29,6 +30,18 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
[BindProperty(SupportsGet = true)]
public bool RememberMe { get; set; }
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkUserId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkTenantId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public string LinkToken { get; set; }
+
public IEnumerable Providers { get; set; }
protected ISmsSender SmsSender { get; }
@@ -78,7 +91,7 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
- rememberMe = RememberMe
+ rememberMe = RememberMe,
});
}
// Éú³ÉÑé֤Âë
@@ -116,7 +129,10 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
provider = Input.SelectedProvider,
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
- rememberMe = RememberMe
+ rememberMe = RememberMe,
+ linkUserId = LinkUserId,
+ linkTenantId = LinkTenantId,
+ linkToken = LinkToken
});
}
}
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/VerifyCode.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/VerifyCode.cshtml.cs
index 13a63c7be..008695c12 100644
--- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/VerifyCode.cshtml.cs
+++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/VerifyCode.cshtml.cs
@@ -1,10 +1,13 @@
+using LINGYUN.Abp.Account.Dto;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
+using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Identity;
+using Volo.Abp.Security.Claims;
namespace LINGYUN.Abp.Account.Web.Pages.Account
{
@@ -39,6 +42,25 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
[BindProperty(SupportsGet = true)]
public bool RememberMe { get; set; }
+ #region LinkUser
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkUserId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public Guid? LinkTenantId { get; set; }
+
+ [HiddenInput]
+ [BindProperty(SupportsGet = true)]
+ public string LinkToken { get; set; }
+
+ protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyServiceProvider.LazyGetRequiredService();
+
+ public IIdentityLinkUserAppService IdentityLinkUserAppService => LazyServiceProvider.LazyGetRequiredService();
+
+ #endregion
+
public VerifyCodeModel(
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache)
{
@@ -67,6 +89,10 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
var result = await SignInManager.TwoFactorSignInAsync(Provider, Input.VerifyCode, RememberMe, Input.RememberBrowser);
if (result.Succeeded)
{
+ if (await VerifyLinkTokenAsync())
+ {
+ await HandleLinkUserLogin(user);
+ }
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
@@ -84,6 +110,63 @@ namespace LINGYUN.Abp.Account.Web.Pages.Account
return Page();
}
}
+
+ #region LinkUser
+ protected async virtual Task HandleLinkUserLogin(IdentityUser user)
+ {
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
+ {
+ await IdentityLinkUserAppService.LinkAsync(new LinkUserInput
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = user.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+
+ using (CurrentTenant.Change(LinkTenantId))
+ {
+ var targetUser = await UserManager.GetByIdAsync(LinkUserId.Value);
+ using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(targetUser)))
+ {
+ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
+ {
+ Identity = IdentitySecurityLogIdentityConsts.Identity,
+ UserName = targetUser.UserName,
+ Action = "LinkUser",
+ ExtraProperties =
+ {
+ { "LinkTenantId", LinkTenantId },
+ { "LinkUserId", LinkUserId }
+ }
+ });
+ }
+ }
+ }
+ }
+
+ protected virtual async Task VerifyLinkTokenAsync()
+ {
+ return !LinkToken.IsNullOrWhiteSpace() && LinkUserId != null
+ && await IdentityLinkUserAppService.VerifyLinkTokenAsync(new VerifyLinkTokenInput
+ {
+ UserId = LinkUserId.Value,
+ TenantId = LinkTenantId,
+ Token = LinkToken
+ });
+ }
+ #endregion
}
public class VerifyCodeInputModel
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs
index 3b1f90c3b..29088a2cf 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/IdentityErrorCodes.cs
@@ -38,4 +38,8 @@ public class IdentityErrorCodes
/// 密码不能与最近{0}次使用的密码相同
///
public const string PasswordInHistoryInValid = "Volo.Abp.Identity:020013";
+ ///
+ /// 关联用户Token无效
+ ///
+ public const string LinkUserTokenInValid = "Volo.Abp.Identity:020014";
}
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
index 82450d78d..80d38aba9 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
@@ -54,6 +54,7 @@
"Volo.Abp.Identity:020011": "Secondary authentication cannot be enabled when an MFA device is not attached!",
"Volo.Abp.Identity:020012": "Invalid authenticator code!",
"Volo.Abp.Identity:020013": "The password cannot be the same as the previous one.!",
+ "Volo.Abp.Identity:020014": "Link user token is invalid!",
"Volo.Abp.Identity:DuplicatePhoneNumber": "Phone number '{0}' is already taken.",
"DisplayName:Abp.Identity.User.SmsNewUserRegister": "Register sms template",
"Description:Abp.Identity.User.SmsNewUserRegister": "When the user registers, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
diff --git a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
index 4187cf83c..2dfb6959b 100644
--- a/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
+++ b/aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
@@ -54,6 +54,7 @@
"Volo.Abp.Identity:020011": "未绑定MFA设备时无法启用二次认证!",
"Volo.Abp.Identity:020012": "无效的验证器代码!",
"Volo.Abp.Identity:020013": "密码不能与之前的密码相同!",
+ "Volo.Abp.Identity:020014": "关联用户Token无效!",
"Volo.Abp.Identity:DuplicatePhoneNumber": "手机号 '{0}' 已存在.",
"DisplayName:Abp.Identity.User.SmsNewUserRegister": "新用户注册模板",
"Description:Abp.Identity.User.SmsNewUserRegister": "新用户通过手机注册账号验证码模板",
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/AbpOpenIddictLinkUserModule.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/AbpOpenIddictLinkUserModule.cs
index 90f711b1a..cc26f27c9 100644
--- a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/AbpOpenIddictLinkUserModule.cs
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/AbpOpenIddictLinkUserModule.cs
@@ -17,6 +17,7 @@ public class AbpOpenIddictLinkUserModule : AbpModule
PreConfigure(builder =>
{
builder.AllowLinkUserFlow();
+ builder.AddEventHandler(LinkUserAuthorizationHandler.Descriptor);
});
}
diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/LinkUserAuthorizationHandler.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/LinkUserAuthorizationHandler.cs
new file mode 100644
index 000000000..20e07745a
--- /dev/null
+++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.LinkUser/LINGYUN/Abp/OpenIddict/LinkUser/LinkUserAuthorizationHandler.cs
@@ -0,0 +1,37 @@
+using Microsoft.AspNetCore;
+using OpenIddict.Server;
+using System.Linq;
+using System.Threading.Tasks;
+using static OpenIddict.Server.OpenIddictServerEvents;
+
+namespace LINGYUN.Abp.OpenIddict.LinkUser;
+
+public class LinkUserAuthorizationHandler : IOpenIddictServerHandler
+{
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseScopedHandler()
+ .SetOrder(2000)
+ .SetType(OpenIddictServerHandlerType.Custom)
+ .Build();
+
+ public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
+ {
+ var request = context.Transaction.GetHttpRequest();
+ if (request == null)
+ {
+ return;
+ }
+
+ var linkUserId = request.Query["LinkUserId"].FirstOrDefault();
+ var linkToken = request.Query["LinkToken"].FirstOrDefault();
+
+ if (string.IsNullOrEmpty(linkUserId) || string.IsNullOrEmpty(linkToken))
+ {
+ return;
+ }
+
+ context.Transaction.SetProperty("LinkUserId", linkUserId);
+ context.Transaction.SetProperty("LinkToken", linkToken);
+ }
+}