#nullable enable using System.Security.Claims; using System.Transactions; using Abp; using Abp.Auditing; using Abp.Configuration; using Abp.Configuration.Startup; using Abp.Dependency; using Abp.Domain.Repositories; using Abp.Domain.Uow; using Abp.Extensions; using Microsoft.AspNetCore.Identity; using VberZero.Authorization.Roles; using VberZero.Authorization.Users; using VberZero.BaseSystem.MultiTenancy; using VberZero.BaseSystem.Roles; using VberZero.BaseSystem.Users; using VberZero.Configuration; using VberZero.IdentityFramework; using VberZero.Settings; namespace VberZero.Authorization; public class VzLogInManager : ITransientDependency { public IClientInfoProvider ClientInfoProvider { get; set; } protected IMultiTenancyConfig MultiTenancyConfig { get; } protected IRepository TenantRepository { get; } protected IUnitOfWorkManager UnitOfWorkManager { get; } protected VzUserManager UserManager { get; } protected ISettingManager SettingManager { get; } protected IRepository UserLoginAttemptRepository { get; } protected IUserManagementConfig UserManagementConfig { get; } protected IIocResolver IocResolver { get; } protected VzRoleManager RoleManager { get; } private readonly IPasswordHasher _passwordHasher; private readonly UserClaimsPrincipalFactory _claimsPrincipalFactory; public VzLogInManager( VzUserManager userManager, IMultiTenancyConfig multiTenancyConfig, IRepository tenantRepository, IUnitOfWorkManager unitOfWorkManager, ISettingManager settingManager, IRepository userLoginAttemptRepository, IUserManagementConfig userManagementConfig, IIocResolver iocResolver, IPasswordHasher passwordHasher, VzRoleManager roleManager, UserClaimsPrincipalFactory claimsPrincipalFactory) { _passwordHasher = passwordHasher; _claimsPrincipalFactory = claimsPrincipalFactory; MultiTenancyConfig = multiTenancyConfig; TenantRepository = tenantRepository; UnitOfWorkManager = unitOfWorkManager; SettingManager = settingManager; UserLoginAttemptRepository = userLoginAttemptRepository; UserManagementConfig = userManagementConfig; IocResolver = iocResolver; RoleManager = roleManager; UserManager = userManager; ClientInfoProvider = NullClientInfoProvider.Instance; } public virtual async Task LoginAsync(UserLoginInfo login, string? tenancyName = null) { return await UnitOfWorkManager.WithUnitOfWorkAsync(async () => { var result = await LoginAsyncInternal(login, tenancyName); await SaveLoginAttemptAsync(result, tenancyName, login.ProviderKey + "@" + login.LoginProvider); return result; }); } protected virtual async Task LoginAsyncInternal(UserLoginInfo login, string? tenancyName) { if (login == null || login.LoginProvider.IsNullOrEmpty() || login.ProviderKey.IsNullOrEmpty()) { throw new ArgumentException("login"); } //Get and check tenant Tenant? tenant = null; if (!MultiTenancyConfig.IsEnabled) { tenant = await GetDefaultTenantAsync(); } else if (!string.IsNullOrWhiteSpace(tenancyName)) { tenant = await TenantRepository.FirstOrDefaultAsync(t => t!.TenancyName == tenancyName); if (tenant == null) { return new AbpLoginResult(VzLoginResultType.InvalidTenancyName); } if (!tenant.IsActive) { return new AbpLoginResult(VzLoginResultType.TenantIsNotActive, tenant); } } int? tenantId = tenant?.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var user = await UserManager.FindAsync(tenantId, login); if (user == null) { return new AbpLoginResult(VzLoginResultType.UnknownExternalLogin, tenant); } return await CreateLoginResultAsync(user, tenant); } } public virtual async Task LoginAsync(string userNameOrEmailOrPhone, string plainPassword, string? tenancyName = null, bool shouldLockout = true) { return await UnitOfWorkManager.WithUnitOfWorkAsync(async () => { var result = await LoginAsyncInternal(userNameOrEmailOrPhone, plainPassword, tenancyName, shouldLockout); await SaveLoginAttemptAsync(result, tenancyName, userNameOrEmailOrPhone); return result; }); } protected virtual async Task LoginAsyncInternal(string userNameOrEmailOrPhone, string plainPassword, string? tenancyName, bool shouldLockout) { if (userNameOrEmailOrPhone.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(userNameOrEmailOrPhone)); } if (plainPassword.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(plainPassword)); } //Get and check tenant Tenant? tenant = null; using (UnitOfWorkManager.Current.SetTenantId(null)) { if (!MultiTenancyConfig.IsEnabled) { tenant = await GetDefaultTenantAsync(); } else if (!string.IsNullOrWhiteSpace(tenancyName)) { tenant = await TenantRepository.FirstOrDefaultAsync(t => t != null && t.TenancyName == tenancyName); if (tenant == null) { return new AbpLoginResult(VzLoginResultType.InvalidTenancyName); } if (!tenant.IsActive) { return new AbpLoginResult(VzLoginResultType.TenantIsNotActive, tenant); } } } var tenantId = tenant?.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { await UserManager.InitializeOptionsAsync(tenantId); //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before UserStore.FindByNameOrEmailAsync var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSourcesAsync(userNameOrEmailOrPhone, plainPassword, tenant); var user = await UserManager.FindByNameOrEmailOrPhoneAsync(tenantId, userNameOrEmailOrPhone); if (user == null) { return new AbpLoginResult(VzLoginResultType.InvaliduserNameOrEmailOrPhone, tenant); } if (await UserManager.IsLockedOutAsync(user)) { return new AbpLoginResult(VzLoginResultType.LockedOut, tenant, user); } if (!loggedInFromExternalSource) { if (!await UserManager.CheckPasswordAsync(user, plainPassword)) { if (shouldLockout) { if (await TryLockOutAsync(tenantId, user.Id)) { return new AbpLoginResult(VzLoginResultType.LockedOut, tenant, user); } } return new AbpLoginResult(VzLoginResultType.InvalidPassword, tenant, user); } await UserManager.ResetAccessFailedCountAsync(user); } return await CreateLoginResultAsync(user, tenant); } } protected virtual async Task CreateLoginResultAsync(User user, Tenant? tenant = null) { if (!user.IsActive) { return new AbpLoginResult(VzLoginResultType.UserIsNotActive); } if (await IsEmailConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsEmailConfirmed) { return new AbpLoginResult(VzLoginResultType.UserEmailIsNotConfirmed); } if (await IsPhoneConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsPhoneNumberConfirmed) { return new AbpLoginResult(VzLoginResultType.UserPhoneNumberIsNotConfirmed); } var principal = await _claimsPrincipalFactory.CreateAsync(user); return new AbpLoginResult( tenant, user, principal.Identity as ClaimsIdentity ); } protected virtual async Task SaveLoginAttemptAsync(AbpLoginResult loginResult, string? tenancyName, string userNameOrEmailOrPhone) { using var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress); var tenantId = loginResult.Tenant?.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var loginAttempt = new UserLoginAttempt { TenantId = tenantId, TenancyName = tenancyName, UserId = loginResult.User.Id, UserNameOrEmailOrPhone = userNameOrEmailOrPhone, Result = loginResult.Result, BrowserInfo = ClientInfoProvider.BrowserInfo, ClientIpAddress = ClientInfoProvider.ClientIpAddress, ClientName = ClientInfoProvider.ComputerName, }; await UserLoginAttemptRepository.InsertAsync(loginAttempt); await UnitOfWorkManager.Current.SaveChangesAsync(); await uow.CompleteAsync(); } } protected virtual void SaveLoginAttempt(AbpLoginResult loginResult, string tenancyName, string userNameOrEmailOrPhone) { using var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress); var tenantId = loginResult.Tenant.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var loginAttempt = new UserLoginAttempt { TenantId = tenantId, TenancyName = tenancyName, UserId = loginResult.User.Id, UserNameOrEmailOrPhone = userNameOrEmailOrPhone, Result = loginResult.Result, BrowserInfo = ClientInfoProvider.BrowserInfo, ClientIpAddress = ClientInfoProvider.ClientIpAddress, ClientName = ClientInfoProvider.ComputerName, }; UserLoginAttemptRepository.Insert(loginAttempt); UnitOfWorkManager.Current.SaveChanges(); uow.Complete(); } } protected virtual async Task TryLockOutAsync(int? tenantId, long userId) { using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress)) { using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var user = await UserManager.FindByIdAsync(userId.ToString()); (await UserManager.AccessFailedAsync(user)).CheckErrors(); var isLockOut = await UserManager.IsLockedOutAsync(user); await UnitOfWorkManager.Current.SaveChangesAsync(); await uow.CompleteAsync(); return isLockOut; } } } protected virtual async Task TryLoginFromExternalAuthenticationSourcesAsync(string userNameOrEmailOrPhone, string plainPassword, Tenant? tenant) { if (!UserManagementConfig.ExternalAuthenticationSources.Any()) { return false; } foreach (var sourceType in UserManagementConfig.ExternalAuthenticationSources) { using (var source = IocResolver.ResolveAsDisposable(sourceType)) { if (await source.Object.TryAuthenticateAsync(userNameOrEmailOrPhone, plainPassword, tenant)) { var tenantId = tenant == null ? (int?)null : tenant.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var user = await UserManager.FindByNameOrEmailOrPhoneAsync(tenantId, userNameOrEmailOrPhone); if (user == null) { user = await source.Object.CreateUserAsync(userNameOrEmailOrPhone, tenant); user.TenantId = tenantId; user.AuthenticationSource = source.Object.Name; user.Password = _passwordHasher.HashPassword(user, Guid.NewGuid().ToString("N") .Left(16)); //Setting a random password since it will not be used user.SetNormalizedNames(); if (user.Roles == null) { user.Roles = new List(); foreach (var defaulRole in RoleManager.Roles .Where(r => r.TenantId == tenantId && r.IsDefault).ToList()) { user.Roles.Add(new UserRole(tenantId, user.Id, defaulRole.Id)); } } await UserManager.CreateAsync(user); } else { await source.Object.UpdateUserAsync(user, tenant); user.AuthenticationSource = source.Object.Name; await UserManager.UpdateAsync(user); } await UnitOfWorkManager.Current.SaveChangesAsync(); return true; } } } } return false; } protected virtual async Task GetDefaultTenantAsync() { var tenant = await TenantRepository.FirstOrDefaultAsync( t => t != null && t.TenancyName == Tenant.DefaultTenantName ); if (tenant == null) { throw new AbpException("如果禁用多租户,则应该有一个“默认”租户!"); } return tenant; } protected virtual Tenant GetDefaultTenant() { var tenant = TenantRepository.FirstOrDefault(t => t != null && t.TenancyName == Tenant.DefaultTenantName); if (tenant == null) { throw new AbpException("如果禁用多租户,则应该有一个“默认”租户!"); } return tenant; } protected virtual async Task IsEmailConfirmationRequiredForLoginAsync(int? tenantId) { if (tenantId.HasValue) { return await SettingManager.GetSettingValueForTenantAsync( VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin, tenantId.Value ); } return await SettingManager.GetSettingValueForApplicationAsync( VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin ); } protected virtual bool IsEmailConfirmationRequiredForLogin(int? tenantId) { if (tenantId.HasValue) { return SettingManager.GetSettingValueForTenant( VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin, tenantId.Value ); } return SettingManager.GetSettingValueForApplication( VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin ); } protected virtual Task IsPhoneConfirmationRequiredForLoginAsync(int? tenantId) { return Task.FromResult(false); } protected virtual bool IsPhoneConfirmationRequiredForLogin(int? tenantId) { return false; } }