IwbLogInManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using System.Transactions;
  6. using Abp;
  7. using Abp.Auditing;
  8. using Abp.Configuration;
  9. using Abp.Configuration.Startup;
  10. using Abp.Dependency;
  11. using Abp.Domain.Repositories;
  12. using Abp.Domain.Uow;
  13. using Abp.Extensions;
  14. using IwbZero.Authorization.Base;
  15. using IwbZero.Authorization.Base.Users;
  16. using IwbZero.Authorization.Roles;
  17. using IwbZero.IdentityFramework;
  18. using IwbZero.MultiTenancy;
  19. using IwbZero.Zero.Configuration;
  20. using Microsoft.AspNet.Identity;
  21. namespace IwbZero.Authorization.Users
  22. {
  23. public abstract class IwbLogInManager<TTenant, TRole, TUser> : ITransientDependency
  24. where TTenant : IwbTenant<TUser>
  25. where TRole : IwbSysRole<TUser>, new()
  26. where TUser : IwbSysUser<TUser>
  27. {
  28. public IClientInfoProvider ClientInfoProvider { get; set; }
  29. protected IMultiTenancyConfig MultiTenancyConfig { get; }
  30. protected IRepository<TTenant> TenantRepository { get; }
  31. protected IUnitOfWorkManager UnitOfWorkManager { get; }
  32. protected IwbUserManager<TRole, TUser> UserManager { get; }
  33. protected ISettingManager SettingManager { get; }
  34. protected IRepository<UserLoginAttempt, long> UserLoginAttemptRepository { get; }
  35. protected IUserManagementConfig UserManagementConfig { get; }
  36. protected IIocResolver IocResolver { get; }
  37. protected IwbRoleManager<TRole, TUser> RoleManager { get; }
  38. protected IwbLogInManager(
  39. IwbUserManager<TRole, TUser> userManager,
  40. IMultiTenancyConfig multiTenancyConfig,
  41. IRepository<TTenant> tenantRepository,
  42. IUnitOfWorkManager unitOfWorkManager,
  43. ISettingManager settingManager,
  44. IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
  45. IUserManagementConfig userManagementConfig,
  46. IIocResolver iocResolver,
  47. IwbRoleManager<TRole, TUser> roleManager)
  48. {
  49. MultiTenancyConfig = multiTenancyConfig;
  50. TenantRepository = tenantRepository;
  51. UnitOfWorkManager = unitOfWorkManager;
  52. SettingManager = settingManager;
  53. UserLoginAttemptRepository = userLoginAttemptRepository;
  54. UserManagementConfig = userManagementConfig;
  55. IocResolver = iocResolver;
  56. RoleManager = roleManager;
  57. UserManager = userManager;
  58. ClientInfoProvider = NullClientInfoProvider.Instance;
  59. }
  60. [UnitOfWork]
  61. public virtual async Task<IwbLoginResult<TTenant, TUser>> LoginAsync(UserLoginInfo login, string tenancyName = null)
  62. {
  63. var result = await LoginAsyncInternal(login, tenancyName);
  64. await SaveLoginAttempt(result, tenancyName, login.ProviderKey + "@" + login.LoginProvider);
  65. return result;
  66. }
  67. protected virtual async Task<IwbLoginResult<TTenant, TUser>> LoginAsyncInternal(UserLoginInfo login, string tenancyName)
  68. {
  69. if (login == null || login.LoginProvider.IsNullOrEmpty() || login.ProviderKey.IsNullOrEmpty())
  70. {
  71. throw new ArgumentException("login");
  72. }
  73. //Get and check tenant
  74. TTenant tenant = null;
  75. if (!MultiTenancyConfig.IsEnabled)
  76. {
  77. tenant = await GetDefaultTenantAsync();
  78. }
  79. else if (!string.IsNullOrWhiteSpace(tenancyName))
  80. {
  81. tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName);
  82. if (tenant == null)
  83. {
  84. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.InvalidTenancyName);
  85. }
  86. if (!tenant.IsActive)
  87. {
  88. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.TenantIsNotActive, tenant);
  89. }
  90. }
  91. int? tenantId = tenant == null ? (int?)null : tenant.Id;
  92. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  93. {
  94. var user = await UserManager.IwbStore.FindAsync(tenantId, login);
  95. if (user == null)
  96. {
  97. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.UnknownExternalLogin, tenant);
  98. }
  99. return await CreateLoginResultAsync(user, tenant);
  100. }
  101. }
  102. [UnitOfWork]
  103. public virtual async Task<IwbLoginResult<TTenant, TUser>> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null, bool shouldLockout = true)
  104. {
  105. var result = await LoginAsyncInternal(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout);
  106. await SaveLoginAttempt(result, tenancyName, userNameOrEmailAddress);
  107. return result;
  108. }
  109. protected virtual async Task<IwbLoginResult<TTenant, TUser>> LoginAsyncInternal(string userNameOrEmailAddress, string plainPassword, string tenancyName, bool shouldLockout)
  110. {
  111. if (userNameOrEmailAddress.IsNullOrEmpty())
  112. {
  113. throw new ArgumentNullException(nameof(userNameOrEmailAddress));
  114. }
  115. if (plainPassword.IsNullOrEmpty())
  116. {
  117. throw new ArgumentNullException(nameof(plainPassword));
  118. }
  119. //Get and check tenant
  120. TTenant tenant = null;
  121. using (UnitOfWorkManager.Current.SetTenantId(null))
  122. {
  123. if (!MultiTenancyConfig.IsEnabled)
  124. {
  125. tenant = await GetDefaultTenantAsync();
  126. }
  127. else if (!string.IsNullOrWhiteSpace(tenancyName))
  128. {
  129. tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName);
  130. if (tenant == null)
  131. {
  132. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.InvalidTenancyName);
  133. }
  134. if (!tenant.IsActive)
  135. {
  136. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.TenantIsNotActive, tenant);
  137. }
  138. }
  139. }
  140. var tenantId = tenant == null ? (int?)null : tenant.Id;
  141. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  142. {
  143. //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before IwbStore.FindByNameOrEmailOrMobileAsync
  144. var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSources(userNameOrEmailAddress, plainPassword, tenant);
  145. var user = await UserManager.IwbStore.FindByNameOrEmailOrMobileAsync(tenantId, userNameOrEmailAddress);
  146. if (user == null)
  147. {
  148. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.InvalidUserNameOrEmailAddress, tenant);
  149. }
  150. if (await UserManager.IsLockedOutAsync(user.Id))
  151. {
  152. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.LockedOut, tenant, user);
  153. }
  154. if (!loggedInFromExternalSource)
  155. {
  156. UserManager.InitializeLockoutSettings(tenantId);
  157. var verificationResult = UserManager.PasswordHasher.VerifyHashedPassword(user.Password, plainPassword);
  158. if (verificationResult == PasswordVerificationResult.Failed)
  159. {
  160. return await GetFailedPasswordValidationAsLoginResultAsync(user, tenant, shouldLockout);
  161. }
  162. if (verificationResult == PasswordVerificationResult.SuccessRehashNeeded)
  163. {
  164. return await GetSuccessRehashNeededAsLoginResultAsync(user, tenant);
  165. }
  166. await UserManager.ResetAccessFailedCountAsync(user.Id);
  167. }
  168. return await CreateLoginResultAsync(user, tenant);
  169. }
  170. }
  171. protected virtual async Task<IwbLoginResult<TTenant, TUser>> GetFailedPasswordValidationAsLoginResultAsync(TUser user, TTenant tenant = null, bool shouldLockout = false)
  172. {
  173. if (shouldLockout)
  174. {
  175. if (await TryLockOutAsync(user.TenantId, user.Id))
  176. {
  177. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.LockedOut, tenant, user);
  178. }
  179. }
  180. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.InvalidPassword, tenant, user);
  181. }
  182. protected virtual async Task<IwbLoginResult<TTenant, TUser>> GetSuccessRehashNeededAsLoginResultAsync(TUser user, TTenant tenant = null, bool shouldLockout = false)
  183. {
  184. return await GetFailedPasswordValidationAsLoginResultAsync(user, tenant, shouldLockout);
  185. }
  186. protected virtual async Task<IwbLoginResult<TTenant, TUser>> CreateLoginResultAsync(TUser user, TTenant tenant = null)
  187. {
  188. if (!user.IsActive)
  189. {
  190. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.UserIsNotActive);
  191. }
  192. if (await IsEmailConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsEmailConfirmed)
  193. {
  194. return new IwbLoginResult<TTenant, TUser>(IwbLoginResultType.UserEmailIsNotConfirmed);
  195. }
  196. return new IwbLoginResult<TTenant, TUser>(
  197. tenant,
  198. user,
  199. await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie)
  200. );
  201. }
  202. protected virtual async Task SaveLoginAttempt(IwbLoginResult<TTenant, TUser> loginResult, string tenancyName, string userNameOrEmailAddress)
  203. {
  204. using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
  205. {
  206. var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null;
  207. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  208. {
  209. var loginAttempt = new UserLoginAttempt
  210. {
  211. TenantId = tenantId,
  212. TenancyName = tenancyName,
  213. UserId = loginResult.User != null ? loginResult.User.Id : (long?)null,
  214. UserNameOrEmailAddress = userNameOrEmailAddress,
  215. Result = loginResult.Result,
  216. BrowserInfo = ClientInfoProvider.BrowserInfo,
  217. ClientIpAddress = ClientInfoProvider.ClientIpAddress,
  218. ClientName = ClientInfoProvider.ComputerName,
  219. };
  220. await UserLoginAttemptRepository.InsertAsync(loginAttempt);
  221. await UnitOfWorkManager.Current.SaveChangesAsync();
  222. await uow.CompleteAsync();
  223. }
  224. }
  225. }
  226. protected virtual async Task<bool> TryLockOutAsync(int? tenantId, long userId)
  227. {
  228. using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
  229. {
  230. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  231. {
  232. (await UserManager.AccessFailedAsync(userId)).CheckErrors();
  233. var isLockOut = await UserManager.IsLockedOutAsync(userId);
  234. await UnitOfWorkManager.Current.SaveChangesAsync();
  235. await uow.CompleteAsync();
  236. return isLockOut;
  237. }
  238. }
  239. }
  240. protected virtual async Task<bool> TryLoginFromExternalAuthenticationSources(string userNameOrEmailAddress, string plainPassword, TTenant tenant)
  241. {
  242. if (!UserManagementConfig.ExternalAuthenticationSources.Any())
  243. {
  244. return false;
  245. }
  246. foreach (var sourceType in UserManagementConfig.ExternalAuthenticationSources)
  247. {
  248. using (var source = IocResolver.ResolveAsDisposable<IExternalAuthenticationSource<TTenant, TUser>>(sourceType))
  249. {
  250. if (await source.Object.TryAuthenticateAsync(userNameOrEmailAddress, plainPassword, tenant))
  251. {
  252. var tenantId = tenant == null ? (int?)null : tenant.Id;
  253. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  254. {
  255. var user = await UserManager.IwbStore.FindByNameOrEmailOrMobileAsync(tenantId, userNameOrEmailAddress);
  256. if (user == null)
  257. {
  258. user = await source.Object.CreateUserAsync(userNameOrEmailAddress, tenant);
  259. user.TenantId = tenantId;
  260. user.AuthenticationSource = source.Object.Name;
  261. user.Password = UserManager.PasswordHasher.HashPassword(Guid.NewGuid().ToString("N").Left(16)); //Setting a random password since it will not be used
  262. user.SetNormalizedNames();
  263. if (user.Roles == null)
  264. {
  265. user.Roles = new List<UserRole>();
  266. foreach (var defaultRole in RoleManager.Roles.Where(r => r.TenantId == tenantId && r.IsDefault).ToList())
  267. {
  268. user.Roles.Add(new UserRole(tenantId, user.Id, defaultRole.Id));
  269. }
  270. }
  271. await UserManager.IwbStore.CreateAsync(user);
  272. }
  273. else
  274. {
  275. await source.Object.UpdateUserAsync(user, tenant);
  276. user.AuthenticationSource = source.Object.Name;
  277. await UserManager.IwbStore.UpdateAsync(user);
  278. }
  279. await UnitOfWorkManager.Current.SaveChangesAsync();
  280. return true;
  281. }
  282. }
  283. }
  284. }
  285. return false;
  286. }
  287. protected virtual async Task<TTenant> GetDefaultTenantAsync()
  288. {
  289. var tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == TenantBase.DefaultTenantName);
  290. if (tenant == null)
  291. {
  292. throw new AbpException("There should be a 'Default' tenant if multi-tenancy is disabled!");
  293. }
  294. return tenant;
  295. }
  296. protected virtual async Task<bool> IsEmailConfirmationRequiredForLoginAsync(int? tenantId)
  297. {
  298. if (tenantId.HasValue)
  299. {
  300. return await SettingManager.GetSettingValueForTenantAsync<bool>(IwbZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin, tenantId.Value);
  301. }
  302. return await SettingManager.GetSettingValueForApplicationAsync<bool>(IwbZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin);
  303. }
  304. }
  305. }