Skip to content

Commit 8f6d1bc

Browse files
authored
Add validation to prevent update of a user or member to an invalid username. (#18263)
Avoid password manager updates of user name field on user details screen.
1 parent f14922b commit 8f6d1bc

File tree

6 files changed

+59
-9
lines changed

6 files changed

+59
-9
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Member/MemberControllerBase.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.AspNetCore.Authorization;
1+
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Mvc;
44
using Umbraco.Cms.Api.Common.Builders;
@@ -58,7 +58,8 @@ protected IActionResult MemberEditingOperationStatusResult(MemberEditingOperatio
5858
.WithTitle("Invalid name supplied")
5959
.Build()),
6060
MemberEditingOperationStatus.InvalidUsername => BadRequest(problemDetailsBuilder
61-
.WithTitle("Invalid username supplied")
61+
.WithTitle("Invalid username")
62+
.WithDetail("The username is either empty or contains one or more invalid characters.")
6263
.Build()),
6364
MemberEditingOperationStatus.InvalidEmail => BadRequest(problemDetailsBuilder
6465
.WithTitle("Invalid email supplied")

src/Umbraco.Cms.Api.Management/Controllers/User/UserOrCurrentUserControllerBase.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ protected IActionResult UserOperationStatusResult(UserOperationStatus status, Er
3535
.WithTitle("Email Cannot be changed")
3636
.WithDetail("Local login is disabled, so the email cannot be changed.")
3737
.Build()),
38+
UserOperationStatus.InvalidUserName => BadRequest(problemDetailsBuilder
39+
.WithTitle("Invalid username")
40+
.WithDetail("The username contains one or more invalid characters.")
41+
.Build()),
3842
UserOperationStatus.DuplicateUserName => BadRequest(problemDetailsBuilder
3943
.WithTitle("Duplicate Username")
4044
.WithDetail("The username is already in use.")

src/Umbraco.Core/Services/OperationStatus/UserOperationStatus.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Umbraco.Cms.Core.Services.OperationStatus;
1+
namespace Umbraco.Cms.Core.Services.OperationStatus;
22

33
/// <summary>
44
/// Used to signal a user operation succeeded or an atomic failure reason
@@ -41,4 +41,5 @@ public enum UserOperationStatus
4141
SelfPasswordResetNotAllowed,
4242
DuplicateId,
4343
InvalidUserType,
44+
InvalidUserName,
4445
}

src/Umbraco.Core/Services/UserService.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using System.ComponentModel.DataAnnotations;
2-
using System.Globalization;
32
using System.Linq.Expressions;
4-
using Microsoft.Extensions.DependencyInjection;
53
using System.Security.Claims;
64
using System.Security.Cryptography;
75
using System.Text.RegularExpressions;
6+
using Microsoft.Extensions.DependencyInjection;
87
using Microsoft.Extensions.Logging;
98
using Microsoft.Extensions.Options;
10-
using Umbraco.Cms.Core.Cache;
119
using Umbraco.Cms.Core.Configuration.Models;
1210
using Umbraco.Cms.Core.DependencyInjection;
1311
using Umbraco.Cms.Core.Editors;
@@ -40,7 +38,6 @@ internal partial class UserService : RepositoryService, IUserService
4038
{
4139
private readonly GlobalSettings _globalSettings;
4240
private readonly SecuritySettings _securitySettings;
43-
private readonly ILogger<UserService> _logger;
4441
private readonly IUserGroupRepository _userGroupRepository;
4542
private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper;
4643
private readonly IServiceScopeFactory _serviceScopeFactory;
@@ -137,7 +134,6 @@ public UserService(
137134
_globalSettings = globalSettings.Value;
138135
_securitySettings = securitySettings.Value;
139136
_contentSettings = contentSettings.Value;
140-
_logger = loggerFactory.CreateLogger<UserService>();
141137
}
142138

143139
/// <summary>
@@ -966,6 +962,15 @@ private async Task<UserOperationStatus> ValidateUserCreateModel(UserCreateModel
966962
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.MissingUser, existingUser);
967963
}
968964

965+
// User names can only contain the configured allowed characters. This is validated by ASP.NET Identity on create
966+
// as the setting is applied to the BackOfficeIdentityOptions, but we need to check ourselves for updates.
967+
var allowedUserNameCharacters = _securitySettings.AllowedUserNameCharacters;
968+
if (model.UserName.Any(c => allowedUserNameCharacters.Contains(c) == false))
969+
{
970+
scope.Complete();
971+
return Attempt.FailWithStatus<IUser?, UserOperationStatus>(UserOperationStatus.InvalidUserName, existingUser);
972+
}
973+
969974
IEnumerable<IUserGroup> allUserGroups = _userGroupRepository.GetMany().ToArray();
970975
var userGroups = allUserGroups.Where(x => model.UserGroupKeys.Contains(x.Key)).ToHashSet();
971976

src/Umbraco.Infrastructure/Services/MemberEditingService.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
using Microsoft.AspNetCore.Identity;
1+
using Microsoft.AspNetCore.Identity;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Options;
5+
using Umbraco.Cms.Core.Configuration.Models;
6+
using Umbraco.Cms.Core.DependencyInjection;
37
using Umbraco.Cms.Core.Models;
48
using Umbraco.Cms.Core.Models.ContentEditing;
59
using Umbraco.Cms.Core.Models.Membership;
@@ -19,7 +23,9 @@ internal sealed class MemberEditingService : IMemberEditingService
1923
private readonly IPasswordChanger<MemberIdentityUser> _passwordChanger;
2024
private readonly ILogger<MemberEditingService> _logger;
2125
private readonly IMemberGroupService _memberGroupService;
26+
private readonly SecuritySettings _securitySettings;
2227

28+
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V16.")]
2329
public MemberEditingService(
2430
IMemberService memberService,
2531
IMemberTypeService memberTypeService,
@@ -29,6 +35,29 @@ public MemberEditingService(
2935
IPasswordChanger<MemberIdentityUser> passwordChanger,
3036
ILogger<MemberEditingService> logger,
3137
IMemberGroupService memberGroupService)
38+
: this(
39+
memberService,
40+
memberTypeService,
41+
memberContentEditingService,
42+
memberManager,
43+
twoFactorLoginService,
44+
passwordChanger,
45+
logger,
46+
memberGroupService,
47+
StaticServiceProvider.Instance.GetRequiredService<IOptions<SecuritySettings>>())
48+
{
49+
}
50+
51+
public MemberEditingService(
52+
IMemberService memberService,
53+
IMemberTypeService memberTypeService,
54+
IMemberContentEditingService memberContentEditingService,
55+
IMemberManager memberManager,
56+
ITwoFactorLoginService twoFactorLoginService,
57+
IPasswordChanger<MemberIdentityUser> passwordChanger,
58+
ILogger<MemberEditingService> logger,
59+
IMemberGroupService memberGroupService,
60+
IOptions<SecuritySettings> securitySettings)
3261
{
3362
_memberService = memberService;
3463
_memberTypeService = memberTypeService;
@@ -38,6 +67,7 @@ public MemberEditingService(
3867
_passwordChanger = passwordChanger;
3968
_logger = logger;
4069
_memberGroupService = memberGroupService;
70+
_securitySettings = securitySettings.Value;
4171
}
4272

4373
public async Task<IMember?> GetAsync(Guid key)
@@ -225,6 +255,14 @@ private async Task<MemberEditingOperationStatus> ValidateMemberDataAsync(MemberE
225255
return MemberEditingOperationStatus.InvalidUsername;
226256
}
227257

258+
// User names can only contain the configured allowed characters. This is validated by ASP.NET Identity on create
259+
// as the setting is applied to the BackOfficeIdentityOptions, but we need to check ourselves for updates.
260+
var allowedUserNameCharacters = _securitySettings.AllowedUserNameCharacters;
261+
if (model.Username.Any(c => allowedUserNameCharacters.Contains(c) == false))
262+
{
263+
return MemberEditingOperationStatus.InvalidUsername;
264+
}
265+
228266
if (model.Email.IsEmail() is false)
229267
{
230268
return MemberEditingOperationStatus.InvalidEmail;

src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user/components/user-workspace-profile-settings/user-workspace-profile-settings.element.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export class UmbUserWorkspaceProfileSettingsElement extends UmbLitElement {
9797
<uui-input
9898
slot="editor"
9999
name="username"
100+
autocomplete="off"
100101
label="${this.localize.term('user_loginname')}"
101102
@change="${this.#onUsernameChange}"
102103
required

0 commit comments

Comments
 (0)