Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
@@ -1,13 +1,17 @@
using Umbraco.Cms.Api.Management.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Security;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.User;
using Umbraco.Cms.Api.Management.ViewModels.User.Current;
using Umbraco.Cms.Core;
using Umbraco.Cms.Api.Management.ViewModels.User.Item;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Media;
Expand All @@ -20,7 +24,6 @@ namespace Umbraco.Cms.Api.Management.Factories;

public class UserPresentationFactory : IUserPresentationFactory
{

private readonly IEntityService _entityService;
private readonly AppCaches _appCaches;
private readonly MediaFileManager _mediaFileManager;
Expand All @@ -31,7 +34,10 @@ public class UserPresentationFactory : IUserPresentationFactory
private readonly IPasswordConfigurationPresentationFactory _passwordConfigurationPresentationFactory;
private readonly IBackOfficeExternalLoginProviders _externalLoginProviders;
private readonly SecuritySettings _securitySettings;
private readonly IUserService _userService;
private readonly IContentService _contentService;

[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
Expand All @@ -43,6 +49,35 @@ public UserPresentationFactory(
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders)
: this(
entityService,
appCaches,
mediaFileManager,
imageUrlGenerator,
userGroupPresentationFactory,
absoluteUrlBuilder,
emailSender,
passwordConfigurationPresentationFactory,
securitySettings,
externalLoginProviders,
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
StaticServiceProvider.Instance.GetRequiredService<IContentService>())
{
}

public UserPresentationFactory(
IEntityService entityService,
AppCaches appCaches,
MediaFileManager mediaFileManager,
IImageUrlGenerator imageUrlGenerator,
IUserGroupPresentationFactory userGroupPresentationFactory,
IAbsoluteUrlBuilder absoluteUrlBuilder,
IEmailSender emailSender,
IPasswordConfigurationPresentationFactory passwordConfigurationPresentationFactory,
IOptionsSnapshot<SecuritySettings> securitySettings,
IBackOfficeExternalLoginProviders externalLoginProviders,
IUserService userService,
IContentService contentService)
{
_entityService = entityService;
_appCaches = appCaches;
Expand All @@ -54,6 +89,8 @@ public UserPresentationFactory(
_externalLoginProviders = externalLoginProviders;
_securitySettings = securitySettings.Value;
_absoluteUrlBuilder = absoluteUrlBuilder;
_userService = userService;
_contentService = contentService;
}

public UserResponseModel CreateResponseModel(IUser user)
Expand Down Expand Up @@ -195,7 +232,7 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
var contentStartNodeIds = user.CalculateContentStartNodeIds(_entityService, _appCaches);
var documentStartNodeKeys = GetKeysFromIds(contentStartNodeIds, UmbracoObjectTypes.Document);

var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();
var permissions = GetAggregatedGranularPermissions(user, presentationGroups);
var fallbackPermissions = presentationGroups.SelectMany(x => x.FallbackPermissions).ToHashSet();

var hasAccessToAllLanguages = presentationGroups.Any(x => x.HasAccessToAllLanguages);
Expand Down Expand Up @@ -225,6 +262,42 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
});
}

private HashSet<IPermissionPresentationModel> GetAggregatedGranularPermissions(IUser user, IEnumerable<UserGroupResponseModel> presentationGroups)
{
var permissions = presentationGroups.SelectMany(x => x.Permissions).ToHashSet();

// The raw permission data consists of several permissions for each document. We want to aggregate this server-side so
// we return one set of aggregate permissions per document that the client will use.

// Get the unique document keys that have granular permissions.
IEnumerable<Guid> documentKeysWithGranularPermissions = permissions
.Where(x => x is DocumentPermissionPresentationModel)
.Cast<DocumentPermissionPresentationModel>()
.Select(x => x.Document.Id)
.Distinct();

var aggregatedPermissions = new HashSet<IPermissionPresentationModel>();
foreach (Guid documentKey in documentKeysWithGranularPermissions)
{
// Retrieve the path of the document.
var path = _contentService.GetById(documentKey)?.Path;
if (string.IsNullOrEmpty(path))
{
continue;
}

// With the path we can call the same logic as used server-side for authorizing access to resources.
EntityPermissionSet permissionsForPath = _userService.GetPermissionsForPath(user, path);
aggregatedPermissions.Add(new DocumentPermissionPresentationModel
{
Document = new ReferenceByIdModel(documentKey),
Verbs = permissionsForPath.GetAllPermissions()
});
}

return aggregatedPermissions;
}

public async Task<CalculatedUserStartNodesResponseModel> CreateCalculatedUserStartNodesResponseModelAsync(IUser user)
{
var mediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService, _appCaches);
Expand Down
5 changes: 2 additions & 3 deletions tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public override IUserGroup Build()
Key = key,
StartContentId = startContentId,
StartMediaId = startMediaId,
Permissions = _permissions
Permissions = _permissions,
};

BuildAllowedSections(userGroup);
Expand All @@ -169,7 +169,6 @@ public override IUserGroup Build()
return userGroup;
}


private void BuildAllowedSections(UserGroup userGroup)
{
foreach (var section in _allowedSections)
Expand Down Expand Up @@ -204,6 +203,6 @@ public static UserGroup CreateUserGroup(
.WithAlias(alias + suffix)
.WithName(name + suffix)
.WithPermissions(permissions ?? new[] { "A", "B", "C" }.ToHashSet())
.WithAllowedSections(allowedSections ?? new[] { "content", "media" })
.WithAllowedSections(allowedSections ?? ["content", "media"])
.Build();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Mapping.Permissions;
Expand Down Expand Up @@ -34,15 +34,15 @@ protected override void ConfigureTestServices(IServiceCollection services)
services.AddTransient<IUserGroupPresentationFactory, UserGroupPresentationFactory>();
services.AddSingleton<IPermissionPresentationFactory, PermissionPresentationFactory>();
services.AddSingleton<DocumentPermissionMapper>();
services.AddSingleton<IPermissionMapper>(x=>x.GetRequiredService<DocumentPermissionMapper>());
services.AddSingleton<IPermissionPresentationMapper>(x=>x.GetRequiredService<DocumentPermissionMapper>());
services.AddSingleton<IPermissionMapper>(x => x.GetRequiredService<DocumentPermissionMapper>());
services.AddSingleton<IPermissionPresentationMapper>(x => x.GetRequiredService<DocumentPermissionMapper>());
}


[Test]
public async Task Can_Map_Create_Model_And_Create()
{
var updateModel = new CreateUserGroupRequestModel()
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
Expand All @@ -53,7 +53,7 @@ public async Task Can_Map_Create_Model_And_Create()
Permissions = new HashSet<IPermissionPresentationModel>()
};

var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);

var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Expand All @@ -71,7 +71,7 @@ public async Task Can_Map_Create_Model_And_Create()
[Test]
public async Task Cannot_Create_UserGroup_With_Unexisting_Document_Reference()
{
var updateModel = new CreateUserGroupRequestModel()
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
Expand All @@ -89,7 +89,7 @@ public async Task Cannot_Create_UserGroup_With_Unexisting_Document_Reference()
}
};

var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);

var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Expand All @@ -102,11 +102,11 @@ public async Task Cannot_Create_UserGroup_With_Unexisting_Document_Reference()
}

[Test]
public async Task Can_Create_Usergroup_With_Empty_Granluar_Permissions_For_Document()
public async Task Can_Create_Usergroup_With_Empty_Granular_Permissions_For_Document()
{
var contentKey = await CreateContent();

var updateModel = new CreateUserGroupRequestModel()
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
Expand All @@ -124,7 +124,7 @@ public async Task Can_Create_Usergroup_With_Empty_Granluar_Permissions_For_Docum
}
};

var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);

var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Expand Down
Loading