AccountController.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.Entity;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Security.Claims;
  7. using System.Threading.Tasks;
  8. using System.Web.Mvc;
  9. using Abp.Auditing;
  10. using Abp.Configuration.Startup;
  11. using Abp.Domain.Uow;
  12. using Abp.Extensions;
  13. using Abp.Localization;
  14. using Abp.Runtime.Session;
  15. using Abp.Threading;
  16. using Abp.Timing;
  17. using Abp.UI;
  18. using Abp.Web.Models;
  19. using Abp.Web.Security.AntiForgery;
  20. using WeOnlineApp.Authorization;
  21. using WeOnlineApp.Authorization.Roles;
  22. using WeOnlineApp.Authorization.Users;
  23. using WeOnlineApp.BaseSystem.Sessions.Dto;
  24. using WeOnlineApp.Configuration;
  25. using WeOnlineApp.Controllers.Results;
  26. using WeOnlineApp.MultiTenancy;
  27. using WeOnlineApp.Models;
  28. using WeOnlineApp.Models.Account;
  29. using IwbZero.Auditing;
  30. using IwbZero.Authorization.Base;
  31. using IwbZero.Authorization.Base.Users;
  32. using IwbZero.Authorization.Users;
  33. using IwbZero.MultiTenancy;
  34. using IwbZero.ToolCommon.LogHelpers;
  35. using Microsoft.AspNet.Identity;
  36. using Microsoft.AspNet.Identity.Owin;
  37. using Microsoft.Owin.Security;
  38. namespace WeOnlineApp.Controllers
  39. {
  40. [AuditLog("用户账号"), AllowAnonymous]
  41. public class AccountController : IwbControllerBase
  42. {
  43. private readonly TenantManager _tenantManager;
  44. private readonly UserManager _userManager;
  45. private readonly RoleManager _roleManager;
  46. private readonly IUnitOfWorkManager _unitOfWorkManager;
  47. private readonly IMultiTenancyConfig _multiTenancyConfig;
  48. private readonly LogInManager _logInManager;
  49. private readonly ILanguageManager _languageManager;
  50. private readonly ITenantCache _tenantCache;
  51. private readonly IAuthenticationManager _authenticationManager;
  52. private readonly IAuditingHelper _auditingHelper;
  53. private readonly IAuditInfoProvider _auditInfoProvider;
  54. public AccountController(
  55. TenantManager tenantManager,
  56. UserManager userManager,
  57. RoleManager roleManager,
  58. IUnitOfWorkManager unitOfWorkManager,
  59. IMultiTenancyConfig multiTenancyConfig,
  60. LogInManager logInManager,
  61. ILanguageManager languageManager,
  62. ITenantCache tenantCache,
  63. IAuthenticationManager authenticationManager, IAuditingHelper auditingHelper, IAuditInfoProvider auditInfoProvider)
  64. {
  65. _tenantManager = tenantManager;
  66. _userManager = userManager;
  67. _roleManager = roleManager;
  68. _unitOfWorkManager = unitOfWorkManager;
  69. _multiTenancyConfig = multiTenancyConfig;
  70. _logInManager = logInManager;
  71. _languageManager = languageManager;
  72. _tenantCache = tenantCache;
  73. _authenticationManager = authenticationManager;
  74. _auditingHelper = auditingHelper;
  75. _auditInfoProvider = auditInfoProvider;
  76. }
  77. #region Login / Logout
  78. [DisableAuditing]
  79. [AuditLog("登陆")]
  80. public async Task<ActionResult> Index(string returnUrl = "")
  81. {
  82. if (string.IsNullOrWhiteSpace(returnUrl))
  83. {
  84. returnUrl = Request.ApplicationPath;
  85. }
  86. ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
  87. TenantLoginInfoDto tenantDto = null;
  88. List<SelectListItem> tenantList = new List<SelectListItem>();
  89. if (_multiTenancyConfig.IsEnabled)
  90. {
  91. if (AbpSession.TenantId.HasValue)
  92. {
  93. var tenant = await _tenantManager.GetByIdAsync(AbpSession.GetTenantId());
  94. tenant = await GetActiveTenantAsync(tenant.TenancyName);
  95. tenantDto = ObjectMapper.Map<TenantLoginInfoDto>(tenant);
  96. }
  97. var tenants = await _tenantManager.GeTenants();
  98. tenantList.Add(new SelectListItem() { Value = "", Text = L("NotSelected") });
  99. foreach (var t in tenants)
  100. {
  101. tenantList.Add(new SelectListItem() { Value = t.TenancyName, Text = t.Name });
  102. }
  103. }
  104. ViewBag.TenantList = tenantList;
  105. return View("Login", new LoginFormViewModel
  106. {
  107. Tenant = tenantDto,
  108. ReturnUrl = returnUrl,
  109. IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled,
  110. IsSelfRegistrationAllowed = false,
  111. MultiTenancySide = AbpSession.MultiTenancySide
  112. });
  113. }
  114. [HttpPost]
  115. [DisableAuditing]
  116. [AuditLog("登陆")]
  117. public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
  118. {
  119. var actionStopwatch = Stopwatch.StartNew();
  120. CheckModelState();
  121. var loginResult = await GetLoginResultAsync(
  122. loginModel.UsernameOrEmailAddress,
  123. loginModel.Password,
  124. GetTenancyNameOrNull()
  125. );
  126. await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe);
  127. if (string.IsNullOrWhiteSpace(returnUrl))
  128. {
  129. returnUrl = Request.ApplicationPath;
  130. }
  131. if (!string.IsNullOrWhiteSpace(returnUrlHash))
  132. {
  133. returnUrl = returnUrl + returnUrlHash;
  134. }
  135. actionStopwatch.Stop();
  136. await WriteLoginLog(loginResult, actionStopwatch);
  137. return Json(new AjaxResponse { TargetUrl = returnUrl });
  138. }
  139. private async Task<IwbLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
  140. {
  141. var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
  142. switch (loginResult.Result)
  143. {
  144. case IwbLoginResultType.Success:
  145. return loginResult;
  146. default:
  147. throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
  148. }
  149. }
  150. private async Task SignInAsync(User user, ClaimsIdentity identity = null, bool rememberMe = false)
  151. {
  152. if (identity == null)
  153. {
  154. identity = await _userManager.CreateIdentityAsync(user, IwbAuthenticationTypes.ApplicationCookie);
  155. }
  156. _authenticationManager.SignOut(IwbAuthenticationTypes.ApplicationCookie);
  157. // Many browsers do not clean up session cookies when you close them. So the rule of thumb must be:
  158. // For having a consistent behaviour across all browsers, don't rely solely on browser behaviour for proper clean-up
  159. // of session cookies. It is safer to use non-session cookies (IsPersistent == true) in bundle with an expiration date.
  160. // See http://blog.petersondave.com/cookies/Session-Cookies-in-Chrome-Firefox-and-Sitecore/
  161. if (rememberMe)
  162. {
  163. _authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, identity);
  164. }
  165. else
  166. {
  167. _authenticationManager.SignIn(
  168. new AuthenticationProperties
  169. {
  170. IsPersistent = true,
  171. ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(int.Parse(System.Configuration.ConfigurationManager.AppSettings["AuthSession.ExpireTimeInMinutes.WhenNotPersistent"] ?? "30"))
  172. },
  173. identity);
  174. }
  175. }
  176. private Exception CreateExceptionForFailedLoginAttempt(IwbLoginResultType result, string usernameOrEmailAddress, string tenancyName)
  177. {
  178. switch (result)
  179. {
  180. case IwbLoginResultType.Success:
  181. return new ApplicationException("Don't call this method with a success result!");
  182. case IwbLoginResultType.InvalidUserNameOrEmailAddress:
  183. case IwbLoginResultType.InvalidPassword:
  184. return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPassword"));
  185. case IwbLoginResultType.InvalidTenancyName:
  186. return new UserFriendlyException(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
  187. case IwbLoginResultType.TenantIsNotActive:
  188. return new UserFriendlyException(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
  189. case IwbLoginResultType.UserIsNotActive:
  190. return new UserFriendlyException(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
  191. case IwbLoginResultType.UserEmailIsNotConfirmed:
  192. return new UserFriendlyException(L("LoginFailed"), "UserEmailIsNotConfirmedAndCanNotLogin");
  193. case IwbLoginResultType.LockedOut:
  194. return new UserFriendlyException(L("LoginFailed"), L("UserLockedOutMessage"));
  195. default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
  196. Logger.Warn("Unhandled login fail reason: " + result);
  197. return new UserFriendlyException(L("LoginFailed"));
  198. }
  199. }
  200. public ActionResult Logout()
  201. {
  202. _authenticationManager.SignOut();
  203. return RedirectToAction("Index");
  204. }
  205. private async Task WriteLoginLog(IwbLoginResult<Tenant, User> loginResult, Stopwatch actionStopwatch)
  206. {
  207. try
  208. {
  209. var auditInfo = new AuditInfo
  210. {
  211. TenantId = loginResult.User.TenantId,
  212. UserId = loginResult.User.Id,
  213. ImpersonatorUserId = AbpSession.ImpersonatorUserId,
  214. ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
  215. ServiceName = "AccountController",
  216. MethodName = "Login",
  217. Parameters = "",
  218. ExecutionTime = Clock.Now,
  219. ExecutionDuration = Convert.ToInt32(actionStopwatch.Elapsed.TotalMilliseconds)
  220. };
  221. _auditInfoProvider.Fill(auditInfo);
  222. await _auditingHelper.SaveAsync(auditInfo);
  223. }
  224. catch (Exception e)
  225. {
  226. this.LogError(e);
  227. }
  228. }
  229. #endregion
  230. #region Register
  231. public ActionResult Register()
  232. {
  233. return RegisterView(new RegisterViewModel());
  234. }
  235. private ActionResult RegisterView(RegisterViewModel model)
  236. {
  237. ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
  238. return View("Register", model);
  239. }
  240. //private bool IsSelfRegistrationEnabled()
  241. //{
  242. // if (!AbpSession.TenantId.HasValue)
  243. // {
  244. // return false; //No registration enabled for host users!
  245. // }
  246. // return true;
  247. //}
  248. [HttpPost]
  249. public virtual async Task<ActionResult> Register(RegisterViewModel model)
  250. {
  251. try
  252. {
  253. CheckModelState();
  254. //Create user
  255. var user = new User
  256. {
  257. Name = model.Name,
  258. Surname = model.Surname,
  259. EmailAddress = model.EmailAddress,
  260. IsActive = true
  261. };
  262. //Get external login info if possible
  263. ExternalLoginInfo externalLoginInfo = null;
  264. if (model.IsExternalLogin)
  265. {
  266. externalLoginInfo = await _authenticationManager.GetExternalLoginInfoAsync();
  267. if (externalLoginInfo == null)
  268. {
  269. throw new ApplicationException("Can not external login!");
  270. }
  271. user.Logins = new List<UserLogin>
  272. {
  273. new UserLogin
  274. {
  275. LoginProvider = externalLoginInfo.Login.LoginProvider,
  276. ProviderKey = externalLoginInfo.Login.ProviderKey
  277. }
  278. };
  279. if (model.UserName.IsNullOrEmpty())
  280. {
  281. model.UserName = model.EmailAddress;
  282. }
  283. model.Password = Authorization.Users.User.CreateRandomPassword();
  284. if (string.Equals(externalLoginInfo.Email, model.EmailAddress, StringComparison.InvariantCultureIgnoreCase))
  285. {
  286. user.IsEmailConfirmed = true;
  287. }
  288. }
  289. else
  290. {
  291. //Username and Password are required if not external login
  292. if (model.UserName.IsNullOrEmpty() || model.Password.IsNullOrEmpty())
  293. {
  294. throw new UserFriendlyException(L("FormIsNotValidMessage"));
  295. }
  296. }
  297. user.UserName = model.UserName;
  298. user.Password = new PasswordHasher().HashPassword(model.Password);
  299. //Switch to the tenant
  300. _unitOfWorkManager.Current.EnableFilter(AbpDataFilters.MayHaveTenant); //TODO: Needed?
  301. _unitOfWorkManager.Current.SetTenantId(AbpSession.GetTenantId());
  302. //Add default roles
  303. user.Roles = new List<UserRole>();
  304. foreach (var defaultRole in await _roleManager.Roles.Where(r => r.IsDefault).ToListAsync())
  305. {
  306. user.Roles.Add(new UserRole { RoleId = defaultRole.Id });
  307. }
  308. //Save user
  309. CheckErrors(await _userManager.CreateAsync(user));
  310. await _unitOfWorkManager.Current.SaveChangesAsync();
  311. //Directly login if possible
  312. if (user.IsActive)
  313. {
  314. IwbLoginResult<Tenant, User> loginResult;
  315. if (externalLoginInfo != null)
  316. {
  317. loginResult = await _logInManager.LoginAsync(externalLoginInfo.Login, GetTenancyNameOrNull());
  318. }
  319. else
  320. {
  321. loginResult = await GetLoginResultAsync(user.UserName, model.Password, GetTenancyNameOrNull());
  322. }
  323. if (loginResult.Result == IwbLoginResultType.Success)
  324. {
  325. await SignInAsync(loginResult.User, loginResult.Identity);
  326. return Redirect(Url.Action("Index", "Home"));
  327. }
  328. Logger.Warn("New registered user could not be login. This should not be normally. login result: " + loginResult.Result);
  329. }
  330. //If can not login, show a register result page
  331. return View("RegisterResult", new RegisterResultViewModel
  332. {
  333. TenancyName = GetTenancyNameOrNull(),
  334. NameAndSurname = user.Name + " " + user.Surname,
  335. UserName = user.UserName,
  336. EmailAddress = user.EmailAddress,
  337. IsActive = user.IsActive
  338. });
  339. }
  340. catch (UserFriendlyException ex)
  341. {
  342. ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
  343. ViewBag.ErrorMessage = ex.Message;
  344. return View("Register", model);
  345. }
  346. }
  347. #endregion
  348. #region External Login
  349. [HttpPost]
  350. [ValidateAntiForgeryToken]
  351. public ActionResult ExternalLogin(string provider, string returnUrl)
  352. {
  353. return new ChallengeResult(
  354. provider,
  355. Url.Action(
  356. "ExternalLoginCallback",
  357. "Account",
  358. new
  359. {
  360. ReturnUrl = returnUrl,
  361. tenancyName = GetTenancyNameOrNull()
  362. })
  363. );
  364. }
  365. [UnitOfWork]
  366. [DisableAbpAntiForgeryTokenValidation]
  367. public virtual async Task<ActionResult> ExternalLoginCallback(string returnUrl, string tenancyName = "")
  368. {
  369. var loginInfo = await _authenticationManager.GetExternalLoginInfoAsync();
  370. if (loginInfo == null)
  371. {
  372. return RedirectToAction("Login");
  373. }
  374. //Try to find tenancy name
  375. if (tenancyName.IsNullOrEmpty())
  376. {
  377. var tenants = await FindPossibleTenantsOfUserAsync(loginInfo.Login);
  378. switch (tenants.Count)
  379. {
  380. case 0:
  381. return await RegisterView(loginInfo);
  382. case 1:
  383. tenancyName = tenants[0].TenancyName;
  384. break;
  385. default:
  386. return View("TenantSelection", model: new TenantSelectionViewModel
  387. {
  388. Action = Url.Action("ExternalLoginCallback", "Account", new { returnUrl }),
  389. Tenants = ObjectMapper.Map<List<TenantSelectionViewModel.TenantInfo>>(tenants)
  390. });
  391. }
  392. }
  393. var loginResult = await _logInManager.LoginAsync(loginInfo.Login, tenancyName);
  394. switch (loginResult.Result)
  395. {
  396. case IwbLoginResultType.Success:
  397. await SignInAsync(loginResult.User, loginResult.Identity);
  398. if (string.IsNullOrWhiteSpace(returnUrl))
  399. {
  400. returnUrl = Url.Action("Index", "Home");
  401. }
  402. return Redirect(returnUrl);
  403. case IwbLoginResultType.UnknownExternalLogin:
  404. return await RegisterView(loginInfo, tenancyName);
  405. default:
  406. throw CreateExceptionForFailedLoginAttempt(loginResult.Result, loginInfo.Email ?? loginInfo.DefaultUserName, tenancyName);
  407. }
  408. }
  409. private async Task<ActionResult> RegisterView(ExternalLoginInfo loginInfo, string tenancyName = null)
  410. {
  411. var name = loginInfo.DefaultUserName;
  412. var surname = loginInfo.DefaultUserName;
  413. var extractedNameAndSurname = TryExtractNameAndSurnameFromClaims(loginInfo.ExternalIdentity.Claims.ToList(), ref name, ref surname);
  414. var viewModel = new RegisterViewModel
  415. {
  416. EmailAddress = loginInfo.Email,
  417. Name = name,
  418. Surname = surname,
  419. IsExternalLogin = true
  420. };
  421. if (!tenancyName.IsNullOrEmpty() && extractedNameAndSurname)
  422. {
  423. return await Register(viewModel);
  424. }
  425. return RegisterView(viewModel);
  426. }
  427. protected virtual async Task<List<Tenant>> FindPossibleTenantsOfUserAsync(UserLoginInfo login)
  428. {
  429. List<User> allUsers;
  430. using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
  431. {
  432. allUsers = await _userManager.FindAllAsync(login);
  433. }
  434. return allUsers
  435. .Where(u => u.TenantId != null)
  436. .Select(u => AsyncHelper.RunSync(() => _tenantManager.FindByIdAsync(u.TenantId.Value)))
  437. .ToList();
  438. }
  439. private static bool TryExtractNameAndSurnameFromClaims(List<Claim> claims, ref string name, ref string surname)
  440. {
  441. string foundName = null;
  442. string foundSurname = null;
  443. var givennameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName);
  444. if (givennameClaim != null && !givennameClaim.Value.IsNullOrEmpty())
  445. {
  446. foundName = givennameClaim.Value;
  447. }
  448. var surnameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname);
  449. if (surnameClaim != null && !surnameClaim.Value.IsNullOrEmpty())
  450. {
  451. foundSurname = surnameClaim.Value;
  452. }
  453. if (foundName == null || foundSurname == null)
  454. {
  455. var nameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name);
  456. if (nameClaim != null)
  457. {
  458. var nameSurName = nameClaim.Value;
  459. if (!nameSurName.IsNullOrEmpty())
  460. {
  461. var lastSpaceIndex = nameSurName.LastIndexOf(' ');
  462. if (lastSpaceIndex < 1 || lastSpaceIndex > (nameSurName.Length - 2))
  463. {
  464. foundName = foundSurname = nameSurName;
  465. }
  466. else
  467. {
  468. foundName = nameSurName.Substring(0, lastSpaceIndex);
  469. foundSurname = nameSurName.Substring(lastSpaceIndex);
  470. }
  471. }
  472. }
  473. }
  474. if (!foundName.IsNullOrEmpty())
  475. {
  476. name = foundName;
  477. }
  478. if (!foundSurname.IsNullOrEmpty())
  479. {
  480. surname = foundSurname;
  481. }
  482. return foundName != null && foundSurname != null;
  483. }
  484. #endregion
  485. #region Common private methods
  486. private async Task<Tenant> GetActiveTenantAsync(string tenancyName)
  487. {
  488. var tenant = await _tenantManager.FindByTenancyNameAsync(tenancyName);
  489. if (tenant == null)
  490. {
  491. throw new UserFriendlyException(L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
  492. }
  493. if (!tenant.IsActive)
  494. {
  495. throw new UserFriendlyException(L("TenantIsNotActive", tenancyName));
  496. }
  497. return tenant;
  498. }
  499. private string GetTenancyNameOrNull()
  500. {
  501. if (!AbpSession.TenantId.HasValue)
  502. {
  503. return null;
  504. }
  505. return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName;
  506. }
  507. #endregion
  508. #region Common Partial Views
  509. [ChildActionOnly]
  510. public PartialViewResult _AccountLanguages()
  511. {
  512. var model = new LanguageSelectionViewModel
  513. {
  514. CurrentLanguage = _languageManager.CurrentLanguage,
  515. Languages = _languageManager.GetLanguages().Where(l => !l.IsDisabled).ToList()
  516. .Where(l => !l.IsDisabled)
  517. .ToList(),
  518. CurrentUrl = Request.Path
  519. };
  520. return PartialView("_AccountLanguages", model);
  521. }
  522. #endregion
  523. }
  524. }