using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; 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 IwbZero.Authorization.Base; using IwbZero.Authorization.Base.Users; using IwbZero.Authorization.Roles; using IwbZero.IdentityFramework; using IwbZero.MultiTenancy; using IwbZero.Zero.Configuration; using Microsoft.AspNet.Identity; namespace IwbZero.Authorization.Users { public abstract class IwbLogInManager : ITransientDependency where TTenant : IwbTenant where TRole : IwbSysRole, new() where TUser : IwbSysUser { public IClientInfoProvider ClientInfoProvider { get; set; } protected IMultiTenancyConfig MultiTenancyConfig { get; } protected IRepository TenantRepository { get; } protected IUnitOfWorkManager UnitOfWorkManager { get; } protected IwbUserManager UserManager { get; } protected ISettingManager SettingManager { get; } protected IRepository UserLoginAttemptRepository { get; } protected IUserManagementConfig UserManagementConfig { get; } protected IIocResolver IocResolver { get; } protected IwbRoleManager RoleManager { get; } protected IwbLogInManager( IwbUserManager userManager, IMultiTenancyConfig multiTenancyConfig, IRepository tenantRepository, IUnitOfWorkManager unitOfWorkManager, ISettingManager settingManager, IRepository userLoginAttemptRepository, IUserManagementConfig userManagementConfig, IIocResolver iocResolver, IwbRoleManager roleManager) { MultiTenancyConfig = multiTenancyConfig; TenantRepository = tenantRepository; UnitOfWorkManager = unitOfWorkManager; SettingManager = settingManager; UserLoginAttemptRepository = userLoginAttemptRepository; UserManagementConfig = userManagementConfig; IocResolver = iocResolver; RoleManager = roleManager; UserManager = userManager; ClientInfoProvider = NullClientInfoProvider.Instance; } [UnitOfWork] public virtual async Task> LoginAsync(UserLoginInfo login, string tenancyName = null) { var result = await LoginAsyncInternal(login, tenancyName); await SaveLoginAttempt(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 TTenant 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 IwbLoginResult(IwbLoginResultType.InvalidTenancyName); } if (!tenant.IsActive) { return new IwbLoginResult(IwbLoginResultType.TenantIsNotActive, tenant); } } int? tenantId = tenant == null ? (int?)null : tenant.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var user = await UserManager.IwbStore.FindAsync(tenantId, login); if (user == null) { return new IwbLoginResult(IwbLoginResultType.UnknownExternalLogin, tenant); } return await CreateLoginResultAsync(user, tenant); } } [UnitOfWork] public virtual async Task> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null, bool shouldLockout = true) { var result = await LoginAsyncInternal(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout); await SaveLoginAttempt(result, tenancyName, userNameOrEmailAddress); return result; } protected virtual async Task> LoginAsyncInternal(string userNameOrEmailAddress, string plainPassword, string tenancyName, bool shouldLockout) { if (userNameOrEmailAddress.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(userNameOrEmailAddress)); } if (plainPassword.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(plainPassword)); } //Get and check tenant TTenant tenant = null; using (UnitOfWorkManager.Current.SetTenantId(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 IwbLoginResult(IwbLoginResultType.InvalidTenancyName); } if (!tenant.IsActive) { return new IwbLoginResult(IwbLoginResultType.TenantIsNotActive, tenant); } } } var tenantId = tenant == null ? (int?)null : tenant.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before IwbStore.FindByNameOrEmailOrMobileAsync var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSources(userNameOrEmailAddress, plainPassword, tenant); var user = await UserManager.IwbStore.FindByNameOrEmailOrMobileAsync(tenantId, userNameOrEmailAddress); if (user == null) { return new IwbLoginResult(IwbLoginResultType.InvalidUserNameOrEmailAddress, tenant); } if (await UserManager.IsLockedOutAsync(user.Id)) { return new IwbLoginResult(IwbLoginResultType.LockedOut, tenant, user); } if (!loggedInFromExternalSource) { UserManager.InitializeLockoutSettings(tenantId); var verificationResult = UserManager.PasswordHasher.VerifyHashedPassword(user.Password, plainPassword); if (verificationResult == PasswordVerificationResult.Failed) { return await GetFailedPasswordValidationAsLoginResultAsync(user, tenant, shouldLockout); } if (verificationResult == PasswordVerificationResult.SuccessRehashNeeded) { return await GetSuccessRehashNeededAsLoginResultAsync(user, tenant); } await UserManager.ResetAccessFailedCountAsync(user.Id); } return await CreateLoginResultAsync(user, tenant); } } protected virtual async Task> GetFailedPasswordValidationAsLoginResultAsync(TUser user, TTenant tenant = null, bool shouldLockout = false) { if (shouldLockout) { if (await TryLockOutAsync(user.TenantId, user.Id)) { return new IwbLoginResult(IwbLoginResultType.LockedOut, tenant, user); } } return new IwbLoginResult(IwbLoginResultType.InvalidPassword, tenant, user); } protected virtual async Task> GetSuccessRehashNeededAsLoginResultAsync(TUser user, TTenant tenant = null, bool shouldLockout = false) { return await GetFailedPasswordValidationAsLoginResultAsync(user, tenant, shouldLockout); } protected virtual async Task> CreateLoginResultAsync(TUser user, TTenant tenant = null) { if (!user.IsActive) { return new IwbLoginResult(IwbLoginResultType.UserIsNotActive); } if (await IsEmailConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsEmailConfirmed) { return new IwbLoginResult(IwbLoginResultType.UserEmailIsNotConfirmed); } return new IwbLoginResult( tenant, user, await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie) ); } protected virtual async Task SaveLoginAttempt(IwbLoginResult loginResult, string tenancyName, string userNameOrEmailAddress) { using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress)) { var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var loginAttempt = new UserLoginAttempt { TenantId = tenantId, TenancyName = tenancyName, UserId = loginResult.User != null ? loginResult.User.Id : (long?)null, UserNameOrEmailAddress = userNameOrEmailAddress, 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 async Task TryLockOutAsync(int? tenantId, long userId) { using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress)) { using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { (await UserManager.AccessFailedAsync(userId)).CheckErrors(); var isLockOut = await UserManager.IsLockedOutAsync(userId); await UnitOfWorkManager.Current.SaveChangesAsync(); await uow.CompleteAsync(); return isLockOut; } } } protected virtual async Task TryLoginFromExternalAuthenticationSources(string userNameOrEmailAddress, string plainPassword, TTenant tenant) { if (!UserManagementConfig.ExternalAuthenticationSources.Any()) { return false; } foreach (var sourceType in UserManagementConfig.ExternalAuthenticationSources) { using (var source = IocResolver.ResolveAsDisposable>(sourceType)) { if (await source.Object.TryAuthenticateAsync(userNameOrEmailAddress, plainPassword, tenant)) { var tenantId = tenant == null ? (int?)null : tenant.Id; using (UnitOfWorkManager.Current.SetTenantId(tenantId)) { var user = await UserManager.IwbStore.FindByNameOrEmailOrMobileAsync(tenantId, userNameOrEmailAddress); if (user == null) { user = await source.Object.CreateUserAsync(userNameOrEmailAddress, tenant); user.TenantId = tenantId; user.AuthenticationSource = source.Object.Name; user.Password = UserManager.PasswordHasher.HashPassword(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 defaultRole in RoleManager.Roles.Where(r => r.TenantId == tenantId && r.IsDefault).ToList()) { user.Roles.Add(new UserRole(tenantId, user.Id, defaultRole.Id)); } } await UserManager.IwbStore.CreateAsync(user); } else { await source.Object.UpdateUserAsync(user, tenant); user.AuthenticationSource = source.Object.Name; await UserManager.IwbStore.UpdateAsync(user); } await UnitOfWorkManager.Current.SaveChangesAsync(); return true; } } } } return false; } protected virtual async Task GetDefaultTenantAsync() { var tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == TenantBase.DefaultTenantName); if (tenant == null) { throw new AbpException("There should be a 'Default' tenant if multi-tenancy is disabled!"); } return tenant; } protected virtual async Task IsEmailConfirmationRequiredForLoginAsync(int? tenantId) { if (tenantId.HasValue) { return await SettingManager.GetSettingValueForTenantAsync(IwbZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin, tenantId.Value); } return await SettingManager.GetSettingValueForApplicationAsync(IwbZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin); } } }