VzLogInManager.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #nullable enable
  2. using System.Security.Claims;
  3. using System.Transactions;
  4. using Abp;
  5. using Abp.Auditing;
  6. using Abp.Configuration;
  7. using Abp.Configuration.Startup;
  8. using Abp.Dependency;
  9. using Abp.Domain.Repositories;
  10. using Abp.Domain.Uow;
  11. using Abp.Extensions;
  12. using Microsoft.AspNetCore.Identity;
  13. using VberZero.Authorization.Roles;
  14. using VberZero.Authorization.Users;
  15. using VberZero.BaseSystem.MultiTenancy;
  16. using VberZero.BaseSystem.Roles;
  17. using VberZero.BaseSystem.Users;
  18. using VberZero.Configuration;
  19. using VberZero.IdentityFramework;
  20. using VberZero.Settings;
  21. namespace VberZero.Authorization;
  22. public class VzLogInManager : ITransientDependency
  23. {
  24. public IClientInfoProvider ClientInfoProvider { get; set; }
  25. protected IMultiTenancyConfig MultiTenancyConfig { get; }
  26. protected IRepository<Tenant?> TenantRepository { get; }
  27. protected IUnitOfWorkManager UnitOfWorkManager { get; }
  28. protected VzUserManager UserManager { get; }
  29. protected ISettingManager SettingManager { get; }
  30. protected IRepository<UserLoginAttempt, long> UserLoginAttemptRepository { get; }
  31. protected IUserManagementConfig UserManagementConfig { get; }
  32. protected IIocResolver IocResolver { get; }
  33. protected VzRoleManager RoleManager { get; }
  34. private readonly IPasswordHasher<User> _passwordHasher;
  35. private readonly UserClaimsPrincipalFactory<User, Role> _claimsPrincipalFactory;
  36. public VzLogInManager(
  37. VzUserManager userManager,
  38. IMultiTenancyConfig multiTenancyConfig,
  39. IRepository<Tenant?> tenantRepository,
  40. IUnitOfWorkManager unitOfWorkManager,
  41. ISettingManager settingManager,
  42. IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
  43. IUserManagementConfig userManagementConfig,
  44. IIocResolver iocResolver,
  45. IPasswordHasher<User> passwordHasher,
  46. VzRoleManager roleManager,
  47. UserClaimsPrincipalFactory<User, Role> claimsPrincipalFactory)
  48. {
  49. _passwordHasher = passwordHasher;
  50. _claimsPrincipalFactory = claimsPrincipalFactory;
  51. MultiTenancyConfig = multiTenancyConfig;
  52. TenantRepository = tenantRepository;
  53. UnitOfWorkManager = unitOfWorkManager;
  54. SettingManager = settingManager;
  55. UserLoginAttemptRepository = userLoginAttemptRepository;
  56. UserManagementConfig = userManagementConfig;
  57. IocResolver = iocResolver;
  58. RoleManager = roleManager;
  59. UserManager = userManager;
  60. ClientInfoProvider = NullClientInfoProvider.Instance;
  61. }
  62. public virtual async Task<AbpLoginResult> LoginAsync(UserLoginInfo login, string? tenancyName = null)
  63. {
  64. return await UnitOfWorkManager.WithUnitOfWorkAsync(async () =>
  65. {
  66. var result = await LoginAsyncInternal(login, tenancyName);
  67. await SaveLoginAttemptAsync(result, tenancyName, login.ProviderKey + "@" + login.LoginProvider);
  68. return result;
  69. });
  70. }
  71. protected virtual async Task<AbpLoginResult> LoginAsyncInternal(UserLoginInfo login, string? tenancyName)
  72. {
  73. if (login == null || login.LoginProvider.IsNullOrEmpty() || login.ProviderKey.IsNullOrEmpty())
  74. {
  75. throw new ArgumentException("login");
  76. }
  77. //Get and check tenant
  78. Tenant? tenant = null;
  79. if (!MultiTenancyConfig.IsEnabled)
  80. {
  81. tenant = await GetDefaultTenantAsync();
  82. }
  83. else if (!string.IsNullOrWhiteSpace(tenancyName))
  84. {
  85. tenant = await TenantRepository.FirstOrDefaultAsync(t => t!.TenancyName == tenancyName);
  86. if (tenant == null)
  87. {
  88. return new AbpLoginResult(VzLoginResultType.InvalidTenancyName);
  89. }
  90. if (!tenant.IsActive)
  91. {
  92. return new AbpLoginResult(VzLoginResultType.TenantIsNotActive, tenant);
  93. }
  94. }
  95. int? tenantId = tenant?.Id;
  96. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  97. {
  98. var user = await UserManager.FindAsync(tenantId, login);
  99. if (user == null)
  100. {
  101. return new AbpLoginResult(VzLoginResultType.UnknownExternalLogin, tenant);
  102. }
  103. return await CreateLoginResultAsync(user, tenant);
  104. }
  105. }
  106. public virtual async Task<AbpLoginResult> LoginAsync(string userNameOrEmailOrPhone, string plainPassword, string? tenancyName = null, bool shouldLockout = true)
  107. {
  108. return await UnitOfWorkManager.WithUnitOfWorkAsync(async () =>
  109. {
  110. var result = await LoginAsyncInternal(userNameOrEmailOrPhone, plainPassword, tenancyName, shouldLockout);
  111. await SaveLoginAttemptAsync(result, tenancyName, userNameOrEmailOrPhone);
  112. return result;
  113. });
  114. }
  115. protected virtual async Task<AbpLoginResult> LoginAsyncInternal(string userNameOrEmailOrPhone, string plainPassword, string? tenancyName, bool shouldLockout)
  116. {
  117. if (userNameOrEmailOrPhone.IsNullOrEmpty())
  118. {
  119. throw new ArgumentNullException(nameof(userNameOrEmailOrPhone));
  120. }
  121. if (plainPassword.IsNullOrEmpty())
  122. {
  123. throw new ArgumentNullException(nameof(plainPassword));
  124. }
  125. //Get and check tenant
  126. Tenant? tenant = null;
  127. using (UnitOfWorkManager.Current.SetTenantId(null))
  128. {
  129. if (!MultiTenancyConfig.IsEnabled)
  130. {
  131. tenant = await GetDefaultTenantAsync();
  132. }
  133. else if (!string.IsNullOrWhiteSpace(tenancyName))
  134. {
  135. tenant = await TenantRepository.FirstOrDefaultAsync(t => t != null && t.TenancyName == tenancyName);
  136. if (tenant == null)
  137. {
  138. return new AbpLoginResult(VzLoginResultType.InvalidTenancyName);
  139. }
  140. if (!tenant.IsActive)
  141. {
  142. return new AbpLoginResult(VzLoginResultType.TenantIsNotActive, tenant);
  143. }
  144. }
  145. }
  146. var tenantId = tenant?.Id;
  147. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  148. {
  149. await UserManager.InitializeOptionsAsync(tenantId);
  150. //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before UserStore.FindByNameOrEmailAsync
  151. var loggedInFromExternalSource =
  152. await TryLoginFromExternalAuthenticationSourcesAsync(userNameOrEmailOrPhone, plainPassword, tenant);
  153. var user = await UserManager.FindByNameOrEmailOrPhoneAsync(tenantId, userNameOrEmailOrPhone);
  154. if (user == null)
  155. {
  156. return new AbpLoginResult(VzLoginResultType.InvaliduserNameOrEmailOrPhone, tenant);
  157. }
  158. if (await UserManager.IsLockedOutAsync(user))
  159. {
  160. return new AbpLoginResult(VzLoginResultType.LockedOut, tenant, user);
  161. }
  162. if (!loggedInFromExternalSource)
  163. {
  164. if (!await UserManager.CheckPasswordAsync(user, plainPassword))
  165. {
  166. if (shouldLockout)
  167. {
  168. if (await TryLockOutAsync(tenantId, user.Id))
  169. {
  170. return new AbpLoginResult(VzLoginResultType.LockedOut, tenant, user);
  171. }
  172. }
  173. return new AbpLoginResult(VzLoginResultType.InvalidPassword, tenant, user);
  174. }
  175. await UserManager.ResetAccessFailedCountAsync(user);
  176. }
  177. return await CreateLoginResultAsync(user, tenant);
  178. }
  179. }
  180. protected virtual async Task<AbpLoginResult> CreateLoginResultAsync(User user,
  181. Tenant? tenant = null)
  182. {
  183. if (!user.IsActive)
  184. {
  185. return new AbpLoginResult(VzLoginResultType.UserIsNotActive);
  186. }
  187. if (await IsEmailConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsEmailConfirmed)
  188. {
  189. return new AbpLoginResult(VzLoginResultType.UserEmailIsNotConfirmed);
  190. }
  191. if (await IsPhoneConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsPhoneNumberConfirmed)
  192. {
  193. return new AbpLoginResult(VzLoginResultType.UserPhoneNumberIsNotConfirmed);
  194. }
  195. var principal = await _claimsPrincipalFactory.CreateAsync(user);
  196. return new AbpLoginResult(
  197. tenant,
  198. user,
  199. principal.Identity as ClaimsIdentity
  200. );
  201. }
  202. protected virtual async Task SaveLoginAttemptAsync(AbpLoginResult loginResult,
  203. string? tenancyName, string userNameOrEmailOrPhone)
  204. {
  205. using var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress);
  206. var tenantId = loginResult.Tenant?.Id;
  207. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  208. {
  209. var loginAttempt = new UserLoginAttempt
  210. {
  211. TenantId = tenantId,
  212. TenancyName = tenancyName,
  213. UserId = loginResult.User.Id,
  214. UserNameOrEmailOrPhone = userNameOrEmailOrPhone,
  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. protected virtual void SaveLoginAttempt(AbpLoginResult loginResult, string tenancyName,
  226. string userNameOrEmailOrPhone)
  227. {
  228. using var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress);
  229. var tenantId = loginResult.Tenant.Id;
  230. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  231. {
  232. var loginAttempt = new UserLoginAttempt
  233. {
  234. TenantId = tenantId,
  235. TenancyName = tenancyName,
  236. UserId = loginResult.User.Id,
  237. UserNameOrEmailOrPhone = userNameOrEmailOrPhone,
  238. Result = loginResult.Result,
  239. BrowserInfo = ClientInfoProvider.BrowserInfo,
  240. ClientIpAddress = ClientInfoProvider.ClientIpAddress,
  241. ClientName = ClientInfoProvider.ComputerName,
  242. };
  243. UserLoginAttemptRepository.Insert(loginAttempt);
  244. UnitOfWorkManager.Current.SaveChanges();
  245. uow.Complete();
  246. }
  247. }
  248. protected virtual async Task<bool> TryLockOutAsync(int? tenantId, long userId)
  249. {
  250. using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
  251. {
  252. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  253. {
  254. var user = await UserManager.FindByIdAsync(userId.ToString());
  255. (await UserManager.AccessFailedAsync(user)).CheckErrors();
  256. var isLockOut = await UserManager.IsLockedOutAsync(user);
  257. await UnitOfWorkManager.Current.SaveChangesAsync();
  258. await uow.CompleteAsync();
  259. return isLockOut;
  260. }
  261. }
  262. }
  263. protected virtual async Task<bool> TryLoginFromExternalAuthenticationSourcesAsync(string userNameOrEmailOrPhone,
  264. string plainPassword, Tenant? tenant)
  265. {
  266. if (!UserManagementConfig.ExternalAuthenticationSources.Any())
  267. {
  268. return false;
  269. }
  270. foreach (var sourceType in UserManagementConfig.ExternalAuthenticationSources)
  271. {
  272. using (var source =
  273. IocResolver.ResolveAsDisposable<IExternalAuthenticationSource>(sourceType))
  274. {
  275. if (await source.Object.TryAuthenticateAsync(userNameOrEmailOrPhone, plainPassword, tenant))
  276. {
  277. var tenantId = tenant == null ? (int?)null : tenant.Id;
  278. using (UnitOfWorkManager.Current.SetTenantId(tenantId))
  279. {
  280. var user = await UserManager.FindByNameOrEmailOrPhoneAsync(tenantId, userNameOrEmailOrPhone);
  281. if (user == null)
  282. {
  283. user = await source.Object.CreateUserAsync(userNameOrEmailOrPhone, tenant);
  284. user.TenantId = tenantId;
  285. user.AuthenticationSource = source.Object.Name;
  286. user.Password =
  287. _passwordHasher.HashPassword(user,
  288. Guid.NewGuid().ToString("N")
  289. .Left(16)); //Setting a random password since it will not be used
  290. user.SetNormalizedNames();
  291. if (user.Roles == null)
  292. {
  293. user.Roles = new List<UserRole>();
  294. foreach (var defaulRole in RoleManager.Roles
  295. .Where(r => r.TenantId == tenantId && r.IsDefault).ToList())
  296. {
  297. user.Roles.Add(new UserRole(tenantId, user.Id, defaulRole.Id));
  298. }
  299. }
  300. await UserManager.CreateAsync(user);
  301. }
  302. else
  303. {
  304. await source.Object.UpdateUserAsync(user, tenant);
  305. user.AuthenticationSource = source.Object.Name;
  306. await UserManager.UpdateAsync(user);
  307. }
  308. await UnitOfWorkManager.Current.SaveChangesAsync();
  309. return true;
  310. }
  311. }
  312. }
  313. }
  314. return false;
  315. }
  316. protected virtual async Task<Tenant?> GetDefaultTenantAsync()
  317. {
  318. var tenant = await TenantRepository.FirstOrDefaultAsync(
  319. t => t != null && t.TenancyName == Tenant.DefaultTenantName
  320. );
  321. if (tenant == null)
  322. {
  323. throw new AbpException("如果禁用多租户,则应该有一个“默认”租户!");
  324. }
  325. return tenant;
  326. }
  327. protected virtual Tenant GetDefaultTenant()
  328. {
  329. var tenant = TenantRepository.FirstOrDefault(t => t != null && t.TenancyName == Tenant.DefaultTenantName);
  330. if (tenant == null)
  331. {
  332. throw new AbpException("如果禁用多租户,则应该有一个“默认”租户!");
  333. }
  334. return tenant;
  335. }
  336. protected virtual async Task<bool> IsEmailConfirmationRequiredForLoginAsync(int? tenantId)
  337. {
  338. if (tenantId.HasValue)
  339. {
  340. return await SettingManager.GetSettingValueForTenantAsync<bool>(
  341. VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin,
  342. tenantId.Value
  343. );
  344. }
  345. return await SettingManager.GetSettingValueForApplicationAsync<bool>(
  346. VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin
  347. );
  348. }
  349. protected virtual bool IsEmailConfirmationRequiredForLogin(int? tenantId)
  350. {
  351. if (tenantId.HasValue)
  352. {
  353. return SettingManager.GetSettingValueForTenant<bool>(
  354. VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin,
  355. tenantId.Value
  356. );
  357. }
  358. return SettingManager.GetSettingValueForApplication<bool>(
  359. VzSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin
  360. );
  361. }
  362. protected virtual Task<bool> IsPhoneConfirmationRequiredForLoginAsync(int? tenantId)
  363. {
  364. return Task.FromResult(false);
  365. }
  366. protected virtual bool IsPhoneConfirmationRequiredForLogin(int? tenantId)
  367. {
  368. return false;
  369. }
  370. }