V14: Current user controller (#14323)
* Add current user data endpoint * Add Change password endpoint * Add SetAvatar * Add get node permissions * Add endpoint for getting currently logged in users linked logins * Add tour service * Add get tours * Add set tour endpoint * Split permissions endpoint in two, one for media and one for document
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Tour;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class GetTourController : TourControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly ITourService _tourService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public GetTourController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
ITourService tourService,
|
||||
IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_tourService = tourService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(UserTourStatusesResponseModel), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetTours()
|
||||
{
|
||||
Guid currentUserKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
Attempt<IEnumerable<UserTourStatus>, TourOperationStatus> toursAttempt = await _tourService.GetAllAsync(currentUserKey);
|
||||
|
||||
if (toursAttempt.Success == false)
|
||||
{
|
||||
return TourOperationStatusResult(toursAttempt.Status);
|
||||
}
|
||||
|
||||
List<TourStatusViewModel> models = _umbracoMapper.MapEnumerable<UserTourStatus, TourStatusViewModel>(toursAttempt.Result);
|
||||
return Ok(new UserTourStatusesResponseModel { TourStatuses = models });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Tour;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class SetTourController : TourControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly ITourService _tourService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public SetTourController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
ITourService tourService,
|
||||
IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_tourService = tourService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SetTour(SetTourStatusRequestModel model)
|
||||
{
|
||||
Guid currentUserKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
UserTourStatus tourStatus = _umbracoMapper.Map<UserTourStatus>(model)!;
|
||||
|
||||
TourOperationStatus attempt = await _tourService.SetAsync(tourStatus, currentUserKey);
|
||||
return TourOperationStatusResult(attempt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Tour;
|
||||
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute("tour")]
|
||||
[ApiExplorerSettings(GroupName = "Tour")]
|
||||
public class TourControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected IActionResult TourOperationStatusResult(TourOperationStatus status) =>
|
||||
status switch
|
||||
{
|
||||
TourOperationStatus.Success => Ok(),
|
||||
TourOperationStatus.UserNotFound => NotFound(new ProblemDetailsBuilder()
|
||||
.WithTitle("User not found")
|
||||
.WithDetail("Was not able to find currently logged in user")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown tour operation status.")
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,8 @@ public class ChangePasswordUserController : UserControllerBase
|
||||
|
||||
public ChangePasswordUserController(
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
IUmbracoMapper mapper,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
@@ -29,6 +30,7 @@ public class ChangePasswordUserController : UserControllerBase
|
||||
|
||||
[HttpPost("change-password/{id:guid}")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesErrorResponseType(typeof(ChangePasswordUserResponseModel))]
|
||||
public async Task<IActionResult> ChangePassword(Guid id, ChangePasswordUserRequestModel model)
|
||||
{
|
||||
var passwordModel = new ChangeUserPasswordModel
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ChangePasswordCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public ChangePasswordCurrentUserController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpPost("change-password")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesErrorResponseType(typeof(ChangePasswordUserResponseModel))]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordUserRequestModel model)
|
||||
{
|
||||
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
var changeModel = new ChangeUserPasswordModel
|
||||
{
|
||||
NewPassword = model.NewPassword,
|
||||
OldPassword = model.OldPassword,
|
||||
UserKey = userKey,
|
||||
};
|
||||
|
||||
Attempt<PasswordChangedModel, UserOperationStatus> response = await _userService.ChangePasswordAsync(userKey, changeModel);
|
||||
|
||||
return response.Success
|
||||
? Ok(_mapper.Map<ChangePasswordUserResponseModel>(response.Result))
|
||||
: UserOperationStatusResult(response.Status, response.Result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute("user/current")]
|
||||
[ApiExplorerSettings(GroupName = "User")]
|
||||
public abstract class CurrentUserControllerBase : UserControllerBase
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class GetDataCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IUserDataService _userDataService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public GetDataCurrentUserController(
|
||||
IUserDataService userDataService,
|
||||
IUmbracoMapper mapper)
|
||||
{
|
||||
_userDataService = userDataService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("data")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(UserDataResponseModel), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> GetUserData()
|
||||
{
|
||||
IEnumerable<UserDataViewModel?> userData = _userDataService.GetUserData().Select(x => _mapper.Map<UserDataViewModel>(x));
|
||||
|
||||
return Task.FromResult<IActionResult>(Ok(new UserDataResponseModel { UserData = userData! }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
public class GetDocumentPermissionsCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public GetDocumentPermissionsCurrentUserController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("permissions/document")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserPermissionsResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetPermissions([FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt = await _userService.GetDocumentPermissionsAsync(CurrentUserKey(_backOfficeSecurityAccessor), ids);
|
||||
|
||||
if (permissionsAttempt.Success is false)
|
||||
{
|
||||
return UserOperationStatusResult(permissionsAttempt.Status);
|
||||
}
|
||||
|
||||
List<UserPermissionViewModel> viewModels = _mapper.MapEnumerable<NodePermissions, UserPermissionViewModel>(permissionsAttempt.Result);
|
||||
|
||||
return Ok(new UserPermissionsResponseModel { Permissions = viewModels });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class GetLinkedLoginsCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public GetLinkedLoginsCurrentUserController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("logins")]
|
||||
[ProducesResponseType(typeof(LinkedLoginsRequestModel), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetLinkedLogins()
|
||||
{
|
||||
Guid currentUserKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus> linkedLoginsAttempt = await _userService.GetLinkedLoginsAsync(currentUserKey);
|
||||
|
||||
if (linkedLoginsAttempt.Success == false)
|
||||
{
|
||||
return UserOperationStatusResult(linkedLoginsAttempt.Status);
|
||||
}
|
||||
|
||||
List<LinkedLoginViewModel> models = _umbracoMapper.MapEnumerable<IIdentityUserLogin, LinkedLoginViewModel>(linkedLoginsAttempt.Result);
|
||||
|
||||
return Ok(new LinkedLoginsRequestModel { LinkedLogins = models });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
public class GetMediaPermissionsCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public GetMediaPermissionsCurrentUserController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IUserService userService,
|
||||
IUmbracoMapper mapper)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_userService = userService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpGet("permissions/media")]
|
||||
[ProducesResponseType(typeof(IEnumerable<UserPermissionsResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetPermissions([FromQuery(Name = "id")] HashSet<Guid> ids)
|
||||
{
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissionsAttempt = await _userService.GetMediaPermissionsAsync(CurrentUserKey(_backOfficeSecurityAccessor), ids);
|
||||
|
||||
if (permissionsAttempt.Success is false)
|
||||
{
|
||||
return UserOperationStatusResult(permissionsAttempt.Status);
|
||||
}
|
||||
|
||||
List<UserPermissionViewModel> viewModels = _mapper.MapEnumerable<NodePermissions, UserPermissionViewModel>(permissionsAttempt.Result);
|
||||
|
||||
return Ok(new UserPermissionsResponseModel { Permissions = viewModels });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.User.Current;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class SetAvatarCurrentUserController : CurrentUserControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public SetAvatarCurrentUserController(
|
||||
IUserService userService,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_userService = userService;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[HttpPost("avatar")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SetAvatar(SetAvatarRequestModel model)
|
||||
{
|
||||
Guid userKey = CurrentUserKey(_backOfficeSecurityAccessor);
|
||||
|
||||
UserOperationStatus result = await _userService.SetAvatarAsync(userKey, model.FileId);
|
||||
|
||||
return result is UserOperationStatus.Success
|
||||
? Ok()
|
||||
: UserOperationStatusResult(result);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
@@ -96,6 +95,14 @@ public abstract class UserControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("Invalid ISO code")
|
||||
.WithDetail("The specified ISO code is invalid.")
|
||||
.Build()),
|
||||
UserOperationStatus.MediaNodeNotFound => NotFound(new ProblemDetailsBuilder()
|
||||
.WithTitle("Media node not found")
|
||||
.WithDetail("The specified media node was not found.")
|
||||
.Build()),
|
||||
UserOperationStatus.ContentNodeNotFound => NotFound(new ProblemDetailsBuilder()
|
||||
.WithTitle("Content node not found")
|
||||
.WithDetail("The specified content node was not found.")
|
||||
.Build()),
|
||||
UserOperationStatus.Forbidden => Forbid(),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown user operation status."),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Cms.Api.Management.Mapping.Tour;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class TourBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddTours(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<TourViewModelsMapDefinition>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ internal static class UsersBuilderExtensions
|
||||
builder.Services.AddTransient<IUserPresentationFactory, UserPresentationFactory>();
|
||||
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<UsersViewModelsMapDefinition>();
|
||||
.Add<UsersViewModelsMapDefinition>()
|
||||
.Add<CurrentUserViewModelsMapDefinition>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public class ManagementApiComposer : IComposer
|
||||
.AddLogViewer()
|
||||
.AddUsers()
|
||||
.AddUserGroups()
|
||||
.AddTours()
|
||||
.AddPackages()
|
||||
.AddEntities()
|
||||
.AddPathFolders()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Tour;
|
||||
|
||||
public class TourViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<UserTourStatus, TourStatusViewModel>((_, _) => new TourStatusViewModel{ Alias = string.Empty}, Map);
|
||||
mapper.Define<SetTourStatusRequestModel, UserTourStatus>((_, _) => new UserTourStatus(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(SetTourStatusRequestModel source, UserTourStatus target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Completed = source.Completed;
|
||||
target.Disabled = source.Disabled;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(UserTourStatus source, TourStatusViewModel target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Completed = source.Completed;
|
||||
target.Disabled = source.Disabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Users;
|
||||
|
||||
public class CurrentUserViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<UserData, UserDataViewModel>((_, _) => new UserDataViewModel {Data = string.Empty, Name = string.Empty }, Map);
|
||||
mapper.Define<NodePermissions, UserPermissionViewModel>((_, _) => new UserPermissionViewModel(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(NodePermissions source, UserPermissionViewModel target, MapperContext context)
|
||||
{
|
||||
target.NodeKey = source.NodeKey;
|
||||
target.Permissions = source.Permissions;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(UserData source, UserDataViewModel target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
target.Data = source.Data;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Users;
|
||||
|
||||
@@ -11,14 +12,24 @@ public class UsersViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
mapper.Define<PasswordChangedModel, ChangePasswordUserResponseModel>((_, _) => new ChangePasswordUserResponseModel(), Map);
|
||||
mapper.Define<UserCreationResult, CreateUserResponseModel>((_, _) => new CreateUserResponseModel(), Map);
|
||||
mapper.Define<IIdentityUserLogin, LinkedLoginViewModel>((_, _) => new LinkedLoginViewModel { ProviderKey = string.Empty, ProviderName = string.Empty }, Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(IIdentityUserLogin source, LinkedLoginViewModel target, MapperContext context)
|
||||
{
|
||||
target.ProviderKey = source.ProviderKey;
|
||||
target.ProviderName = source.LoginProvider;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(UserCreationResult source, CreateUserResponseModel target, MapperContext context)
|
||||
{
|
||||
target.UserId = source.CreatedUser?.Key ?? Guid.Empty;
|
||||
target.InitialPassword = source.InitialPassword;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(PasswordChangedModel source, ChangePasswordUserResponseModel target, MapperContext context)
|
||||
{
|
||||
target.ResetPassword = source.ResetPassword;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
|
||||
public class SetTourStatusRequestModel : TourStatusViewModel
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
|
||||
public class UserTourStatusesResponseModel
|
||||
{
|
||||
public required IEnumerable<TourStatusViewModel> TourStatuses { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Tour;
|
||||
|
||||
public class TourStatusViewModel
|
||||
{
|
||||
public required string Alias { get; set; }
|
||||
|
||||
public bool Completed { get; set; }
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
|
||||
public class UserDataResponseModel
|
||||
{
|
||||
public IEnumerable<UserDataViewModel> UserData { get; set; } = Enumerable.Empty<UserDataViewModel>();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
|
||||
public class UserDataViewModel
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
|
||||
public required string Data { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
|
||||
public class UserPermissionViewModel
|
||||
{
|
||||
public Guid NodeKey { get; set; }
|
||||
|
||||
public IEnumerable<string> Permissions { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
|
||||
|
||||
public class UserPermissionsResponseModel
|
||||
{
|
||||
public IEnumerable<UserPermissionViewModel> Permissions { get; set; } = Array.Empty<UserPermissionViewModel>();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
|
||||
public class LinkedLoginViewModel
|
||||
{
|
||||
public required string ProviderName { get; set; }
|
||||
|
||||
public required string ProviderKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.User;
|
||||
|
||||
public class LinkedLoginsRequestModel
|
||||
{
|
||||
public IEnumerable<LinkedLoginViewModel> LinkedLogins { get; set; } = Enumerable.Empty<LinkedLoginViewModel>();
|
||||
}
|
||||
@@ -282,6 +282,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddTransient<IUserGroupAuthorizationService, UserGroupAuthorizationService>();
|
||||
Services.AddUnique<IUserGroupService, UserGroupService>();
|
||||
Services.AddUnique<IUserService, UserService>();
|
||||
Services.AddUnique<ITourService, TourService>();
|
||||
Services.AddUnique<IWebProfilerService, WebProfilerService>();
|
||||
Services.AddUnique<ILocalizationService, LocalizationService>();
|
||||
Services.AddUnique<IDictionaryItemService, DictionaryItemService>();
|
||||
|
||||
11
src/Umbraco.Core/Models/NodePermissions.cs
Normal file
11
src/Umbraco.Core/Models/NodePermissions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A model representing a set of permissions for a given node.
|
||||
/// </summary>
|
||||
public class NodePermissions
|
||||
{
|
||||
public Guid NodeKey { get; set; }
|
||||
|
||||
public IEnumerable<string> Permissions { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
@@ -18,4 +18,6 @@ public interface ICoreBackOfficeUserManager
|
||||
Task<Attempt<string, UserOperationStatus>> GenerateEmailConfirmationTokenAsync(IUser user);
|
||||
|
||||
Task<Attempt<UserUnlockResult, UserOperationStatus>> UnlockUser(IUser user);
|
||||
|
||||
Task<Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus>> GetLoginsAsync(IUser user);
|
||||
}
|
||||
|
||||
22
src/Umbraco.Core/Services/ITourService.cs
Normal file
22
src/Umbraco.Core/Services/ITourService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface ITourService
|
||||
{
|
||||
/// <summary>
|
||||
/// Persists a <see cref="UserTourStatus"/> for a user.
|
||||
/// </summary>
|
||||
/// <param name="status">The status to persist.</param>
|
||||
/// <param name="userKey">The key of the user to persist it for.</param>
|
||||
/// <returns>An operation status specifying if the operation was successful.</returns>
|
||||
Task<TourOperationStatus> SetAsync(UserTourStatus status, Guid userKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="UserTourStatus"/> for a user.
|
||||
/// </summary>
|
||||
/// <param name="userKey">The key of the user to get tour data for.</param>
|
||||
/// <returns>An attempt containing an enumerable of <see cref="UserTourStatus"/> and a status.</returns>
|
||||
Task<Attempt<IEnumerable<UserTourStatus>, TourOperationStatus>> GetAllAsync(Guid userKey);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
@@ -74,6 +75,8 @@ public interface IUserService : IMembershipUserService
|
||||
|
||||
Task<UserOperationStatus> ClearAvatarAsync(Guid userKey);
|
||||
|
||||
Task<Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus>> GetLinkedLoginsAsync(Guid userKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all users that the requesting user is allowed to see.
|
||||
/// </summary>
|
||||
@@ -200,6 +203,22 @@ public interface IUserService : IMembershipUserService
|
||||
/// <param name="sectionAlias">Alias of the section to remove</param>
|
||||
void DeleteSectionFromAllUserGroups(string sectionAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Get explicitly assigned content permissions for a user and node keys.
|
||||
/// </summary>
|
||||
/// <param name="userKey">Key of user to retrieve permissions for. </param>
|
||||
/// <param name="mediaKeys">The keys of the media to get permissions for.</param>
|
||||
/// <returns>An attempt indicating if the operation was a success as well as a more detailed <see cref="UserOperationStatus"/>, and an enumerable of permissions.</returns>
|
||||
Task<Attempt<IEnumerable<NodePermissions>, UserOperationStatus>> GetMediaPermissionsAsync(Guid userKey, IEnumerable<Guid> mediaKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Get explicitly assigned media permissions for a user and node keys.
|
||||
/// </summary>
|
||||
/// <param name="userKey">Key of user to retrieve permissions for. </param>
|
||||
/// <param name="contentKeys">The keys of the content to get permissions for.</param>
|
||||
/// <returns>An attempt indicating if the operation was a success as well as a more detailed <see cref="UserOperationStatus"/>, and an enumerable of permissions.</returns>
|
||||
Task<Attempt<IEnumerable<NodePermissions>, UserOperationStatus>> GetDocumentPermissionsAsync(Guid userKey, IEnumerable<Guid> contentKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Get explicitly assigned permissions for a user and optional node ids
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum TourOperationStatus
|
||||
{
|
||||
Success,
|
||||
UserNotFound,
|
||||
}
|
||||
@@ -25,5 +25,7 @@ public enum UserOperationStatus
|
||||
InvalidIsoCode,
|
||||
ContentStartNodeNotFound,
|
||||
MediaStartNodeNotFound,
|
||||
ContentNodeNotFound,
|
||||
MediaNodeNotFound,
|
||||
UnknownFailure,
|
||||
}
|
||||
|
||||
88
src/Umbraco.Core/Services/TourService.cs
Normal file
88
src/Umbraco.Core/Services/TourService.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
|
||||
/**
|
||||
* TODO: This implementation is not the greatest,
|
||||
* ideally we should store tour information in its own table
|
||||
* making it its own feature, instead of an ad-hoc "add-on" to users.
|
||||
* additionally we should probably not store it as a JSON blob, but instead as a proper table.
|
||||
* For now we'll keep doing the deserialize/serialize dance here,
|
||||
* because there is no reason to spend cycles to deserialize/serialize the tour data every time we fetch/save a user.
|
||||
*/
|
||||
public class TourService : ITourService
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public TourService(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IUserService userService)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TourOperationStatus> SetAsync(UserTourStatus status, Guid userKey)
|
||||
{
|
||||
IUser? user = await _userService.GetAsync(userKey);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return TourOperationStatus.UserNotFound;
|
||||
}
|
||||
|
||||
// If the user currently have no tour data, we can just add the data and save it.
|
||||
if (string.IsNullOrWhiteSpace(user.TourData))
|
||||
{
|
||||
List<UserTourStatus> tours = new() { status };
|
||||
user.TourData = _jsonSerializer.Serialize(tours);
|
||||
_userService.Save(user);
|
||||
|
||||
return TourOperationStatus.Success;
|
||||
}
|
||||
|
||||
// Otherwise we have to check it it already exists, and if so, replace it.
|
||||
List<UserTourStatus> existingTours =
|
||||
_jsonSerializer.Deserialize<IEnumerable<UserTourStatus>>(user.TourData)?.ToList() ?? new List<UserTourStatus>();
|
||||
UserTourStatus? found = existingTours.FirstOrDefault(x => x.Alias == status.Alias);
|
||||
|
||||
if (found is not null)
|
||||
{
|
||||
existingTours.Remove(found);
|
||||
}
|
||||
|
||||
existingTours.Add(status);
|
||||
|
||||
user.TourData = _jsonSerializer.Serialize(existingTours);
|
||||
_userService.Save(user);
|
||||
return TourOperationStatus.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Attempt<IEnumerable<UserTourStatus>, TourOperationStatus>> GetAllAsync(Guid userKey)
|
||||
{
|
||||
IUser? user = await _userService.GetAsync(userKey);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(TourOperationStatus.UserNotFound, Enumerable.Empty<UserTourStatus>());
|
||||
}
|
||||
|
||||
// No tour data, we'll just return empty.
|
||||
if (string.IsNullOrWhiteSpace(user.TourData))
|
||||
{
|
||||
return Attempt.SucceedWithStatus(TourOperationStatus.Success, Enumerable.Empty<UserTourStatus>());
|
||||
}
|
||||
|
||||
IEnumerable<UserTourStatus> tours = _jsonSerializer.Deserialize<IEnumerable<UserTourStatus>>(user.TourData)
|
||||
?? Enumerable.Empty<UserTourStatus>();
|
||||
|
||||
return Attempt.SucceedWithStatus(TourOperationStatus.Success, tours);
|
||||
}
|
||||
}
|
||||
@@ -1642,6 +1642,26 @@ internal class UserService : RepositoryService, IUserService
|
||||
return backOfficeUserStore.GetUsersAsync(keys.ToArray());
|
||||
}
|
||||
|
||||
public async Task<Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus>> GetLinkedLoginsAsync(Guid userKey)
|
||||
{
|
||||
using IServiceScope scope = _serviceScopeFactory.CreateScope();
|
||||
IBackOfficeUserStore backOfficeUserStore = scope.ServiceProvider.GetRequiredService<IBackOfficeUserStore>();
|
||||
|
||||
IUser? user = await backOfficeUserStore.GetAsync(userKey);
|
||||
if (user is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<ICollection<IIdentityUserLogin>, UserOperationStatus>(UserOperationStatus.UserNotFound, Array.Empty<IIdentityUserLogin>());
|
||||
}
|
||||
|
||||
ICoreBackOfficeUserManager manager = scope.ServiceProvider.GetRequiredService<ICoreBackOfficeUserManager>();
|
||||
|
||||
Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus> loginsAttempt = await manager.GetLoginsAsync(user);
|
||||
|
||||
return loginsAttempt.Success is false
|
||||
? Attempt.FailWithStatus<ICollection<IIdentityUserLogin>, UserOperationStatus>(loginsAttempt.Status, Array.Empty<IIdentityUserLogin>())
|
||||
: Attempt.SucceedWithStatus(UserOperationStatus.Success, loginsAttempt.Result);
|
||||
}
|
||||
|
||||
public IEnumerable<IUser> GetUsersById(params int[]? ids)
|
||||
{
|
||||
using IServiceScope scope = _serviceScopeFactory.CreateScope();
|
||||
@@ -1900,6 +1920,84 @@ internal class UserService : RepositoryService, IUserService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Attempt<IEnumerable<NodePermissions>, UserOperationStatus>> GetMediaPermissionsAsync(Guid userKey, IEnumerable<Guid> mediaKeys)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
Attempt<Dictionary<Guid, int>?> idAttempt = CreateIdKeyMap(mediaKeys, UmbracoObjectTypes.Media);
|
||||
|
||||
if (idAttempt.Success is false || idAttempt.Result is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.MediaNodeNotFound, Enumerable.Empty<NodePermissions>());
|
||||
}
|
||||
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissions = await GetPermissionsAsync(userKey, idAttempt.Result);
|
||||
scope.Complete();
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Attempt<IEnumerable<NodePermissions>, UserOperationStatus>> GetDocumentPermissionsAsync(Guid userKey, IEnumerable<Guid> contentKeys)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
Attempt<Dictionary<Guid, int>?> idAttempt = CreateIdKeyMap(contentKeys, UmbracoObjectTypes.Document);
|
||||
|
||||
if (idAttempt.Success is false || idAttempt.Result is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.ContentNodeNotFound, Enumerable.Empty<NodePermissions>());
|
||||
}
|
||||
|
||||
Attempt<IEnumerable<NodePermissions>, UserOperationStatus> permissions = await GetPermissionsAsync(userKey, idAttempt.Result);
|
||||
scope.Complete();
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
|
||||
private async Task<Attempt<IEnumerable<NodePermissions>, UserOperationStatus>> GetPermissionsAsync(Guid userKey, Dictionary<Guid, int> nodes)
|
||||
{
|
||||
IUser? user = await GetAsync(userKey);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Attempt.FailWithStatus(UserOperationStatus.UserNotFound, Enumerable.Empty<NodePermissions>());
|
||||
}
|
||||
|
||||
EntityPermissionCollection permissionsCollection = _userGroupRepository.GetPermissions(
|
||||
user.Groups.ToArray(),
|
||||
true,
|
||||
nodes.Select(x => x.Value).ToArray());
|
||||
|
||||
var results = new List<NodePermissions>();
|
||||
foreach (KeyValuePair<Guid, int> node in nodes)
|
||||
{
|
||||
var permissions = permissionsCollection.GetAllPermissions(node.Value).ToArray();
|
||||
results.Add(new NodePermissions { NodeKey = node.Key, Permissions = permissions });
|
||||
}
|
||||
|
||||
return Attempt.SucceedWithStatus<IEnumerable<NodePermissions>, UserOperationStatus>(UserOperationStatus.Success, results);
|
||||
}
|
||||
|
||||
private Attempt<Dictionary<Guid, int>?> CreateIdKeyMap(IEnumerable<Guid> nodeKeys, UmbracoObjectTypes objectType)
|
||||
{
|
||||
// We'll return this as a dictionary we can link the id and key again later.
|
||||
Dictionary<Guid, int> idKeys = new();
|
||||
|
||||
foreach (Guid key in nodeKeys)
|
||||
{
|
||||
Attempt<int> idAttempt = _entityService.GetId(key, objectType);
|
||||
if (idAttempt.Success is false)
|
||||
{
|
||||
return Attempt.Fail<Dictionary<Guid, int>?>(null);
|
||||
}
|
||||
|
||||
idKeys[key] = idAttempt.Result;
|
||||
}
|
||||
|
||||
return Attempt.Succeed<Dictionary<Guid, int>?>(idKeys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get explicitly assigned permissions for a user and optional node ids
|
||||
/// </summary>
|
||||
|
||||
@@ -330,4 +330,16 @@ public class BackOfficeUserManager : UmbracoUserManager<BackOfficeIdentityUser,
|
||||
|
||||
return Attempt.SucceedWithStatus(UserOperationStatus.Success, token);
|
||||
}
|
||||
|
||||
public async Task<Attempt<ICollection<IIdentityUserLogin>, UserOperationStatus>> GetLoginsAsync(IUser user)
|
||||
{
|
||||
BackOfficeIdentityUser? identityUser = await FindByIdAsync(user.Id.ToString());
|
||||
if (identityUser is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<ICollection<IIdentityUserLogin>, UserOperationStatus>(UserOperationStatus.UserNotFound, Array.Empty<IIdentityUserLogin>());
|
||||
}
|
||||
|
||||
return Attempt.SucceedWithStatus(UserOperationStatus.Success, identityUser.Logins);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user