using Abp; using Abp.AspNetCore.Mvc.Authorization; using Abp.Configuration; using Abp.Configuration.Startup; using Abp.Domain.Uow; using Abp.Extensions; using Abp.Notifications; using Abp.Threading; using Abp.Timing; using Abp.UI; using Abp.Web.Models; using Abp.Zero.Configuration; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; using System.Security.Claims; using Abp.Auditing; using VberAdmin.Authorization; using VberAdmin.Authorization.Users; using VberAdmin.Controllers; using VberAdmin.Identity; using VberAdmin.MultiTenancy; using VberAdmin.Web.Models.Account; using VberZero.AppService.Authorization; using VberZero.Authorization; using VberZero.Authorization.Users; using VberZero.BaseSystem.MultiTenancy; using VberZero.BaseSystem.Users; using VberZero.MultiTenancy; namespace VberAdmin.Web.Controllers; [DisableAuditing] public class AccountController : VberAdminControllerBase { private readonly UserManager _userManager; private readonly TenantManager _tenantManager; private readonly IMultiTenancyConfig _multiTenancyConfig; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly VzLoginResultTypeHelper _vzLoginResultTypeHelper; private readonly LogInManager _logInManager; private readonly SignInManager _signInManager; private readonly UserRegistrationManager _userRegistrationManager; private readonly ITenantCache _tenantCache; private readonly INotificationPublisher _notificationPublisher; public AccountController( UserManager userManager, IMultiTenancyConfig multiTenancyConfig, TenantManager tenantManager, IUnitOfWorkManager unitOfWorkManager, VzLoginResultTypeHelper vzLoginResultTypeHelper, LogInManager logInManager, SignInManager signInManager, UserRegistrationManager userRegistrationManager, ITenantCache tenantCache, INotificationPublisher notificationPublisher) { _userManager = userManager; _multiTenancyConfig = multiTenancyConfig; _tenantManager = tenantManager; _unitOfWorkManager = unitOfWorkManager; _vzLoginResultTypeHelper = vzLoginResultTypeHelper; _logInManager = logInManager; _signInManager = signInManager; _userRegistrationManager = userRegistrationManager; _tenantCache = tenantCache; _notificationPublisher = notificationPublisher; } #region Login / Logout public ActionResult Login(string userNameOrEmailAddress = "", string returnUrl = "", string successMessage = "") { if (string.IsNullOrWhiteSpace(returnUrl)) { returnUrl = GetAppHomeUrl(); } return View(new LoginFormViewModel { ReturnUrl = returnUrl, IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled, IsSelfRegistrationAllowed = IsSelfRegistrationEnabled(), MultiTenancySide = AbpSession.MultiTenancySide }); } [HttpPost] [UnitOfWork] public virtual async Task Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "") { returnUrl = NormalizeReturnUrl(returnUrl); if (!string.IsNullOrWhiteSpace(returnUrlHash)) { returnUrl = returnUrl + returnUrlHash; } var tenancyName = await _userManager.GetTenancyNameAsync(loginModel.UsernameOrEmailOrPhone); var loginResult = await GetLoginResultAsync(loginModel.UsernameOrEmailOrPhone, loginModel.Password, tenancyName); await _signInManager.SignInAsync(loginResult.Identity, loginModel.RememberMe); await UnitOfWorkManager.Current.SaveChangesAsync(); return Json(new AjaxResponse { TargetUrl = returnUrl }); } public async Task Logout() { await _signInManager.SignOutAsync(); return RedirectToAction("Login"); } private async Task GetLoginResultAsync(string usernameOrEmailOrPhone, string password, string tenancyName) { var loginResult = await _logInManager.LoginAsync(usernameOrEmailOrPhone, password, tenancyName); switch (loginResult.Result) { case VzLoginResultType.Success: return loginResult; default: throw _vzLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailOrPhone, tenancyName); } } #endregion Login / Logout #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] [UnitOfWork] public async Task Register(RegisterViewModel model) { try { ExternalLoginInfo externalLoginInfo = null; if (model.IsExternalLogin) { externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); if (externalLoginInfo == null) { throw new Exception("Can not external login!"); } model.UserName = model.EmailAddress; model.Password = VberZero.BaseSystem.Users.User.CreateRandomPassword(); } else { if (model.UserName.IsNullOrEmpty() || model.Password.IsNullOrEmpty()) { throw new UserFriendlyException(L("FormIsNotValidMessage")); } } var user = await _userRegistrationManager.RegisterAsync( model.Name, model.Surname, model.EmailAddress, model.UserName, model.Password, true // Assumed email address is always confirmed. Change this if you want to implement email confirmation. ); // Getting tenant-specific settings var isEmailConfirmationRequiredForLogin = await SettingManager.GetSettingValueAsync(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin); if (model.IsExternalLogin) { Debug.Assert(externalLoginInfo != null); if (string.Equals(externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email), model.EmailAddress, StringComparison.OrdinalIgnoreCase)) { user.IsEmailConfirmed = true; } user.Logins = new List { new UserLogin { LoginProvider = externalLoginInfo.LoginProvider, ProviderKey = externalLoginInfo.ProviderKey, TenantId = user.TenantId } }; } await _unitOfWorkManager.Current.SaveChangesAsync(); Debug.Assert(user.TenantId != null); var tenant = await _tenantManager.GetByIdAsync(user.TenantId.Value); // Directly login if possible if (user.IsActive && (user.IsEmailConfirmed || !isEmailConfirmationRequiredForLogin)) { AbpLoginResult loginResult; if (externalLoginInfo != null) { loginResult = await _logInManager.LoginAsync(externalLoginInfo, tenant.TenancyName); } else { loginResult = await GetLoginResultAsync(user.UserName, model.Password, tenant.TenancyName); } if (loginResult.Result == VzLoginResultType.Success) { if (loginResult.Identity != null) await _signInManager.SignInAsync(loginResult.Identity, false); return Redirect(GetAppHomeUrl()); } Logger.Warn("New registered user could not be login. This should not be normally. login result: " + loginResult.Result); } return View("RegisterResult", new RegisterResultViewModel { TenancyName = tenant.TenancyName, NameAndSurname = user.Name + " " + user.Surname, UserName = user.UserName, EmailAddress = user.EmailAddress, IsEmailConfirmed = user.IsEmailConfirmed, IsActive = user.IsActive, IsEmailConfirmationRequiredForLogin = isEmailConfirmationRequiredForLogin }); } catch (UserFriendlyException ex) { ViewBag.ErrorMessage = ex.Message; return View("Register", model); } } #endregion Register #region External Login [HttpPost] [ValidateAntiForgeryToken] public ActionResult ExternalLogin(string provider, string returnUrl) { //var redirectUrl = Url.Action( "ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); return Challenge( // TODO: ...? // new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties // { // Items = { { "LoginProvider", provider } }, // RedirectUri = redirectUrl // }, provider ); } [UnitOfWork] public virtual async Task ExternalLoginCallback(string returnUrl, string remoteError = null) { returnUrl = NormalizeReturnUrl(returnUrl); if (remoteError != null) { Logger.Error("Remote Error in ExternalLoginCallback: " + remoteError); throw new UserFriendlyException(L("CouldNotCompleteLoginOperation")); } var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); if (externalLoginInfo == null) { Logger.Warn("Could not get information from external login."); return RedirectToAction(nameof(Login)); } await _signInManager.SignOutAsync(); var tenancyName = GetTenancyNameOrNull(); var loginResult = await _logInManager.LoginAsync(externalLoginInfo, tenancyName); switch (loginResult.Result) { case VzLoginResultType.Success: await _signInManager.SignInAsync(loginResult.Identity, false); return Redirect(returnUrl); case VzLoginResultType.UnknownExternalLogin: return await RegisterForExternalLogin(externalLoginInfo); default: throw _vzLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( loginResult.Result, externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? externalLoginInfo.ProviderKey, tenancyName ); } } private async Task RegisterForExternalLogin(ExternalLoginInfo externalLoginInfo) { var email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email); var nameinfo = ExternalLoginInfoHelper.GetNameAndSurnameFromClaims(externalLoginInfo.Principal.Claims.ToList()); var viewModel = new RegisterViewModel { EmailAddress = email, Name = nameinfo.name, Surname = nameinfo.surname, IsExternalLogin = true, ExternalLoginAuthSchema = externalLoginInfo.LoginProvider }; if (nameinfo.name != null && nameinfo.surname != null && email != null) { return await Register(viewModel); } return RegisterView(viewModel); } [UnitOfWork] protected virtual async Task> FindPossibleTenantsOfUserAsync(UserLoginInfo login) { List 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(); } #endregion External Login #region Helpers public ActionResult RedirectToAppHome() { return RedirectToAction("Index", "Home"); } public string GetAppHomeUrl() { return Url.Action("Index", "Home"); } #endregion Helpers #region Common private string GetTenancyNameOrNull() { if (!AbpSession.TenantId.HasValue) { return null; } return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName; } private string NormalizeReturnUrl(string returnUrl, Func defaultValueBuilder = null) { if (defaultValueBuilder == null) { defaultValueBuilder = GetAppHomeUrl; } if (returnUrl.IsNullOrEmpty()) { return defaultValueBuilder(); } if (Url.IsLocalUrl(returnUrl)) { return returnUrl; } return defaultValueBuilder(); } #endregion Common #region Etc /// /// This is a demo code to demonstrate sending notification to default tenant admin and host admin uers. /// Don't use this code in production !!! /// /// /// [AbpMvcAuthorize] public async Task TestNotification(string message = "") { if (message.IsNullOrEmpty()) { message = "This is a test notification, created at " + Clock.Now; } var defaultTenantAdmin = new UserIdentifier(1, 2); var hostAdmin = new UserIdentifier(null, 1); await _notificationPublisher.PublishAsync( "App.SimpleMessage", new MessageNotificationData(message), severity: NotificationSeverity.Info, userIds: new[] { defaultTenantAdmin, hostAdmin } ); return Content("Sent notification: " + message); } #endregion Etc }