Skip to content
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2092,6 +2092,7 @@ export default {
permissionsDefault: 'Default permissions',
permissionsGranular: 'Granular permissions',
permissionsGranularHelp: 'Set permissions for specific nodes',
permissionsExtensions: 'Third-party permissions',
granularRightsLabel: 'Documents',
granularRightsDescription: 'Assign permissions to specific documents',
permissionsEntityGroup_document: 'Document',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context-token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbUserGroupPermissionsListBaseElement } from './user-group-permission-list-base.element.js';

@customElement('umb-user-group-entity-user-permission-list')
export class UmbUserGroupEntityUserPermissionListElement extends UmbLitElement {
@state()
private _fallBackPermissions?: Array<string>;

export class UmbUserGroupEntityUserPermissionListElement extends UmbUserGroupPermissionsListBaseElement {
@state()
private _groups: Array<{ entityType: string; headline: string }> = [];

#userGroupWorkspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE;

constructor() {
super();

this.#observeEntityUserPermissions();

this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#userGroupWorkspaceContext = instance;
this.observe(
this.#userGroupWorkspaceContext?.fallbackPermissions,
(fallbackPermissions) => {
this._fallBackPermissions = fallbackPermissions;
},
'umbUserGroupEntityUserPermissionsObserver',
);
});
}

#observeEntityUserPermissions() {
Expand All @@ -50,14 +31,6 @@ export class UmbUserGroupEntityUserPermissionListElement extends UmbLitElement {
);
}

#onPermissionChange(event: UmbSelectionChangeEvent) {
event.stopPropagation();
const target = event.target as any;
const verbs = target.allowedVerbs;
if (verbs === undefined || verbs === null) throw new Error('The verbs are not defined');
this.#userGroupWorkspaceContext?.setFallbackPermissions(verbs);
}

override render() {
return html` ${this._groups.map((group) => this.#renderPermissionsForEntityType(group))}`;
}
Expand All @@ -68,11 +41,9 @@ export class UmbUserGroupEntityUserPermissionListElement extends UmbLitElement {
<umb-input-entity-user-permission
.entityType=${group.entityType}
.allowedVerbs=${this._fallBackPermissions || []}
@change=${this.#onPermissionChange}></umb-input-entity-user-permission>
@change=${this.onPermissionChange}></umb-input-entity-user-permission>
`;
}

static override styles = [UmbTextStyles];
}

export default UmbUserGroupEntityUserPermissionListElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import type {
ManifestExtensionPermissions,
ManifestExtensionUserPermission,
} from '@umbraco-cms/backoffice/user-permission';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
import { UmbUserGroupPermissionsListBaseElement } from './user-group-permission-list-base.element.js';

@customElement('umb-user-group-extension-permission-list')
export class UmbUserGroupExtensionPermissionsListElement extends UmbUserGroupPermissionsListBaseElement {
@state()
private _manifests?: ManifestExtensionUserPermission[];

@state()
private _extensionElements = new Map<string, HTMLElement>();

@state()
private _extensions?: ManifestExtensionPermissions[];

constructor() {
super();

this.#observeExtensionPermissions();
this.#observeEntityUserPermissions();
}

#observeExtensionPermissions() {
this.observe(
umbExtensionsRegistry.byType('extensionPermissions'),
async (manifests) => {
this._extensions = manifests;

// Pre-create extension elements for each manifest
for (const manifest of manifests) {
if (!manifest.element && !manifest.js) continue;
const element = await createExtensionElement(manifest);

if (element) {
this._extensionElements.set(manifest.meta.extensionAlias, element);
}
}
this.requestUpdate('_extensionElements');
},
'umbExtensionPermissionsObserver',
);
}

#observeEntityUserPermissions() {
this.observe(
umbExtensionsRegistry.byType('extensionUserPermission'),
(manifests) => (this._manifests = manifests),
'umbExtensionUserPermissionsObserver',
);
}

#groupPermissionsForExtension(manifests: ManifestExtensionUserPermission[]) {
const entityTypes = [...new Set(manifests.flatMap((manifest) => manifest.forEntityTypes))];

return entityTypes
.map((entityType) => ({
entityType,
headline: this.localize.term(`user_permissionsEntityGroup_${entityType}`),
}))
.sort((a, b) => a.headline.localeCompare(b.headline));
}

#renderProperty(manifest: ManifestExtensionPermissions) {
const label = manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label;
const description = manifest.meta.descriptionKey
? this.localize.term(manifest.meta.descriptionKey)
: manifest.meta.description;

const permissions = this._manifests?.filter((x) => x.forExtension === manifest.meta.extensionAlias) || [];
const groupedPermissions = this.#groupPermissionsForExtension(permissions);

return html`
<umb-property-layout .label=${label || ''} .description=${description || ''}>
<div slot="editor">
${this._extensionElements.get(manifest.meta.extensionAlias)}
${groupedPermissions.map(
(group) =>
html` <h4>${group.headline}</h4>
<umb-input-extension-user-permission
.forExtension=${manifest.meta.extensionAlias}
.entityType=${group.entityType}
.allowedVerbs=${this._fallBackPermissions || []}
@change=${this.onPermissionChange}></umb-input-extension-user-permission>`,
)}
</div>
</umb-property-layout>
`;
}

override render() {
if (!this._extensions) return nothing;

return html`${this._extensions.map((extension) => this.#renderProperty(extension))}`;
}
}

export default UmbUserGroupExtensionPermissionsListElement;

declare global {
interface HTMLElementTagNameMap {
'umb-user-group-extension-permission-list': UmbUserGroupExtensionPermissionsListElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { html, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context-token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';

export abstract class UmbUserGroupPermissionsListBaseElement extends UmbLitElement {
protected userGroupWorkspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE;

@state()
protected _fallBackPermissions?: Array<string>;

constructor() {
super();

this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.userGroupWorkspaceContext = instance;
this.observe(
this.userGroupWorkspaceContext?.fallbackPermissions,
(fallbackPermissions) => {
this._fallBackPermissions = fallbackPermissions;
},
'umbUserGroupFallbackPermissionsObserver',
);
});
}

protected renderPermissionsForEntityType(group: { entityType: string; headline: string }) {
return html`
<h4>${group.headline}</h4>
<umb-input-entity-user-permission
.entityType=${group.entityType}
.allowedVerbs=${this._fallBackPermissions || []}
@change=${this.onPermissionChange}></umb-input-entity-user-permission>
`;
}

protected onPermissionChange(event: UmbSelectionChangeEvent) {
event.stopPropagation();
const target = event.target as any;
const verbs = target.allowedVerbs;

if (verbs === undefined || verbs === null) throw new Error('The verbs are not defined');

this.userGroupWorkspaceContext?.setFallbackPermissions(verbs);
}

static override styles = [UmbTextStyles];
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { UmbInputWithAliasElement } from '@umbraco-cms/backoffice/component

import './components/user-group-entity-user-permission-list.element.js';
import './components/user-group-granular-permission-list.element.js';
import './components/user-group-extension-permission-list.element.js';

@customElement('umb-user-group-workspace-editor')
export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
Expand Down Expand Up @@ -256,6 +257,11 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
<div slot="headline"><umb-localize key="user_permissionsGranular"></umb-localize></div>
<umb-user-group-granular-permission-list></umb-user-group-granular-permission-list>
</uui-box>

<uui-box>
<div slot="headline"><umb-localize key="user_permissionsExtensions"></umb-localize></div>
<umb-user-group-extension-permission-list></umb-user-group-extension-permission-list>
</uui-box>
</umb-stack>
</div>
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import './input-entity-user-permission/input-entity-user-permission.element.js';
import './input-user-permission-verb/input-user-permission-verb.element.js';
import './input-extension-user-permission/input-extension-user-permission.element.js';
import './input-user-permission-base.element.js';

export * from './input-entity-user-permission/input-entity-user-permission.element.js';
export * from './input-user-permission-verb/input-user-permission-verb.element.js';
export * from './input-extension-user-permission/input-extension-user-permission.element.js';
export * from './input-user-permission-base.element.js';
Original file line number Diff line number Diff line change
@@ -1,120 +1,24 @@
import type { ManifestEntityUserPermission } from '../../entity-user-permission.extension.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, state, nothing, ifDefined, css } from '@umbraco-cms/backoffice/external/lit';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import type { UmbUserPermissionVerbElement } from '@umbraco-cms/backoffice/user';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbInputUserPermissionBaseElement } from '../input-user-permission-base.element.js';

@customElement('umb-input-entity-user-permission')
export class UmbInputEntityUserPermissionElement extends UmbFormControlMixin(UmbLitElement) {
@property({ type: String, attribute: 'entity-type' })
public get entityType(): string {
return this._entityType;
}
public set entityType(value: string) {
if (value === this._entityType) return;
this._entityType = value;
this.#observeEntityUserPermissions();
}
private _entityType: string = '';

@property({ attribute: false })
allowedVerbs: Array<string> = [];

@state()
private _manifests: Array<ManifestEntityUserPermission> = [];

#manifestObserver?: UmbObserverController<Array<ManifestEntityUserPermission>>;

protected override getFormElement() {
return undefined;
}

#isAllowed(permissionVerbs: Array<string>) {
return permissionVerbs.every((verb) => this.allowedVerbs.includes(verb));
}
export class UmbInputEntityUserPermissionElement extends UmbInputUserPermissionBaseElement<ManifestEntityUserPermission> {

#observeEntityUserPermissions() {
this.#manifestObserver?.destroy();
observePermissions() {
this.manifestObserver?.destroy();

this.#manifestObserver = this.observe(
this.manifestObserver = this.observe(
umbExtensionsRegistry.byType('entityUserPermission'),
(userPermissionManifests) => {
this._manifests = userPermissionManifests.filter((manifest) =>
this.manifests = userPermissionManifests.filter((manifest) =>
manifest.forEntityTypes.includes(this.entityType),
);
},
'umbUserPermissionManifestsObserver',
);
}

#onChangeUserPermission(event: UmbChangeEvent, permissionVerbs: Array<string>) {
event.stopPropagation();
const target = event.target as UmbUserPermissionVerbElement;
if (target.allowed) {
this.#addUserPermission(permissionVerbs);
} else {
this.#removeUserPermission(permissionVerbs);
}
}

#addUserPermission(permissionVerbs: Array<string>) {
const verbs = [...this.allowedVerbs, ...permissionVerbs];
// ensure we only have unique verbs
this.allowedVerbs = [...new Set(verbs)];
this.dispatchEvent(new UmbChangeEvent());
}

#removeUserPermission(permissionVerbs: Array<string>) {
this.allowedVerbs = this.allowedVerbs.filter((p) => !permissionVerbs.includes(p));
this.dispatchEvent(new UmbChangeEvent());
}

override render() {
return html`${this.#renderGroupedPermissions(this._manifests)} `;
}

#renderGroupedPermissions(permissionManifests: Array<ManifestEntityUserPermission>) {
// TODO: groupBy is not known by TS yet
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const groupedPermissions = Object.groupBy(
permissionManifests,
(manifest: ManifestEntityUserPermission) => manifest.meta.group,
) as Record<string, Array<ManifestEntityUserPermission>>;
return html`
${Object.entries(groupedPermissions).map(
([group, manifests]) => html`
${group !== 'undefined'
? html` <h5><umb-localize .key=${`actionCategories_${group}`}>${group}</umb-localize></h5> `
: nothing}
<div>${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)}</div>
`,
)}
`;
}

#renderPermission(manifest: ManifestEntityUserPermission) {
return html` <umb-input-user-permission-verb
label=${ifDefined(manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name)}
description=${ifDefined(manifest.meta.description ? this.localize.string(manifest.meta.description) : undefined)}
?allowed=${this.#isAllowed(manifest.meta.verbs)}
@change=${(event: UmbChangeEvent) =>
this.#onChangeUserPermission(event, manifest.meta.verbs)}></umb-input-user-permission-verb>`;
}

override disconnectedCallback() {
super.disconnectedCallback();
this.#manifestObserver?.destroy();
}

static override styles = css`
umb-input-user-permission-verb:not(:last-of-type) {
border-bottom: 1px solid var(--uui-color-divider);
}
`;
}

export default UmbInputEntityUserPermissionElement;
Expand Down
Loading
Loading