From 8d3c31fbb13c19f1045c2fccc2e34bb2d7906352 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 8 Dec 2011 16:53:28 -0800 Subject: [PATCH] Fixed #315 for realz, actually allowing log on with email. --- .../AuthenticationControllerFacts.cs | 43 ++++++++++++++----- Facts/Services/UsersServiceFacts.cs | 41 +++++++++++++++++- .../Controllers/AuthenticationController.cs | 4 +- Website/RequestModels/SignInRequest.cs | 6 +-- Website/Services/IUserService.cs | 4 +- Website/Services/UserService.cs | 9 ++-- 6 files changed, 83 insertions(+), 24 deletions(-) diff --git a/Facts/Controllers/AuthenticationControllerFacts.cs b/Facts/Controllers/AuthenticationControllerFacts.cs index 25edbf10a0..ebff097306 100644 --- a/Facts/Controllers/AuthenticationControllerFacts.cs +++ b/Facts/Controllers/AuthenticationControllerFacts.cs @@ -24,18 +24,39 @@ public void WillShowTheViewWithErrorsIfTheModelStateIsInvalid() } [Fact] - public void WillLogTheUserOnWhenTheUsernameAndPasswordAreValidAndUserIsConfirmed() + public void CanLogTheUserOnWithUserName() { var formsAuthSvc = new Mock(); var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword("theUsername", "thePassword")) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) .Returns(new User("theUsername", null) { EmailAddress = "confirmed@example.com" }); var controller = CreateController( formsAuthSvc: formsAuthSvc, userSvc: userSvc); controller.LogOn( - new SignInRequest() { UserName = "theUsername", Password = "thePassword" }, + new SignInRequest() { UserNameOrEmail = "theUsername", Password = "thePassword" }, + "theReturnUrl"); + + formsAuthSvc.Verify(x => x.SetAuthCookie( + "theUsername", + true, + null)); + } + + [Fact] + public void CanLogTheUserOnWithEmailAddress() + { + var formsAuthSvc = new Mock(); + var userSvc = new Mock(); + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword("confirmed@example.com", "thePassword")) + .Returns(new User("theUsername", null) { EmailAddress = "confirmed@example.com" }); + var controller = CreateController( + formsAuthSvc: formsAuthSvc, + userSvc: userSvc); + + controller.LogOn( + new SignInRequest() { UserNameOrEmail = "confirmed@example.com", Password = "thePassword" }, "theReturnUrl"); formsAuthSvc.Verify(x => x.SetAuthCookie( @@ -50,14 +71,14 @@ public void WillNotLogTheUserOnWhenTheUsernameAndPasswordAreValidAndUserIsNotCon var formsAuthSvc = new Mock(); formsAuthSvc.Setup(x => x.SetAuthCookie(It.IsAny(), It.IsAny(), null)).Throws(new InvalidOperationException()); var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword("theUsername", "thePassword")) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) .Returns(new User("theUsername", null)); var controller = CreateController( formsAuthSvc: formsAuthSvc, userSvc: userSvc); controller.LogOn( - new SignInRequest() { UserName = "theUsername", Password = "thePassword" }, + new SignInRequest() { UserNameOrEmail = "theUsername", Password = "thePassword" }, "theReturnUrl"); } @@ -66,7 +87,7 @@ public void WillLogTheUserOnWithRolesWhenTheUsernameAndPasswordAreValidAndUserIs { var formsAuthSvc = new Mock(); var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword("theUsername", "thePassword")) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) .Returns(new User("theUsername", null) { Roles = new[] { new Role { Name = "Administrators" } }, @@ -77,7 +98,7 @@ public void WillLogTheUserOnWithRolesWhenTheUsernameAndPasswordAreValidAndUserIs userSvc: userSvc); controller.LogOn( - new SignInRequest() { UserName = "theUsername", Password = "thePassword" }, + new SignInRequest() { UserNameOrEmail = "theUsername", Password = "thePassword" }, "theReturnUrl"); formsAuthSvc.Verify(x => x.SetAuthCookie( @@ -90,7 +111,7 @@ public void WillLogTheUserOnWithRolesWhenTheUsernameAndPasswordAreValidAndUserIs public void WillInvalidateModelStateAndShowTheViewWithErrorsWhenTheUsernameAndPasswordAreNotValid() { var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword(It.IsAny(), It.IsAny())) .Returns((User)null); var controller = CreateController(userSvc: userSvc); @@ -106,7 +127,7 @@ public void WillInvalidateModelStateAndShowTheViewWithErrorsWhenTheUsernameAndPa public void WillRedirectToTheReturnUrl() { var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword(It.IsAny(), It.IsAny())) .Returns(new User("theUsername", null) { EmailAddress = "confirmed@example.com" }); var controller = CreateController( userSvc: userSvc, @@ -140,7 +161,7 @@ public void WillLogTheUserOff() public void WillRedirectToTheReturnUrl() { var userSvc = new Mock(); - userSvc.Setup(x => x.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) + userSvc.Setup(x => x.FindByUsernameOrEmailAddressAndPassword(It.IsAny(), It.IsAny())) .Returns(new User("theUsername", null)); var controller = CreateController( userSvc: userSvc, @@ -182,4 +203,4 @@ public void WillRedirectToTheReturnUrl() return controller.Object; } } -} +} \ No newline at end of file diff --git a/Facts/Services/UsersServiceFacts.cs b/Facts/Services/UsersServiceFacts.cs index 65310bdcbf..69a4da2864 100644 --- a/Facts/Services/UsersServiceFacts.cs +++ b/Facts/Services/UsersServiceFacts.cs @@ -46,7 +46,7 @@ public void WillThrowIfTheEmailAddressIsAlreadyInUse() } [Fact] - public void WillHasThePassword() + public void WillHashThePassword() { var cryptoSvc = new Mock(); cryptoSvc @@ -550,6 +550,43 @@ public void ThrowsArgumentExceptionForNullUser() } } + public class TheFindByUsernameOrEmailAndPasswordMethod + { + [Fact] + public void FindsUsersByUserName() + { + var user = new User { Username = "theUsername", HashedPassword = "thePassword", EmailAddress = "test@example.com" }; + var userRepository = new Mock>(); + userRepository.Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); + + var crypto = new Mock(); + crypto.Setup(c => c.ValidateSaltedHash(It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); + + var service = CreateUsersService(cryptoSvc: crypto, userRepo: userRepository); + + var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword"); + Assert.NotNull(foundByUserName); + Assert.Same(user, foundByUserName); + } + + [Fact] + public void FindsUsersByEmailAddress() + { + var user = new User { Username = "theUsername", HashedPassword = "thePassword", EmailAddress = "test@example.com" }; + var userRepository = new Mock>(); + userRepository.Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); + + var crypto = new Mock(); + crypto.Setup(c => c.ValidateSaltedHash(It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); + + var service = CreateUsersService(cryptoSvc: crypto, userRepo: userRepository); + + var foundByEmailAddress = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); + Assert.NotNull(foundByEmailAddress); + Assert.Same(user, foundByEmailAddress); + } + } + static UserService CreateUsersService( GallerySetting settings = null, Mock cryptoSvc = null, @@ -576,4 +613,4 @@ public void ThrowsArgumentExceptionForNullUser() return userSvc.Object; } } -} +} \ No newline at end of file diff --git a/Website/Controllers/AuthenticationController.cs b/Website/Controllers/AuthenticationController.cs index 02dd9ad51a..35a9477f4a 100644 --- a/Website/Controllers/AuthenticationController.cs +++ b/Website/Controllers/AuthenticationController.cs @@ -33,8 +33,8 @@ public virtual ActionResult LogOn(SignInRequest request, string returnUrl) if (!ModelState.IsValid) return View(); - var user = userSvc.FindByUsernameAndPassword( - request.UserName, + var user = userSvc.FindByUsernameOrEmailAddressAndPassword( + request.UserNameOrEmail, request.Password); if (user == null) diff --git a/Website/RequestModels/SignInRequest.cs b/Website/RequestModels/SignInRequest.cs index a42220e090..7cec3673ad 100644 --- a/Website/RequestModels/SignInRequest.cs +++ b/Website/RequestModels/SignInRequest.cs @@ -5,9 +5,9 @@ namespace NuGetGallery public class SignInRequest { [Required] - [Display(Name = "Username")] - [Hint("Enter your username.")] - public string UserName { get; set; } + [Display(Name = "Username or Email")] + [Hint("Enter your username or email address.")] + public string UserNameOrEmail { get; set; } [Required] [DataType(DataType.Password)] diff --git a/Website/Services/IUserService.cs b/Website/Services/IUserService.cs index beac9ee24d..fe17b8f147 100644 --- a/Website/Services/IUserService.cs +++ b/Website/Services/IUserService.cs @@ -19,8 +19,8 @@ public interface IUserService User FindByUsername(string username); - User FindByUsernameAndPassword( - string username, + User FindByUsernameOrEmailAddressAndPassword( + string usernameOrEmailAddress, string password); string GenerateApiKey(string username); diff --git a/Website/Services/UserService.cs b/Website/Services/UserService.cs index 2d637bca11..04baa1e135 100644 --- a/Website/Services/UserService.cs +++ b/Website/Services/UserService.cs @@ -116,13 +116,14 @@ public virtual User FindByUsername(string username) .SingleOrDefault(); } - public virtual User FindByUsernameAndPassword( - string username, + public virtual User FindByUsernameOrEmailAddressAndPassword( + string usernameOrEmailAddress, string password) { // TODO: validate input - var user = FindByUsername(username); + var user = FindByUsername(usernameOrEmailAddress) + ?? FindByEmailAddress(usernameOrEmailAddress); if (user == null) return null; @@ -149,7 +150,7 @@ public string GenerateApiKey(string username) public bool ChangePassword(string username, string oldPassword, string newPassword) { - var user = FindByUsernameAndPassword(username, oldPassword); + var user = FindByUsernameOrEmailAddressAndPassword(username, oldPassword); if (user == null) { return false;