* 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>
372 lines
16 KiB
C#
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;
|
|
}
|
|
}
|