diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 563d9b80f8..4c373b2bc8 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,12 +1,19 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Reflection; using System.Security.Cryptography; +using System.Threading.Tasks; using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.Owin; using Moq; using Newtonsoft.Json; using NUnit.Framework; @@ -19,12 +26,10 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.ControllerTesting; using Umbraco.Tests.TestHelpers.Entities; @@ -39,6 +44,9 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Web.Routing; using Umbraco.Core.Media; +using Umbraco.Net; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; namespace Umbraco.Tests.Web.Controllers { @@ -62,7 +70,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task Save_User() + public async Task Save_User() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -149,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_Empty() + public async Task GetPagedUsers_Empty() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -183,7 +191,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_10() + public async Task GetPagedUsers_10() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -227,7 +235,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_Fips() + public async Task GetPagedUsers_Fips() { await RunFipsTest("GetPagedUsers", mock => { @@ -246,7 +254,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetById_Fips() + public async Task GetById_Fips() { const int mockUserId = 1234; var user = MockedUser.CreateUser(); @@ -264,7 +272,7 @@ namespace Umbraco.Tests.Web.Controllers } - private async System.Threading.Tasks.Task RunFipsTest(string action, Action> userServiceSetup, + private async Task RunFipsTest(string action, Action> userServiceSetup, Action> verification, object routeDefaults = null, string url = null) { @@ -324,5 +332,185 @@ namespace Umbraco.Tests.Web.Controllers } } } + + [Test] + public async Task PostUnlockUsers_When_UserIds_Not_Supplied_Expect_Ok_Response() + { + var usersController = CreateSut(); + + usersController.Request = new HttpRequestMessage(); + + var response = await usersController.PostUnlockUsers(new int[0]); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [Test] + public void PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny())) + .ReturnsAsync((BackOfficeIdentityUser) null); + + Assert.ThrowsAsync(async () => await usersController.PostUnlockUsers(new[] {1})); + } + + [Test] + public async Task PostUnlockUsers_When_User_Lockout_Update_Fails_Expect_Failure_Response() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + const string expectedMessage = "identity error!"; + var user = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + + mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny())) + .ReturnsAsync(user); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny())) + .ReturnsAsync(IdentityResult.Failed(new IdentityError {Description = expectedMessage})); + + var response = await usersController.PostUnlockUsers(new[] { 1 }); + + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + Assert.True(response.Headers.TryGetValues("X-Status-Reason", out var values)); + Assert.True(values.Contains("Validation failed")); + + var responseContent = response.Content as ObjectContent; + var responseValue = responseContent?.Value as HttpError; + Assert.NotNull(responseValue); + Assert.True(responseValue.Message.Contains(expectedMessage)); + Assert.True(responseValue.Message.Contains(user.Id.ToString())); + } + + [Test] + public async Task PostUnlockUsers_When_One_UserId_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + var user = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + + mockUserManager.Setup(x => x.FindByIdAsync(user.Id.ToString())) + .ReturnsAsync(user); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + + var response = await usersController.PostUnlockUsers(new[] { user.Id }); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var responseContent = response.Content as ObjectContent; + var notifications = responseContent?.Value as SimpleNotificationModel; + Assert.NotNull(notifications); + Assert.AreEqual(user.Name, notifications.Message); + mockUserManager.Verify(); + } + + [Test] + public async Task PostUnlockUsers_When_Multiple_UserIds_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + var user1 = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + var user2 = new BackOfficeIdentityUser( + new Mock().Object, + 2, + new List()) + { + Name = "alice" + }; + var userIdsToLock = new[] {user1.Id, user2.Id}; + + mockUserManager.Setup(x => x.FindByIdAsync(user1.Id.ToString())) + .ReturnsAsync(user1); + mockUserManager.Setup(x => x.FindByIdAsync(user2.Id.ToString())) + .ReturnsAsync(user2); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user1, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user2, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + + var response = await usersController.PostUnlockUsers(userIdsToLock); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var responseContent = response.Content as ObjectContent; + var notifications = responseContent?.Value as SimpleNotificationModel; + Assert.NotNull(notifications); + Assert.AreEqual(userIdsToLock.Length.ToString(), notifications.Message); + mockUserManager.Verify(); + } + + private UsersController CreateSut(IMock> mockUserManager = null) + { + var mockLocalizedTextService = new Mock(); + mockLocalizedTextService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns((string key, CultureInfo ci, IDictionary tokens) + => tokens.Aggregate("", (current, next) => current + (current == string.Empty ? "" : ",") + next.Value)); + + var usersController = new UsersController( + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ServiceContext.CreatePartial(localizedTextService: mockLocalizedTextService.Object), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance()); + + var mockOwinContext = new Mock(); + var mockUserManagerMarker = new Mock(); + + mockOwinContext.Setup(x => x.Get(It.IsAny())) + .Returns(mockUserManagerMarker.Object); + mockUserManagerMarker.Setup(x => x.GetManager(It.IsAny())) + .Returns(mockUserManager?.Object ?? CreateMockUserManager().Object); + + usersController.Request = new HttpRequestMessage(); + usersController.Request.Properties["MS_OwinContext"] = mockOwinContext.Object; + usersController.Request.Properties[HttpPropertyKeys.RequestContextKey] = new HttpRequestContext {Configuration = new HttpConfiguration()}; + + return usersController; + } + + private static Mock> CreateMockUserManager() + { + return new Mock>( + new Mock().Object, + new Mock().Object, + new Mock>().Object, + null, null, null, null, null, null, null); + } } } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 8e73fa1d02..e58cf686ee 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -701,38 +701,29 @@ namespace Umbraco.Web.Editors [AdminUsersAuthorize("userIds")] public async Task PostUnlockUsers([FromUri]int[] userIds) { - if (userIds.Length <= 0) - return Request.CreateResponse(HttpStatusCode.OK); - - var user = await UserManager.FindByIdAsync(userIds[0].ToString()); - - if (userIds.Length == 1) - { - - - var unlockResult = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); - if (unlockResult.Succeeded == false) - { - return Request.CreateValidationErrorResponse( - string.Format("Could not unlock for user {0} - error {1}", userIds[0], unlockResult.Errors.First())); - } - - return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name })); - } + if (userIds.Length <= 0) return Request.CreateResponse(HttpStatusCode.OK); foreach (var u in userIds) { + var user = await UserManager.FindByIdAsync(u.ToString()); + if (user == null) throw new InvalidOperationException(); + var unlockResult = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); if (unlockResult.Succeeded == false) { return Request.CreateValidationErrorResponse( - string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.First())); + string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.First().Description)); + } + + if (userIds.Length == 1) + { + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] {user.Name})); } } return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] { userIds.Length.ToString() })); + Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] {userIds.Length.ToString()})); } [AdminUsersAuthorize("userIds")]