From 0714fc4734f81ba0fc0ec289728270be3b2a53b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 22 Jun 2020 14:11:01 +0200 Subject: [PATCH] Migrated some of the UserControllerTests Signed-off-by: Bjarke Berg --- .../Builders/UserBuilder.cs | 27 +- .../Builders/UserGroupBuilder.cs | 2 +- .../Controllers/UsersControllerTests.cs | 321 +++++ .../Umbraco.Tests.Integration.csproj | 1 + .../Web/Controllers/UsersControllerTests.cs | 1052 ++++++++--------- .../Extensions/LinkGeneratorExtensions.cs | 52 +- .../Filters/ExceptionViewModel.cs | 11 + .../Filters/JsonExceptionFilterAttribute.cs | 20 +- .../IgnoreRequiredAttributsResolver.cs | 19 + .../UmbracoJsonModelBinderProvider.cs | 12 +- .../Security/BackOfficeOwinUserManager.cs | 16 +- 11 files changed, 947 insertions(+), 586 deletions(-) create mode 100644 src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs create mode 100644 src/Umbraco.Web.Common/Filters/ExceptionViewModel.cs create mode 100644 src/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributsResolver.cs diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 506b24085e..3ca15620ea 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; @@ -41,6 +43,7 @@ namespace Umbraco.Tests.Common.Builders private int? _sessionTimeout; private int[] _startContentIds; private int[] _startMediaIds; + private readonly List>> _userGroupBuilders = new List>>(); public UserBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -82,6 +85,8 @@ namespace Umbraco.Tests.Common.Builders return this; } + + public UserBuilder WithSessionTimeout(int sessionTimeout) { _sessionTimeout = sessionTimeout; @@ -113,7 +118,7 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var id = _id ?? 1; + var id = _id ?? 0; var defaultLang = _defaultLang ?? "en"; var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(defaultLang).Build(); var key = _key ?? Guid.NewGuid(); @@ -134,8 +139,9 @@ namespace Umbraco.Tests.Common.Builders var sessionTimeout = _sessionTimeout ?? 0; var startContentIds = _startContentIds ?? new int[0]; var startMediaIds = _startMediaIds ?? new int[0]; + var groups = _userGroupBuilders.Select(x => x.Build()); - return new User( + var result = new User( globalSettings, name, email, @@ -158,8 +164,25 @@ namespace Umbraco.Tests.Common.Builders StartContentIds = startContentIds, StartMediaIds = startMediaIds, }; + foreach (var readOnlyUserGroup in groups) + { + result.AddGroup(readOnlyUserGroup.ToReadOnlyGroup()); + } + + + return result; } + public UserGroupBuilder> AddUserGroup() + { + var builder = new UserGroupBuilder>(this); + + _userGroupBuilders.Add(builder); + + return builder; + } + + int? IWithIdBuilder.Id { get => _id; diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index 9e11a766ae..6bdc766ee2 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -92,7 +92,7 @@ namespace Umbraco.Tests.Common.Builders public override IUserGroup Build() { - var id = _id ?? 1; + var id = _id ?? 0; var name = _name ?? ("TestUserGroup" + _suffix); var alias = _alias ?? ("testUserGroup" + _suffix); var userCount = _userCount ?? 0; diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs new file mode 100644 index 0000000000..202eba5392 --- /dev/null +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/UsersControllerTests.cs @@ -0,0 +1,321 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Testing; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.Formatters; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Tests.Integration.TestServerTest.Controllers +{ + // [Explicit("We need to fix the tests on buildserver and when running multiple tests in one run")] + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class UsersControllerTests : UmbracoTestServerTestBase + { + [Test] + public async Task Save_User() + { + var url = PrepareUrl(x => x.PostSaveUser(null)); + + var userService = GetRequiredService(); + + var user = new UserBuilder() + .AddUserGroup() + .WithAlias("writer") // Needs to be an existing alias + .Done() + .Build(); + + userService.Save(user); + + var userSave = new UserSave + { + Id = user.Id, + Email = user.Email, + Username = user.Username, + Culture = "en", + Name = user.Name, + UserGroups = user.Groups.Select(x => x.Alias).ToArray() + }; + // Act + var response = await Client.PostAsync(url, + new StringContent(JsonConvert.SerializeObject(userSave), Encoding.UTF8, + MediaTypeNames.Application.Json)); + + // Assert + + Assert.Multiple(() => + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings + { + ContractResolver = new IgnoreRequiredAttributsResolver() + }); + Assert.AreEqual(userSave.Name, actual.Name); + Assert.AreEqual(userSave.Id, actual.Id); + Assert.AreEqual(userSave.Email, actual.Email); + var userGroupAliases = actual.UserGroups.Select(x => x.Alias).ToArray(); + CollectionAssert.AreEquivalent(userSave.UserGroups, userGroupAliases); + }); + } + + [Test] + public async Task GetPagedUsers_Empty() + { + //We get page 2 to force an empty response because there always in the useradmin user + var url = PrepareUrl(x => x.GetPagedUsers(2, 10, "username", Direction.Ascending, null, null, string.Empty)); + + // Act + var response = await Client.GetAsync(url); + + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + var actual = JsonConvert.DeserializeObject>(body, new JsonSerializerSettings + { + ContractResolver = new IgnoreRequiredAttributsResolver() + }); + Assert.Multiple(() => + { + Assert.IsNotNull(actual); + Assert.AreEqual(1, actual.TotalItems); + CollectionAssert.IsEmpty(actual.Items); + }); + } + + [Test] + public async Task GetPagedUsers_11() + { + var totalNumberOfUsers = 11; + var pageSize = totalNumberOfUsers - 1; + var url = PrepareUrl(x => x.GetPagedUsers(1, pageSize, "username", Direction.Ascending, null, null, string.Empty)); + + var userService = GetRequiredService(); + + for (int i = 1; i < totalNumberOfUsers; i++) // We already has admin user = -1, so we start from 1 + { + var user = new UserBuilder() + .WithName($"Test user {i}") + .AddUserGroup() + .WithAlias("writer") // Needs to be an existing alias + .Done() + .Build(); + + userService.Save(user); + } + + // Act + var response = await Client.GetAsync(url); + + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + var actual = JsonConvert.DeserializeObject>(body, new JsonSerializerSettings + { + ContractResolver = new IgnoreRequiredAttributsResolver() + }); + Assert.Multiple(() => + { + Assert.IsNotNull(actual); + Assert.AreEqual(totalNumberOfUsers, actual.TotalItems); + Assert.AreEqual(pageSize, actual.Items.Count()); + }); + } + + [Test] + public async Task PostUnlockUsers_When_UserIds_Not_Supplied_Expect_Ok_Response() + { + var url = PrepareUrl(x => x.PostUnlockUsers(Array.Empty())); + + // Act + var response = await Client.PostAsync(url, new StringContent(string.Empty)); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [Test] + public async Task PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException() + { + var userId = 42; // Must not exist + var url = PrepareUrl(x => x.PostUnlockUsers(new []{userId})); + + // Act + var response = await Client.PostAsync(url, new StringContent(string.Empty)); + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + + var actual = JsonConvert.DeserializeObject(body, new JsonSerializerSettings + { + ContractResolver = new IgnoreRequiredAttributsResolver() + }); + Assert.Multiple(() => + { + var expected = new InvalidOperationException(); + Assert.IsNotNull(actual); + Assert.AreEqual(expected.GetType(), actual.ExceptionType); + Assert.AreEqual(expected.Message, actual.ExceptionMessage); + }); + } + + // [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(); + // } + // + // [Test] + // public async Task GetPagedUsers_Fips() + // { + // await RunFipsTest("GetPagedUsers", mock => + // { + // var users = MockedUser.CreateMulipleUsers(10); + // long outVal = 10; + // mock.Setup(service => service.GetAll( + // It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + // It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + // .Returns(() => users); + // }, response => + // { + // var obj = JsonConvert.DeserializeObject>(response.Item2); + // Assert.AreEqual(10, obj.TotalItems); + // Assert.AreEqual(10, obj.Items.Count()); + // }); + // } +// +// [Test] +// public async Task GetById_Fips() +// { +// const int mockUserId = 1234; +// var user = MockedUser.CreateUser(); +// +// await RunFipsTest("GetById", mock => +// { +// mock.Setup(service => service.GetUserById(1234)) +// .Returns((int i) => i == mockUserId ? user : null); +// }, response => +// { +// var obj = JsonConvert.DeserializeObject(response.Item2); +// Assert.AreEqual(user.Username, obj.Username); +// Assert.AreEqual(user.Email, obj.Email); +// }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); +// } + } +} diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index c38b34db2c..09a4558a84 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 58718a4064..92560cf485 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,526 +1,526 @@ -// 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; -// using Umbraco.Core; -// using Umbraco.Core.Cache; -// using Umbraco.Web.Composing; -// using Umbraco.Core.Configuration; -// using Umbraco.Core.IO; -// using Umbraco.Core.Logging; -// using Umbraco.Core.Models; -// using Umbraco.Core.Models.Membership; -// using Umbraco.Core.Persistence; -// using Umbraco.Core.Persistence.Mappers; -// using Umbraco.Core.Persistence.Querying; -// using Umbraco.Core.Services; -// using Umbraco.Tests.TestHelpers; -// using Umbraco.Tests.TestHelpers.ControllerTesting; -// using Umbraco.Tests.TestHelpers.Entities; -// using Umbraco.Tests.Testing; -// using Umbraco.Web; -// using Umbraco.Web.Editors; -// using Umbraco.Web.Features; -// using Umbraco.Web.Models.ContentEditing; -// using IUser = Umbraco.Core.Models.Membership.IUser; -// using Umbraco.Core.Mapping; -// using Umbraco.Core.Configuration.UmbracoSettings; -// using Umbraco.Core.Hosting; -// using Umbraco.Web.Routing; -// using Umbraco.Core.Media; -// using Umbraco.Net; -// using Umbraco.Persistance.SqlCe; -// using Umbraco.Web.Security; -// using BackOfficeIdentityUser = Umbraco.Core.BackOffice.BackOfficeIdentityUser; -// -// namespace Umbraco.Tests.Web.Controllers -// { -// [TestFixture] -// [UmbracoTest(Database = UmbracoTestOptions.Database.None)] -// public class UsersControllerTests : TestWithDatabaseBase -// { -// protected override void ComposeApplication(bool withApplication) -// { -// base.ComposeApplication(withApplication); -// //if (!withApplication) return; -// -// // replace the true IUserService implementation with a mock -// // so that each test can configure the service to their liking -// Composition.RegisterUnique(f => Mock.Of()); -// -// // kill the true IEntityService too -// Composition.RegisterUnique(f => Mock.Of()); -// -// Composition.RegisterUnique(); -// } -// -// [Test] -// public async Task Save_User() -// { -// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) -// { -// var userServiceMock = Mock.Get(ServiceContext.UserService); -// -// userServiceMock.Setup(service => service.Save(It.IsAny(), It.IsAny())) -// .Callback((IUser u, bool raiseEvents) => -// { -// u.Id = 1234; -// }); -// userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) -// .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); -// userServiceMock.Setup(service => service.GetUserGroupsByAlias(It.IsAny())) -// .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); -// userServiceMock.Setup(service => service.GetUserById(It.IsAny())) -// .Returns((int id) => id == 1234 ? new User(TestObjects.GetGlobalSettings(), 1234, "Test", "test@test.com", "test@test.com", "", null, new List(), new int[0], new int[0]) : null); -// -// var usersController = new UsersController( -// Factory.GetInstance(), -// umbracoContextAccessor, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// ShortStringHelper, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance() -// -// ); -// return usersController; -// } -// -// var userSave = new UserSave -// { -// Id = 1234, -// Email = "test@test.com", -// Username = "test@test.com", -// Culture = "en", -// Name = "Test", -// UserGroups = new[] { "writers" } -// }; -// -// var runner = new TestRunner(CtrlFactory); -// var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, -// new ObjectContent(userSave, new JsonMediaTypeFormatter())); -// var obj = JsonConvert.DeserializeObject(response.Item2); -// -// Assert.AreEqual(userSave.Name, obj.Name); -// Assert.AreEqual(1234, obj.Id); -// Assert.AreEqual(userSave.Email, obj.Email); -// var userGroupAliases = obj.UserGroups.Select(x => x.Alias).ToArray(); -// foreach (var group in userSave.UserGroups) -// { -// Assert.IsTrue(userGroupAliases.Contains(group)); -// } -// } -// -// private void MockForGetPagedUsers() -// { -// Mock.Get(Current.SqlContext) -// .Setup(x => x.Query()) -// .Returns(new Query(Current.SqlContext)); -// -// var syntax = new SqlCeSyntaxProvider(); -// -// Mock.Get(Current.SqlContext) -// .Setup(x => x.SqlSyntax) -// .Returns(syntax); -// -// var mappers = new MapperCollection(new [] -// { -// new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) -// }); -// -// Mock.Get(Current.SqlContext) -// .Setup(x => x.Mappers) -// .Returns(mappers); -// } -// -// [Test] -// public async Task GetPagedUsers_Empty() -// { -// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) -// { -// var usersController = new UsersController( -// Factory.GetInstance(), -// umbracoContextAccessor, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// ShortStringHelper, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance() -// ); -// return usersController; -// } -// -// MockForGetPagedUsers(); -// -// var runner = new TestRunner(CtrlFactory); -// var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); -// -// var obj = JsonConvert.DeserializeObject>(response.Item2); -// Assert.AreEqual(0, obj.TotalItems); -// } -// -// [Test] -// public async Task GetPagedUsers_10() -// { -// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) -// { -// //setup some mocks -// var userServiceMock = Mock.Get(ServiceContext.UserService); -// var users = MockedUser.CreateMulipleUsers(10); -// long outVal = 10; -// userServiceMock.Setup(service => service.GetAll( -// It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) -// .Returns(() => users); -// -// var usersController = new UsersController( -// Factory.GetInstance(), -// umbracoContextAccessor, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// ShortStringHelper, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance() -// ); -// return usersController; -// } -// -// MockForGetPagedUsers(); -// -// var runner = new TestRunner(CtrlFactory); -// var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); -// -// var obj = JsonConvert.DeserializeObject>(response.Item2); -// Assert.AreEqual(10, obj.TotalItems); -// Assert.AreEqual(10, obj.Items.Count()); -// } -// -// [Test] -// public async Task GetPagedUsers_Fips() -// { -// await RunFipsTest("GetPagedUsers", mock => -// { -// var users = MockedUser.CreateMulipleUsers(10); -// long outVal = 10; -// mock.Setup(service => service.GetAll( -// It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) -// .Returns(() => users); -// }, response => -// { -// var obj = JsonConvert.DeserializeObject>(response.Item2); -// Assert.AreEqual(10, obj.TotalItems); -// Assert.AreEqual(10, obj.Items.Count()); -// }); -// } -// -// [Test] -// public async Task GetById_Fips() -// { -// const int mockUserId = 1234; -// var user = MockedUser.CreateUser(); -// -// await RunFipsTest("GetById", mock => -// { -// mock.Setup(service => service.GetUserById(1234)) -// .Returns((int i) => i == mockUserId ? user : null); -// }, response => -// { -// var obj = JsonConvert.DeserializeObject(response.Item2); -// Assert.AreEqual(user.Username, obj.Username); -// Assert.AreEqual(user.Email, obj.Email); -// }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); -// } -// -// -// private async Task RunFipsTest(string action, Action> userServiceSetup, -// Action> verification, -// object routeDefaults = null, string url = null) -// { -// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) -// { -// //setup some mocks -// var userServiceMock = Mock.Get(ServiceContext.UserService); -// userServiceSetup(userServiceMock); -// -// var usersController = new UsersController( -// Factory.GetInstance(), -// umbracoContextAccessor, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// ShortStringHelper, -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance(), -// Factory.GetInstance() -// ); -// return usersController; -// } -// -// // Testing what happens if the system were configured to only use FIPS-compliant algorithms -// var typ = typeof(CryptoConfig); -// var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); -// var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); -// var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); -// var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; -// -// try -// { -// if (!originalFipsValue) -// { -// haveFld.SetValue(null, true); -// isFld.SetValue(null, true); -// } -// -// MockForGetPagedUsers(); -// -// var runner = new TestRunner(CtrlFactory); -// var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url); -// verification(response); -// } -// finally -// { -// if (!originalFipsValue) -// { -// haveFld.SetValue(null, false); -// isFld.SetValue(null, false); -// } -// } -// } -// -// [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(), -// 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); -// } -// } -// } +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; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Web.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.ControllerTesting; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; +using Umbraco.Web; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.Models.ContentEditing; +using IUser = Umbraco.Core.Models.Membership.IUser; +using Umbraco.Core.Mapping; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Hosting; +using Umbraco.Web.Routing; +using Umbraco.Core.Media; +using Umbraco.Net; +using Umbraco.Persistance.SqlCe; +using Umbraco.Web.Security; +using BackOfficeIdentityUser = Umbraco.Core.BackOffice.BackOfficeIdentityUser; + +namespace Umbraco.Tests.Web.Controllers +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.None)] + public class UsersControllerTests : TestWithDatabaseBase + { + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + //if (!withApplication) return; + + // replace the true IUserService implementation with a mock + // so that each test can configure the service to their liking + Composition.RegisterUnique(f => Mock.Of()); + + // kill the true IEntityService too + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); + } + + [Test] + public async Task Save_User() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) + { + var userServiceMock = Mock.Get(ServiceContext.UserService); + + userServiceMock.Setup(service => service.Save(It.IsAny(), It.IsAny())) + .Callback((IUser u, bool raiseEvents) => + { + u.Id = 1234; + }); + userServiceMock.Setup(service => service.GetAllUserGroups(It.IsAny())) + .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); + userServiceMock.Setup(service => service.GetUserGroupsByAlias(It.IsAny())) + .Returns(new[] { Mock.Of(group => group.Id == 123 && group.Alias == "writers" && group.Name == "Writers") }); + userServiceMock.Setup(service => service.GetUserById(It.IsAny())) + .Returns((int id) => id == 1234 ? new User(TestObjects.GetGlobalSettings(), 1234, "Test", "test@test.com", "test@test.com", "", null, new List(), new int[0], new int[0]) : null); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance() + + ); + return usersController; + } + + var userSave = new UserSave + { + Id = 1234, + Email = "test@test.com", + Username = "test@test.com", + Culture = "en", + Name = "Test", + UserGroups = new[] { "writers" } + }; + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", "PostSaveUser", HttpMethod.Post, + new ObjectContent(userSave, new JsonMediaTypeFormatter())); + var obj = JsonConvert.DeserializeObject(response.Item2); + + Assert.AreEqual(userSave.Name, obj.Name); + Assert.AreEqual(1234, obj.Id); + Assert.AreEqual(userSave.Email, obj.Email); + var userGroupAliases = obj.UserGroups.Select(x => x.Alias).ToArray(); + foreach (var group in userSave.UserGroups) + { + Assert.IsTrue(userGroupAliases.Contains(group)); + } + } + + private void MockForGetPagedUsers() + { + Mock.Get(Current.SqlContext) + .Setup(x => x.Query()) + .Returns(new Query(Current.SqlContext)); + + var syntax = new SqlCeSyntaxProvider(); + + Mock.Get(Current.SqlContext) + .Setup(x => x.SqlSyntax) + .Returns(syntax); + + var mappers = new MapperCollection(new [] + { + new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) + }); + + Mock.Get(Current.SqlContext) + .Setup(x => x.Mappers) + .Returns(mappers); + } + + [Test] + public async Task GetPagedUsers_Empty() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) + { + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance() + ); + return usersController; + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); + + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(0, obj.TotalItems); + } + + [Test] + public async Task GetPagedUsers_10() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) + { + //setup some mocks + var userServiceMock = Mock.Get(ServiceContext.UserService); + var users = MockedUser.CreateMulipleUsers(10); + long outVal = 10; + userServiceMock.Setup(service => service.GetAll( + It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => users); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance() + ); + return usersController; + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); + + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(10, obj.TotalItems); + Assert.AreEqual(10, obj.Items.Count()); + } + + [Test] + public async Task GetPagedUsers_Fips() + { + await RunFipsTest("GetPagedUsers", mock => + { + var users = MockedUser.CreateMulipleUsers(10); + long outVal = 10; + mock.Setup(service => service.GetAll( + It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => users); + }, response => + { + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(10, obj.TotalItems); + Assert.AreEqual(10, obj.Items.Count()); + }); + } + + [Test] + public async Task GetById_Fips() + { + const int mockUserId = 1234; + var user = MockedUser.CreateUser(); + + await RunFipsTest("GetById", mock => + { + mock.Setup(service => service.GetUserById(1234)) + .Returns((int i) => i == mockUserId ? user : null); + }, response => + { + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(user.Username, obj.Username); + Assert.AreEqual(user.Email, obj.Email); + }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); + } + + + private async Task RunFipsTest(string action, Action> userServiceSetup, + Action> verification, + object routeDefaults = null, string url = null) + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) + { + //setup some mocks + var userServiceMock = Mock.Get(ServiceContext.UserService); + userServiceSetup(userServiceMock); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance() + ); + return usersController; + } + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url); + verification(response); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } + + [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(), + 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.Common/Extensions/LinkGeneratorExtensions.cs b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs index 0ce314c6d3..dbfbee912e 100644 --- a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Dynamic; using System.Linq; using Umbraco.Core; using Microsoft.AspNetCore.Routing; @@ -56,10 +58,17 @@ namespace Umbraco.Extensions public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, object id = null) where T : UmbracoApiControllerBase { - return linkGenerator.GetUmbracoApiService(actionName, typeof(T), id); + return linkGenerator.GetUmbracoApiService(actionName, typeof(T), new Dictionary() + { + ["id"] = id + }); } - + public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, IDictionary values) + where T : UmbracoApiControllerBase + { + return linkGenerator.GetUmbracoApiService(actionName, typeof(T), values); + } public static string GetUmbracoApiServiceBaseUrl(this LinkGenerator linkGenerator, Expression> methodSelector) where T : UmbracoApiControllerBase @@ -81,35 +90,30 @@ namespace Umbraco.Extensions /// /// /// - public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, object id = null) + public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, IDictionary dict = null) { if (actionName == null) throw new ArgumentNullException(nameof(actionName)); if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); - if (area.IsNullOrWhiteSpace()) + if (dict is null) { - if (id == null) - { - return linkGenerator.GetPathByAction(actionName, controllerName); - } - else - { - return linkGenerator.GetPathByAction(actionName, controllerName, new { id = id }); - } + dict = new Dictionary(); } - else + + + + if (!area.IsNullOrWhiteSpace()) { - if (id == null) - { - return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area }); - } - else - { - return linkGenerator.GetPathByAction(actionName, controllerName, new { area = area, id = id }); - } + dict["area"] = area; } + + + var values = dict.Aggregate(new ExpandoObject() as IDictionary, + (a, p) => { a.Add(p.Key, p.Value); return a; }); + + return linkGenerator.GetPathByAction(actionName, controllerName, values); } /// @@ -120,7 +124,7 @@ namespace Umbraco.Extensions /// /// /// - public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, object id = null) + public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, IDictionary values = null) { if (actionName == null) throw new ArgumentNullException(nameof(actionName)); if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); @@ -137,7 +141,7 @@ namespace Umbraco.Extensions //set the area to the plugin area area = metaData.AreaName; } - return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id); + return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, values); } public static string GetUmbracoApiService(this LinkGenerator linkGenerator, Expression> methodSelector) @@ -155,7 +159,7 @@ namespace Umbraco.Extensions { return linkGenerator.GetUmbracoApiService(method.Name); } - return linkGenerator.GetUmbracoApiService(method.Name, methodParams.Values.First()); + return linkGenerator.GetUmbracoApiService(method.Name, methodParams); } } } diff --git a/src/Umbraco.Web.Common/Filters/ExceptionViewModel.cs b/src/Umbraco.Web.Common/Filters/ExceptionViewModel.cs new file mode 100644 index 0000000000..917e00bb02 --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/ExceptionViewModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace Umbraco.Web.Common.Filters +{ + public class ExceptionViewModel + { + public string ExceptionMessage { get; set; } + public Type ExceptionType { get; set; } + public string StackTrace { get; set; } + } +} diff --git a/src/Umbraco.Web.Common/Filters/JsonExceptionFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/JsonExceptionFilterAttribute.cs index d95ef5b9b8..1ff8ede0ab 100644 --- a/src/Umbraco.Web.Common/Filters/JsonExceptionFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/JsonExceptionFilterAttribute.cs @@ -45,23 +45,15 @@ namespace Umbraco.Web.Common.Filters private object GetModel(Exception ex) { - object error; + var error = new ExceptionViewModel + { + ExceptionMessage = ex.Message + }; if (_hostingEnvironment.IsDebugMode) { - error = new - { - ExceptionMessage = ex.Message, - ExceptionType = ex.GetType(), - StackTrace = ex.StackTrace - }; - } - else - { - error = new - { - ExceptionMessage = ex.Message - }; + error.ExceptionType = ex.GetType(); + error.StackTrace = ex.StackTrace; } return error; diff --git a/src/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributsResolver.cs b/src/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributsResolver.cs new file mode 100644 index 0000000000..86186dea41 --- /dev/null +++ b/src/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributsResolver.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Umbraco.Web.Common.Formatters +{ + public class IgnoreRequiredAttributsResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + + property.Required = Required.Default; + + return property; + } + } +} diff --git a/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs b/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs index 16cd3fa6df..2b5ea1b4fe 100644 --- a/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs +++ b/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs @@ -9,6 +9,7 @@ using System.Buffers; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Umbraco.Web.Common.Formatters; namespace Umbraco.Web.Common.ModelBinding { @@ -44,18 +45,7 @@ namespace Umbraco.Web.Common.ModelBinding jsonOptions) }; } - private class IgnoreRequiredAttributsResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - property.Required = Required.Default; - - return property; - } - } - } } diff --git a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs index 2ef28cf22a..7bc5cd9294 100644 --- a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Security public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; public BackOfficeOwinUserManager( - IPasswordConfiguration passwordConfiguration, + IUserPasswordConfiguration passwordConfiguration, IIpResolver ipResolver, IUserStore store, IOptions optionsAccessor, @@ -30,14 +30,14 @@ namespace Umbraco.Web.Security BackOfficeIdentityErrorDescriber errors, IDataProtectionProvider dataProtectionProvider, ILogger> logger) - : base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger) + : base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger, passwordConfiguration) { PasswordConfiguration = passwordConfiguration; InitUserManager(this, dataProtectionProvider); } - + #region Static Create methods - + /// /// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager /// @@ -54,7 +54,7 @@ namespace Umbraco.Web.Security ILogger> logger) { var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper); - + return Create( passwordConfiguration, ipResolver, @@ -68,7 +68,7 @@ namespace Umbraco.Web.Security /// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance /// public static BackOfficeOwinUserManager Create( - IPasswordConfiguration passwordConfiguration, + IUserPasswordConfiguration passwordConfiguration, IIpResolver ipResolver, IUserStore customUserStore, BackOfficeIdentityErrorDescriber errors, @@ -88,7 +88,7 @@ namespace Umbraco.Web.Security options.Password.RequireDigit = passwordConfiguration.RequireDigit; options.Password.RequireLowercase = passwordConfiguration.RequireLowercase; options.Password.RequireUppercase = passwordConfiguration.RequireUppercase; - + // Ensure Umbraco security stamp claim type is used options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; @@ -109,7 +109,7 @@ namespace Umbraco.Web.Security new OptionsWrapper(options), userValidators, passwordValidators, - new BackOfficeLookupNormalizer(), + new BackOfficeLookupNormalizer(), errors, dataProtectionProvider, logger);