Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ status is ContentTypeOperationStatus.Success
.WithTitle("Duplicate property type alias")
.WithDetail("One or more property type aliases are already in use, all property type aliases must be unique.")
.Build()),
ContentTypeOperationStatus.NotAllowed => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("Operation not permitted")
.WithDetail("The attempted operation was not permitted, likely due to a permission/configuration mismatch with the operation.")
.Build()),
_ => new ObjectResult("Unknown content type operation status") { StatusCode = StatusCodes.Status500InternalServerError },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Member;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;

namespace Umbraco.Cms.Api.Management.Controllers.Member;
Expand All @@ -13,11 +14,16 @@ public class ByKeyMemberController : MemberControllerBase
{
private readonly IMemberEditingService _memberEditingService;
private readonly IMemberPresentationFactory _memberPresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

public ByKeyMemberController(IMemberEditingService memberEditingService, IMemberPresentationFactory memberPresentationFactory)
public ByKeyMemberController(
IMemberEditingService memberEditingService,
IMemberPresentationFactory memberPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_memberEditingService = memberEditingService;
_memberPresentationFactory = memberPresentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}

[HttpGet("{id:guid}")]
Expand All @@ -32,7 +38,7 @@ public async Task<IActionResult> ByKey(Guid id)
return MemberNotFound();
}

MemberResponseModel model = await _memberPresentationFactory.CreateResponseModelAsync(member);
MemberResponseModel model = await _memberPresentationFactory.CreateResponseModelAsync(member, CurrentUser(_backOfficeSecurityAccessor));
return Ok(model);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;

namespace Umbraco.Cms.Api.Management.Controllers.Member.Filter;
Expand All @@ -16,13 +17,16 @@ public class FilterMemberFilterController : MemberFilterControllerBase
{
private readonly IMemberService _memberService;
private readonly IMemberPresentationFactory _memberPresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

public FilterMemberFilterController(
IMemberService memberService,
IMemberPresentationFactory memberPresentationFactory)
IMemberPresentationFactory memberPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_memberService = memberService;
_memberPresentationFactory = memberPresentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}

[HttpGet]
Expand Down Expand Up @@ -53,7 +57,7 @@ public async Task<IActionResult> Filter(

var pageViewModel = new PagedViewModel<MemberResponseModel>
{
Items = await _memberPresentationFactory.CreateMultipleAsync(members.Items),
Items = await _memberPresentationFactory.CreateMultipleAsync(members.Items, CurrentUser(_backOfficeSecurityAccessor)),
Total = members.Total,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
Expand All @@ -13,12 +13,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
public class ByKeyMemberTypeController : MemberTypeControllerBase
{
private readonly IMemberTypeService _memberTypeService;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IMemberTypePresentationFactory _memberTypePresentationFactory;

public ByKeyMemberTypeController(IMemberTypeService memberTypeService, IUmbracoMapper umbracoMapper)
public ByKeyMemberTypeController(IMemberTypeService memberTypeService, IMemberTypePresentationFactory memberTypePresentationFactory)
{
_memberTypeService = memberTypeService;
_umbracoMapper = umbracoMapper;
_memberTypePresentationFactory = memberTypePresentationFactory;
}

[HttpGet("{id:guid}")]
Expand All @@ -27,13 +27,13 @@ public ByKeyMemberTypeController(IMemberTypeService memberTypeService, IUmbracoM
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ByKey(Guid id)
{
IMemberType? MemberType = await _memberTypeService.GetAsync(id);
if (MemberType == null)
IMemberType? memberType = await _memberTypeService.GetAsync(id);
if (memberType is null)
{
return OperationStatusResult(ContentTypeOperationStatus.NotFound);
}

MemberTypeResponseModel model = _umbracoMapper.Map<MemberTypeResponseModel>(MemberType)!;
return await Task.FromResult(Ok(model));
MemberTypeResponseModel model = await _memberTypePresentationFactory.CreateResponseModelAsync(memberType);
return Ok(model);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal static class MemberTypeBuilderExtensions
{
internal static IUmbracoBuilder AddMemberTypes(this IUmbracoBuilder builder)
{
builder.Services.AddTransient<IMemberTypePresentationFactory, MemberTypePresentationFactory>();
builder.Services.AddTransient<IMemberTypeEditingPresentationFactory, MemberTypeEditingPresentationFactory>();

builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<MemberTypeMapDefinition>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;

namespace Umbraco.Cms.Api.Management.Factories;

public interface IMemberPresentationFactory
{
Task<MemberResponseModel> CreateResponseModelAsync(IMember member);
Task<MemberResponseModel> CreateResponseModelAsync(IMember member, IUser currentUser);

Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members);
Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members, IUser currentUser);

MemberItemResponseModel CreateItemResponseModel(IMemberEntitySlim entity);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Models;

namespace Umbraco.Cms.Api.Management.Factories;

public interface IMemberTypePresentationFactory
{
Task<MemberTypeResponseModel> CreateResponseModelAsync(IMemberType memberType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,53 @@
using Umbraco.Cms.Api.Management.ViewModels.Member;
using Umbraco.Cms.Api.Management.ViewModels.Member.Item;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Management.Factories;

internal sealed class MemberPresentationFactory : IMemberPresentationFactory
{
private readonly IUmbracoMapper _umbracoMapper;
private readonly IMemberService _memberService;
private readonly IMemberTypeService _memberTypeService;
private readonly ITwoFactorLoginService _twoFactorLoginService;

public MemberPresentationFactory(
IUmbracoMapper umbracoMapper,
IMemberService memberService,
IMemberTypeService memberTypeService,
ITwoFactorLoginService twoFactorLoginService)
{
_umbracoMapper = umbracoMapper;
_memberService = memberService;
_memberTypeService = memberTypeService;
_twoFactorLoginService = twoFactorLoginService;
}

public async Task<MemberResponseModel> CreateResponseModelAsync(IMember member)
public async Task<MemberResponseModel> CreateResponseModelAsync(IMember member, IUser currentUser)
{
MemberResponseModel responseModel = _umbracoMapper.Map<MemberResponseModel>(member)!;

responseModel.IsTwoFactorEnabled = await _twoFactorLoginService.IsTwoFactorEnabledAsync(member.Key);
responseModel.Groups = _memberService.GetAllRoles(member.Username);

return responseModel;
return currentUser.HasAccessToSensitiveData()
? responseModel
: await RemoveSensitiveDataAsync(member, responseModel);
}

public async Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members)
public async Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members, IUser currentUser)
{
var memberResponseModels = new List<MemberResponseModel>();
foreach (IMember member in members)
{
memberResponseModels.Add(await CreateResponseModelAsync(member));
memberResponseModels.Add(await CreateResponseModelAsync(member, currentUser));
}

return memberResponseModels;
Expand Down Expand Up @@ -72,4 +80,29 @@ public IEnumerable<VariantItemResponseModel> CreateVariantsItemResponseModels(IM

public MemberTypeReferenceResponseModel CreateMemberTypeReferenceResponseModel(IMemberEntitySlim entity)
=> _umbracoMapper.Map<MemberTypeReferenceResponseModel>(entity)!;

private async Task<MemberResponseModel> RemoveSensitiveDataAsync(IMember member, MemberResponseModel responseModel)
{
// these properties are considered sensitive; some of them are not nullable, so for
// those we can't do much more than force revert them to their default values.
responseModel.IsApproved = false;
responseModel.IsLockedOut = false;
responseModel.IsTwoFactorEnabled = false;
responseModel.FailedPasswordAttempts = 0;
responseModel.LastLoginDate = null;
responseModel.LastLockoutDate = null;
responseModel.LastPasswordChangeDate = null;

IMemberType memberType = await _memberTypeService.GetAsync(member.ContentType.Key)
?? throw new InvalidOperationException($"The member type {member.ContentType.Alias} could not be found");

var sensitivePropertyAliases = memberType.GetSensitivePropertyTypeAliases().ToArray();

// remove all properties whose property types are flagged as sensitive
responseModel.Values = responseModel.Values
.Where(valueModel => sensitivePropertyAliases.InvariantContains(valueModel.Alias) is false)
.ToArray();

return responseModel;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public MemberTypeCreateModel MapCreateModel(CreateMemberTypeRequestModel request
createModel.Key = requestModel.Id;
createModel.Compositions = MapCompositions(requestModel.Compositions);

MapPropertyTypeSensitivityAndVisibility(createModel.Properties, requestModel.Properties);

return createModel;
}

Expand All @@ -40,6 +42,8 @@ public MemberTypeUpdateModel MapUpdateModel(UpdateMemberTypeRequestModel request

updateModel.Compositions = MapCompositions(requestModel.Compositions);

MapPropertyTypeSensitivityAndVisibility(updateModel.Properties, requestModel.Properties);

return updateModel;
}

Expand All @@ -50,4 +54,23 @@ private IEnumerable<Composition> MapCompositions(IEnumerable<MemberTypeCompositi
=> MapCompositions(documentTypeCompositions
.DistinctBy(c => c.MemberType.Id)
.ToDictionary(c => c.MemberType.Id, c => c.CompositionType));

private void MapPropertyTypeSensitivityAndVisibility<TRequestPropertyTypeModel>(
IEnumerable<MemberTypePropertyTypeModel> propertyTypes,
IEnumerable<TRequestPropertyTypeModel> requestPropertyTypes)
where TRequestPropertyTypeModel : MemberTypePropertyTypeModelBase
{
var requestModelPropertiesByAlias = requestPropertyTypes.ToDictionary(p => p.Alias);
foreach (MemberTypePropertyTypeModel propertyType in propertyTypes)
{
if (requestModelPropertiesByAlias.TryGetValue(propertyType.Alias, out TRequestPropertyTypeModel? requestPropertyType) is false)
{
throw new InvalidOperationException($"Could not find the property type model {propertyType.Alias} in the request");
}

propertyType.IsSensitive = requestPropertyType.IsSensitive;
propertyType.MemberCanView = requestPropertyType.Visibility.MemberCanView;
propertyType.MemberCanEdit = requestPropertyType.Visibility.MemberCanEdit;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;

namespace Umbraco.Cms.Api.Management.Factories;

internal sealed class MemberTypePresentationFactory : IMemberTypePresentationFactory
{
private readonly IUmbracoMapper _umbracoMapper;

public MemberTypePresentationFactory(IUmbracoMapper umbracoMapper)
=> _umbracoMapper = umbracoMapper;

public Task<MemberTypeResponseModel> CreateResponseModelAsync(IMemberType memberType)
{
MemberTypeResponseModel model = _umbracoMapper.Map<MemberTypeResponseModel>(memberType)!;

foreach (MemberTypePropertyTypeResponseModel propertyType in model.Properties)
{
propertyType.IsSensitive = memberType.IsSensitiveProperty(propertyType.Alias);

propertyType.Visibility.MemberCanEdit = memberType.MemberCanEditProperty(propertyType.Alias);
propertyType.Visibility.MemberCanView = memberType.MemberCanViewProperty(propertyType.Alias);
}

return Task.FromResult(model);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
var hasAccessToAllLanguages = presentationGroups.Any(x => x.HasAccessToAllLanguages);

var allowedSections = presentationGroups.SelectMany(x => x.Sections).ToHashSet();

return await Task.FromResult(new CurrentUserResponseModel()
{
Id = presentationUser.Id,
Expand All @@ -178,7 +179,8 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
Permissions = permissions,
FallbackPermissions = fallbackPermissions,
HasAccessToAllLanguages = hasAccessToAllLanguages,
AllowedSections = allowedSections
HasAccessToSensitiveData = user.HasAccessToSensitiveData(),
AllowedSections = allowedSections,
});
}

Expand Down
Loading