From 6f38a57c8a1f41c5cb52832fac957d25fc051f6b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 9 Apr 2025 11:08:28 +0200 Subject: [PATCH] Document permission inheritance in UI (#18935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * check the full path for permissions * fix race condition * wip update permission when variants change * Populate ancestor keys on document tree response items. * Populate ancestor keys on document collection response items. * Update OpenApi.json * generate server models * update types * map data * add ancestor context * set ancestors in context * use ancestor context in tree * clean up * provide ancestor context from a collection item * provide ancestor context from structure context * Use array of objects rather than Ids for the ancestor collection. * Update OpenApi.json. * add ancestor data to mocks * set ancestors ids in mocks * omit ancestors for recycle bin item * use correct models for document blueprint mock data * remove constructor * mock documents for testing * add user group permission test data * wip document user permission condition tests * generate new server models * update data efter server models update * clean up * Update entity-actions-table-column-view.element.ts * longer time for not found to appear * use arg * observe alias * set new the right place * remove const --------- Co-authored-by: Andy Butland Co-authored-by: Niels Lyngsø Co-authored-by: Niels Lyngsø --- .../document-blueprint.data.ts | 27 ++-- .../document-blueprint.db.ts | 47 ++---- .../document/data/permissions-test.data.ts | 129 +++++++++++++++++ .../src/mocks/data/document/document.data.ts | 2 + .../src/mocks/data/document/document.db.ts | 16 ++- .../mocks/data/user-group/user-group.data.ts | 24 +++- ...ntity-actions-table-column-view.element.ts | 10 +- .../src/packages/core/entity/constants.ts | 1 + .../ancestors.entity-context-token.ts | 4 + .../ancestors/ancestors.entity-context.ts | 38 +++++ .../entity/contexts/ancestors/constants.ts | 1 + .../core/entity/contexts/ancestors/index.ts | 1 + .../src/packages/core/entity/index.ts | 2 + ...t-tree-structure-workspace-context-base.ts | 12 ++ .../not-found/route-not-found.element.ts | 2 +- .../entity-detail-workspace-base.ts | 4 +- .../document-collection.server.data-source.ts | 6 + .../documents/documents/collection/types.ts | 2 + ...ntity-actions-table-column-view.element.ts | 36 +++++ .../document-table-collection-view.element.ts | 8 +- .../save-and-publish.action.ts | 6 +- .../documents/recycle-bin/tree/types.ts | 2 +- .../tree/document-tree.server.data-source.ts | 6 + .../tree-item/document-tree-item.context.ts | 7 + .../documents/documents/tree/types.ts | 2 + ...document-user-permission.condition.test.ts | 134 ++++++++++++++++++ .../document-user-permission.condition.ts | 38 +++-- .../workspace/document-workspace.context.ts | 38 +++-- 28 files changed, 515 insertions(+), 90 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/mocks/data/document/data/permissions-test.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context-token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-entity-actions-table-column-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts index d2a1eb9fe8..3c4f1445de 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.data.ts @@ -1,30 +1,25 @@ -import type { UmbMockDocumentModel } from '../document/document.data.js'; -import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { + DocumentVariantStateModel, + type DocumentBlueprintItemResponseModel, + type DocumentBlueprintResponseModel, + type DocumentBlueprintTreeItemResponseModel, +} from '@umbraco-cms/backoffice/external/backend-api'; // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbMockDocumentBlueprintModel extends UmbMockDocumentModel {} +export type UmbMockDocumentBlueprintModel = DocumentBlueprintResponseModel & + DocumentBlueprintItemResponseModel & + DocumentBlueprintTreeItemResponseModel; export const data: Array = [ { - ancestors: [], - urls: [ - { - culture: 'en-US', - url: '/', - }, - ], - template: null, id: 'the-simplest-document-id', - createDate: '2023-02-06T15:32:05.350038', - parent: null, documentType: { id: 'the-simplest-document-type-id', icon: 'icon-document', }, hasChildren: false, - noAccess: false, - isProtected: false, - isTrashed: false, + isFolder: false, + name: 'The Simplest Document Blueprint', variants: [ { state: DocumentVariantStateModel.DRAFT, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.db.ts index 6d6eec6720..7e09bcbc50 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-blueprint/document-blueprint.db.ts @@ -8,10 +8,10 @@ import type { UmbMockDocumentBlueprintModel } from './document-blueprint.data.js import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { - CreateDocumentRequestModel, - DocumentItemResponseModel, - DocumentResponseModel, - DocumentTreeItemResponseModel, + CreateDocumentBlueprintRequestModel, + DocumentBlueprintItemResponseModel, + DocumentBlueprintResponseModel, + DocumentBlueprintTreeItemResponseModel, DocumentValueResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; @@ -23,41 +23,34 @@ export class UmbDocumentBlueprintMockDB extends UmbEntityMockDbBase) { - super(data); - } } -const treeItemMapper = (model: UmbMockDocumentBlueprintModel): Omit => { +const treeItemMapper = (model: UmbMockDocumentBlueprintModel): DocumentBlueprintTreeItemResponseModel => { const documentType = umbDocumentTypeMockDb.read(model.documentType.id); if (!documentType) throw new Error(`Document type with id ${model.documentType.id} not found`); return { - ancestors: model.ancestors, documentType: { icon: documentType.icon, id: documentType.id, }, hasChildren: model.hasChildren, id: model.id, - isProtected: model.isProtected, - isTrashed: model.isTrashed, - noAccess: model.noAccess, + isFolder: model.isFolder, + name: model.name, parent: model.parent, - variants: model.variants, - createDate: model.createDate, }; }; -const createMockDocumentBlueprintMapper = (request: CreateDocumentRequestModel): UmbMockDocumentBlueprintModel => { +const createMockDocumentBlueprintMapper = ( + request: CreateDocumentBlueprintRequestModel, +): UmbMockDocumentBlueprintModel => { const documentType = umbDocumentTypeMockDb.read(request.documentType.id); if (!documentType) throw new Error(`Document type with id ${request.documentType.id} not found`); const now = new Date().toString(); return { - ancestors: [], documentType: { id: documentType.id, icon: documentType.icon, @@ -65,10 +58,8 @@ const createMockDocumentBlueprintMapper = (request: CreateDocumentRequestModel): }, hasChildren: false, id: request.id ? request.id : UmbId.new(), - createDate: now, - isProtected: false, - isTrashed: false, - noAccess: false, + isFolder: false, + name: request.variants[0].name, parent: request.parent, values: request.values as DocumentValueResponseModel[], variants: request.variants.map((variantRequest) => { @@ -82,35 +73,27 @@ const createMockDocumentBlueprintMapper = (request: CreateDocumentRequestModel): publishDate: null, }; }), - urls: [], }; }; -const detailResponseMapper = (model: UmbMockDocumentBlueprintModel): DocumentResponseModel => { +const detailResponseMapper = (model: UmbMockDocumentBlueprintModel): DocumentBlueprintResponseModel => { return { documentType: model.documentType, id: model.id, - isTrashed: model.isTrashed, - template: model.template, - urls: model.urls, values: model.values, variants: model.variants, }; }; -const itemMapper = (model: UmbMockDocumentBlueprintModel): DocumentItemResponseModel => { +const itemMapper = (model: UmbMockDocumentBlueprintModel): DocumentBlueprintItemResponseModel => { return { documentType: { collection: model.documentType.collection, icon: model.documentType.icon, id: model.documentType.id, }, - hasChildren: model.hasChildren, id: model.id, - isProtected: model.isProtected, - isTrashed: model.isTrashed, - parent: model.parent, - variants: model.variants, + name: model.name, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/data/permissions-test.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/data/permissions-test.data.ts new file mode 100644 index 0000000000..4d08835b57 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/data/permissions-test.data.ts @@ -0,0 +1,129 @@ +import type { UmbMockDocumentModel } from '../document.data.js'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; + +const permissionsTestDocument = { + ancestors: [], + urls: [ + { + culture: null, + url: '/', + }, + ], + template: null, + id: 'permissions-document-id', + createDate: '2023-02-06T15:32:05.350038', + parent: null, + documentType: { + id: 'the-simplest-document-type-id', + icon: 'icon-document', + }, + hasChildren: false, + noAccess: false, + isProtected: false, + isTrashed: false, + values: [], + variants: [ + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:32:24.957009', + culture: null, + segment: null, + name: 'Permissions', + createDate: '2023-02-06T15:32:05.350038', + updateDate: '2023-02-06T15:32:24.957009', + }, + ], +}; + +export const data: Array = [ + permissionsTestDocument, + { + ...permissionsTestDocument, + ancestors: [{ id: 'permissions-document-id' }], + hasChildren: false, + id: 'permissions-1-document-id', + parent: { id: 'permissions-document-id' }, + urls: [ + { + culture: null, + url: '/permission-1', + }, + ], + variants: permissionsTestDocument.variants.map((variant) => ({ + ...variant, + name: 'Permissions 1', + })), + }, + { + ...permissionsTestDocument, + ancestors: [{ id: 'permissions-document-id' }], + hasChildren: true, + id: 'permissions-2-document-id', + parent: { id: 'permissions-document-id' }, + urls: [ + { + culture: null, + url: '/permissions-2', + }, + ], + variants: permissionsTestDocument.variants.map((variant) => ({ + ...variant, + name: 'Permissions 2', + })), + }, + { + ...permissionsTestDocument, + ancestors: [{ id: 'permissions-document-id' }, { id: 'permissions-2-document-id' }], + hasChildren: true, + id: 'permission-2-1-document-id', + parent: { id: 'permissions-2-document-id' }, + urls: [ + { + culture: null, + url: '/permissions-1/permissions-2-1', + }, + ], + variants: permissionsTestDocument.variants.map((variant) => ({ + ...variant, + name: 'Permissions 2.1', + })), + }, + { + ...permissionsTestDocument, + ancestors: [{ id: 'permissions-document-id' }, { id: 'permissions-2-document-id' }], + hasChildren: false, + id: 'permissions-2-2-document-id', + parent: { id: 'permissions-2-document-id' }, + urls: [ + { + culture: null, + url: '/permissions-1/permissions-2-2', + }, + ], + variants: permissionsTestDocument.variants.map((variant) => ({ + ...variant, + name: 'Permissions 2.2', + })), + }, + { + ...permissionsTestDocument, + ancestors: [ + { id: 'permissions-document-id' }, + { id: 'permissions-2-document-id' }, + { id: 'permissions-2-2-document-id' }, + ], + hasChildren: false, + id: 'permission-2-2-1-document-id', + parent: { id: 'permissions-2-2-document-id' }, + urls: [ + { + culture: null, + url: '/permissions-1/permissions-2-2/permissions-2-2-1', + }, + ], + variants: permissionsTestDocument.variants.map((variant) => ({ + ...variant, + name: 'Permissions 2.2.1', + })), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index d3af2fc471..f7d847d210 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -1,3 +1,4 @@ +import { data as permissionsTestData } from './data/permissions-test.data.js'; import type { DocumentItemResponseModel, DocumentResponseModel, @@ -1240,4 +1241,5 @@ export const data: Array = [ }, ], }, + ...permissionsTestData, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts index f06f8b1da3..3626791391 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts @@ -78,10 +78,24 @@ const createMockDocumentMapper = (request: CreateDocumentRequestModel): UmbMockD const documentType = umbDocumentTypeMockDb.read(request.documentType.id); if (!documentType) throw new Error(`Document type with id ${request.documentType.id} not found`); + const isRoot = request.parent === null || request.parent === undefined; + let ancestors: Array<{ id: string }> = []; + + if (!isRoot) { + const parentId = request.parent!.id; + + const parentAncestors = umbDocumentMockDb.tree.getAncestorsOf({ descendantId: parentId }).map((ancestor) => { + return { + id: ancestor.id, + }; + }); + ancestors = [...parentAncestors, { id: parentId }]; + } + const now = new Date().toString(); return { - ancestors: [], + ancestors, documentType: { id: documentType.id, icon: documentType.icon, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts index fdbf6cdb99..d2f7f94e8f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts @@ -26,7 +26,29 @@ export const data: Array = [ 'Umb.Document.PublicAccess', 'Umb.Document.Rollback', ], - permissions: [], + permissions: [ + { + $type: 'DocumentPermissionPresentationModel', + document: { + id: 'permissions-document-id', + }, + verbs: ['Umb.Document.Read'], + }, + { + $type: 'DocumentPermissionPresentationModel', + document: { + id: 'permissions-2-document-id', + }, + verbs: ['Umb.Document.Create', 'Umb.Document.Read'], + }, + { + $type: 'DocumentPermissionPresentationModel', + document: { + id: 'permissions-2-2-document-id', + }, + verbs: ['Umb.Document.Delete', 'Umb.Document.Read'], + }, + ], sections: [ UMB_CONTENT_SECTION_ALIAS, 'Umb.Section.Media', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts index f625894db3..f1b112ff09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts @@ -1,16 +1,12 @@ import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -const elementName = 'umb-entity-actions-table-column-view'; -@customElement(elementName) +@customElement('umb-entity-actions-table-column-view') export class UmbEntityActionsTableColumnViewElement extends UmbLitElement { @property({ attribute: false }) value?: UmbEntityModel; - @state() - _isOpen = false; - override render() { if (!this.value) return nothing; @@ -23,6 +19,6 @@ export class UmbEntityActionsTableColumnViewElement extends UmbLitElement { declare global { interface HTMLElementTagNameMap { - [elementName]: UmbEntityActionsTableColumnViewElement; + 'umb-entity-actions-table-column-view': UmbEntityActionsTableColumnViewElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts new file mode 100644 index 0000000000..3ed4ee95ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts @@ -0,0 +1 @@ +export * from './contexts/ancestors/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context-token.ts new file mode 100644 index 0000000000..bcb07aed03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context-token.ts @@ -0,0 +1,4 @@ +import type { UmbAncestorsEntityContext } from './ancestors.entity-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_ANCESTORS_ENTITY_CONTEXT = new UmbContextToken('UmbAncestorsEntityContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context.ts new file mode 100644 index 0000000000..eecdffbd06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/ancestors.entity-context.ts @@ -0,0 +1,38 @@ +import { UMB_ANCESTORS_ENTITY_CONTEXT } from './ancestors.entity-context-token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +/** + * A entity context for the ancestors + * @class UmbAncestorsEntityContext + * @augments {UmbContextBase} + * @implements {UmbAncestorsEntityContext} + */ +export class UmbAncestorsEntityContext extends UmbContextBase { + #ancestors = new UmbArrayState([], (x) => x.unique); + ancestors = this.#ancestors.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_ANCESTORS_ENTITY_CONTEXT); + } + + /** + * Gets the ancestors state + * @returns {Array} - The ancestors state + * @memberof UmbAncestorsEntityContext + */ + getAncestors(): Array { + return this.#ancestors.getValue(); + } + + /** + * Sets the ancestors state + * @param {Array} ancestors - The ancestors state + * @memberof UmbAncestorsEntityContext + */ + setAncestors(ancestors: Array) { + this.#ancestors.setValue(ancestors); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/constants.ts new file mode 100644 index 0000000000..f9f9c304ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/constants.ts @@ -0,0 +1 @@ +export { UMB_ANCESTORS_ENTITY_CONTEXT } from './ancestors.entity-context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/index.ts new file mode 100644 index 0000000000..b38afa73e8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/ancestors/index.ts @@ -0,0 +1 @@ +export { UmbAncestorsEntityContext } from './ancestors.entity-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts index 737d7bf482..d064b7c97a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts @@ -1,3 +1,5 @@ export { UMB_ENTITY_CONTEXT } from './entity.context-token.js'; export { UmbEntityContext } from './entity.context.js'; +export * from './constants.js'; +export * from './contexts/ancestors/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts index 8d89588a1d..4233b340f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts @@ -5,6 +5,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_VARIANT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbAncestorsEntityContext } from '@umbraco-cms/backoffice/entity'; interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; @@ -21,6 +22,8 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um #parent = new UmbObjectState(undefined); public readonly parent = this.#parent.asObservable(); + #ancestorContext = new UmbAncestorsEntityContext(this); + constructor(host: UmbControllerHost, args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs) { // TODO: set up context token super(host, 'UmbMenuStructureWorkspaceContext'); @@ -85,6 +88,15 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um }; }); + const ancestorEntities = data.map((treeItem) => { + return { + unique: treeItem.unique, + entityType: treeItem.entityType, + }; + }); + + this.#ancestorContext.setAncestors(ancestorEntities); + structureItems.push(...ancestorItems); const parent = structureItems[structureItems.length - 2]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/not-found/route-not-found.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/not-found/route-not-found.element.ts index 522228a482..e5537de497 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/not-found/route-not-found.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/not-found/route-not-found.element.ts @@ -33,7 +33,7 @@ export class UmbRouteNotFoundElement extends UmbLitElement { align-items: center; height: 100%; opacity: 0; - animation: fadeIn 6s 0.2s forwards; + animation: fadeIn 5s 5s forwards; } @keyframes fadeIn { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index d37cce3c3f..f02d3942d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -171,6 +171,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< return (await this._getDataPromise) as GetDataType; } this.resetState(); + this.setIsNew(false); this.#entityContext.setUnique(unique); this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Loading ${this.getEntityType()} Details` }); await this.#init; @@ -182,7 +183,6 @@ export abstract class UmbEntityDetailWorkspaceContextBase< if (data) { this._data.setPersisted(data); this._data.setCurrent(data); - this.setIsNew(false); this.observe( response.asObservable(), @@ -246,8 +246,8 @@ export abstract class UmbEntityDetailWorkspaceContextBase< data = { ...data, ...this.modalContext.data.preset }; } - this.#entityContext.setUnique(data.unique); this.setIsNew(true); + this.#entityContext.setUnique(data.unique); this._data.setPersisted(data); this._data.setCurrent(data); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts index 04977651d4..ef0685282e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts @@ -37,6 +37,12 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS const variant = item.variants[0]; const model: UmbDocumentCollectionItemModel = { + ancestors: item.ancestors.map((ancestor) => { + return { + unique: ancestor.id, + entityType: UMB_DOCUMENT_ENTITY_TYPE, + }; + }), unique: item.id, entityType: UMB_DOCUMENT_ENTITY_TYPE, contentTypeAlias: item.documentType.alias, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts index 55107999fc..a1ec0ecc44 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts @@ -1,5 +1,6 @@ import type { UmbDocumentEntityType } from '../entity.js'; import type { UmbDocumentItemVariantModel } from '../item/repository/types.js'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterModel { @@ -12,6 +13,7 @@ export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterMod } export interface UmbDocumentCollectionItemModel { + ancestors: Array; unique: string; entityType: UmbDocumentEntityType; creator?: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-entity-actions-table-column-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-entity-actions-table-column-view.element.ts new file mode 100644 index 0000000000..3a67955958 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-entity-actions-table-column-view.element.ts @@ -0,0 +1,36 @@ +import type { UmbDocumentCollectionItemModel } from '../../../types.js'; +import { UmbAncestorsEntityContext } from '@umbraco-cms/backoffice/entity'; +import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-document-entity-actions-table-column-view') +export class UmbDocumentEntityActionsTableColumnViewElement extends UmbLitElement { + @property({ attribute: false }) + public get value(): UmbDocumentCollectionItemModel | undefined { + return this._value; + } + public set value(value: UmbDocumentCollectionItemModel | undefined) { + this._value = value; + this.#ancestorContext.setAncestors(this._value?.ancestors ?? []); + } + + private _value?: UmbDocumentCollectionItemModel | undefined; + + #ancestorContext = new UmbAncestorsEntityContext(this); + + override render() { + if (!this._value) return nothing; + + return html` + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + ['umb-document-entity-actions-table-column-view']: UmbDocumentEntityActionsTableColumnViewElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts index 3ce823efc3..d06973e3a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -19,6 +19,7 @@ import type { UmbTableSelectedEvent, } from '@umbraco-cms/backoffice/components'; +import './column-layouts/document-entity-actions-table-column-view.element.js'; import './column-layouts/document-table-column-name.element.js'; import './column-layouts/document-table-column-state.element.js'; @@ -143,11 +144,8 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { if (column.alias === 'entityActions') { return { columnAlias: 'entityActions', - value: html``, + value: html``, }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts index 142e5393a5..0aa52c0b8b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts @@ -16,14 +16,14 @@ export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceAction will first be triggered when the condition is changed to permitted */ this.disable(); - const condition = new UmbDocumentUserPermissionCondition(host, { + new UmbDocumentUserPermissionCondition(host, { host, config: { alias: 'Umb.Condition.UserPermission.Document', allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH], }, - onChange: () => { - if (condition.permitted) { + onChange: (permitted) => { + if (permitted) { this.enable(); } else { this.disable(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts index 3db26df828..7aac1d4011 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/types.ts @@ -2,7 +2,7 @@ import type { UmbDocumentTreeItemModel } from '../../tree/index.js'; import type { UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbDocumentRecycleBinTreeItemModel extends UmbDocumentTreeItemModel {} +export interface UmbDocumentRecycleBinTreeItemModel extends Omit {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbDocumentRecycleBinTreeRootModel extends UmbTreeRootModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts index 23134882cd..8c67372759 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts @@ -66,6 +66,12 @@ const getAncestorsOf = (args: UmbTreeAncestorsOfRequestArgs) => const mapper = (item: DocumentTreeItemResponseModel): UmbDocumentTreeItemModel => { return { + ancestors: item.ancestors.map((ancestor) => { + return { + unique: ancestor.id, + entityType: UMB_DOCUMENT_ENTITY_TYPE, + }; + }), unique: item.id, parent: { unique: item.parent ? item.parent.id : null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts index ee3b089159..cf556635f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts @@ -3,6 +3,7 @@ import { UmbDocumentItemDataResolver } from '../../item/index.js'; import { UmbDefaultTreeItemContext } from '@umbraco-cms/backoffice/tree'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; +import { UmbAncestorsEntityContext } from '@umbraco-cms/backoffice/entity'; export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext< UmbDocumentTreeItemModel, @@ -10,12 +11,14 @@ export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext< > { // TODO: Provide this together with the EntityContext, ideally this takes part via a extension-type [NL] #isTrashedContext = new UmbIsTrashedEntityContext(this); + #ancestorsContext = new UmbAncestorsEntityContext(this); #item = new UmbDocumentItemDataResolver(this); readonly name = this.#item.name; readonly icon = this.#item.icon; readonly isDraft = this.#item.isDraft; + readonly ancestors = this._treeItem.asObservablePart((item) => item?.ancestors ?? []); readonly isTrashed = this._treeItem.asObservablePart((item) => item?.isTrashed ?? false); constructor(host: UmbControllerHost) { @@ -24,6 +27,10 @@ export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext< this.observe(this.isTrashed, (isTrashed) => { this.#isTrashedContext.setIsTrashed(isTrashed); }); + + this.observe(this.ancestors, (ancestors) => { + this.#ancestorsContext.setAncestors(ancestors); + }); } public override setTreeItem(treeItem: UmbDocumentTreeItemModel | undefined) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts index fb7fb4ee5b..7afd0ae918 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts @@ -7,8 +7,10 @@ import type { } from '@umbraco-cms/backoffice/tree'; import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; export interface UmbDocumentTreeItemModel extends UmbTreeItemModel { + ancestors: Array; entityType: UmbDocumentEntityType; noAccess: boolean; isTrashed: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.test.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.test.ts new file mode 100644 index 0000000000..f69fdb8375 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.test.ts @@ -0,0 +1,134 @@ +import { expect } from '@open-wc/testing'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; +import { UmbCurrentUserContext, UmbCurrentUserStore } from '@umbraco-cms/backoffice/current-user'; +import { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; +import { UmbDocumentUserPermissionCondition } from './document-user-permission.condition'; +import { UmbAncestorsEntityContext, UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { + UMB_DOCUMENT_ENTITY_TYPE, + UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, + UMB_USER_PERMISSION_DOCUMENT_READ, +} from '@umbraco-cms/backoffice/document'; + +@customElement('test-controller-host') +class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) { + currentUserContext = new UmbCurrentUserContext(this); + entityContext = new UmbEntityContext(this); + ancestorsContext = new UmbAncestorsEntityContext(this); + + constructor() { + super(); + new UmbNotificationContext(this); + new UmbCurrentUserStore(this); + } + + async init() { + await this.currentUserContext.load(); + } + + setEntity(entity: UmbEntityModel) { + this.entityContext.setUnique(entity.unique); + this.entityContext.setEntityType(entity.entityType); + } + + setEntityAncestors(ancestors: Array) { + this.ancestorsContext.setAncestors(ancestors); + } +} + +describe('UmbDocumentUserPermissionCondition', () => { + let hostElement: UmbTestControllerHostElement; + let condition: UmbDocumentUserPermissionCondition; + + beforeEach(async () => { + hostElement = new UmbTestControllerHostElement(); + document.body.appendChild(hostElement); + await hostElement.init(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Specific permissions', () => { + it('should return true if a user has permissions', (done) => { + // Sets the current entity data + hostElement.setEntity({ + unique: 'permissions-document-id', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + }); + + // This entity does not have any ancestors. + hostElement.setEntityAncestors([]); + + // We expect to find the read permission on the current entity + condition = new UmbDocumentUserPermissionCondition(hostElement, { + host: hostElement, + config: { + alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, + allOf: [UMB_USER_PERMISSION_DOCUMENT_READ], + }, + onChange: (permitted) => { + expect(permitted).to.be.true; + done(); + }, + }); + }); + }); + + describe('Inherited permissions', () => { + it('should inherit permissions from closest ancestor with specific permissions set', (done) => { + // Sets the current entity data + hostElement.setEntity({ + unique: 'permissions-document-1-id', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + }); + + // Sets the ancestors of the current entity. These are the ancestors that will be checked for permissions. + hostElement.setEntityAncestors([{ unique: 'permissions-document-id', entityType: UMB_DOCUMENT_ENTITY_TYPE }]); + + // We expect to find the read permission on the ancestor + condition = new UmbDocumentUserPermissionCondition(hostElement, { + host: hostElement, + config: { + alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, + allOf: [UMB_USER_PERMISSION_DOCUMENT_READ], + }, + onChange: (permitted) => { + expect(permitted).to.be.true; + done(); + }, + }); + }); + }); + + describe('Fallback Permissions', () => { + it('should use the fallback permissions if no specific permissions are set for the entity or ancestors', (done) => { + // Sets the current entity to a document without permissions + hostElement.setEntity({ + unique: 'no-permissions-document-id', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + }); + + // Sets the ancestors of the current entity. These are the ancestors that will be checked for permissions. + // This ancestor does not have any permissions either. + hostElement.setEntityAncestors([ + { unique: 'no-permissions-parent-document-id', entityType: UMB_DOCUMENT_ENTITY_TYPE }, + ]); + + // We expect to find the read permission in the fallback permissions + condition = new UmbDocumentUserPermissionCondition(hostElement, { + host: hostElement, + config: { + alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS, + allOf: [UMB_USER_PERMISSION_DOCUMENT_READ], + }, + onChange: (permitted) => { + expect(permitted).to.be.true; + done(); + }, + }); + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.ts index 060e758808..a91b99689a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.ts @@ -1,7 +1,7 @@ import { isDocumentUserPermission } from '../utils.js'; import type { UmbDocumentUserPermissionConditionConfig } from './types.js'; import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; -import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; +import { UMB_ANCESTORS_ENTITY_CONTEXT, UMB_ENTITY_CONTEXT, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -20,6 +20,7 @@ export class UmbDocumentUserPermissionCondition extends UmbControllerBase implem #documentPermissions: Array = []; #fallbackPermissions: string[] = []; #onChange: UmbOnChangeCallbackType; + #ancestors: Array = []; constructor( host: UmbControllerHost, @@ -54,6 +55,17 @@ export class UmbDocumentUserPermissionCondition extends UmbControllerBase implem 'umbUserPermissionEntityContextObserver', ); }); + + this.consumeContext(UMB_ANCESTORS_ENTITY_CONTEXT, (instance) => { + this.observe( + instance?.ancestors, + (ancestors) => { + this.#ancestors = ancestors.map((item) => item.unique); + this.#checkPermissions(); + }, + 'observeAncestors', + ); + }); } #checkPermissions() { @@ -68,21 +80,29 @@ export class UmbDocumentUserPermissionCondition extends UmbControllerBase implem return; } - /* If there are document permission we check if there are permissions for the current document - If there aren't we use the fallback permissions */ + // If there are document permissions, we need to check the full path to see if any permissions are defined for the current document + // If we find multiple permissions in the same path, we will apply the closest one if (hasDocumentPermissions) { - const permissionsForCurrentDocument = this.#documentPermissions.find( - (permission) => permission.document.id === this.#unique, - ); + // Path including the current document and all ancestors + const path = [...this.#ancestors, this.#unique].filter((unique) => unique !== null); + // Reverse the path to find the closest document permission quickly + const reversedPath = [...path].reverse(); + const documentPermissionsMap = new Map(this.#documentPermissions.map((p) => [p.document.id, p])); + + // Find the closest document permission in the path + const closestDocumentPermission = reversedPath.find((id) => documentPermissionsMap.has(id)); + + // Retrieve the corresponding permission data + const match = closestDocumentPermission ? documentPermissionsMap.get(closestDocumentPermission) : undefined; // no permissions for the current document - use the fallback permissions - if (!permissionsForCurrentDocument) { + if (!match) { this.#check(this.#fallbackPermissions); return; } - // we found permissions for the current document - check them - this.#check(permissionsForCurrentDocument.verbs); + // we found permissions - check them + this.#check(match.verbs); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 2134817942..baeeaddab3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -99,7 +99,13 @@ export class UmbDocumentWorkspaceContext allOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE], }, onChange: (permitted: boolean) => { + if (permitted === this.#userCanCreate) return; this.#userCanCreate = permitted; + this.#setReadOnlyStateForUserPermission( + UMB_USER_PERMISSION_DOCUMENT_CREATE, + this.#userCanCreate, + 'You do not have permission to create documents.', + ); }, }, ]); @@ -110,11 +116,31 @@ export class UmbDocumentWorkspaceContext allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE], }, onChange: (permitted: boolean) => { + if (permitted === this.#userCanUpdate) return; this.#userCanUpdate = permitted; + this.#setReadOnlyStateForUserPermission( + UMB_USER_PERMISSION_DOCUMENT_UPDATE, + this.#userCanUpdate, + 'You do not have permission to update documents.', + ); }, }, ]); + this.observe(this.variants, () => { + this.#setReadOnlyStateForUserPermission( + UMB_USER_PERMISSION_DOCUMENT_CREATE, + this.#userCanCreate, + 'You do not have permission to create documents.', + ); + + this.#setReadOnlyStateForUserPermission( + UMB_USER_PERMISSION_DOCUMENT_UPDATE, + this.#userCanUpdate, + 'You do not have permission to update documents.', + ); + }); + this.routes.setRoutes([ { path: UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN.toString(), @@ -147,13 +173,6 @@ export class UmbDocumentWorkspaceContext const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const documentTypeUnique = info.match.params.documentTypeUnique; await this.create({ entityType: parentEntityType, unique: parentUnique }, documentTypeUnique); - - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_CREATE, - this.#userCanCreate, - 'You do not have permission to create documents.', - ); - new UmbWorkspaceIsNewRedirectController( this, this, @@ -168,11 +187,6 @@ export class UmbDocumentWorkspaceContext this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); const unique = info.match.params.unique; await this.load(unique); - this.#setReadOnlyStateForUserPermission( - UMB_USER_PERMISSION_DOCUMENT_UPDATE, - this.#userCanUpdate, - 'You do not have permission to update documents.', - ); }, }, ]);