User endpoint additions and corrections (#15773)
* Make create user endpoint work with the supplied id Return 201 instead of 200 with correct resource identifier * Add ResetPassword endpoint * Bring changepassword route inline with other resource actions * Fixed User endpoints not advertising all their possible response codes/ models Fixed certain endpoints not authorizing targeted user(s) versus the admin needs admin authorization requirement Fixed a user not found response bug for the update flow Fix spacing * Fixed CurrentUser endpoints not advertising all their possible response codes/ models Fix incorrect responseStatus in UserService.GetPermissionsAsync * Update OpenApi definition Fix smal model oversights in previous commits * Update incorrect Response type * Check for duplicate id's in user create validation * Remove unnecasary returnmodel from changepassword Renamed the model to it's remaining usage * rename bad constructor parameter * Renamed method parameters for better readability and usage * Fixed wrong userkey being passed down because of (refactored) bad naming Technically doesn't change anything as the two id's should be the same in this case (reset with token is always for self) * Fixed resetpassword bug * Update openapi * Update src/Umbraco.Core/Services/UserService.cs Co-authored-by: Kenn Jacobsen <kja@umbraco.dk> * Remove old password from change user password request model Only makes sense when doing it for the logged in user => current endpoint --------- Co-authored-by: Sven Geusens <sge@umbraco.dk> Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User;
|
||||
|
||||
@@ -15,35 +19,46 @@ namespace Umbraco.Cms.Api.Management.Controllers.User;
|
||||
public class ChangePasswordUserController : UserControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public ChangePasswordUserController(
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpPost("change-password/{id:guid}")]
|
||||
[HttpPost("{id:guid}/change-password")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesErrorResponseType(typeof(ChangePasswordUserResponseModel))]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ChangePassword(Guid id, ChangePasswordUserRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
UserPermissionResource.WithKeys(id),
|
||||
AuthorizationPolicies.AdminUserEditsRequireAdmin);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
var passwordModel = new ChangeUserPasswordModel
|
||||
{
|
||||
NewPassword = model.NewPassword,
|
||||
OldPassword = model.OldPassword,
|
||||
UserKey = id,
|
||||
};
|
||||
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.ChangePasswordAsync(CurrentUserKey(_backOfficeSecurityAccessor), passwordModel);
|
||||
|
||||
return response.Success
|
||||
? Ok(_mapper.Map<ChangePasswordUserResponseModel>(response.Result))
|
||||
? Ok()
|
||||
: UserOperationStatusResult(response.Status, response.Result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -23,6 +24,9 @@ public class ClearAvatarUserController : UserControllerBase
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpDelete("avatar/{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ClearAvatar(Guid id)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
|
||||
@@ -24,6 +24,7 @@ public class CreateInitialPasswordUserController : UserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateInitialPassword(CreateInitialPasswordUserRequestModel model)
|
||||
{
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.CreateInitialPasswordAsync(model.User.Id, model.Token, model.Password);
|
||||
|
||||
@@ -35,8 +35,9 @@ public class CreateUserController : UserControllerBase
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(CreateUserResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Create(CreateUserRequestModel model)
|
||||
{
|
||||
UserCreateModel createModel = await _presentationFactory.CreateCreationModelAsync(model);
|
||||
@@ -44,7 +45,7 @@ public class CreateUserController : UserControllerBase
|
||||
Attempt<UserCreationResult, UserOperationStatus> result = await _userService.CreateAsync(CurrentUserKey(_backOfficeSecurityAccessor), createModel, true);
|
||||
|
||||
return result.Success
|
||||
? Ok(_mapper.Map<CreateUserResponseModel>(result.Result))
|
||||
? CreatedAtId<ByKeyUserController>(controller => nameof(controller.ByKey), result.Result.CreatedUser!.Key)
|
||||
: UserOperationStatusResult(result.Status, result.Result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -16,22 +16,20 @@ public class ChangePasswordCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public ChangePasswordCurrentUserController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper)
|
||||
IUserService userService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpPost("change-password")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesErrorResponseType(typeof(ChangePasswordUserResponseModel))]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordUserRequestModel model)
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordCurrentUserRequestModel model)
|
||||
{
|
||||
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
@@ -45,7 +43,7 @@ public class ChangePasswordCurrentUserController : CurrentUserControllerBase
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.ChangePasswordAsync(userKey, changeModel);
|
||||
|
||||
return response.Success
|
||||
? Ok(_mapper.Map<ChangePasswordUserResponseModel>(response.Result))
|
||||
? Ok()
|
||||
: UserOperationStatusResult(response.Status, response.Result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class GetDocumentPermissionsCurrentUserController : CurrentUserController
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("permissions/document")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserPermissionsResponseModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetPermissions([FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt = await _userService.GetDocumentPermissionsAsync(CurrentUserKey(_backOfficeSecurityAccessor), ids);
|
||||
|
||||
@@ -30,7 +30,8 @@ public class GetMediaPermissionsCurrentUserController : CurrentUserControllerBas
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("permissions/media")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserPermissionsResponseModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(UserPermissionsResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetPermissions([FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt = await _userService.GetMediaPermissionsAsync(CurrentUserKey(_backOfficeSecurityAccessor), ids);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class GetPermissionsCurrentUserController : CurrentUserControllerBase
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("permissions")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserPermissionsResponseModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(UserPermissionsResponseModel), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetPermissions([FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt = await _userService.GetPermissionsAsync(CurrentUserKey(_backOfficeSecurityAccessor), ids.ToArray());
|
||||
|
||||
@@ -32,6 +32,7 @@ public class SetAvatarCurrentUserController : CurrentUserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpPost("avatar")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> SetAvatar(SetAvatarRequestModel model)
|
||||
{
|
||||
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
@@ -29,6 +30,9 @@ public class DeleteUserController : UserControllerBase
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpDelete("{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> DeleteUser(Guid id)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
|
||||
@@ -33,6 +33,7 @@ public class DisableUserController : UserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DisableUsers(DisableUserRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
|
||||
@@ -33,6 +33,7 @@ public class EnableUserController : UserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> EnableUsers(EnableUserRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
|
||||
@@ -34,6 +34,7 @@ public class GetAllUserController : UserControllerBase
|
||||
[HttpGet]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<UserResponseModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetAll(int skip = 0, int take = 100)
|
||||
{
|
||||
Attempt<PagedModel<IUser>?, UserOperationStatus> attempt = await _userService.GetAllAsync(CurrentUserKey(_backOfficeSecurityAccessor), skip, take);
|
||||
|
||||
@@ -33,6 +33,8 @@ public class InviteUserController : UserControllerBase
|
||||
[HttpPost("invite")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Invite(InviteUserRequestModel model)
|
||||
{
|
||||
UserInviteModel userInvite = await _userPresentationFactory.CreateInviteModelAsync(model);
|
||||
|
||||
@@ -30,6 +30,7 @@ public class ResendInviteUserController : UserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ResendInvite(ResendInviteUserRequestModel model)
|
||||
{
|
||||
UserResendInviteModel resendInviteModel = await _userPresentationFactory.CreateResendInviteModelAsync(model);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ResetPasswordUserController : UserControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public ResetPasswordUserController(
|
||||
IUserService userService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUmbracoMapper mapper,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_userService = userService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_mapper = mapper;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/reset-password")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(ResetPasswordUserResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ResetPassword(Guid id)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
UserPermissionResource.WithKeys(id),
|
||||
AuthorizationPolicies.AdminUserEditsRequireAdmin);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.ResetPasswordAsync(CurrentUserKey(_backOfficeSecurityAccessor), id);
|
||||
|
||||
return response.Success
|
||||
? Ok(_mapper.Map<ResetPasswordUserResponseModel>(response.Result))
|
||||
: UserOperationStatusResult(response.Status, response.Result);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ public class SetAvatarUserController : UserControllerBase
|
||||
[HttpPost("avatar/{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> SetAvatar(Guid id, SetAvatarRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -8,6 +11,8 @@ using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User;
|
||||
|
||||
@@ -17,20 +22,37 @@ public class UpdateUserController : UserControllerBase
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUserPresentationFactory _userPresentationFactory;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public UpdateUserController(
|
||||
IUserService userService,
|
||||
IUserPresentationFactory userPresentationFactory, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
IUserPresentationFactory userPresentationFactory,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_userService = userService;
|
||||
_userPresentationFactory = userPresentationFactory;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateUserRequestModel model)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
UserPermissionResource.WithKeys(id),
|
||||
AuthorizationPolicies.AdminUserEditsRequireAdmin);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
// We have to use an intermediate save model, and cannot map it directly to an IUserModel
|
||||
// This is because we need to compare the updated values with what the user already has, for audit purposes.
|
||||
UserUpdateModel updateModel = await _userPresentationFactory.CreateUpdateModelAsync(id, model);
|
||||
|
||||
@@ -61,9 +61,9 @@ public abstract class UserOrCurrentUserControllerBase : ManagementApiControllerB
|
||||
.WithTitle("Cannot delete")
|
||||
.WithDetail("A user cannot delete itself.")
|
||||
.Build()),
|
||||
UserOperationStatus.OldPasswordRequired => BadRequest(problemDetailsBuilder
|
||||
UserOperationStatus.SelfOldPasswordRequired => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Old password required")
|
||||
.WithDetail("The old password is required to change the password of the specified user.")
|
||||
.WithDetail("The old password is required to change your own password.")
|
||||
.Build()),
|
||||
UserOperationStatus.InvalidAvatar => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid avatar")
|
||||
@@ -117,6 +117,10 @@ public abstract class UserOrCurrentUserControllerBase : ManagementApiControllerB
|
||||
.WithTitle("Invalid user state")
|
||||
.WithDetail("The target user is not in the invite state.")
|
||||
.Build()),
|
||||
UserOperationStatus.SelfPasswordResetNotAllowed => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Self password reset not allowed")
|
||||
.WithDetail("It is not allowed to reset the password for the account you are logged in to.")
|
||||
.Build()),
|
||||
UserOperationStatus.Forbidden => Forbidden(),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
|
||||
.WithTitle("Unknown user operation status.")
|
||||
|
||||
@@ -23,6 +23,7 @@ public class VerifyInviteUserController : UserControllerBase
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Invite(VerifyInviteUserRequestModel model)
|
||||
{
|
||||
Attempt<UserOperationStatus> result = await _userService.VerifyInviteAsync(model.User.Id, model.Token);
|
||||
|
||||
@@ -78,6 +78,7 @@ public class UserPresentationFactory : IUserPresentationFactory
|
||||
{
|
||||
var createModel = new UserCreateModel
|
||||
{
|
||||
Id = requestModel.Id,
|
||||
Email = requestModel.Email,
|
||||
Name = requestModel.Name,
|
||||
UserName = requestModel.UserName,
|
||||
|
||||
@@ -11,7 +11,7 @@ public class UsersViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<PasswordChangedModel, ChangePasswordUserResponseModel>((_, _) => new ChangePasswordUserResponseModel(), Map);
|
||||
mapper.Define<PasswordChangedModel, ResetPasswordUserResponseModel>((_, _) => new ResetPasswordUserResponseModel(), Map);
|
||||
mapper.Define<UserCreationResult, CreateUserResponseModel>((_, _) => new CreateUserResponseModel { User = new() }, Map);
|
||||
mapper.Define<IIdentityUserLogin, LinkedLoginViewModel>((_, _) => new LinkedLoginViewModel { ProviderKey = string.Empty, ProviderName = string.Empty }, Map);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class UsersViewModelsMapDefinition : IMapDefinition
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(PasswordChangedModel source, ChangePasswordUserResponseModel target, MapperContext context)
|
||||
private void Map(PasswordChangedModel source, ResetPasswordUserResponseModel target, MapperContext context)
|
||||
{
|
||||
target.ResetPassword = source.ResetPassword;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,4 @@ public class ChangePasswordUserRequestModel
|
||||
/// The new password.
|
||||
/// </summary>
|
||||
public required string NewPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The old password.
|
||||
/// </summary>
|
||||
public string? OldPassword { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public class CreateUserRequestModel : UserPresentationBase
|
||||
{
|
||||
|
||||
public Guid? Id { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
|
||||
public class ChangePasswordCurrentUserRequestModel : ChangePasswordUserRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The old password.
|
||||
/// </summary>
|
||||
public string? OldPassword { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
|
||||
public class ChangePasswordUserResponseModel
|
||||
public class ResetPasswordUserResponseModel
|
||||
{
|
||||
public string? ResetPassword { get; set; }
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
public class UserCreateModel
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
@@ -17,6 +17,7 @@ public interface ICoreBackOfficeUserManager
|
||||
Task<IdentityCreationResult> CreateForInvite(UserCreateModel createModel);
|
||||
|
||||
Task<Attempt<string, UserOperationStatus>> GenerateEmailConfirmationTokenAsync(IUser user);
|
||||
|
||||
Task<Attempt<string, UserOperationStatus>> GeneratePasswordResetTokenAsync(IUser user);
|
||||
|
||||
Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockUser(IUser user);
|
||||
@@ -24,6 +25,10 @@ public interface ICoreBackOfficeUserManager
|
||||
Task<Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus>> GetLoginsAsync(IUser user);
|
||||
|
||||
Task<bool> IsEmailConfirmationTokenValidAsync(IUser user, string token);
|
||||
|
||||
Task<bool> IsResetPasswordTokenValidAsync(IUser user, string token);
|
||||
|
||||
void NotifyForgotPasswordRequested(IPrincipal user, string toString);
|
||||
|
||||
public string GeneratePassword();
|
||||
}
|
||||
|
||||
@@ -51,33 +51,33 @@ public interface IUserService : IMembershipUserService
|
||||
/// <remarks>
|
||||
/// This creates both the Umbraco user and the identity user.
|
||||
/// </remarks>
|
||||
/// <param name="userKey">The key of the user performing the operation.</param>
|
||||
/// <param name="performingUserKey">The key of the user performing the operation.</param>
|
||||
/// <param name="model">Model to create the user from.</param>
|
||||
/// <param name="approveUser">Specifies if the user should be enabled be default. Defaults to false.</param>
|
||||
/// <returns>An attempt indicating if the operation was a success as well as a more detailed <see cref="UserOperationStatus"/>.</returns>
|
||||
Task<Attempt<UserCreationResult, UserOperationStatus>> CreateAsync(Guid userKey, UserCreateModel model, bool approveUser = false);
|
||||
Task<Attempt<UserCreationResult, UserOperationStatus>> CreateAsync(Guid performingUserKey, UserCreateModel model, bool approveUser = false);
|
||||
|
||||
Task<Attempt<UserInvitationResult, UserOperationStatus>> InviteAsync(Guid userKey, UserInviteModel model);
|
||||
Task<Attempt<UserInvitationResult, UserOperationStatus>> InviteAsync(Guid performingUserKey, UserInviteModel model);
|
||||
|
||||
Task<Attempt<UserOperationStatus>> VerifyInviteAsync(Guid userKey, string token);
|
||||
|
||||
Task<Attempt<PasswordChangedModel, UserOperationStatus>> CreateInitialPasswordAsync(Guid userKey, string token, string password);
|
||||
|
||||
Task<Attempt<IUser?, UserOperationStatus>> UpdateAsync(Guid userKey, UserUpdateModel model);
|
||||
Task<Attempt<IUser?, UserOperationStatus>> UpdateAsync(Guid performingUserKey, UserUpdateModel model);
|
||||
|
||||
Task<UserOperationStatus> SetAvatarAsync(Guid userKey, Guid temporaryFileKey);
|
||||
|
||||
Task<UserOperationStatus> DeleteAsync(Guid userKey, ISet<Guid> keys);
|
||||
Task<UserOperationStatus> DeleteAsync(Guid performingUserKey, ISet<Guid> keys);
|
||||
|
||||
Task<UserOperationStatus> DeleteAsync(Guid userKey, Guid key) => DeleteAsync(userKey, new HashSet<Guid> { key });
|
||||
Task<UserOperationStatus> DeleteAsync(Guid performingUserKey, Guid key) => DeleteAsync(performingUserKey, new HashSet<Guid> { key });
|
||||
|
||||
Task<UserOperationStatus> DisableAsync(Guid userKey, ISet<Guid> keys);
|
||||
Task<UserOperationStatus> DisableAsync(Guid performingUserKey, ISet<Guid> keys);
|
||||
|
||||
Task<UserOperationStatus> EnableAsync(Guid userKey, ISet<Guid> keys);
|
||||
Task<UserOperationStatus> EnableAsync(Guid performingUserKey, ISet<Guid> keys);
|
||||
|
||||
Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockAsync(Guid userKey, params Guid[] keys);
|
||||
Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockAsync(Guid performingUserKey, params Guid[] keys);
|
||||
|
||||
Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(Guid userKey, ChangeUserPasswordModel model);
|
||||
Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(Guid performingUserKey, ChangeUserPasswordModel model);
|
||||
|
||||
Task<UserOperationStatus> ClearAvatarAsync(Guid userKey);
|
||||
|
||||
@@ -86,11 +86,11 @@ public interface IUserService : IMembershipUserService
|
||||
/// <summary>
|
||||
/// Gets all users that the requesting user is allowed to see.
|
||||
/// </summary>
|
||||
/// <param name="userKey">The Key of the user requesting the users.</param>
|
||||
/// <param name="performingUserKey">The Key of the user requesting the users.</param>
|
||||
/// <param name="skip">Amount to skip.</param>
|
||||
/// <param name="take">Amount to take.</param>
|
||||
/// <returns>All users that the user is allowed to see.</returns>
|
||||
Task<Attempt<PagedModel<IUser>?, UserOperationStatus>> GetAllAsync(Guid userKey, int skip, int take) => throw new NotImplementedException();
|
||||
Task<Attempt<PagedModel<IUser>?, UserOperationStatus>> GetAllAsync(Guid performingUserKey, int skip, int take) => throw new NotImplementedException();
|
||||
|
||||
public Task<Attempt<PagedModel<IUser>, UserOperationStatus>> FilterAsync(
|
||||
Guid userKey,
|
||||
@@ -406,4 +406,6 @@ public interface IUserService : IMembershipUserService
|
||||
Task<Attempt<UserOperationStatus>> SendResetPasswordEmailAsync(string userEmail);
|
||||
|
||||
Task<Attempt<UserInvitationResult, UserOperationStatus>> ResendInvitationAsync(Guid performingUserKey, UserResendInviteModel model);
|
||||
|
||||
Task<Attempt<PasswordChangedModel, UserOperationStatus>> ResetPasswordAsync(Guid performingUserKey, Guid userKey);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public enum UserOperationStatus
|
||||
CannotDisableSelf,
|
||||
CannotDeleteSelf,
|
||||
CannotDisableInvitedUser,
|
||||
OldPasswordRequired,
|
||||
SelfOldPasswordRequired,
|
||||
InvalidAvatar,
|
||||
InvalidIsoCode,
|
||||
InvalidInviteToken,
|
||||
@@ -35,5 +35,7 @@ public enum UserOperationStatus
|
||||
MediaNodeNotFound,
|
||||
UnknownFailure,
|
||||
CannotPasswordReset,
|
||||
NotInInviteState
|
||||
NotInInviteState,
|
||||
SelfPasswordResetNotAllowed,
|
||||
DuplicateId,
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ internal abstract class TwoFactorLoginServiceBase
|
||||
public virtual async Task<Attempt<IEnumerable<UserTwoFactorProviderModel>, TwoFactorOperationStatus>> GetProviderNamesAsync(Guid userKey)
|
||||
{
|
||||
IEnumerable<string> allProviders = _twoFactorLoginService.GetAllProviderNames();
|
||||
var userProviders =(await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(userKey)).ToHashSet();
|
||||
var userProviders = (await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(userKey)).ToHashSet();
|
||||
|
||||
IEnumerable<UserTwoFactorProviderModel> result = allProviders.Select(x => new UserTwoFactorProviderModel(x, userProviders.Contains(x)));
|
||||
return Attempt.SucceedWithStatus(TwoFactorOperationStatus.Success, result);
|
||||
|
||||
@@ -603,12 +603,12 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Attempt<UserCreationResult, UserOperationStatus>> CreateAsync(Guid userKey, UserCreateModel model, bool approveUser = false)
|
||||
public async Task<Attempt<UserCreationResult, UserOperationStatus>> CreateAsync(Guid performingUserKey, UserCreateModel model, bool approveUser = false)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
using IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -622,7 +622,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return Attempt.FailWithStatus(UserOperationStatus.MissingUserGroup, new UserCreationResult());
|
||||
}
|
||||
|
||||
UserOperationStatus result = ValidateUserCreateModel(model);
|
||||
UserOperationStatus result = await ValidateUserCreateModel(model);
|
||||
if (result != UserOperationStatus.Success)
|
||||
{
|
||||
return Attempt.FailWithStatus(result, new UserCreationResult());
|
||||
@@ -724,12 +724,12 @@ internal class UserService : RepositoryService, IUserService
|
||||
|
||||
return Attempt.Succeed(UserOperationStatus.Success);
|
||||
}
|
||||
public async Task<Attempt<UserInvitationResult, UserOperationStatus>> InviteAsync(Guid userKey, UserInviteModel model)
|
||||
public async Task<Attempt<UserInvitationResult, UserOperationStatus>> InviteAsync(Guid performingUserKey, UserInviteModel model)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
using IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -743,7 +743,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return Attempt.FailWithStatus(UserOperationStatus.MissingUserGroup, new UserInvitationResult());
|
||||
}
|
||||
|
||||
UserOperationStatus validationResult = ValidateUserCreateModel(model);
|
||||
UserOperationStatus validationResult = await ValidateUserCreateModel(model);
|
||||
|
||||
if (validationResult is not UserOperationStatus.Success)
|
||||
{
|
||||
@@ -858,7 +858,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return Attempt.SucceedWithStatus(UserOperationStatus.Success, new UserInvitationResult { InvitedUser = invitedUser });
|
||||
}
|
||||
|
||||
private UserOperationStatus ValidateUserCreateModel(UserCreateModel model)
|
||||
private async Task<UserOperationStatus> ValidateUserCreateModel(UserCreateModel model)
|
||||
{
|
||||
if (_securitySettings.UsernameIsEmail && model.UserName != model.Email)
|
||||
{
|
||||
@@ -869,6 +869,11 @@ internal class UserService : RepositoryService, IUserService
|
||||
return UserOperationStatus.InvalidEmail;
|
||||
}
|
||||
|
||||
if (model.Id is not null && await GetAsync(model.Id.Value) is not null)
|
||||
{
|
||||
return UserOperationStatus.DuplicateId;
|
||||
}
|
||||
|
||||
if (GetByEmail(model.Email) is not null)
|
||||
{
|
||||
return UserOperationStatus.DuplicateEmail;
|
||||
@@ -887,7 +892,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return UserOperationStatus.Success;
|
||||
}
|
||||
|
||||
public async Task<Attempt<IUser?, UserOperationStatus>> UpdateAsync(Guid userKey, UserUpdateModel model)
|
||||
public async Task<Attempt<IUser?, UserOperationStatus>> UpdateAsync(Guid performingUserKey, UserUpdateModel model)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
using IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
|
||||
@@ -897,10 +902,10 @@ internal class UserService : RepositoryService, IUserService
|
||||
|
||||
if (existingUser is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.MissingUser, existingUser);
|
||||
return Attempt.FailWithStatus(UserOperationStatus.UserNotFound, existingUser);
|
||||
}
|
||||
|
||||
IUser? performingUser = await userStore.GetAsync(userKey);
|
||||
IUser? performingUser = await userStore.GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -1091,7 +1096,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return keys;
|
||||
}
|
||||
|
||||
public async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(Guid userKey, ChangeUserPasswordModel model)
|
||||
public async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ChangePasswordAsync(Guid performingUserKey, ChangeUserPasswordModel model)
|
||||
{
|
||||
IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
@@ -1103,15 +1108,16 @@ internal class UserService : RepositoryService, IUserService
|
||||
return Attempt.FailWithStatus(UserOperationStatus.UserNotFound, new PasswordChangedModel());
|
||||
}
|
||||
|
||||
IUser? performingUser = await userStore.GetAsync(userKey);
|
||||
IUser? performingUser = await userStore.GetAsync(performingUserKey);
|
||||
if (performingUser is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.MissingUser, new PasswordChangedModel());
|
||||
}
|
||||
|
||||
// require old password for self change when outside of invite or resetByToken flows
|
||||
if (performingUser.UserState != UserState.Invited && performingUser.Username == user.Username && string.IsNullOrEmpty(model.OldPassword) && string.IsNullOrEmpty(model.ResetPasswordToken))
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.OldPasswordRequired, new PasswordChangedModel());
|
||||
return Attempt.FailWithStatus(UserOperationStatus.SelfOldPasswordRequired, new PasswordChangedModel());
|
||||
}
|
||||
|
||||
if (performingUser.IsAdmin() is false && user.IsAdmin())
|
||||
@@ -1121,7 +1127,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
|
||||
if (string.IsNullOrEmpty(model.ResetPasswordToken) is false)
|
||||
{
|
||||
Attempt<UserOperationStatus> verifyPasswordResetAsync = await VerifyPasswordResetAsync(userKey, model.ResetPasswordToken);
|
||||
Attempt<UserOperationStatus> verifyPasswordResetAsync = await VerifyPasswordResetAsync(model.UserKey, model.ResetPasswordToken);
|
||||
if (verifyPasswordResetAsync.Result != UserOperationStatus.Success)
|
||||
{
|
||||
return Attempt.FailWithStatus(verifyPasswordResetAsync.Result, new PasswordChangedModel());
|
||||
@@ -1147,11 +1153,11 @@ internal class UserService : RepositoryService, IUserService
|
||||
return Attempt.SucceedWithStatus(UserOperationStatus.Success, result.Result ?? new PasswordChangedModel());
|
||||
}
|
||||
|
||||
public async Task<Attempt<PagedModel<IUser>?, UserOperationStatus>> GetAllAsync(Guid userKey, int skip, int take)
|
||||
public async Task<Attempt<PagedModel<IUser>?, UserOperationStatus>> GetAllAsync(Guid performingUserKey, int skip, int take)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
|
||||
IUser? requestingUser = await GetAsync(userKey);
|
||||
IUser? requestingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (requestingUser is null)
|
||||
{
|
||||
@@ -1364,7 +1370,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<UserOperationStatus> DeleteAsync(Guid userKey, ISet<Guid> keys)
|
||||
public async Task<UserOperationStatus> DeleteAsync(Guid performingUserKey, ISet<Guid> keys)
|
||||
{
|
||||
if(keys.Any() is false)
|
||||
{
|
||||
@@ -1372,7 +1378,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -1412,7 +1418,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return UserOperationStatus.Success;
|
||||
}
|
||||
|
||||
public async Task<UserOperationStatus> DisableAsync(Guid userKey, ISet<Guid> keys)
|
||||
public async Task<UserOperationStatus> DisableAsync(Guid performingUserKey, ISet<Guid> keys)
|
||||
{
|
||||
if(keys.Any() is false)
|
||||
{
|
||||
@@ -1420,7 +1426,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -1458,7 +1464,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return UserOperationStatus.Success;
|
||||
}
|
||||
|
||||
public async Task<UserOperationStatus> EnableAsync(Guid userKey, ISet<Guid> keys)
|
||||
public async Task<UserOperationStatus> EnableAsync(Guid performingUserKey, ISet<Guid> keys)
|
||||
{
|
||||
if(keys.Any() is false)
|
||||
{
|
||||
@@ -1466,7 +1472,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -1528,7 +1534,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
return UserOperationStatus.Success;
|
||||
}
|
||||
|
||||
public async Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockAsync(Guid userKey, params Guid[] keys)
|
||||
public async Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockAsync(Guid performingUserKey, params Guid[] keys)
|
||||
{
|
||||
if (keys.Length == 0)
|
||||
{
|
||||
@@ -1536,7 +1542,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
IUser? performingUser = await GetAsync(userKey);
|
||||
IUser? performingUser = await GetAsync(performingUserKey);
|
||||
|
||||
if (performingUser is null)
|
||||
{
|
||||
@@ -2102,6 +2108,40 @@ internal class UserService : RepositoryService, IUserService
|
||||
return changePasswordAttempt;
|
||||
}
|
||||
|
||||
public async Task<Attempt<PasswordChangedModel, UserOperationStatus>> ResetPasswordAsync(Guid performingUserKey, Guid userKey)
|
||||
{
|
||||
if (performingUserKey.Equals(userKey))
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.SelfPasswordResetNotAllowed, new PasswordChangedModel());
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
using IServiceScope serviceScope = _serviceScopeFactory.CreateScope();
|
||||
|
||||
ICoreBackOfficeUserManager backOfficeUserManager = serviceScope.ServiceProvider.GetRequiredService<ICoreBackOfficeUserManager>();
|
||||
|
||||
var generatedPassword = backOfficeUserManager.GeneratePassword();
|
||||
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> changePasswordAttempt =
|
||||
await ChangePasswordAsync(performingUserKey, new ChangeUserPasswordModel
|
||||
{
|
||||
NewPassword = generatedPassword,
|
||||
UserKey = userKey,
|
||||
});
|
||||
|
||||
scope.Complete();
|
||||
|
||||
// todo tidy this up
|
||||
// this should be part of the result of the ChangePasswordAsync() method
|
||||
// but the model requires NewPassword
|
||||
// and the passwordChanger does not have a codePath that deals with generating
|
||||
if (changePasswordAttempt.Success)
|
||||
{
|
||||
changePasswordAttempt.Result.ResetPassword = generatedPassword;
|
||||
}
|
||||
|
||||
return changePasswordAttempt;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -2234,7 +2274,7 @@ internal class UserService : RepositoryService, IUserService
|
||||
results.Add(new NodePermissions { NodeKey = idKeyMap[nodeId], Permissions = permissions });
|
||||
}
|
||||
|
||||
return Attempt.SucceedWithStatus<IEnumerable<NodePermissions>, UserOperationStatus>(UserOperationStatus.UserNotFound, results);
|
||||
return Attempt.SucceedWithStatus<IEnumerable<NodePermissions>, UserOperationStatus>(UserOperationStatus.Success, results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -123,7 +123,7 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser
|
||||
/// <param name="email">This is allowed to be null (but would need to be filled in if trying to persist this instance)</param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="name"></param>
|
||||
public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string? username, string email, string culture, string? name = null)
|
||||
public static BackOfficeIdentityUser CreateNew(GlobalSettings globalSettings, string? username, string email, string culture, string? name = null, Guid? id = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
@@ -139,8 +139,13 @@ public class BackOfficeIdentityUser : UmbracoIdentityUser
|
||||
user.DisableChangeTracking();
|
||||
user.UserName = username;
|
||||
user.Email = email;
|
||||
|
||||
user.Id = string.Empty;
|
||||
|
||||
if (id is not null)
|
||||
{
|
||||
user.Key = id.Value;
|
||||
}
|
||||
|
||||
user.HasIdentity = false;
|
||||
user._culture = culture;
|
||||
user.Name = name;
|
||||
|
||||
@@ -140,8 +140,10 @@ public class BackOfficeUserStore :
|
||||
StartContentIds = user.StartContentIds ?? new int[] { },
|
||||
StartMediaIds = user.StartMediaIds ?? new int[] { },
|
||||
IsLockedOut = user.IsLockedOut,
|
||||
Key = user.Key,
|
||||
};
|
||||
|
||||
|
||||
// we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
|
||||
var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins));
|
||||
var isTokensPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.LoginTokens));
|
||||
|
||||
@@ -296,9 +296,9 @@ public class BackOfficeUserManager : UmbracoUserManager<BackOfficeIdentityUser,
|
||||
_globalSettings,
|
||||
createModel.UserName,
|
||||
createModel.Email,
|
||||
_globalSettings.DefaultUILanguage);
|
||||
|
||||
identityUser.Name = createModel.Name;
|
||||
_globalSettings.DefaultUILanguage,
|
||||
createModel.Name,
|
||||
createModel.Id);
|
||||
|
||||
IdentityResult created = await CreateAsync(identityUser);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user