From 4d760a54c89b2e03aca8651c8a4a4e19a95f166e Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 16 Dec 2022 11:17:56 +0100 Subject: [PATCH 1/3] Set redirect URL when building model (#12969) Co-authored-by: Elitsa Marinovska (cherry picked from commit 544cb306023482fb03acfaef2ac63343fbc13786) --- src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs index 8ce6ab5595..b3736e63e8 100644 --- a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs +++ b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs @@ -56,6 +56,7 @@ public class RegisterModelBuilder : MemberModelBuilderBase var model = new RegisterModel { + RedirectUrl = _redirectUrl, MemberTypeAlias = providedOrDefaultMemberTypeAlias, UsernameIsEmail = _usernameIsEmail, MemberProperties = _lookupProperties From ab82549fd6fff6208832fa21dc2bc30ae2ffd398 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 16 Dec 2022 14:23:31 +0100 Subject: [PATCH 2/3] Support space (and more) in tag groups (#13589) --- .../directives/components/tags/umbtagseditor.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js index 8238a2be66..7ae96e0cd5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tags/umbtagseditor.directive.js @@ -92,7 +92,7 @@ var sources = { //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options // name = the data set name, we'll make this the tag group name + culture - name: vm.config.group + (vm.culture ? vm.culture : ""), + name: (vm.config.group + (vm.culture ? vm.culture : "")).replace(/\W/g, '-'), display: "text", //source: tagsHound source: function (query, syncCallback, asyncCallback) { From d62e2d731bcf0e5acf0648f87ad42eeaa33ae971 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 20 Dec 2022 09:53:54 +0100 Subject: [PATCH 3/3] V10: Dont disable invited users (#13600) * Dont allow disable user when invited * Use data instead of selection * return succesfully disabled users * Disable disable button when invited * Add integration tests * Remove unused usings * Update src/Umbraco.Web.BackOffice/Controllers/UsersController.cs Co-authored-by: Kenn Jacobsen * Create DisabledUsersModel * use data.disabledUsers * Return OK if no users to be saved * User disabledUsersModel Co-authored-by: Zeegaan Co-authored-by: Kenn Jacobsen --- .../Controllers/UsersController.cs | 35 +++++++-- .../Models/DisabledUsersModel.cs | 13 ++++ .../users/views/users/users.controller.js | 3 +- .../Controllers/UsersControllerTests.cs | 72 +++++++++++++++++-- 4 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Web.Common/Models/DisabledUsersModel.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index f734d8626b..04e4c64645 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -35,6 +35,7 @@ using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.Models; using Umbraco.Cms.Web.Common.Security; using Umbraco.Extensions; @@ -785,22 +786,44 @@ public class UsersController : BackOfficeNotificationsController return ValidationProblem("The current user cannot disable itself"); } - IUser[] users = _userService.GetUsersById(userIds).ToArray(); + var users = _userService.GetUsersById(userIds).ToList(); + List skippedUsers = new(); foreach (IUser u in users) { + if (u.UserState is UserState.Invited) + { + _logger.LogWarning("Could not disable invited user {Username}", u.Name); + skippedUsers.Add(u); + continue; + } + u.IsApproved = false; u.InvitedDate = null; } - _userService.Save(users); + users = users.Except(skippedUsers).ToList(); - if (users.Length > 1) + if (users.Any()) { - return Ok(_localizedTextService.Localize("speechBubbles", "disableUsersSuccess", - new[] { userIds.Length.ToString() })); + _userService.Save(users); + } + else + { + return Ok(new DisabledUsersModel()); } - return Ok(_localizedTextService.Localize("speechBubbles", "disableUserSuccess", new[] { users[0].Name })); + var disabledUsersModel = new DisabledUsersModel + { + DisabledUserIds = users.Select(x => x.Id), + }; + + var message= users.Count > 1 + ? _localizedTextService.Localize("speechBubbles", "disableUsersSuccess", new[] { userIds.Length.ToString() }) + : _localizedTextService.Localize("speechBubbles", "disableUserSuccess", new[] { users[0].Name }); + + var header = _localizedTextService.Localize("general", "success"); + disabledUsersModel.Notifications.Add(new BackOfficeNotification(header, message, NotificationStyle.Success)); + return Ok(disabledUsersModel); } /// diff --git a/src/Umbraco.Web.Common/Models/DisabledUsersModel.cs b/src/Umbraco.Web.Common/Models/DisabledUsersModel.cs new file mode 100644 index 0000000000..f6712ab193 --- /dev/null +++ b/src/Umbraco.Web.Common/Models/DisabledUsersModel.cs @@ -0,0 +1,13 @@ +using System.Runtime.Serialization; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Web.Common.Models; + +[DataContract] +public class DisabledUsersModel : INotificationModel +{ + public List Notifications { get; } = new(); + + [DataMember(Name = "disabledUserIds")] + public IEnumerable DisabledUserIds { get; set; } = Enumerable.Empty(); +} diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 99cbe31f66..823f7ee6fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -334,7 +334,7 @@ vm.disableUserButtonState = "busy"; usersResource.disableUsers(vm.selection).then(function (data) { // update userState - vm.selection.forEach(function (userId) { + data.disabledUserIds.forEach(function (userId) { var user = getUserFromArrayById(userId, vm.users); if (user) { user.userState = "Disabled"; @@ -808,6 +808,7 @@ if (user.userDisplayState && user.userDisplayState.key === "Invited") { vm.allowEnableUser = false; + vm.allowDisableUser = false; } if (user.userDisplayState && user.userDisplayState.key === "LockedOut") { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs index 6fcd1d7eee..c89841260a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs @@ -1,14 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -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.Cms.Core; @@ -21,7 +16,7 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Integration.TestServerTest; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Formatters; -using Umbraco.Extensions; +using Umbraco.Cms.Web.Common.Models; namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers; @@ -231,4 +226,69 @@ public class UsersControllerTests : UmbracoTestServerTestBase Assert.AreEqual($"Unlocked {users.Count()} users", actual.Message); }); } + + [Test] + public async Task Cannot_Disable_Invited_User() + { + var userService = GetRequiredService(); + + var user = new UserBuilder() + .AddUserGroup() + .WithAlias("writer") // Needs to be an existing alias + .Done() + .Build(); + + user.LastLoginDate = default; + user.InvitedDate = DateTime.Now; + userService.Save(user); + var createdUser = userService.GetByEmail("test@test.com"); + + // Act + var url = PrepareApiControllerUrl(x => x.PostDisableUsers(new []{createdUser.Id})); + var response = await Client.PostAsync(url, null); + + // Assert + Assert.Multiple(() => + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + }); + } + + [Test] + public async Task Can_Disable_Active_User() + { + var userService = GetRequiredService(); + + var user = new UserBuilder() + .AddUserGroup() + .WithAlias("writer") // Needs to be an existing alias + .Done() + .Build(); + + user.IsApproved = true; + userService.Save(user); + + var createdUser = userService.GetByEmail("test@test.com"); + + // Act + var url = PrepareApiControllerUrl(x => x.PostDisableUsers(new[] { createdUser.Id })); + var response = await Client.PostAsync(url, null); + + // Assert + Assert.Multiple(() => + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + var affectedUsers = JsonConvert.DeserializeObject(body, new JsonSerializerSettings { ContractResolver = new IgnoreRequiredAttributesResolver() }); + Assert.AreEqual(affectedUsers!.DisabledUserIds.First(), createdUser!.Id); + + var disabledUser = userService.GetByEmail("test@test.com"); + Assert.AreEqual(disabledUser!.UserState, UserState.Disabled); + }); + } }