Files
Umbraco-CMS/tests/Umbraco.Tests.Integration/ManagementApi/Factories/UserGroupPresentationFactoryTests.cs
Niels Lyngsø 16ee7227ae Feature: Document Property Value Permission (#18997)
* add property visibility state manager

* implement in structure manager

* filter properties based on visibility

* wip document type structure permissions

* rename

* register entity permission for document type property

* add entity permission for media type property

* pass fallback permissions to document granular permissions

* set as preset

* clean up

* wip document type property picker

* add preset value

* Update input-document-type-structure-granular-user-permission.element.ts

* move files

* rename

* Update input-document-value-granular-user-permission.element.ts

* remove temp test

* Update manifests.ts

* remove unused

* Update input-document-value-granular-user-permission.element.ts

* rename see permission + add write permission

* fix missing type

* require property type unique

* add unique to property type

* rename to property type

* map to unique

* deprecate id on property type

* return unique from property picker

* more explicit naming

* use type

* render detail

* Update input-document-value-granular-user-permission.element.ts

* wip modal flow

* clean up

* add headlines

* hide actions

* pass preset value

* add edit permission method

* include property in permission name

* add read and write managers

* implement read and write state managers

* Update content-type-structure-manager.class.ts

* enforce property permissions

* Storage for granular permissions at property type level

* add guards

* make variant property version

* Rename server models to include "property"

* generate server types

* add permissionType to model

* add mappers to user group permission data

* add mapper to current user permission data

* destroy

* clear state

* use permission type for guard check

* add permission type

* require specific permission type

* use correct schema type

* add mappings

* clean up

* log errors

* fix mapping

* null check for icon

* use fallback if there is no forDataModel

* add translations

* sort group alphabetically

* add empty state for no verbs

* organize folders

* always require unique and variant id

* Allow storing empty lists of verbs

* pass variant id to all states

* Remove empty verbs

* add alias to name

* prevent picking the same property type multiple times

* fix lint errors

* fix create state by observing variant options

* move to workspace context

* Update document-property-value-user-permission.workspace-context.ts

* Update content-editor-properties.element.ts

* clean up

* Rename models (last time, promise!)

* Add migration for default document property value permissions

* generate new server models

* update after model changes

* Correct the default permission identifiers

* Add default permissions to newly created DBs

* Add validation and clean-up

* rename to visibility state

* rename to view

* add helpers

* apply to blocks

* Update document-property-value-user-permission.workspace-context.ts

* disable view and write state by default

* add tests for start and stopping a state

* throw errors if adding to a state that is not running

* export consts

* export consts

* fix circular

* fix circular

* set the entity type when setting values

* only apply for block in document values

* split logic

* start states for document blocks

* only apply states when state is running

* Fixed typos in test method names.

* add readonly type

* Enforce: AllowEditInvariantFromNonDefault configuration (#18758)

* add read only state

* handle read only property state in properties element

* prevent editing shared props on non default

* enforce configuration

* clean up

* set variant id

* move to property module

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>

* remove unnecessary messages

* make sure to destroy consumer

* Thoughts as TODO

* use Entry type

* use Entry type

* get rid of things not yet released

* clean up

* use generic methods

* TODO comment

* use generic observable

* catch if not found

* move variant id out of property type

* mega refactor temp commit

* Guard Manager

* set readOnly as a property on property editors

* further rename

* remove property state managers

* revert state manager

* fix sorting rule

* mega rename and correction

* refactor properties elements

* todo note

* clean up

* impl

* mega refactor moving permission guards to workspace

* rename

* type change

* rearrange

* correct import

* fix tests

* correct tests

* reset viewGuards block

* type correction

* refactor read only for user permissions setting

* todo note

* align property element

* await promise

* impl view guard property filtering

* correct const name

* fix fallback user permissions in mock data

* correct property type id mock data

* toggle permissions example

* complex permission

* Move migration to 16.0.

* rename fallBackToDisallowed to fallbackToNotPermitted

* clean up setReadOnlyStateForUserPermission

* capital o

* align read only naming

* rename method

* add js docs

* remove unused

* correct method name

* add js docs

* add js docs

* camel case function

* fix eslint problems

* camelcase const

* align method names

* remove unused

* fix host

* fix spelling mistake

* align naming

* fix spelling mistake

* add alias

* use read only state methods

* camel case function

* correct method name

* add js docs

* camelcase function

* camel case function

* align method names

* change method name wording

* Include document property value permissions in the current user's aggregated permissions.

* use is read only

* delete unused

* fix implementation of AllowEditInvariantFromNonDefault

* don't know what is happening here. Local is it lower on github it is higher

* Update document-workspace.context.ts

* revert to v16 dev

* simplify if statement

* make it explicit that these are ui only permissions

* add action label for read

* remove duplicates

* use read instead of browse

* align description

* use document instead of node

* make the base class abstract

* extend in test

* Update guard.manager.base.test.ts

* fix example

* style adjustment

* group styling

* refactor guard rule resolving

* remove imports

* remove console.log

* improve disconnected context consumer rejection message

* fix publishableVariantsFilter

* Update document-workspace.context.ts

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: kjac <kja@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2025-04-11 14:16:59 +02:00

372 lines
16 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Mapping.Permissions;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership.Permissions;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.ContentTypeEditing;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.TestHelpers;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Factories;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
internal sealed class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
{
public IUserGroupPresentationFactory UserGroupPresentationFactory => GetRequiredService<IUserGroupPresentationFactory>();
public IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
public ITemplateService TemplateService => GetRequiredService<ITemplateService>();
public IContentTypeEditingService ContentTypeEditingService => GetRequiredService<IContentTypeEditingService>();
public IContentEditingService ContentEditingService => GetRequiredService<IContentEditingService>();
protected override void ConfigureTestServices(IServiceCollection services)
{
services.AddTransient<IUserGroupPresentationFactory, UserGroupPresentationFactory>();
services.AddSingleton<IPermissionPresentationFactory, PermissionPresentationFactory>();
services.AddSingleton<IPermissionMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionMapper, DocumentPropertyValuePermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPropertyValuePermissionMapper>();
}
[Test]
public async Task Can_Map_Create_Model_And_Create()
{
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
};
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
Assert.IsEmpty(userGroup.GranularPermissions);
});
}
[Test]
public async Task Cannot_Create_UserGroup_With_Unexisting_Document_Reference()
{
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
{
new DocumentPermissionPresentationModel()
{
Document = new ReferenceByIdModel(Guid.NewGuid()),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Assert.Multiple(() =>
{
Assert.IsFalse(userGroupCreateAttempt.Success);
Assert.AreEqual(UserGroupOperationStatus.DocumentPermissionKeyNotFound, userGroupCreateAttempt.Status);
});
}
[Test]
public async Task Cannot_Create_UserGroup_With_Unexisting_DocumentType_Reference()
{
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
{
new DocumentPropertyValuePermissionPresentationModel()
{
DocumentType = new ReferenceByIdModel(Guid.NewGuid()),
PropertyType = new ReferenceByIdModel(Guid.NewGuid()),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Assert.Multiple(() =>
{
Assert.IsFalse(userGroupCreateAttempt.Success);
Assert.AreEqual(UserGroupOperationStatus.DocumentTypePermissionKeyNotFound, userGroupCreateAttempt.Status);
});
}
[Test]
public async Task Can_Create_Usergroup_With_Empty_Granular_Permissions_For_Document()
{
var contentKey = await CreateContent();
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPermissionPresentationModel()
{
Document = new ReferenceByIdModel(contentKey),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
Assert.IsNotEmpty(userGroup.GranularPermissions);
Assert.AreEqual(contentKey, userGroup.GranularPermissions.First().Key);
Assert.AreEqual(string.Empty, userGroup.GranularPermissions.First().Permission);
});
}
[Test]
public async Task Can_Create_Usergroup_With_Granular_Permissions_For_Document_PropertyValue()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType = contentType.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType.Key),
PropertyType = new ReferenceByIdModel(propertyType.Key),
Verbs = new HashSet<string>(["Some", "Another"])
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
});
Assert.AreEqual(2, userGroup.GranularPermissions.Count);
var documentTypeGranularPermissions = userGroup.GranularPermissions.OfType<DocumentPropertyValueGranularPermission>().ToArray();
Assert.AreEqual(2, documentTypeGranularPermissions.Length);
Assert.Multiple(() =>
{
Assert.IsTrue(documentTypeGranularPermissions.All(x => x.Key == contentType.Key));
Assert.AreEqual($"{propertyType.Key}|Some", documentTypeGranularPermissions.First().Permission);
Assert.AreEqual($"{propertyType.Key}|Another", documentTypeGranularPermissions.Last().Permission);
});
}
[Test]
public async Task Can_Create_Usergroup_With_Granular_Permissions_For_Document_PropertyValue_Without_Verbs()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType = contentType.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType.Key),
PropertyType = new ReferenceByIdModel(propertyType.Key),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
});
Assert.AreEqual(1, userGroup.GranularPermissions.Count);
var documentTypeGranularPermissions = userGroup.GranularPermissions.OfType<DocumentPropertyValueGranularPermission>().ToArray();
Assert.AreEqual(1, documentTypeGranularPermissions.Length);
Assert.Multiple(() =>
{
Assert.IsTrue(documentTypeGranularPermissions.All(x => x.Key == contentType.Key));
Assert.AreEqual($"{propertyType.Key}|", documentTypeGranularPermissions.First().Permission);
});
}
[Test]
public async Task Usergroup_Granular_Permissions_For_Document_PropertyValue_Are_Cleaned_Up_When_DocumentType_Is_Deleted()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType1 = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var contentType2 = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(alias: "anotherAlias", defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType1 = contentType1.PropertyTypes.First();
var propertyType2 = contentType2.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType1.Key),
PropertyType = new ReferenceByIdModel(propertyType1.Key),
Verbs = new HashSet<string>(["Some", "Another"])
},
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType2.Key),
PropertyType = new ReferenceByIdModel(propertyType2.Key),
Verbs = new HashSet<string>(["Even", "More"])
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.AreEqual(4, userGroupCreateAttempt.Result!.GranularPermissions.Count);
var deleteResult = await GetRequiredService<IContentTypeService>().DeleteAsync(contentType1.Key, Constants.Security.SuperUserKey);
Assert.AreEqual(ContentTypeOperationStatus.Success, deleteResult);
var userGroup = await UserGroupService.GetAsync(userGroupCreateAttempt.Result!.Key);
Assert.IsNotNull(userGroup);
Assert.AreEqual(2, userGroup.GranularPermissions.Count);
}
private async Task<Guid> CreateContent()
{
// NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested.
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
// Create and Save ContentType "umbTextpage" -> 1051 (template), 1052 (content type)
var contentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType("umbTextpage", "Textpage", defaultTemplateKey: template.Key);
var contentTypeAttempt = await ContentTypeEditingService.CreateAsync(contentTypeCreateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(contentTypeAttempt.Success);
var contentTypeResult = contentTypeAttempt.Result;
var contentTypeUpdateModel = ContentTypeUpdateHelper.CreateContentTypeUpdateModel(contentTypeResult);
contentTypeUpdateModel.AllowedContentTypes = new[]
{
new ContentTypeSort(contentTypeResult.Key, 0, contentTypeCreateModel.Alias),
};
var updatedContentTypeResult = await ContentTypeEditingService.UpdateAsync(contentTypeResult, contentTypeUpdateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(updatedContentTypeResult.Success);
// Create and Save Content "Homepage" based on "umbTextpage" -> 1053
var textPage = ContentEditingBuilder.CreateSimpleContent(updatedContentTypeResult.Result.Key);
var createContentResultTextPage = await ContentEditingService.CreateAsync(textPage, Constants.Security.SuperUserKey);
Assert.IsTrue(createContentResultTextPage.Success);
return createContentResultTextPage.Result.Content.Key;
}
}