| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- using System;
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Diagnostics;
- using System.Linq;
- using System.Security.Claims;
- using System.Threading.Tasks;
- using System.Web.Mvc;
- using Abp.Auditing;
- using Abp.Configuration.Startup;
- using Abp.Domain.Uow;
- using Abp.Extensions;
- using Abp.Localization;
- using Abp.Runtime.Session;
- using Abp.Threading;
- using Abp.Timing;
- using Abp.UI;
- using Abp.Web.Models;
- using Abp.Web.Security.AntiForgery;
- using WeApp.Authorization;
- using WeApp.Authorization.Roles;
- using WeApp.Authorization.Users;
- using WeApp.BaseSystem.Sessions.Dto;
- using WeApp.Configuration;
- using WeApp.Controllers.Results;
- using WeApp.MultiTenancy;
- using WeApp.Models;
- using WeApp.Models.Account;
- using IwbZero.Auditing;
- using IwbZero.Authorization.Base;
- using IwbZero.Authorization.Base.Users;
- using IwbZero.Authorization.Users;
- using IwbZero.MultiTenancy;
- using IwbZero.ToolCommon.LogHelpers;
- using Microsoft.AspNet.Identity;
- using Microsoft.AspNet.Identity.Owin;
- using Microsoft.Owin.Security;
- namespace WeApp.Controllers
- {
- [AuditLog("用户账号"), AllowAnonymous]
- public class AccountController : IwbControllerBase
- {
- private readonly TenantManager _tenantManager;
- private readonly UserManager _userManager;
- private readonly RoleManager _roleManager;
- private readonly IUnitOfWorkManager _unitOfWorkManager;
- private readonly IMultiTenancyConfig _multiTenancyConfig;
- private readonly LogInManager _logInManager;
- private readonly ILanguageManager _languageManager;
- private readonly ITenantCache _tenantCache;
- private readonly IAuthenticationManager _authenticationManager;
- private readonly IAuditingHelper _auditingHelper;
- private readonly IAuditInfoProvider _auditInfoProvider;
- public AccountController(
- TenantManager tenantManager,
- UserManager userManager,
- RoleManager roleManager,
- IUnitOfWorkManager unitOfWorkManager,
- IMultiTenancyConfig multiTenancyConfig,
- LogInManager logInManager,
- ILanguageManager languageManager,
- ITenantCache tenantCache,
- IAuthenticationManager authenticationManager, IAuditingHelper auditingHelper, IAuditInfoProvider auditInfoProvider)
- {
- _tenantManager = tenantManager;
- _userManager = userManager;
- _roleManager = roleManager;
- _unitOfWorkManager = unitOfWorkManager;
- _multiTenancyConfig = multiTenancyConfig;
- _logInManager = logInManager;
- _languageManager = languageManager;
- _tenantCache = tenantCache;
- _authenticationManager = authenticationManager;
- _auditingHelper = auditingHelper;
- _auditInfoProvider = auditInfoProvider;
- }
- #region Login / Logout
- [DisableAuditing]
- [AuditLog("登陆")]
- public async Task<ActionResult> Index(string returnUrl = "")
- {
- if (string.IsNullOrWhiteSpace(returnUrl))
- {
- returnUrl = Request.ApplicationPath;
- }
- ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
- TenantLoginInfoDto tenantDto = null;
- List<SelectListItem> tenantList = new List<SelectListItem>();
- if (_multiTenancyConfig.IsEnabled)
- {
- if (AbpSession.TenantId.HasValue)
- {
- var tenant = await _tenantManager.GetByIdAsync(AbpSession.GetTenantId());
- tenant = await GetActiveTenantAsync(tenant.TenancyName);
- tenantDto = ObjectMapper.Map<TenantLoginInfoDto>(tenant);
- }
- var tenants = await _tenantManager.GeTenants();
- tenantList.Add(new SelectListItem() { Value = "", Text = L("NotSelected") });
- foreach (var t in tenants)
- {
- tenantList.Add(new SelectListItem() { Value = t.TenancyName, Text = t.Name });
- }
- }
- ViewBag.TenantList = tenantList;
- return View("Login", new LoginFormViewModel
- {
- Tenant = tenantDto,
- ReturnUrl = returnUrl,
- IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled,
- IsSelfRegistrationAllowed = false,
- MultiTenancySide = AbpSession.MultiTenancySide
- });
- }
- [HttpPost]
- [DisableAuditing]
- [AuditLog("登陆")]
- public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
- {
- var actionStopwatch = Stopwatch.StartNew();
- CheckModelState();
- var loginResult = await GetLoginResultAsync(
- loginModel.UsernameOrEmailAddress,
- loginModel.Password,
- GetTenancyNameOrNull()
- );
- await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe);
- if (string.IsNullOrWhiteSpace(returnUrl))
- {
- returnUrl = Request.ApplicationPath;
- }
- if (!string.IsNullOrWhiteSpace(returnUrlHash))
- {
- returnUrl = returnUrl + returnUrlHash;
- }
- actionStopwatch.Stop();
- await WriteLoginLog(loginResult, actionStopwatch);
- return Json(new AjaxResponse { TargetUrl = returnUrl });
- }
- private async Task<IwbLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
- {
- var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
- switch (loginResult.Result)
- {
- case IwbLoginResultType.Success:
- return loginResult;
- default:
- throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
- }
- }
- private async Task SignInAsync(User user, ClaimsIdentity identity = null, bool rememberMe = false)
- {
- if (identity == null)
- {
- identity = await _userManager.CreateIdentityAsync(user, IwbAuthenticationTypes.ApplicationCookie);
- }
- _authenticationManager.SignOut(IwbAuthenticationTypes.ApplicationCookie);
- // Many browsers do not clean up session cookies when you close them. So the rule of thumb must be:
- // For having a consistent behaviour across all browsers, don't rely solely on browser behaviour for proper clean-up
- // of session cookies. It is safer to use non-session cookies (IsPersistent == true) in bundle with an expiration date.
- // See http://blog.petersondave.com/cookies/Session-Cookies-in-Chrome-Firefox-and-Sitecore/
- if (rememberMe)
- {
- _authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, identity);
- }
- else
- {
- _authenticationManager.SignIn(
- new AuthenticationProperties
- {
- IsPersistent = true,
- ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(int.Parse(System.Configuration.ConfigurationManager.AppSettings["AuthSession.ExpireTimeInMinutes.WhenNotPersistent"] ?? "30"))
- },
- identity);
- }
- }
- private Exception CreateExceptionForFailedLoginAttempt(IwbLoginResultType result, string usernameOrEmailAddress, string tenancyName)
- {
- switch (result)
- {
- case IwbLoginResultType.Success:
- return new ApplicationException("Don't call this method with a success result!");
- case IwbLoginResultType.InvalidUserNameOrEmailAddress:
- case IwbLoginResultType.InvalidPassword:
- return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPassword"));
- case IwbLoginResultType.InvalidTenancyName:
- return new UserFriendlyException(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
- case IwbLoginResultType.TenantIsNotActive:
- return new UserFriendlyException(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
- case IwbLoginResultType.UserIsNotActive:
- return new UserFriendlyException(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
- case IwbLoginResultType.UserEmailIsNotConfirmed:
- return new UserFriendlyException(L("LoginFailed"), "UserEmailIsNotConfirmedAndCanNotLogin");
- case IwbLoginResultType.LockedOut:
- return new UserFriendlyException(L("LoginFailed"), L("UserLockedOutMessage"));
- default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
- Logger.Warn("Unhandled login fail reason: " + result);
- return new UserFriendlyException(L("LoginFailed"));
- }
- }
- public ActionResult Logout()
- {
- _authenticationManager.SignOut();
- return RedirectToAction("Index");
- }
- private async Task WriteLoginLog(IwbLoginResult<Tenant, User> loginResult, Stopwatch actionStopwatch)
- {
- try
- {
- var auditInfo = new AuditInfo
- {
- TenantId = loginResult.User.TenantId,
- UserId = loginResult.User.Id,
- ImpersonatorUserId = AbpSession.ImpersonatorUserId,
- ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,
- ServiceName = "AccountController",
- MethodName = "Login",
- Parameters = "",
- ExecutionTime = Clock.Now,
- ExecutionDuration = Convert.ToInt32(actionStopwatch.Elapsed.TotalMilliseconds)
- };
- _auditInfoProvider.Fill(auditInfo);
- await _auditingHelper.SaveAsync(auditInfo);
- }
- catch (Exception e)
- {
- this.LogError(e);
- }
- }
- #endregion
- #region Register
- public ActionResult Register()
- {
- return RegisterView(new RegisterViewModel());
- }
- private ActionResult RegisterView(RegisterViewModel model)
- {
- ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
- return View("Register", model);
- }
- //private bool IsSelfRegistrationEnabled()
- //{
- // if (!AbpSession.TenantId.HasValue)
- // {
- // return false; //No registration enabled for host users!
- // }
- // return true;
- //}
- [HttpPost]
- public virtual async Task<ActionResult> Register(RegisterViewModel model)
- {
- try
- {
- CheckModelState();
- //Create user
- var user = new User
- {
- Name = model.Name,
- Surname = model.Surname,
- EmailAddress = model.EmailAddress,
- IsActive = true
- };
- //Get external login info if possible
- ExternalLoginInfo externalLoginInfo = null;
- if (model.IsExternalLogin)
- {
- externalLoginInfo = await _authenticationManager.GetExternalLoginInfoAsync();
- if (externalLoginInfo == null)
- {
- throw new ApplicationException("Can not external login!");
- }
- user.Logins = new List<UserLogin>
- {
- new UserLogin
- {
- LoginProvider = externalLoginInfo.Login.LoginProvider,
- ProviderKey = externalLoginInfo.Login.ProviderKey
- }
- };
- if (model.UserName.IsNullOrEmpty())
- {
- model.UserName = model.EmailAddress;
- }
- model.Password = Authorization.Users.User.CreateRandomPassword();
- if (string.Equals(externalLoginInfo.Email, model.EmailAddress, StringComparison.InvariantCultureIgnoreCase))
- {
- user.IsEmailConfirmed = true;
- }
- }
- else
- {
- //Username and Password are required if not external login
- if (model.UserName.IsNullOrEmpty() || model.Password.IsNullOrEmpty())
- {
- throw new UserFriendlyException(L("FormIsNotValidMessage"));
- }
- }
- user.UserName = model.UserName;
- user.Password = new PasswordHasher().HashPassword(model.Password);
- //Switch to the tenant
- _unitOfWorkManager.Current.EnableFilter(AbpDataFilters.MayHaveTenant); //TODO: Needed?
- _unitOfWorkManager.Current.SetTenantId(AbpSession.GetTenantId());
- //Add default roles
- user.Roles = new List<UserRole>();
- foreach (var defaultRole in await _roleManager.Roles.Where(r => r.IsDefault).ToListAsync())
- {
- user.Roles.Add(new UserRole { RoleId = defaultRole.Id });
- }
- //Save user
- CheckErrors(await _userManager.CreateAsync(user));
- await _unitOfWorkManager.Current.SaveChangesAsync();
- //Directly login if possible
- if (user.IsActive)
- {
- IwbLoginResult<Tenant, User> loginResult;
- if (externalLoginInfo != null)
- {
- loginResult = await _logInManager.LoginAsync(externalLoginInfo.Login, GetTenancyNameOrNull());
- }
- else
- {
- loginResult = await GetLoginResultAsync(user.UserName, model.Password, GetTenancyNameOrNull());
- }
- if (loginResult.Result == IwbLoginResultType.Success)
- {
- await SignInAsync(loginResult.User, loginResult.Identity);
- return Redirect(Url.Action("Index", "Home"));
- }
- Logger.Warn("New registered user could not be login. This should not be normally. login result: " + loginResult.Result);
- }
- //If can not login, show a register result page
- return View("RegisterResult", new RegisterResultViewModel
- {
- TenancyName = GetTenancyNameOrNull(),
- NameAndSurname = user.Name + " " + user.Surname,
- UserName = user.UserName,
- EmailAddress = user.EmailAddress,
- IsActive = user.IsActive
- });
- }
- catch (UserFriendlyException ex)
- {
- ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled;
- ViewBag.ErrorMessage = ex.Message;
- return View("Register", model);
- }
- }
- #endregion
- #region External Login
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult ExternalLogin(string provider, string returnUrl)
- {
- return new ChallengeResult(
- provider,
- Url.Action(
- "ExternalLoginCallback",
- "Account",
- new
- {
- ReturnUrl = returnUrl,
- tenancyName = GetTenancyNameOrNull()
- })
- );
- }
- [UnitOfWork]
- [DisableAbpAntiForgeryTokenValidation]
- public virtual async Task<ActionResult> ExternalLoginCallback(string returnUrl, string tenancyName = "")
- {
- var loginInfo = await _authenticationManager.GetExternalLoginInfoAsync();
- if (loginInfo == null)
- {
- return RedirectToAction("Login");
- }
- //Try to find tenancy name
- if (tenancyName.IsNullOrEmpty())
- {
- var tenants = await FindPossibleTenantsOfUserAsync(loginInfo.Login);
- switch (tenants.Count)
- {
- case 0:
- return await RegisterView(loginInfo);
- case 1:
- tenancyName = tenants[0].TenancyName;
- break;
- default:
- return View("TenantSelection", model: new TenantSelectionViewModel
- {
- Action = Url.Action("ExternalLoginCallback", "Account", new { returnUrl }),
- Tenants = ObjectMapper.Map<List<TenantSelectionViewModel.TenantInfo>>(tenants)
- });
- }
- }
- var loginResult = await _logInManager.LoginAsync(loginInfo.Login, tenancyName);
- switch (loginResult.Result)
- {
- case IwbLoginResultType.Success:
- await SignInAsync(loginResult.User, loginResult.Identity);
- if (string.IsNullOrWhiteSpace(returnUrl))
- {
- returnUrl = Url.Action("Index", "Home");
- }
- return Redirect(returnUrl);
- case IwbLoginResultType.UnknownExternalLogin:
- return await RegisterView(loginInfo, tenancyName);
- default:
- throw CreateExceptionForFailedLoginAttempt(loginResult.Result, loginInfo.Email ?? loginInfo.DefaultUserName, tenancyName);
- }
- }
- private async Task<ActionResult> RegisterView(ExternalLoginInfo loginInfo, string tenancyName = null)
- {
- var name = loginInfo.DefaultUserName;
- var surname = loginInfo.DefaultUserName;
- var extractedNameAndSurname = TryExtractNameAndSurnameFromClaims(loginInfo.ExternalIdentity.Claims.ToList(), ref name, ref surname);
- var viewModel = new RegisterViewModel
- {
- EmailAddress = loginInfo.Email,
- Name = name,
- Surname = surname,
- IsExternalLogin = true
- };
- if (!tenancyName.IsNullOrEmpty() && extractedNameAndSurname)
- {
- return await Register(viewModel);
- }
- return RegisterView(viewModel);
- }
- protected virtual async Task<List<Tenant>> FindPossibleTenantsOfUserAsync(UserLoginInfo login)
- {
- List<User> allUsers;
- using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
- {
- allUsers = await _userManager.FindAllAsync(login);
- }
- return allUsers
- .Where(u => u.TenantId != null)
- .Select(u => AsyncHelper.RunSync(() => _tenantManager.FindByIdAsync(u.TenantId.Value)))
- .ToList();
- }
- private static bool TryExtractNameAndSurnameFromClaims(List<Claim> claims, ref string name, ref string surname)
- {
- string foundName = null;
- string foundSurname = null;
- var givennameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName);
- if (givennameClaim != null && !givennameClaim.Value.IsNullOrEmpty())
- {
- foundName = givennameClaim.Value;
- }
- var surnameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname);
- if (surnameClaim != null && !surnameClaim.Value.IsNullOrEmpty())
- {
- foundSurname = surnameClaim.Value;
- }
- if (foundName == null || foundSurname == null)
- {
- var nameClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name);
- if (nameClaim != null)
- {
- var nameSurName = nameClaim.Value;
- if (!nameSurName.IsNullOrEmpty())
- {
- var lastSpaceIndex = nameSurName.LastIndexOf(' ');
- if (lastSpaceIndex < 1 || lastSpaceIndex > (nameSurName.Length - 2))
- {
- foundName = foundSurname = nameSurName;
- }
- else
- {
- foundName = nameSurName.Substring(0, lastSpaceIndex);
- foundSurname = nameSurName.Substring(lastSpaceIndex);
- }
- }
- }
- }
- if (!foundName.IsNullOrEmpty())
- {
- name = foundName;
- }
- if (!foundSurname.IsNullOrEmpty())
- {
- surname = foundSurname;
- }
- return foundName != null && foundSurname != null;
- }
- #endregion
- #region Common private methods
- private async Task<Tenant> GetActiveTenantAsync(string tenancyName)
- {
- var tenant = await _tenantManager.FindByTenancyNameAsync(tenancyName);
- if (tenant == null)
- {
- throw new UserFriendlyException(L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
- }
- if (!tenant.IsActive)
- {
- throw new UserFriendlyException(L("TenantIsNotActive", tenancyName));
- }
- return tenant;
- }
- private string GetTenancyNameOrNull()
- {
- if (!AbpSession.TenantId.HasValue)
- {
- return null;
- }
- return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName;
- }
- #endregion
- #region Common Partial Views
- [ChildActionOnly]
- public PartialViewResult _AccountLanguages()
- {
- var model = new LanguageSelectionViewModel
- {
- CurrentLanguage = _languageManager.CurrentLanguage,
- Languages = _languageManager.GetLanguages().Where(l => !l.IsDisabled).ToList()
- .Where(l => !l.IsDisabled)
- .ToList(),
- CurrentUrl = Request.Path
- };
- return PartialView("_AccountLanguages", model);
- }
- #endregion
- }
- }
|