public void Login_RemeberMe_HappyPath() { AuthenticationController controller; var hasBeenSaved = false; LoginViewModel vm; var utcNow = new DateTime(2012, 08, 31, 0, 0, 0, DateTimeKind.Utc); User user; try { user = this.db.Users.Single(u => u.UserName == "andrerpena"); var mr = new MockRepository(); controller = mr.CreateController<AuthenticationController>( setupNewDb: db2 => db2.SavingChanges += (s, e) => { hasBeenSaved = true; }); controller.UtcNowGetter = () => utcNow; // Creating ViewModel, and setting the ModelState of the controller. vm = new LoginViewModel { UserNameOrEmail = "andrerpena", PracticeIdentifier = "consultoriodrhouse", Password = "******", RememberMe = true, }; Mvc3TestHelper.SetModelStateErrors(controller, vm); } catch (Exception ex) { InconclusiveInit(ex); return; } // Creating a new user without an e-mail. // This must be ok, no exceptions, no validation errors. ActionResult actionResult; { actionResult = controller.Login(vm); } // Assertions. Assert.IsNotNull(actionResult, "The result of the controller method is null."); Assert.IsInstanceOfType(actionResult, typeof(RedirectToRouteResult)); var redirectResult = (RedirectToRouteResult)actionResult; Assert.AreEqual(redirectResult.RouteValues["action"], "Index"); Assert.IsTrue(controller.ModelState.IsValid, "ModelState should be valid."); Assert.IsTrue(hasBeenSaved, "The database should be changed, but it was not."); // Assert user is logged-in. Assert.IsTrue( controller.HttpContext.Response.Cookies.Keys.Cast<string>().Contains(".ASPXAUTH"), "Authentication cookie should be present in the Response."); var authCookie = controller.HttpContext.Response.Cookies[".ASPXAUTH"]; Assert.IsNotNull(authCookie, @"Response.Cookies["".ASPXAUTH""] must not be null."); Assert.IsTrue(authCookie.Expires > utcNow, "Cookie expire date must be set to the future."); var ticket = System.Web.Security.FormsAuthentication.Decrypt(authCookie.Value); Assert.AreEqual("andrerpena", ticket.Name); Assert.IsTrue(ticket.Expiration > utcNow, "Ticket expire date must be set to the future."); Assert.IsTrue(ticket.IsPersistent, "Ticket must be persistent."); var token = SecurityTokenHelper.FromString(ticket.UserData); Assert.AreEqual(user.Id, token.UserData.Id); Assert.AreEqual("André Pena", token.UserData.FullName); Assert.AreEqual("*****@*****.**", token.UserData.Email); Assert.AreEqual("consultoriodrhouse", token.UserData.PracticeIdentifier); Assert.AreEqual(false, token.UserData.IsUsingDefaultPassword); }
/// <summary> /// Logs an user in. /// </summary> /// <param name="cookieCollection"> /// Cookie collection that is going to hold an encrypted cookie with informations about the user. /// </param> /// <param name="loginModel"> /// Model containing login informations such as practice-name, user-name and password. /// </param> /// <param name="dbUserSet"> /// Object set used to get informations about the user. /// No data will be saved to this object set. /// </param> /// <param name="loggedInUser"> /// Out parameter returning the database User object representing the logged in user, only if the /// login succeded. Otherwise null. /// </param> /// <returns>Returns whether the login succeded or not.</returns> public static bool Login(HttpCookieCollection cookieCollection, LoginViewModel loginModel, IObjectSet<User> dbUserSet, out User loggedInUser, DateTime utcNow) { loggedInUser = null; try { string securityToken; loggedInUser = AuthenticateUser(loginModel.UserNameOrEmail, loginModel.Password, loginModel.PracticeIdentifier, dbUserSet, out securityToken); if (loggedInUser != null) { var expiryDate = utcNow.AddYears(1); var ticket = new FormsAuthenticationTicket( 1, loginModel.UserNameOrEmail, utcNow, expiryDate, loginModel.RememberMe, securityToken, FormsAuthentication.FormsCookiePath); var encryptedTicket = FormsAuthentication.Encrypt(ticket); var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { Expires = loginModel.RememberMe ? utcNow.AddYears(1) : DateTime.MinValue }; cookieCollection.Add(cookie); return true; } } catch { // Any excpetion will be ignored here, and the login will just fail. } // add log information about this exception FormsAuthentication.SignOut(); return false; }
public void Login_NotRemeberMe_HappyPath() { AuthenticationController controller; LoginViewModel vm; var utcNow = new DateTime(2012, 08, 31, 0, 0, 0, DateTimeKind.Utc); try { var mr = new MockRepository(); controller = mr.CreateController<AuthenticationController>(); controller.UtcNowGetter = () => utcNow; // Creating ViewModel, and setting the ModelState of the controller. vm = new LoginViewModel { UserNameOrEmail = "andrerpena", PracticeIdentifier = "consultoriodrhouse", Password = "******", RememberMe = false, }; Mvc3TestHelper.SetModelStateErrors(controller, vm); } catch (Exception ex) { InconclusiveInit(ex); return; } // Creating a new user without an e-mail. // This must be ok, no exceptions, no validation errors. { controller.Login(vm); } // Assert user is logged-in. Assert.IsTrue( controller.HttpContext.Response.Cookies.Keys.Cast<string>().Contains(".ASPXAUTH"), "Authentication cookie should be present in the Response."); var authCookie = controller.HttpContext.Response.Cookies[".ASPXAUTH"]; Assert.IsNotNull(authCookie, @"Response.Cookies["".ASPXAUTH""] must not be null."); Assert.IsTrue(authCookie.Expires == DateTime.MinValue, "Cookie expire date must be set to DateTime.MinValue."); var ticket = System.Web.Security.FormsAuthentication.Decrypt(authCookie.Value); Assert.AreEqual("andrerpena", ticket.Name); Assert.IsTrue(ticket.Expiration > utcNow, "Ticket expire date must be set to the future."); Assert.IsFalse(ticket.IsPersistent, "Ticket must not be persistent."); }
public ActionResult Login(LoginViewModel loginModel) { User user; var cookieCollection = this.HttpContext.Response.Cookies; if (!SecurityManager.Login(cookieCollection, loginModel, this.db.Users, out user, this.GetUtcNow())) { this.ModelState.Clear(); this.ModelState.AddModelError(() => loginModel.PracticeIdentifier, "As credenciais informadas não estão corretas."); } if (!this.ModelState.IsValid) { this.ViewBag.LoginFailed = true; return this.View(loginModel); } user.LastActiveOn = this.GetUtcNow(); user.SYS_PasswordAlt = null; // clearing sys password (this password can only be used once) this.db.SaveChanges(); // We only use the returnUrl if it is a valid URL // allowing an invalid URL is a security issue { bool useReturnUrl = false; if (!string.IsNullOrEmpty(loginModel.ReturnUrl)) { try { // extract practice name from returnUrl var routeData = RouteHelper.GetRouteDataByUrl("~" + loginModel.ReturnUrl); if (routeData.Values.ContainsKey("practice")) useReturnUrl = loginModel.PracticeIdentifier == (string)routeData.Values["practice"]; } catch { // the returnUrl must be invalid, let's just ignore it } } if (!useReturnUrl) loginModel.ReturnUrl = null; } if (loginModel.Password == Constants.DEFAULT_PASSWORD) { return this.RedirectToAction("ChangePassword", "Users", new { area = "App", practice = loginModel.PracticeIdentifier }); } else if (!string.IsNullOrWhiteSpace(loginModel.ReturnUrl)) { return this.Redirect(loginModel.ReturnUrl); } else { // if the user is a doctor, redirect to the specific doctor profile if (user.DoctorId != null) return this.RedirectToAction("Index", "DoctorHome", new { area = "App", practice = loginModel.PracticeIdentifier, doctor = user.Doctor.UrlIdentifier }); return this.RedirectToAction("Index", "PracticeHome", new { area = "App", practice = loginModel.PracticeIdentifier }); } }
public ActionResult Login(string returnUrl) { var viewModel = new LoginViewModel(); if (!string.IsNullOrEmpty(returnUrl)) { try { // extract practice name from returnUrl var routeData = RouteHelper.GetRouteDataByUrl("~" + returnUrl); if (routeData != null && routeData.Values.ContainsKey("practice")) viewModel.PracticeIdentifier = (string)routeData.Values["practice"]; if (this.User.Identity.IsAuthenticated) this.ModelState.AddModelError( "returnUrl", "Suas credenciais não te permitem acessar esta área do Cerebello. " + "Entre com as credenciais apropriadas nos campos abaixo."); else this.ModelState.AddModelError( "returnUrl", "Entre com suas credenciais para poder acessar o Cerebello."); viewModel.ReturnUrl = returnUrl; } catch { // the returnUrl must be invalid, let's just ignore it } } return this.View(viewModel); }
public ActionResult CreateAccount(CreateAccountViewModel registrationData) { // If the user being edited is a medic, then we must check the fields that are required for medics. if (!registrationData.IsDoctor) { // Removing validation error of medic properties, because this user is not a medic. this.ModelState.ClearPropertyErrors(() => registrationData.MedicCRM); this.ModelState.ClearPropertyErrors(() => registrationData.MedicalEntityId); this.ModelState.ClearPropertyErrors(() => registrationData.MedicalSpecialtyId); this.ModelState.ClearPropertyErrors(() => registrationData.MedicalSpecialtyName); this.ModelState.ClearPropertyErrors(() => registrationData.MedicalEntityJurisdiction); } if (this.ModelState.Remove(e => e.ErrorMessage.Contains("requerido"))) this.ModelState.AddModelError("MultipleItems", "É necessário preencher todos os campos."); // Normalizing name properties. if (!string.IsNullOrEmpty(registrationData.PracticeName)) registrationData.PracticeName = Regex.Replace(registrationData.PracticeName, @"\s+", " ").Trim(); if (!string.IsNullOrEmpty(registrationData.FullName)) registrationData.FullName = Regex.Replace(registrationData.FullName, @"\s+", " ").Trim(); var urlPracticeId = StringHelper.GenerateUrlIdentifier(registrationData.PracticeName); var subscription = StringHelper.FirstNonEmpty(registrationData.Subscription, "trial"); if (!validSubscriptions.Contains(subscription) || registrationData.AsTrial == true) subscription = "trial"; // Note: Url identifier for the name of the user, don't need any verification. // The name of the user must be unique inside a practice, not the entire database. var alreadyLoggedUser = this.User as AuthenticatedPrincipal; Practice practiceToReuse = null; if (alreadyLoggedUser != null) { practiceToReuse = this.db.Practices .SingleOrDefault(p => p.UrlIdentifier == alreadyLoggedUser.Profile.PracticeIdentifier && p.AccountContract.IsPartialBillingInfo); } var alreadyExistingPractice = this.db.Practices.SingleOrDefault(p => p.UrlIdentifier == urlPracticeId); if (alreadyExistingPractice != null) { if (alreadyExistingPractice.AccountContract != null && alreadyExistingPractice.AccountContract.IsPartialBillingInfo) practiceToReuse = practiceToReuse ?? alreadyExistingPractice; else { // killing practice that already exists if it expires, and freeing the name for a new practice if (alreadyExistingPractice.AccountExpiryDate < this.GetUtcNow()) { alreadyExistingPractice.AccountDisabled = true; alreadyExistingPractice.UrlIdentifier += " !expired"; // change this, so that a new practice with this name can be created. this.db.SaveChanges(); } else { this.ModelState.AddModelError( () => registrationData.PracticeName, "Nome de consultório já está em uso."); } } } var utcNow = this.GetUtcNow(); // Creating the new user. User user; if (practiceToReuse == null) { var result = SecurityManager.CreateUser(out user, registrationData, this.db.Users, utcNow, null); if (result == CreateUserResult.InvalidUserNameOrPassword) { // Note: nothing to do because user-name and password fields are already validated. } if (result == CreateUserResult.UserNameAlreadyInUse) { this.ModelState.AddModelError( () => registrationData.UserName, // Todo: this message is also used in the App -> UsersController. "O nome de usuário não pode ser registrado pois já está em uso. " + "Note que nomes de usuário diferenciados por acentos, " + "maiúsculas/minúsculas ou por '.', '-' ou '_' não são permitidos." + "(Não é possível cadastrar 'MiguelAngelo' e 'miguel.angelo' no mesmo consultório."); } } else { user = this.db.Users.SingleOrDefault(u => u.Id == alreadyLoggedUser.Profile.Id && u.PracticeId == practiceToReuse.Id); SecurityManager.UpdateUser(user, registrationData, this.db.Users, utcNow); } if (user != null) { string timeZoneId = null; if (registrationData.PracticeProvince != null) timeZoneId = TimeZoneDataAttribute.GetAttributeFromEnumValue((TypeEstadoBrasileiro)registrationData.PracticeProvince.Value).Id; user.Practice = practiceToReuse ?? new Practice(); user.Practice.Name = registrationData.PracticeName; user.Practice.UrlIdentifier = urlPracticeId; user.Practice.CreatedOn = utcNow; user.Practice.WindowsTimeZoneId = timeZoneId; user.Practice.Province = registrationData.PracticeProvince; user.Practice.PhoneMain = registrationData.PracticePhone; // Setting the BirthDate of the user as a person. user.Person.DateOfBirth = PracticeController.ConvertToUtcDateTime(user.Practice, registrationData.DateOfBirth ?? new DateTime()); user.IsOwner = true; if (user.Administrator == null) user.Administrator = new Administrator { }; bool isNewDoctor = false; // when the user is a doctor, we need to fill the properties of the doctor if (registrationData.IsDoctor) { // if user is already a doctor, we just edit the properties // otherwise we create a new doctor instance if (user.Doctor == null) { user.Doctor = new Doctor(); isNewDoctor = true; } user.Doctor.CRM = registrationData.MedicCRM; if (registrationData.MedicalSpecialtyId != null) { var ms = this.db.SYS_MedicalSpecialty .Single(ms1 => ms1.Id == registrationData.MedicalSpecialtyId); user.Doctor.MedicalSpecialtyCode = ms.Code; user.Doctor.MedicalSpecialtyName = ms.Name; } if (registrationData.MedicalEntityId != null) { var me = this.db.SYS_MedicalEntity .Single(me1 => me1.Id == registrationData.MedicalEntityId); user.Doctor.MedicalEntityCode = me.Code; user.Doctor.MedicalEntityName = me.Name; } user.Doctor.MedicalEntityJurisdiction = ((TypeEstadoBrasileiro)registrationData.MedicalEntityJurisdiction).ToString(); // Creating an unique UrlIdentifier for this doctor. // This is the first doctor, so there will be no conflicts. var urlId = UsersController.GetUniqueDoctorUrlId(this.db.Doctors, registrationData.FullName, null); if (urlId == null) { this.ModelState.AddModelError( () => registrationData.FullName, // Todo: this message is also used in the UserController. "Quantidade máxima de homônimos excedida."); } user.Doctor.UrlIdentifier = urlId; } else { // todo: create a program that clears all orphaned Doctor objects user.Doctor = null; } if (practiceToReuse == null) this.db.Users.AddObject(user); if (this.ModelState.IsValid) { MailMessage emailMessageToUser = null; if (subscription == "trial") { // Creating confirmation email, with a token. emailMessageToUser = this.EmailMessageToUser(user, utcNow, subscription == "trial"); // Sending e-mail to tell us the good news. this.SendAccountCreatedSelfEmail(registrationData, user); } // If the ModelState is still valid, then save objects to the database, // and send confirmation email message to the user. using (emailMessageToUser) { // Saving changes to the DB. this.db.SaveChanges(); if (subscription == "trial") { // Creating a new trial account contract. var contract = user.Practice.AccountContract ?? new AccountContract(); contract.Practice = user.Practice; contract.ContractTypeId = (int)ContractTypes.TrialContract; contract.IsTrial = true; contract.IssuanceDate = utcNow; contract.StartDate = utcNow; contract.EndDate = null; // indeterminated contract.CustomText = null; contract.DoctorsLimit = null; contract.PatientsLimit = 50; // fixed limit for trial account // no billings contract.BillingAmount = null; contract.BillingDueDay = null; contract.BillingPaymentMethod = null; contract.BillingPeriodCount = null; contract.BillingPeriodSize = null; contract.BillingPeriodType = null; contract.BillingDiscountAmount = null; user.Practice.AccountExpiryDate = utcNow.AddHours(Constants.MAX_HOURS_TO_VERIFY_TRIAL_ACCOUNT); user.Practice.AccountContract = contract; if (practiceToReuse == null) this.db.AccountContracts.AddObject(contract); } else { // Creating a new account contract, getting info from the subscription string. var dicData = new Dictionary<string, dynamic>(StringComparer.InvariantCultureIgnoreCase) { { "1M", new { Price = Bus.Pro.PRICE_MONTH, PeriodSize = 1 } }, { "3M", new { Price = Bus.Pro.PRICE_QUARTER, PeriodSize = 3 } }, { "6M", new { Price = Bus.Pro.PRICE_SEMESTER, PeriodSize = 6 } }, { "12M", new { Price = Bus.Pro.PRICE_YEAR, PeriodSize = 12 } } }; var data = dicData[subscription]; var contract = user.Practice.AccountContract ?? new AccountContract(); contract.Practice = user.Practice; contract.ContractTypeId = (int)ContractTypes.ProfessionalContract; contract.IsTrial = false; contract.IssuanceDate = utcNow; contract.StartDate = null; // inderterminated (will be defined when user pays) contract.EndDate = null; // indeterminated contract.CustomText = null; contract.DoctorsLimit = null; // unknown at this moment (will be defined after user fills payment info) contract.PatientsLimit = null; // fixed limit for trial account // billings data can be redefined when the user fills payment info // for now these are the default values contract.IsPartialBillingInfo = true; // indicates that the billing info for this contract must be defined by the user contract.BillingAmount = Bus.Pro.PRICE_MONTH * (int)data.PeriodSize; contract.BillingDueDay = null; // payment method has no default (will be defined in the payment-info step) contract.BillingPaymentMethod = null; // payment method has no default (will be defined in the payment-info step) contract.BillingPeriodCount = null; contract.BillingPeriodSize = data.PeriodSize; contract.BillingPeriodType = "M"; contract.BillingDiscountAmount = (Bus.Pro.PRICE_MONTH * (int)data.PeriodSize) - data.Price; user.Practice.AccountExpiryDate = utcNow + Constants.MaxTimeToVerifyProfessionalAccount; user.Practice.AccountContract = contract; if (practiceToReuse == null) this.db.AccountContracts.AddObject(contract); } this.db.SaveChanges(); // if the new user is a doctor, create some other useful things // like some medical-certificates and a default health-insurance if (isNewDoctor) BusHelper.FillNewDoctorUtilityBelt(user.Doctor); if (practiceToReuse == null) { // adding message to the user so that he/she completes his/her profile informations // todo: add complete profile notification // If practice is being reused then notification was already sent. var notificationData = new CompletePracticeInfoNotificationData(); var notificationDataString = new JavaScriptSerializer().Serialize(notificationData); var dbNotification = new Notification { CreatedOn = this.GetUtcNow(), PracticeId = user.PracticeId, Data = notificationDataString, UserToId = user.Id, Type = NotificationConstants.COMPLETE_INFO_NOTIFICATION_TYPE, }; this.db.Notifications.AddObject(dbNotification); NotificationsHub.BroadcastDbNotification(dbNotification, notificationData); } if (practiceToReuse == null) { // If practice is being reused then these values were already set. user.Practice.Owner = user; user.Person.PracticeId = user.PracticeId; user.Administrator.PracticeId = user.PracticeId; if (user.Doctor != null) user.Doctor.PracticeId = user.PracticeId; } this.db.SaveChanges(); // Sending the confirmation e-mail to the new user. // This must be synchronous. // If practice is being reused then an email was already sent. if (practiceToReuse == null && emailMessageToUser != null) this.TrySendEmail(emailMessageToUser); // Log the user in. var loginModel = new LoginViewModel { Password = registrationData.Password, PracticeIdentifier = user.Practice.UrlIdentifier, RememberMe = false, UserNameOrEmail = registrationData.UserName, }; if (!SecurityManager.Login(this.HttpContext.Response.Cookies, loginModel, this.db.Users, out user, this.GetUtcNow())) { throw new Exception("Login cannot fail."); } if (subscription == "trial") return this.RedirectToAction("CreateAccountCompleted", new { practice = user.Practice.UrlIdentifier }); else return this.RedirectToAction("SetAccountPaymentInfo", new { practice = user.Practice.UrlIdentifier }); } } } this.ViewBag.MedicalSpecialtyOptions = this.db.SYS_MedicalSpecialty .ToList() .Select(me => new SelectListItem { Value = me.Id.ToString(), Text = me.Name }) .ToList(); this.ViewBag.MedicalEntityOptions = this.db.SYS_MedicalEntity .ToList() .Select(me => new SelectListItem { Value = me.Id.ToString(), Text = me.Name }) .ToList(); return this.View(registrationData); }
public ActionResult VerifyPracticeAndEmail(VerifyPracticeAndEmailViewModel viewModel) { var utcNow = this.GetUtcNow(); User user = null; // If user is not logged yet, we will use the userName and password to login. if (this.Request.IsAuthenticated) { var authenticatedPrincipal = this.User as AuthenticatedPrincipal; if (authenticatedPrincipal == null) throw new Exception( "HttpContext.User should be a AuthenticatedPrincipal when the user is authenticated"); if (authenticatedPrincipal.Profile.PracticeIdentifier == viewModel.PracticeIdentifier) user = this.db.Users.FirstOrDefault(u => u.Id == authenticatedPrincipal.Profile.Id); } if (user != null || this.Request.HttpMethod == "GET") { this.ModelState.Remove(() => viewModel.PracticeIdentifier); this.ModelState.Remove(() => viewModel.UserNameOrEmail); this.ModelState.Remove(() => viewModel.Password); this.ModelState.Remove(() => viewModel.Token); } if (user == null) { var loginModel = new LoginViewModel { PracticeIdentifier = viewModel.PracticeIdentifier ?? "", UserNameOrEmail = viewModel.UserNameOrEmail ?? "", Password = viewModel.Password ?? "", RememberMe = viewModel.RememberMe, }; var cookieCollection = this.HttpContext.Response.Cookies; if (!this.ModelState.IsValid || !SecurityManager.Login(cookieCollection, loginModel, this.db.Users, out user, this.GetUtcNow())) { this.ViewBag.LoginFailed = true; } else { user.LastActiveOn = this.GetUtcNow(); user.SYS_PasswordAlt = null; this.db.SaveChanges(); if (loginModel.Password == Constants.DEFAULT_PASSWORD) throw new Exception("Cannot create initial user with a default password."); } } var isTokenValid = TokenId.IsValid(viewModel.Token); GLB_Token token = null; if (isTokenValid && user != null) { var tokenId = new TokenId(viewModel.Token); // Getting verification token, using the informations. var tokenName = string.Format("Practice={0}&UserName={1}", viewModel.PracticeIdentifier, user.UserName); token = this.db.GLB_Token.SingleOrDefault(tk => tk.Id == tokenId.Id && tk.Value == tokenId.Value && tk.Type == "VerifyPracticeAndEmail" && tk.Name == tokenName); } var practice = this.db.Practices .SingleOrDefault(p => p.UrlIdentifier == viewModel.PracticeIdentifier); if (token == null) isTokenValid = false; if (practice == null && this.Request.HttpMethod != "GET" && !this.ModelState.HasPropertyErrors(() => viewModel.PracticeIdentifier)) this.ModelState.AddModelError(() => viewModel.PracticeIdentifier, "Consultório não foi achado."); if (token != null && practice != null) { // setting practice verification data if (utcNow <= token.ExpirationDate) { practice.VerificationDate = utcNow; practice.VerificationMethod = "EMAIL"; } else isTokenValid = false; // Destroying token... it has been used with success, and is no longer needed. this.db.GLB_Token.DeleteObject(token); // Saving changes. // Note: even if ModelState.IsValid is false, // we need to save the changes to invalidate token when it expires. this.db.SaveChanges(); } if (!isTokenValid && user != null) { this.ModelState.AddModelError(() => viewModel.Token, "Problema com o token."); } if (this.ModelState.IsValid && user != null && practice != null) { return this.RedirectToAction( "Welcome", "Home", new { area = "", practice = practice.UrlIdentifier }); } viewModel.Password = null; // cannot allow password going to the view. return this.View(viewModel); }