Merge branch 'main' into bugfix/create-user-updates-after-new-endpoints

This commit is contained in:
Mads Rasmussen
2024-02-29 08:52:03 +01:00
committed by GitHub
57 changed files with 810 additions and 781 deletions

View File

@@ -107,6 +107,7 @@ export type { DocumentCollectionResponseModel } from './models/DocumentCollectio
export type { DocumentConfigurationResponseModel } from './models/DocumentConfigurationResponseModel';
export type { DocumentItemResponseModel } from './models/DocumentItemResponseModel';
export type { DocumentNotificationResponseModel } from './models/DocumentNotificationResponseModel';
export type { DocumentPermissionPresentationModel } from './models/DocumentPermissionPresentationModel';
export type { DocumentRecycleBinItemResponseModel } from './models/DocumentRecycleBinItemResponseModel';
export type { DocumentResponseModel } from './models/DocumentResponseModel';
export type { DocumentTreeItemResponseModel } from './models/DocumentTreeItemResponseModel';
@@ -362,10 +363,10 @@ export type { TemplateQueryResultItemPresentationModel } from './models/Template
export type { TemplateQueryResultResponseModel } from './models/TemplateQueryResultResponseModel';
export type { TemplateQuerySettingsResponseModel } from './models/TemplateQuerySettingsResponseModel';
export type { TemplateResponseModel } from './models/TemplateResponseModel';
export type { TemporaryFileConfigurationResponseModel } from './models/TemporaryFileConfigurationResponseModel';
export type { TemporaryFileResponseModel } from './models/TemporaryFileResponseModel';
export type { TourStatusModel } from './models/TourStatusModel';
export type { TreeItemPresentationModel } from './models/TreeItemPresentationModel';
export type { UnknownTypePermissionPresentationModel } from './models/UnknownTypePermissionPresentationModel';
export type { UnlockUsersRequestModel } from './models/UnlockUsersRequestModel';
export type { UnpublishDocumentRequestModel } from './models/UnpublishDocumentRequestModel';
export type { UpdateContentForDocumentRequestModel } from './models/UpdateContentForDocumentRequestModel';

View File

@@ -3,6 +3,9 @@
/* tslint:disable */
/* eslint-disable */
import type { DocumentPermissionPresentationModel } from './DocumentPermissionPresentationModel';
import type { UnknownTypePermissionPresentationModel } from './UnknownTypePermissionPresentationModel';
export type CurrentUserResponseModel = {
id: string;
email: string;
@@ -14,6 +17,8 @@ export type CurrentUserResponseModel = {
avatarUrls: Array<string>;
languages: Array<string>;
hasAccessToAllLanguages: boolean;
permissions: Array<string>;
fallbackPermissions: Array<string>;
permissions: Array<(DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel)>;
allowedSections: Array<string>;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ReferenceByIdModel } from './ReferenceByIdModel';
export type DocumentPermissionPresentationModel = {
$type: string;
document: ReferenceByIdModel;
verbs: Array<string>;
};

View File

@@ -1,12 +0,0 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TemporaryFileConfigurationResponseModel = {
imageFileTypes: Array<string>;
disallowedUploadedFilesExtensions: Array<string>;
allowedUploadedFileExtensions: Array<string>;
maxFileSize?: number | null;
};

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UnknownTypePermissionPresentationModel = {
$type: string;
verbs: Array<string>;
context: string;
};

View File

@@ -3,7 +3,9 @@
/* tslint:disable */
/* eslint-disable */
import type { DocumentPermissionPresentationModel } from './DocumentPermissionPresentationModel';
import type { ReferenceByIdModel } from './ReferenceByIdModel';
import type { UnknownTypePermissionPresentationModel } from './UnknownTypePermissionPresentationModel';
export type UserGroupBaseModel = {
name: string;
@@ -15,6 +17,7 @@ export type UserGroupBaseModel = {
documentRootAccess: boolean;
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
permissions: Array<string>;
fallbackPermissions: Array<string>;
permissions: Array<(DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel)>;
};

View File

@@ -2,7 +2,6 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TemporaryFileConfigurationResponseModel } from '../models/TemporaryFileConfigurationResponseModel';
import type { TemporaryFileResponseModel } from '../models/TemporaryFileResponseModel';
import type { CancelablePromise } from '../core/CancelablePromise';
@@ -86,7 +85,7 @@ export class TemporaryFileResource {
* @returns any Success
* @throws ApiError
*/
public static getTemporaryFileConfiguration(): CancelablePromise<TemporaryFileConfigurationResponseModel> {
public static getTemporaryFileConfiguration(): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/temporary-file/configuration',

View File

@@ -47,10 +47,10 @@ const createDetailMockMapper = (request: CreateDataTypeRequestModel): UmbMockDat
editorAlias: request.editorAlias,
editorUiAlias: request.editorUiAlias,
values: request.values,
canIgnoreStartNodes: false,
isFolder: false,
hasChildren: false,
isDeletable: true,
canIgnoreStartNodes: false,
};
};

View File

@@ -1,9 +1,4 @@
import type { UserGroupItemResponseModel, UserGroupResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import {
UMB_USER_PERMISSION_DOCUMENT_CREATE,
UMB_USER_PERMISSION_DOCUMENT_DELETE,
UMB_USER_PERMISSION_DOCUMENT_READ,
} from '@umbraco-cms/backoffice/document';
export type UmbMockUserGroupModel = UserGroupResponseModel & UserGroupItemResponseModel;
@@ -13,10 +8,29 @@ export const data: Array<UmbMockUserGroupModel> = [
name: 'Administrators',
icon: 'icon-medal',
documentStartNode: { id: 'all-property-editors-document-id' },
fallbackPermissions: [
'Umb.Document.Read',
'Umb.Document.Create',
'Umb.Document.Update',
'Umb.Document.Delete',
'Umb.Document.CreateBlueprint',
'Umb.Document.Notifications',
'Umb.Document.Publish',
'Umb.Document.Permissions',
'Umb.Document.Unpublish',
'Umb.Document.Duplicate',
'Umb.Document.Move',
'Umb.Document.Sort',
'Umb.Document.CultureAndHostnames',
'Umb.Document.PublicAccess',
'Umb.Document.Rollback',
],
permissions: [
UMB_USER_PERMISSION_DOCUMENT_READ,
UMB_USER_PERMISSION_DOCUMENT_CREATE,
UMB_USER_PERMISSION_DOCUMENT_DELETE,
{
$type: 'DocumentPermissionPresentationModel',
verbs: ['Umb.Document.Rollback'],
document: { id: 'simple-document-id' },
},
],
sections: [],
languages: [],
@@ -30,7 +44,22 @@ export const data: Array<UmbMockUserGroupModel> = [
name: 'Editors',
icon: 'icon-tools',
documentStartNode: { id: 'all-property-editors-document-id' },
permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE],
fallbackPermissions: [
'Umb.Document.Read',
'Umb.Document.Create',
'Umb.Document.Update',
'Umb.Document.Delete',
'Umb.Document.CreateBlueprint',
'Umb.Document.Notifications',
'Umb.Document.Publish',
'Umb.Document.Unpublish',
'Umb.Document.Duplicate',
'Umb.Document.Move',
'Umb.Document.Sort',
'Umb.Document.PublicAccess',
'Umb.Document.Rollback',
],
permissions: [],
sections: [],
languages: [],
hasAccessToAllLanguages: true,
@@ -43,7 +72,8 @@ export const data: Array<UmbMockUserGroupModel> = [
name: 'Sensitive data',
icon: 'icon-lock',
documentStartNode: { id: 'all-property-editors-document-id' },
permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE],
fallbackPermissions: [],
permissions: [],
sections: [],
languages: [],
hasAccessToAllLanguages: true,
@@ -56,7 +86,8 @@ export const data: Array<UmbMockUserGroupModel> = [
name: 'Translators',
icon: 'icon-globe',
documentStartNode: { id: 'all-property-editors-document-id' },
permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE],
fallbackPermissions: ['Umb.Document.Read', 'Umb.Document.Update'],
permissions: [],
sections: [],
languages: [],
hasAccessToAllLanguages: true,
@@ -69,7 +100,13 @@ export const data: Array<UmbMockUserGroupModel> = [
name: 'Writers',
icon: 'icon-edit',
documentStartNode: { id: 'all-property-editors-document-id' },
permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE],
fallbackPermissions: [
'Umb.Document.Read',
'Umb.Document.Create',
'Umb.Document.Update',
'Umb.Document.Notifications',
],
permissions: [],
sections: [],
languages: [],
hasAccessToAllLanguages: true,

View File

@@ -5,6 +5,8 @@ import type { UmbMockUserGroupModel } from './user-group.data.js';
import { data } from './user-group.data.js';
import type {
CreateUserGroupRequestModel,
DocumentPermissionPresentationModel,
UnknownTypePermissionPresentationModel,
UserGroupItemResponseModel,
UserGroupResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
@@ -24,14 +26,17 @@ export class UmbUserGroupMockDB extends UmbEntityMockDbBase<UmbMockUserGroupMode
* @return {*} {string[]}
* @memberof UmbUserGroupData
*/
getPermissions(userGroupIds: string[]): string[] {
getPermissions(
userGroupIds: string[],
): Array<DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel> {
const permissions = this.data
.filter((userGroup) => userGroupIds.includes(userGroup.id || ''))
.filter((userGroup) => userGroupIds.includes(userGroup.id))
.map((userGroup) => (userGroup.permissions?.length ? userGroup.permissions : []))
.flat();
// Remove duplicates
return [...new Set(permissions)];
const uniqueArray = Array.from(new Set(permissions.map((e) => JSON.stringify(e)))).map((e) => JSON.parse(e));
return uniqueArray;
}
}
@@ -55,6 +60,7 @@ const createMockMapper = (item: CreateUserGroupRequestModel): UmbMockUserGroupMo
mediaRootAccess: item.mediaRootAccess,
mediaStartNode: item.mediaStartNode,
name: item.name,
fallbackPermissions: item.fallbackPermissions,
permissions: item.permissions,
sections: item.sections,
};
@@ -72,6 +78,7 @@ const detailResponseMapper = (item: UmbMockUserGroupModel): UserGroupResponseMod
mediaRootAccess: item.mediaRootAccess,
mediaStartNode: item.mediaStartNode,
name: item.name,
fallbackPermissions: item.fallbackPermissions,
permissions: item.permissions,
sections: item.sections,
};

View File

@@ -1,50 +0,0 @@
import { UmbEntityData } from './entity.data.js';
import {
UMB_DOCUMENT_ENTITY_TYPE,
UMB_USER_PERMISSION_DOCUMENT_CREATE,
UMB_USER_PERMISSION_DOCUMENT_READ,
} from '@umbraco-cms/backoffice/document';
export type UserPermissionModel = {
id: string;
target: unknown;
permissions: Array<string>;
};
export const data: Array<UserPermissionModel> = [
{
id: '408074bb-f776-485e-b85e-c2473e45663b',
target: {
entityType: UMB_DOCUMENT_ENTITY_TYPE,
documentId: 'simple-document-id',
userGroupId: 'user-group-administrators-id',
},
permissions: [UMB_USER_PERMISSION_DOCUMENT_READ],
},
{
id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88',
target: {
entityType: UMB_DOCUMENT_ENTITY_TYPE,
documentId: 'simple-document-id',
userGroupId: 'user-group-editors-id',
},
permissions: [UMB_USER_PERMISSION_DOCUMENT_READ, UMB_USER_PERMISSION_DOCUMENT_CREATE],
},
{
id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88',
target: {
entityType: UMB_DOCUMENT_ENTITY_TYPE,
documentId: 'c05da24d-7740-447b-9cdc-bd8ce2172e38',
userGroupId: 'user-group-administrators-id',
},
permissions: [UMB_USER_PERMISSION_DOCUMENT_READ],
},
];
class UmbUserPermissionData extends UmbEntityData<UserPermissionModel> {
constructor() {
super(data);
}
}
export const umbUserPermissionData = new UmbUserPermissionData();

View File

@@ -64,7 +64,9 @@ class UmbUserMockDB extends UmbEntityMockDbBase<UmbMockUserModel> {
languages: [],
documentStartNodeIds: firstUser.documentStartNodeIds,
mediaStartNodeIds: firstUser.mediaStartNodeIds,
fallbackPermissions: [],
permissions,
allowedSections: [],
};
}

View File

@@ -1,8 +1,8 @@
export class UmbDeselectedEvent extends Event {
public static readonly TYPE = 'deselected';
public unique: string;
public unique: string | null;
public constructor(unique: string) {
public constructor(unique: string | null) {
// mimics the native change event
super(UmbDeselectedEvent.TYPE, { bubbles: true, composed: false, cancelable: false });
this.unique = unique;

View File

@@ -1,8 +1,8 @@
export class UmbSelectedEvent extends Event {
public static readonly TYPE = 'selected';
public unique: string;
public unique: string | null;
public constructor(unique: string) {
public constructor(unique: string | null) {
// mimics the native change event
super(UmbSelectedEvent.TYPE, { bubbles: true, composed: false, cancelable: false });
this.unique = unique;

View File

@@ -1,12 +1,13 @@
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestUserPermission extends ManifestBase {
type: 'userPermission';
meta: MetaUserPermission;
export interface ManifestEntityUserPermission extends ManifestBase {
type: 'entityUserPermission';
meta: MetaEntityUserPermission;
}
export interface MetaUserPermission {
export interface MetaEntityUserPermission {
entityType: string;
verbs: Array<string>;
label?: string;
labelKey?: string;
description?: string;

View File

@@ -32,8 +32,8 @@ import type { ManifestWorkspaceAction } from './workspace-action.model.js';
import type { ManifestWorkspaceContext } from './workspace-context.model.js';
import type { ManifestWorkspaceFooterApp } from './workspace-footer-app.model.js';
import type { ManifestWorkspaceView } from './workspace-view.model.js';
import type { ManifestUserPermission } from './user-permission.model.js';
import type { ManifestUserGranularPermission } from './user-granular-permission.model.js';
import type { ManifestEntityUserPermission } from './entity-user-permission.model.js';
import type { ManifestGranularUserPermission } from './user-granular-permission.model.js';
import type { ManifestCollectionAction } from './collection-action.model.js';
import type {
ManifestBase,
@@ -72,7 +72,7 @@ export type * from './tinymce-plugin.model.js';
export type * from './tree-item.model.js';
export type * from './tree.model.js';
export type * from './user-granular-permission.model.js';
export type * from './user-permission.model.js';
export type * from './entity-user-permission.model.js';
export type * from './user-profile-app.model.js';
export type * from './workspace-action.model.js';
export type * from './workspace-context.model.js';
@@ -126,6 +126,6 @@ export type ManifestTypes =
| ManifestWorkspaceContext
| ManifestWorkspaceFooterApp
| ManifestWorkspaceView
| ManifestUserPermission
| ManifestUserGranularPermission
| ManifestEntityUserPermission
| ManifestGranularUserPermission
| ManifestBase;

View File

@@ -1,10 +1,14 @@
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestUserGranularPermission extends ManifestElement {
export interface ManifestGranularUserPermission extends ManifestElement {
type: 'userGranularPermission';
meta: MetaUserGranularPermission;
meta: MetaGranularUserPermission;
}
export interface MetaUserGranularPermission {
entityType: string;
export interface MetaGranularUserPermission {
schemaType: string;
label?: string;
labelKey?: string;
description?: string;
descriptionKey?: string;
}

View File

@@ -1,7 +1,7 @@
import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UmbTreePickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UmbTreeElement, UmbTreeItemModelBase, UmbTreeSelectionConfiguration } from '@umbraco-cms/backoffice/tree';
@customElement('umb-tree-picker-modal')
@@ -29,11 +29,21 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
this._selectionConfiguration.multiple = this.data?.multiple ?? false;
}
#onSelectionChange(e: CustomEvent) {
e.stopPropagation();
const element = e.target as UmbTreeElement;
#onSelectionChange(event: UmbSelectionChangeEvent) {
event.stopPropagation();
const element = event.target as UmbTreeElement;
this.value = { selection: element.getSelection() };
this.dispatchEvent(new UmbSelectionChangeEvent());
this.modalContext?.dispatchEvent(new UmbSelectionChangeEvent());
}
#onSelected(event: UmbSelectedEvent) {
event.stopPropagation();
this.modalContext?.dispatchEvent(new UmbSelectedEvent(event.unique));
}
#onDeselected(event: UmbDeselectedEvent) {
event.stopPropagation();
this.modalContext?.dispatchEvent(new UmbDeselectedEvent(event.unique));
}
render() {
@@ -44,6 +54,8 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
?hide-tree-root=${this.data?.hideTreeRoot}
alias=${ifDefined(this.data?.treeAlias)}
@selection-change=${this.#onSelectionChange}
@selected=${this.#onSelected}
@deselected=${this.#onDeselected}
.selectionConfiguration=${this._selectionConfiguration}
.filter=${this.data?.filter}
.selectableFilter=${this.data?.pickableFilter}></umb-tree>

View File

@@ -3,12 +3,11 @@ import { UmbModalToken } from './modal-token.js';
export interface UmbEntityUserPermissionSettingsModalData {
unique: string;
entityType: string;
allowedPermissions: Array<string>;
headline?: string;
}
export type UmbEntityUserPermissionSettingsModalValue = {
allowedPermissions: Array<string>;
allowedVerbs: Array<string>;
};
export const UMB_ENTITY_USER_PERMISSION_MODAL = new UmbModalToken<

View File

@@ -1,6 +1,6 @@
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
/**
@@ -8,7 +8,7 @@ import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observab
* @export
* @class UmbSelectionManager
*/
export class UmbSelectionManager<ValueType = string | null> extends UmbBaseController {
export class UmbSelectionManager<ValueType extends string | null = string | null> extends UmbBaseController {
#selectable = new UmbBooleanState(false);
public readonly selectable = this.#selectable.asObservable();
@@ -105,6 +105,7 @@ export class UmbSelectionManager<ValueType = string | null> extends UmbBaseContr
if (this.isSelected(unique)) return;
const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique];
this.#selection.setValue(newSelection);
this.getHostElement().dispatchEvent(new UmbSelectedEvent(unique));
this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent());
}
@@ -117,6 +118,7 @@ export class UmbSelectionManager<ValueType = string | null> extends UmbBaseContr
if (this.getSelectable() === false) return;
const newSelection = this.getSelection().filter((x) => x !== unique);
this.#selection.setValue(newSelection);
this.getHostElement().dispatchEvent(new UmbDeselectedEvent(unique));
this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent());
}

View File

@@ -1,3 +1,2 @@
export * from './input-document/input-document.element.js';
export * from './input-document-granular-permission/input-document-granular-permission.element.js';
export * from './input-document-root-picker/input-document-root-picker.element.js';

View File

@@ -1,110 +0,0 @@
import { UmbDocumentItemRepository } from '../../repository/index.js';
import type { UmbDocumentItemModel } from '../../repository/item/types.js';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT, UMB_DOCUMENT_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
@customElement('umb-input-document-granular-permission')
export class UmbInputDocumentGranularPermissionElement extends FormControlMixin(UmbLitElement) {
private _selectedIds: Array<string> = [];
public get selectedIds(): Array<string> {
return this._selectedIds;
}
public set selectedIds(ids: Array<string>) {
this._selectedIds = ids;
super.value = ids.join(',');
this.#observePickedDocuments();
}
@property()
public set value(idsString: string) {
if (idsString !== this._value) {
this.selectedIds = splitStringToArray(idsString);
}
}
@state()
private _items?: Array<UmbDocumentItemModel>;
#documentItemRepository = new UmbDocumentItemRepository(this);
#modalContext?: UmbModalManagerContext;
#pickedItemsObserver?: UmbObserverController<Array<UmbDocumentItemModel>>;
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => (this.#modalContext = instance));
}
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.firstUpdated(_changedProperties);
this.#observePickedDocuments();
}
protected getFormElement() {
return undefined;
}
async #observePickedDocuments() {
this.#pickedItemsObserver?.destroy();
const { asObservable } = await this.#documentItemRepository.requestItems(this._selectedIds);
this.#pickedItemsObserver = this.observe(asObservable(), (items) => (this._items = items));
}
#openDocumentPicker() {
// We send a shallow copy(good enough as its just an array of ids) of our this._selectedIds, as we don't want the modal to manipulate our data:
// TODO: Use value instead:
const modalContext = this.#modalContext?.open(UMB_DOCUMENT_PICKER_MODAL, {
value: {
selection: [...this._selectedIds],
},
});
//modalContext?.onSubmit().then((value) => {
//this.#setSelection(selection);
//});
}
#setSelection(newSelection: Array<string>) {
this.selectedIds = newSelection;
this.dispatchEvent(new UmbChangeEvent());
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.#pickedItemsObserver?.destroy();
}
render() {
return html`
${this._items?.map((item) => this.#renderItem(item))}
<uui-button id="add-button" look="placeholder" @click=${this.#openDocumentPicker} label="open">Add</uui-button>
`;
}
#renderItem(item: UmbDocumentItemModel) {
return html` <div>Render something here ${item.unique}</div> `;
}
static styles = [
css`
#add-button {
width: 100%;
}
`,
];
}
export default UmbInputDocumentGranularPermissionElement;
declare global {
interface HTMLElementTagNameMap {
'umb-input-document-granular-permission': UmbInputDocumentGranularPermissionElement;
}
}

View File

@@ -5,7 +5,6 @@ import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.actio
import { UmbUnpublishDocumentEntityAction } from './unpublish.action.js';
import { UmbRollbackDocumentEntityAction } from './rollback.action.js';
import { manifests as createManifests } from './create/manifests.js';
import { manifests as permissionManifests } from './permissions/manifests.js';
import { manifests as publicAccessManifests } from './public-access/manifests.js';
import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js';
import {
@@ -17,7 +16,6 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const entityActions: Array<ManifestTypes> = [
...createManifests,
...permissionManifests,
...publicAccessManifests,
...cultureAndHostnamesManifests,
{

View File

@@ -1,30 +0,0 @@
import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js';
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
import { UmbDocumentPermissionsEntityAction } from './permissions.action.js';
import type { ManifestEntityAction, ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
const entityActions: Array<ManifestEntityAction> = [
{
type: 'entityAction',
alias: 'Umb.EntityAction.Document.Permissions',
name: 'Document Permissions Entity Action',
api: UmbDocumentPermissionsEntityAction,
meta: {
icon: 'icon-vcard',
label: 'Permissions (TBD)',
repositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
entityTypes: [UMB_DOCUMENT_ENTITY_TYPE],
},
},
];
const modals: Array<ManifestModal> = [
{
type: 'modal',
alias: 'Umb.Modal.Permissions',
name: 'Permissions Modal',
js: () => import('./permissions-modal.element.js'),
},
];
export const manifests = [...entityActions, ...modals];

View File

@@ -1,170 +0,0 @@
import { UmbDocumentPermissionRepository } from '../../user-permissions/index.js';
import { UmbDocumentItemRepository } from '../../repository/index.js';
import { UmbUserGroupItemRepository, UMB_USER_GROUP_PICKER_MODAL } from '@umbraco-cms/backoffice/user-group';
import { html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
UmbEntityUserPermissionSettingsModalData,
UmbEntityUserPermissionSettingsModalValue,
UmbModalContext,
UmbModalManagerContext,
} from '@umbraco-cms/backoffice/modal';
import { UMB_ENTITY_USER_PERMISSION_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
type UmbUserGroupRefData = {
id: string;
name?: string;
icon?: string | null;
permissions: Array<string>;
};
@customElement('umb-permissions-modal')
export class UmbPermissionsModalElement extends UmbLitElement {
@property({ attribute: false })
modalContext?: UmbModalContext<UmbEntityUserPermissionSettingsModalData, UmbEntityUserPermissionSettingsModalValue>;
@property({ type: Object })
data?: UmbEntityUserPermissionSettingsModalData;
@state()
_entityItem?: any;
@state()
_userGroupRefs: Array<UmbUserGroupRefData> = [];
#userPermissions: Array<any> = [];
#userGroupIemRepository = new UmbUserGroupItemRepository(this);
#documentPermissionRepository = new UmbDocumentPermissionRepository(this);
#documentItemRepository = new UmbDocumentItemRepository(this);
#modalManagerContext?: UmbModalManagerContext;
#userGroupPickerModal?: UmbModalContext;
private _handleConfirm() {
this.modalContext?.submit();
}
private _handleCancel() {
this.modalContext?.reject();
}
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this.#modalManagerContext = instance;
});
}
protected async firstUpdated(): Promise<void> {
if (!this.data?.unique) throw new Error('Could not load permissions, no unique was provided');
this.#getEntityItem(this.data.unique);
this.#getEntityPermissions(this.data.unique);
}
async #getEntityItem(unique: string) {
const { data } = await this.#documentItemRepository.requestItems([unique]);
if (!data) throw new Error('Could not load item');
this._entityItem = data[0];
}
async #getEntityPermissions(unique: string) {
const { data } = await this.#documentPermissionRepository.requestPermissions(unique);
if (data) {
this.#userPermissions = data;
this.#mapToUserGroupRefs();
}
}
async #mapToUserGroupRefs() {
const userGroupIds = [...new Set(this.#userPermissions.map((permission) => permission.target.userGroupId))];
const { data } = await this.#userGroupIemRepository.requestItems(userGroupIds);
const userGroups = data ?? [];
this._userGroupRefs = this.#userPermissions.map((entry) => {
const userGroup = userGroups.find((userGroup) => userGroup.unique == entry.target.userGroupId);
return {
id: entry.target.userGroupId,
name: userGroup?.name,
icon: userGroup?.icon,
permissions: entry.permissions,
};
});
}
#openUserGroupPickerModal() {
if (!this.#modalManagerContext) return;
this.#userGroupPickerModal = this.#modalManagerContext.open(UMB_USER_GROUP_PICKER_MODAL);
this.#userGroupPickerModal.addEventListener(UmbSelectedEvent.TYPE, (event) =>
this.#openUserPermissionsModal((event as UmbSelectedEvent).unique),
);
}
#openUserPermissionsModal(id: string) {
if (!id) throw new Error('Could not open permissions modal, no id was provided');
if (!this.data?.entityType) throw new Error('Could not open permissions modal, no entity type was provided');
const userGroupRef = this._userGroupRefs.find((userGroup) => userGroup.id == id);
const modalContext = this.#modalManagerContext?.open(UMB_ENTITY_USER_PERMISSION_MODAL, {
data: {
unique: id,
entityType: this.data.entityType,
allowedPermissions: userGroupRef?.permissions || [],
headline: `Permissions for ${userGroupRef?.name}`,
},
});
modalContext?.onSubmit().then((value) => {
console.log(value);
});
}
render() {
return html`
<umb-body-layout headline="Permissions for ${this._entityItem?.name}">
<uui-box>
Permissions set for User Groups for document: ${this.data?.entityType}:
<uui-ref-list>
${this._userGroupRefs.map(
(userGroup) =>
html`<umb-user-group-ref
name=${ifDefined(userGroup.name)}
.userPermissionAliases=${userGroup.permissions}
@open=${() => this.#openUserPermissionsModal(userGroup.id)}
standalone>
${userGroup.icon ? html`<uui-icon slot="icon" name=${userGroup.icon}></uui-icon>` : nothing}
</umb-user-group-ref>`,
)}
</uui-ref-list>
<uui-button style="width: 100%;" @click=${this.#openUserGroupPickerModal} look="placeholder"
>Select user group</uui-button
>
</uui-box>
<uui-button slot="actions" id="cancel" label="Cancel" @click="${this._handleCancel}">Cancel</uui-button>
<uui-button
slot="actions"
id="confirm"
color="positive"
look="primary"
label="Save"
@click=${this._handleConfirm}></uui-button>
</umb-body-layout>
`;
}
static styles = [UmbTextStyles];
}
export default UmbPermissionsModalElement;
declare global {
interface HTMLElementTagNameMap {
'umb-permissions-modal': UmbPermissionsModalElement;
}
}

View File

@@ -1,29 +0,0 @@
import type { UmbDocumentDetailRepository } from '../../repository/index.js';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT, UMB_PERMISSIONS_MODAL } from '@umbraco-cms/backoffice/modal';
export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase<UmbDocumentDetailRepository> {
#modalManagerContext?: UmbModalManagerContext;
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
super(host, repositoryAlias, unique, entityType);
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this.#modalManagerContext = instance;
});
}
async execute() {
if (!this.repository) return;
if (!this.#modalManagerContext) return;
this.#modalManagerContext.open(UMB_PERMISSIONS_MODAL, {
data: {
unique: this.unique,
entityType: 'document',
},
});
}
}

View File

@@ -1,3 +1,4 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
import type { UmbDocumentItemModel } from './types.js';
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api';
@@ -32,6 +33,7 @@ const getItems = (uniques: Array<string>) => DocumentResource.getItemDocument({
const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => {
return {
entityType: UMB_DOCUMENT_ENTITY_TYPE,
unique: item.id,
isTrashed: item.isTrashed,
isProtected: item.isProtected,

View File

@@ -1,7 +1,9 @@
import type { UmbDocumentEntityType } from '../../entity.js';
import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbDocumentItemModel {
entityType: UmbDocumentEntityType;
name: string; // TODO: this is not correct. We need to get it from the variants. This is a temp solution.
unique: string;
isTrashed: boolean;

View File

@@ -8,7 +8,7 @@ export const UMB_USER_PERMISSION_DOCUMENT_PUBLISH = 'Umb.UserPermission.Document
export const UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS = 'Umb.UserPermission.Document.Permissions';
export const UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL = 'Umb.UserPermission.Document.SendForApproval';
export const UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH = 'Umb.UserPermission.Document.Unpublish';
export const UMB_USER_PERMISSION_DOCUMENT_COPY = 'Umb.UserPermission.Document.Copy';
export const UMB_USER_PERMISSION_DOCUMENT_DUPLICATE = 'Umb.UserPermission.Document.Duplicate';
export const UMB_USER_PERMISSION_DOCUMENT_MOVE = 'Umb.UserPermission.Document.Move';
export const UMB_USER_PERMISSION_DOCUMENT_SORT = 'Umb.UserPermission.Document.Sort';
export const UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES = 'Umb.UserPermission.Document.CultureAndHostnames';

View File

@@ -0,0 +1,251 @@
import type { UmbDocumentUserPermissionModel } from '../types.js';
import { UmbDocumentItemRepository, type UmbDocumentItemModel } from '../../repository/index.js';
import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import {
UMB_DOCUMENT_PICKER_MODAL,
UMB_ENTITY_USER_PERMISSION_MODAL,
UMB_MODAL_MANAGER_CONTEXT,
} from '@umbraco-cms/backoffice/modal';
import type { UmbDeselectedEvent } from '@umbraco-cms/backoffice/event';
import { UmbChangeEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-input-document-granular-user-permission')
export class UmbInputDocumentGranularUserPermissionElement extends FormControlMixin(UmbLitElement) {
_permissions: Array<UmbDocumentUserPermissionModel> = [];
public get permissions(): Array<UmbDocumentUserPermissionModel> {
return this._permissions;
}
public set permissions(value: Array<UmbDocumentUserPermissionModel>) {
this._permissions = value;
const uniques = value.map((item) => item.document.id);
this.#observePickedDocuments(uniques);
}
@state()
private _items?: Array<UmbDocumentItemModel>;
#documentItemRepository = new UmbDocumentItemRepository(this);
#modalManagerContext?: UmbModalManagerContext;
#documentPickerModalContext?: any;
#entityUserPermissionModalContext?: any;
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => (this.#modalManagerContext = instance));
}
protected getFormElement() {
return undefined;
}
async #observePickedDocuments(uniques: Array<string>) {
const { asObservable } = await this.#documentItemRepository.requestItems(uniques);
this.observe(asObservable(), (items) => (this._items = items));
}
async #editGranularPermission(item: UmbDocumentItemModel) {
const currentPermissionVerbs = this.#getPermissionForDocument(item.unique)?.verbs ?? [];
const result = await this.#selectEntityUserPermissionsForDocument(item, currentPermissionVerbs);
// don't do anything if the verbs have not been updated
if (JSON.stringify(result) === JSON.stringify(currentPermissionVerbs)) return;
// update permission with new verbs
this.permissions = this._permissions.map((permission) => {
if (permission.document.id === item.unique) {
return {
...permission,
verbs: result,
};
}
return permission;
});
this.dispatchEvent(new UmbChangeEvent());
}
#addGranularPermission() {
this.#documentPickerModalContext = this.#modalManagerContext?.open(UMB_DOCUMENT_PICKER_MODAL, {
data: {
hideTreeRoot: true,
// prevent already selected items to be picked again
// TODO: this type is wrong. It should be the tree item type
pickableFilter: (treeItem: UmbDocumentItemModel) =>
!this._items?.map((i) => i.unique).includes(treeItem.unique),
},
});
this.#documentPickerModalContext?.addEventListener(UmbSelectedEvent.TYPE, async (event: UmbDeselectedEvent) => {
const selectedEvent = event as UmbSelectedEvent;
const unique = selectedEvent.unique;
if (!unique) return;
const documentItem = await this.#requestDocumentItem(unique);
const result = await this.#selectEntityUserPermissionsForDocument(documentItem);
this.#documentPickerModalContext?.reject();
const permissionItem: UmbDocumentUserPermissionModel = {
$type: 'DocumentPermissionPresentationModel',
document: { id: unique },
verbs: result,
};
this.permissions = [...this._permissions, permissionItem];
this.dispatchEvent(new UmbChangeEvent());
});
}
async #requestDocumentItem(unique: string) {
if (!unique) throw new Error('Could not open permissions modal, no unique was provided');
const { data } = await this.#documentItemRepository.requestItems([unique]);
const documentItem = data?.[0];
if (!documentItem) throw new Error('No document item found');
return documentItem;
}
async #selectEntityUserPermissionsForDocument(item: UmbDocumentItemModel, allowedVerbs: Array<string> = []) {
// TODO: get correct variant name
const name = item.variants[0]?.name;
const headline = name ? `Permissions for ${name}` : 'Permissions';
this.#entityUserPermissionModalContext = this.#modalManagerContext?.open(UMB_ENTITY_USER_PERMISSION_MODAL, {
data: {
unique: item.unique,
entityType: item.entityType,
headline,
},
value: {
allowedVerbs,
},
});
try {
const value = await this.#entityUserPermissionModalContext?.onSubmit();
return value?.allowedVerbs;
} catch (error) {
return allowedVerbs;
}
}
#removeGranularPermission(item: UmbDocumentItemModel) {
const permission = this.#getPermissionForDocument(item.unique);
if (!permission) return;
this.permissions = this._permissions.filter((v) => JSON.stringify(v) !== JSON.stringify(permission));
this.dispatchEvent(new UmbChangeEvent());
}
render() {
return html`${this.#renderItems()} ${this.#renderAddButton()}`;
}
#renderItems() {
if (!this._items) return;
return html`<uui-ref-list>
${repeat(
this._items,
(item) => item.unique,
(item) => this.#renderRef(item),
)}
</uui-ref-list>`;
}
#renderAddButton() {
return html`<uui-button
id="add-button"
look="placeholder"
@click=${this.#addGranularPermission}
label=${this.localize.term('general_add')}></uui-button>`;
}
#renderRef(item: UmbDocumentItemModel) {
if (!item.unique) return;
// TODO: get correct variant name
const name = item.variants[0]?.name;
const permissionNames = this.#getPermissionNamesForDocument(item.unique);
return html`
<uui-ref-node .name=${name} .detail=${permissionNames || ''}>
${this.#renderIcon(item)} ${this.#renderIsTrashed(item)}
<uui-action-bar slot="actions">
${this.#renderEditButton(item)} ${this.#renderRemoveButton(item)}
</uui-action-bar>
</uui-ref-node>
`;
}
#renderIcon(item: UmbDocumentItemModel) {
if (!item.documentType.icon) return;
return html`<uui-icon slot="icon" name=${item.documentType.icon}></uui-icon>`;
}
#renderIsTrashed(item: UmbDocumentItemModel) {
if (!item.isTrashed) return;
return html`<uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag>`;
}
#renderEditButton(item: UmbDocumentItemModel) {
return html`
<uui-button
@click=${() => this.#editGranularPermission(item)}
label=${this.localize.term('general_edit')}></uui-button>
`;
}
#renderRemoveButton(item: UmbDocumentItemModel) {
return html`<uui-button
@click=${() => this.#removeGranularPermission(item)}
label=${this.localize.term('general_remove')}></uui-button>`;
}
#getPermissionForDocument(unique: string) {
return this._permissions?.find((permission) => permission.document.id === unique);
}
#getPermissionNamesForDocument(unique: string) {
const permission = this.#getPermissionForDocument(unique);
if (!permission) return;
return umbExtensionsRegistry
.getAllExtensions()
.filter((manifest) => manifest.type === 'entityUserPermission')
.filter((manifest) =>
(manifest as ManifestEntityUserPermission).meta.verbs.every((verb) => permission.verbs.includes(verb)),
)
.map((m) => {
const manifest = m as ManifestEntityUserPermission;
if (manifest.meta.labelKey) {
return this.localize.term(manifest.meta.labelKey);
} else if (manifest.meta.label) {
return manifest.meta.label;
} else {
return manifest.name;
}
})
.join(', ');
}
static styles = [
css`
#add-button {
width: 100%;
}
`,
];
}
export default UmbInputDocumentGranularUserPermissionElement;
declare global {
interface HTMLElementTagNameMap {
'umb-document-granular-user-permission': UmbInputDocumentGranularUserPermissionElement;
}
}

View File

@@ -1,3 +1,4 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js';
import {
UMB_USER_PERMISSION_DOCUMENT_READ,
UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT,
@@ -6,10 +7,9 @@ import {
UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS,
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS,
UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL,
UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH,
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
UMB_USER_PERMISSION_DOCUMENT_COPY,
UMB_USER_PERMISSION_DOCUMENT_DUPLICATE,
UMB_USER_PERMISSION_DOCUMENT_MOVE,
UMB_USER_PERMISSION_DOCUMENT_SORT,
UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES,
@@ -18,172 +18,177 @@ import {
} from './constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import type {
ManifestUserGranularPermission,
ManifestUserPermission,
ManifestGranularUserPermission,
ManifestEntityUserPermission,
} from '@umbraco-cms/backoffice/extension-registry';
const permissions: Array<ManifestUserPermission> = [
const permissions: Array<ManifestEntityUserPermission> = [
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_READ,
name: 'Read Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Read'],
labelKey: 'actions_browse',
descriptionKey: 'actionDescriptions_browse',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT,
name: 'Create Document Blueprint User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.CreateBlueprint'],
labelKey: 'actions_createblueprint',
descriptionKey: 'actionDescriptions_createblueprint',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_DELETE,
name: 'Delete Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Delete'],
labelKey: 'actions_delete',
descriptionKey: 'actionDescriptions_delete',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_CREATE,
name: 'Create Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Create'],
labelKey: 'actions_create',
descriptionKey: 'actionDescriptions_create',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS,
name: 'Document Notifications User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Notifications'],
labelKey: 'actions_notify',
descriptionKey: 'actionDescriptions_notify',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
name: 'Publish Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Publish'],
labelKey: 'actions_publish',
descriptionKey: 'actionDescriptions_publish',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS,
name: 'Document Permissions User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Permissions'],
labelKey: 'actions_setPermissions',
descriptionKey: 'actionDescriptions_rights',
},
},
{
type: 'userPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL,
name: 'Send Document For Approval User Permission',
meta: {
entityType: 'document',
labelKey: 'actions_sendtopublish',
descriptionKey: 'actionDescriptions_sendtopublish',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH,
name: 'Unpublish Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Unpublish'],
labelKey: 'actions_unpublish',
descriptionKey: 'actionDescriptions_unpublish',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_UPDATE,
name: 'Update Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Update'],
labelKey: 'actions_update',
descriptionKey: 'actionDescriptions_update',
},
},
{
type: 'userPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_COPY,
name: 'Copy Document User Permission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_DUPLICATE,
name: 'Duplicate Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Duplicate'],
labelKey: 'actions_copy',
descriptionKey: 'actionDescriptions_copy',
group: 'structure',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_MOVE,
name: 'Move Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Move'],
labelKey: 'actions_move',
descriptionKey: 'actionDescriptions_move',
group: 'structure',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_SORT,
name: 'Sort Document User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Sort'],
labelKey: 'actions_sort',
descriptionKey: 'actionDescriptions_sort',
group: 'structure',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES,
name: 'Document Culture And Hostnames User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.CultureAndHostnames'],
labelKey: 'actions_assigndomain',
descriptionKey: 'actionDescriptions_assignDomain',
group: 'administration',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS,
name: 'Document Public Access User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.PublicAccess'],
labelKey: 'actions_protect',
descriptionKey: 'actionDescriptions_protect',
group: 'administration',
},
},
{
type: 'userPermission',
type: 'entityUserPermission',
alias: UMB_USER_PERMISSION_DOCUMENT_ROLLBACK,
name: 'Document Rollback User Permission',
meta: {
entityType: 'document',
entityType: UMB_DOCUMENT_ENTITY_TYPE,
verbs: ['Umb.Document.Rollback'],
labelKey: 'actions_rollback',
descriptionKey: 'actionDescriptions_rollback',
group: 'administration',
@@ -191,14 +196,17 @@ const permissions: Array<ManifestUserPermission> = [
},
];
export const granularPermissions: Array<ManifestUserGranularPermission> = [
export const granularPermissions: Array<ManifestGranularUserPermission> = [
{
type: 'userGranularPermission',
alias: 'Umb.UserGranularPermission.Document',
name: 'Document Granular User Permission',
js: () => import('../components/input-document-granular-permission/input-document-granular-permission.element.js'),
element: () =>
import('./input-document-granular-user-permission/input-document-granular-user-permission.element.js'),
meta: {
entityType: 'document',
schemaType: 'DocumentPermissionPresentationModel',
label: 'Documents',
description: 'Assign permissions to specific documents',
},
},
];

View File

@@ -0,0 +1,6 @@
import type { UmbUserPermissionModel } from '@umbraco-cms/backoffice/user-permission';
export interface UmbDocumentUserPermissionModel extends UmbUserPermissionModel {
// TODO: this should be unique instead of an id, but we currently have now way to map a mixed server response.
document: { id: string };
}

View File

@@ -1,12 +1,11 @@
import './components/index.js';
export * from './repository/index.js';
export * from './workspace/index.js';
export * from './tracked-reference/index.js';
export * from './components/index.js';
export * from './entity.js';
export * from './repository/index.js';
export * from './tracked-reference/index.js';
export * from './user-permissions/index.js';
export * from './utils/index.js';
export * from './workspace/index.js';
export * from './conditions/index.js';
export { UMB_MEDIA_TREE_ALIAS } from './tree/index.js';

View File

@@ -7,7 +7,6 @@ import { manifests as propertyEditorsManifests } from './property-editors/manife
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as sectionViewManifests } from './section-view/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as userPermissionManifests } from './user-permissions/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests = [
@@ -20,6 +19,5 @@ export const manifests = [
...repositoryManifests,
...sectionViewManifests,
...treeManifests,
...userPermissionManifests,
...workspaceManifests,
];

View File

@@ -1 +0,0 @@
export * from './manifests.js';

View File

@@ -1,31 +0,0 @@
import type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_USER_PERMISSION_MEDIA_MOVE = 'Umb.UserPermission.Media.Move';
export const UMB_USER_PERMISSION_MEDIA_COPY = 'Umb.UserPermission.Media.Copy';
const permissions: Array<ManifestUserPermission> = [
{
type: 'userPermission',
alias: UMB_USER_PERMISSION_MEDIA_MOVE,
name: 'Move Media Item User Permission',
meta: {
entityType: 'media',
label: 'Move',
description: 'Allow access to move media items',
group: 'structure',
},
},
{
type: 'userPermission',
alias: UMB_USER_PERMISSION_MEDIA_COPY,
name: 'Copy Media Item User Permission',
meta: {
entityType: 'media',
label: 'Copy',
description: 'Allow access to copy a media item',
group: 'structure',
},
},
];
export const manifests = [...permissions];

View File

@@ -40,6 +40,7 @@ export class UmbCurrentUserServerDataSource {
avatarUrls: data.avatarUrls,
languages: data.languages,
hasAccessToAllLanguages: data.hasAccessToAllLanguages,
fallbackPermissions: data.fallbackPermissions,
permissions: data.permissions,
};
return { data: user };

View File

@@ -1,3 +1,8 @@
import type {
DocumentPermissionPresentationModel,
UnknownTypePermissionPresentationModel,
} from '@umbraco-cms/backoffice/external/backend-api';
export interface UmbCurrentUserModel {
unique: string;
email: string;
@@ -9,5 +14,6 @@ export interface UmbCurrentUserModel {
avatarUrls: Array<string>;
languages: Array<string>;
hasAccessToAllLanguages: boolean;
permissions: Array<string>;
fallbackPermissions: Array<string>;
permissions: Array<DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel>;
}

View File

@@ -4,8 +4,16 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export const isCurrentUser = async (host: UmbControllerHost, userUnique: string) => {
const ctrl = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT);
const currentUserContext = await ctrl.asPromise();
let currentUserContext = await ctrl.asPromise();
ctrl.destroy();
const controller = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT, (context) => {
currentUserContext = context;
});
await controller.asPromise();
controller.destroy();
return await currentUserContext!.isUserCurrentUser(userUnique);
};

View File

@@ -45,6 +45,7 @@ export class UmbUserGroupCollectionServerDataSource implements UmbCollectionData
documentRootAccess: item.documentRootAccess,
mediaStartNode: item.mediaStartNode ? { unique: item.mediaStartNode.id } : null,
mediaRootAccess: item.mediaRootAccess,
fallbackPermissions: item.fallbackPermissions,
permissions: item.permissions,
};
return userGroup;

View File

@@ -1,7 +1,7 @@
import { UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { map } from '@umbraco-cms/backoffice/external/rxjs';
@@ -25,7 +25,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) {
async #observeUserPermissions(value: Array<string>) {
if (value) {
this.observe(
umbExtensionsRegistry.byType('userPermission').pipe(
umbExtensionsRegistry.byType('entityUserPermission').pipe(
map((manifests) => {
return manifests.filter((manifest) => manifest.alias && value.includes(manifest.alias));
}),
@@ -38,7 +38,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) {
}
}
#setUserPermissionLabels(manifests: Array<ManifestUserPermission>) {
#setUserPermissionLabels(manifests: Array<ManifestEntityUserPermission>) {
this.#userPermissionLabels = manifests.map((manifest) =>
manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label ?? '',
);

View File

@@ -48,6 +48,7 @@ export class UmbUserGroupServerDataSource implements UmbDetailDataSource<UmbUser
documentRootAccess: false,
mediaStartNode: null,
mediaRootAccess: false,
fallbackPermissions: [],
permissions: [],
};
@@ -83,6 +84,7 @@ export class UmbUserGroupServerDataSource implements UmbDetailDataSource<UmbUser
documentRootAccess: data.documentRootAccess,
mediaStartNode: data.mediaStartNode ? { unique: data.mediaStartNode.id } : null,
mediaRootAccess: data.mediaRootAccess,
fallbackPermissions: data.fallbackPermissions,
permissions: data.permissions,
};
@@ -109,6 +111,7 @@ export class UmbUserGroupServerDataSource implements UmbDetailDataSource<UmbUser
documentRootAccess: model.documentRootAccess,
mediaStartNode: model.mediaStartNode ? { id: model.mediaStartNode.unique } : null,
mediaRootAccess: model.mediaRootAccess,
fallbackPermissions: model.fallbackPermissions,
permissions: model.permissions,
};
@@ -146,6 +149,7 @@ export class UmbUserGroupServerDataSource implements UmbDetailDataSource<UmbUser
documentRootAccess: model.documentRootAccess,
mediaStartNode: model.mediaStartNode ? { id: model.mediaStartNode.unique } : null,
mediaRootAccess: model.mediaRootAccess,
fallbackPermissions: model.fallbackPermissions,
permissions: model.permissions,
};

View File

@@ -13,5 +13,7 @@ export interface UmbUserGroupDetailModel {
documentRootAccess: boolean;
mediaStartNode: { unique: string } | null;
mediaRootAccess: boolean;
permissions: Array<string>;
fallbackPermissions: Array<string>;
// TODO: add type
permissions: Array<any>;
}

View File

@@ -1,14 +1,14 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context.js';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
@customElement('umb-user-group-default-permission-list')
export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement {
@customElement('umb-user-group-entity-user-permission-list')
export class UmbUserGroupEntityUserPermissionListElement extends UmbLitElement {
@state()
private _userGroupDefaultPermissions?: Array<string>;
private _fallBackPermissions?: Array<string>;
@state()
private _entityTypes: Array<string> = [];
@@ -18,32 +18,36 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement {
constructor() {
super();
this.#observeUserPermissions();
this.#observeEntityUserPermissions();
this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#userGroupWorkspaceContext = instance;
this.observe(
this.#userGroupWorkspaceContext.data,
(userGroup) => (this._userGroupDefaultPermissions = userGroup?.permissions),
'umbUserGroupPermissionsObserver',
this.#userGroupWorkspaceContext.fallbackPermissions,
(fallbackPermissions) => {
this._fallBackPermissions = fallbackPermissions;
},
'umbUserGroupEntityUserPermissionsObserver',
);
});
}
#observeUserPermissions() {
#observeEntityUserPermissions() {
this.observe(
umbExtensionsRegistry.byType('userPermission'),
(userPermissionManifests) => {
this._entityTypes = [...new Set(userPermissionManifests.map((manifest) => manifest.meta.entityType))];
umbExtensionsRegistry.byType('entityUserPermission'),
(manifests) => {
this._entityTypes = [...new Set(manifests.map((manifest) => manifest.meta.entityType))];
},
'umbUserPermissionsObserver',
);
}
#onSelectedUserPermission(event: UmbSelectionChangeEvent) {
#onPermissionChange(event: UmbSelectionChangeEvent) {
event.stopPropagation();
const target = event.target as any;
const selection = target.selectedPermissions;
this.#userGroupWorkspaceContext?.setDefaultPermissions(selection);
const verbs = target.allowedVerbs;
if (verbs === undefined || verbs === null) throw new Error('The verbs are not defined');
this.#userGroupWorkspaceContext?.setFallbackPermissions(verbs);
}
render() {
@@ -53,20 +57,20 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement {
#renderPermissionsByEntityType(entityType: string) {
return html`
<h4><umb-localize .key=${`user_permissionsEntityGroup_${entityType}`}>${entityType}</umb-localize></h4>
<umb-entity-user-permission-settings-list
<umb-input-entity-user-permission
.entityType=${entityType}
.selectedPermissions=${this._userGroupDefaultPermissions || []}
@selection-change=${this.#onSelectedUserPermission}></umb-entity-user-permission-settings-list>
.allowedVerbs=${this._fallBackPermissions || []}
@change=${this.#onPermissionChange}></umb-input-entity-user-permission>
`;
}
static styles = [UmbTextStyles];
}
export default UmbUserGroupDefaultPermissionListElement;
export default UmbUserGroupEntityUserPermissionListElement;
declare global {
interface HTMLElementTagNameMap {
'umb-user-group-default-permission-list': UmbUserGroupDefaultPermissionListElement;
'umb-user-group-default-permission-list': UmbUserGroupEntityUserPermissionListElement;
}
}

View File

@@ -1,12 +1,16 @@
import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context.js';
import type { UmbUserGroupDetailModel } from '../../types.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
import type { ManifestGranularUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { filterFrozenArray } from '@umbraco-cms/backoffice/observable-api';
@customElement('umb-user-group-granular-permission-list')
export class UmbUserGroupGranularPermissionListElement extends UmbLitElement {
@state()
private _userGroup?: UmbUserGroupDetailModel;
_extensionElements: Array<HTMLElement> = [];
#workspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE;
@@ -15,12 +19,90 @@ export class UmbUserGroupGranularPermissionListElement extends UmbLitElement {
this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(this.#workspaceContext.data, (userGroup) => (this._userGroup = userGroup), 'umbUserGroupObserver');
});
this.#observeExtensionRegistry();
}
#observeExtensionRegistry() {
this.observe(umbExtensionsRegistry.byType('userGranularPermission'), (manifests) => {
if (!manifests) {
this._extensionElements = [];
return;
}
manifests.forEach(async (manifest) => this.#extensionElementSetup(manifest));
});
}
async #extensionElementSetup(manifest: ManifestGranularUserPermission) {
const element = (await createExtensionElement(manifest)) as any;
if (!element) throw new Error(`Failed to create extension element for manifest ${manifest.alias}`);
if (!this.#workspaceContext) throw new Error('User Group Workspace context is not available');
this.observe(
this.#workspaceContext.data,
(userGroup) => {
if (!userGroup) return;
const schemaType = manifest.meta.schemaType;
const permissionsForSchemaType =
userGroup.permissions.filter((permission) => permission.$type === schemaType) || [];
element.permissions = permissionsForSchemaType;
element.manifest = manifest;
element.addEventListener(UmbChangeEvent.TYPE, this.#onValueChange);
},
'umbUserGroupGranularPermissionObserver',
);
this._extensionElements.push(element);
this.requestUpdate('_extensionElements');
}
#onValueChange = (e: UmbChangeEvent) => {
e.stopPropagation();
// TODO: make interface
const target = e.target as any;
const schemaType = target.manifest?.meta.schemaType;
if (!schemaType) throw new Error('Schema type is not available');
/* Remove all permissions of the same schema type from
the user group and append the new permissions.
We do it this way to support appends, updates and deletion without we know the
exact action but on the changed value */
const storedValueWithoutSchemaTypeItems = filterFrozenArray(
this.#workspaceContext?.getPermissions() || [],
(x) => x.$type !== schemaType,
);
const permissions = target.permissions || [];
const newCombinedValue = [...storedValueWithoutSchemaTypeItems, ...permissions];
this.#workspaceContext?.setPermissions(newCombinedValue);
};
render() {
return html`<umb-extension-slot type="userGranularPermission"></umb-extension-slot>`;
return html`${this._extensionElements.map((element) => this.#renderProperty(element))}`;
}
#renderProperty(element: any) {
const manifest = element.manifest as ManifestGranularUserPermission;
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;
return html`
<umb-property-layout .label=${label || ''} .description=${description || ''}>
<div slot="editor">${element}</div>
</umb-property-layout>
`;
}
disconnectedCallback(): void {
this._extensionElements.forEach((element) => element.removeEventListener(UmbChangeEvent.TYPE, this.#onValueChange));
super.disconnectedCallback();
}
}

View File

@@ -11,7 +11,7 @@ import type { UmbInputSectionElement } from '@umbraco-cms/backoffice/components'
import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media';
import './components/user-group-default-permission-list.element.js';
import './components/user-group-entity-user-permission-list.element.js';
import './components/user-group-granular-permission-list.element.js';
@customElement('umb-user-group-workspace-editor')
@@ -123,15 +123,16 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
<uui-box>
<div slot="headline"><umb-localize key="user_permissionsDefault"></umb-localize></div>
<umb-user-group-default-permission-list></umb-user-group-default-permission-list>
<umb-property-layout label="Entity permissions" description="Assign permissions for an entity type">
<umb-user-group-entity-user-permission-list slot="editor"></umb-user-group-entity-user-permission-list>
</umb-property-layout>
</uui-box>
<!-- Temp disabled because it is work in progress
<uui-box>
<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>
-->
`;
}

View File

@@ -1,5 +1,6 @@
import { UmbUserGroupDetailRepository } from '../repository/detail/index.js';
import type { UmbUserGroupDetailModel } from '../types.js';
import type { UmbUserPermissionModel } from '@umbraco-cms/backoffice/user-permission';
import type { UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace';
import { UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
@@ -16,6 +17,18 @@ export class UmbUserGroupWorkspaceContext
#data = new UmbObjectState<UmbUserGroupDetailModel | undefined>(undefined);
data = this.#data.asObservable();
readonly name = this.#data.asObservablePart((data) => data?.name || '');
readonly icon = this.#data.asObservablePart((data) => data?.icon || null);
readonly sections = this.#data.asObservablePart((data) => data?.sections || []);
readonly languages = this.#data.asObservablePart((data) => data?.languages || []);
readonly hasAccessToAllLanguages = this.#data.asObservablePart((data) => data?.hasAccessToAllLanguages || false);
readonly documentStartNode = this.#data.asObservablePart((data) => data?.documentStartNode || null);
readonly documentRootAccess = this.#data.asObservablePart((data) => data?.documentRootAccess || false);
readonly mediaStartNode = this.#data.asObservablePart((data) => data?.mediaStartNode || null);
readonly mediaRootAccess = this.#data.asObservablePart((data) => data?.mediaRootAccess || false);
readonly fallbackPermissions = this.#data.asObservablePart((data) => data?.fallbackPermissions || []);
readonly permissions = this.#data.asObservablePart((data) => data?.permissions || []);
constructor(host: UmbControllerHost) {
super(host, 'Umb.Workspace.UserGroup');
}
@@ -82,42 +95,37 @@ export class UmbUserGroupWorkspaceContext
}
/**
* Sets the user group default permissions.
* @param {Array<string>} permissionAliases
* Gets the user group user permissions.
* @memberof UmbUserGroupWorkspaceContext
*/
setDefaultPermissions(permissionAliases: Array<string>) {
this.#data.update({ permissions: permissionAliases });
}
/**
* Gets the user group default permissions.
* @memberof UmbUserGroupWorkspaceContext
*/
getDefaultPermissions() {
getPermissions() {
return this.#data.getValue()?.permissions ?? [];
}
/**
* Allows a default permission on the user group.
* @param {string} permissionAlias
* Sets the user group user permissions.
* @param {Array<UmbUserPermissionModel>} permissions
* @memberof UmbUserGroupWorkspaceContext
*/
allowDefaultPermission(permissionAlias: string) {
const permissions = this.#data.getValue()?.permissions ?? [];
const newValue = [...permissions, permissionAlias];
this.#data.update({ permissions: newValue });
setPermissions(permissions: Array<UmbUserPermissionModel>) {
this.#data.update({ permissions: permissions });
}
/**
* Disallows a default permission on the user group.
* @param {string} permissionAlias
* Gets the user group fallback permissions.
* @memberof UmbUserGroupWorkspaceContext
*/
disallowDefaultPermission(permissionAlias: string) {
const permissions = this.#data.getValue()?.permissions ?? [];
const newValue = permissions.filter((alias) => alias !== permissionAlias);
this.#data.update({ permissions: newValue });
getFallbackPermissions() {
return this.#data.getValue()?.fallbackPermissions ?? [];
}
/**
* Sets the user group fallback permissions.
* @param {Array<string>} fallbackPermissions
* @memberof UmbUserGroupWorkspaceContext
*/
setFallbackPermissions(fallbackPermissions: Array<string>) {
this.#data.update({ fallbackPermissions });
}
}

View File

@@ -1,110 +0,0 @@
import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
import type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import type { UmbUserPermissionSettingElement } from '@umbraco-cms/backoffice/user';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-entity-user-permission-settings-list')
export class UmbEntityUserPermissionSettingsListElement extends 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.#observeUserPermissions();
}
private _entityType: string = '';
@property({ attribute: false })
selectedPermissions: Array<string> = [];
@state()
private _manifests: Array<ManifestUserPermission> = [];
#manifestObserver?: UmbObserverController<Array<ManifestUserPermission>>;
#isAllowed(permissionAlias: string) {
return this.selectedPermissions?.includes(permissionAlias);
}
#observeUserPermissions() {
this.#manifestObserver?.destroy();
this.#manifestObserver = this.observe(
umbExtensionsRegistry.byType('userPermission'),
(userPermissionManifests) => {
this._manifests = userPermissionManifests.filter((manifest) => manifest.meta.entityType === this.entityType);
},
'umbUserPermissionManifestsObserver',
);
}
#onChangeUserPermission(event: UmbChangeEvent, permissionAlias: string) {
event.stopPropagation();
const target = event.target as UmbUserPermissionSettingElement;
target.allowed ? this.#addUserPermission(permissionAlias) : this.#removeUserPermission(permissionAlias);
}
#addUserPermission(permissionAlias: string) {
this.selectedPermissions = [...this.selectedPermissions, permissionAlias];
this.dispatchEvent(new UmbSelectionChangeEvent());
}
#removeUserPermission(permissionAlias: string) {
this.selectedPermissions = this.selectedPermissions.filter((alias) => alias !== permissionAlias);
this.dispatchEvent(new UmbSelectionChangeEvent());
}
render() {
return html`${this.#renderGroupedPermissions(this._manifests)} `;
}
#renderGroupedPermissions(permissionManifests: Array<ManifestUserPermission>) {
// 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: ManifestUserPermission) => manifest.meta.group,
) as Record<string, Array<ManifestUserPermission>>;
return html`
${Object.entries(groupedPermissions).map(
([group, manifests]) => html`
${group !== 'undefined'
? html` <h5><umb-localize .key=${`actionCategories_${group}`}>${group}</umb-localize></h5> `
: nothing}
${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)}
`,
)}
`;
}
#renderPermission(manifest: ManifestUserPermission) {
return html` <umb-user-permission-setting
label=${ifDefined(manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label)}
description=${ifDefined(
manifest.meta.descriptionKey ? this.localize.term(manifest.meta.descriptionKey) : manifest.meta.description,
)}
?allowed=${this.#isAllowed(manifest.alias)}
@change=${(event: UmbChangeEvent) =>
this.#onChangeUserPermission(event, manifest.alias)}></umb-user-permission-setting>`;
}
disconnectedCallback() {
super.disconnectedCallback();
this.#manifestObserver?.destroy();
}
}
export default UmbEntityUserPermissionSettingsListElement;
declare global {
interface HTMLElementTagNameMap {
'umb-entity-user-permission-settings-list': UmbEntityUserPermissionSettingsListElement;
}
}

View File

@@ -1,3 +1,5 @@
import './entity-user-permission-settings-list/entity-user-permission-settings-list.element.js';
import './input-entity-user-permission/input-entity-user-permission.element.js';
import './input-user-permission-verb/input-user-permission-verb.element.js';
export * from './entity-user-permission-settings-list/entity-user-permission-settings-list.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';

View File

@@ -0,0 +1,116 @@
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, state, nothing, ifDefined } 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 { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-input-entity-user-permission')
export class UmbInputEntityUserPermissionElement extends FormControlMixin(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 getFormElement() {
return undefined;
}
#isAllowed(permissionVerbs: Array<string>) {
return permissionVerbs.every((verb) => this.allowedVerbs.includes(verb));
}
#observeEntityUserPermissions() {
this.#manifestObserver?.destroy();
this.#manifestObserver = this.observe(
umbExtensionsRegistry.byType('entityUserPermission'),
(userPermissionManifests) => {
this._manifests = userPermissionManifests.filter((manifest) => manifest.meta.entityType === this.entityType);
},
'umbUserPermissionManifestsObserver',
);
}
#onChangeUserPermission(event: UmbChangeEvent, permissionVerbs: Array<string>) {
event.stopPropagation();
const target = event.target as UmbUserPermissionVerbElement;
target.allowed ? this.#addUserPermission(permissionVerbs) : 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());
}
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}
${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)}
`,
)}
`;
}
#renderPermission(manifest: ManifestEntityUserPermission) {
return html` <umb-input-user-permission-verb
label=${ifDefined(manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label)}
description=${ifDefined(
manifest.meta.descriptionKey ? this.localize.term(manifest.meta.descriptionKey) : manifest.meta.description,
)}
?allowed=${this.#isAllowed(manifest.meta.verbs)}
@change=${(event: UmbChangeEvent) =>
this.#onChangeUserPermission(event, manifest.meta.verbs)}></umb-input-user-permission-verb>`;
}
disconnectedCallback() {
super.disconnectedCallback();
this.#manifestObserver?.destroy();
}
}
export default UmbInputEntityUserPermissionElement;
declare global {
interface HTMLElementTagNameMap {
'umb-input-entity-user-permission': UmbInputEntityUserPermissionElement;
}
}

View File

@@ -1,11 +1,11 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-user-permission-setting')
export class UmbUserPermissionSettingElement extends UmbLitElement {
@customElement('umb-input-user-permission-verb')
export class UmbUserPermissionVerbElement extends UmbLitElement {
@property({ type: String, attribute: true })
label: string = '';
@@ -54,10 +54,10 @@ export class UmbUserPermissionSettingElement extends UmbLitElement {
];
}
export default UmbUserPermissionSettingElement;
export default UmbUserPermissionVerbElement;
declare global {
interface HTMLElementTagNameMap {
'umb-user-permission-setting': UmbUserPermissionSettingElement;
'umb-input-user-permission-verb': UmbUserPermissionVerbElement;
}
}

View File

@@ -21,7 +21,7 @@ export class UmbUserPermissionCondition extends UmbBaseController implements Umb
this.observe(
context.currentUser,
(currentUser) => {
this.permitted = currentUser?.permissions?.includes(this.config.match) || false;
//this.permitted = currentUser?.permissions?.includes(this.config.match) || false;
this.#onChange();
},
'umbUserPermissionConditionObserver',

View File

@@ -1,15 +1,4 @@
export * from './components/index.js';
export * from './conditions/index.js';
export type UserPermissionModel<PermissionTargetType> = {
id: string;
target: PermissionTargetType;
permissions: Array<string>;
};
// TODO: this should only be known by the document
export type UmbDocumentGranularPermission = {
entityType: 'document';
documentId: string;
userGroupId: string;
};
export type { UmbUserPermissionModel } from './types.js';

View File

@@ -1,11 +1,10 @@
import { html, customElement, css, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, css, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import type {
UmbEntityUserPermissionSettingsModalData,
UmbEntityUserPermissionSettingsModalValue} from '@umbraco-cms/backoffice/modal';
import {
UmbModalBaseElement,
UmbEntityUserPermissionSettingsModalValue,
} from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-entity-user-permission-settings-modal')
@@ -16,7 +15,6 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle
set data(data: UmbEntityUserPermissionSettingsModalData | undefined) {
super.data = data;
this._entityType = data?.entityType;
this._allowedPermissions = data?.allowedPermissions ?? [];
this._headline = data?.headline ?? this._headline;
}
@@ -26,17 +24,9 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle
@state()
_entityType?: string;
@state()
_allowedPermissions: Array<string> = [];
private _handleConfirm() {
this.value = { allowedPermissions: this._allowedPermissions };
this.modalContext?.submit();
}
#onSelectedUserPermission(event: UmbSelectionChangeEvent) {
#onPermissionChange(event: UmbSelectionChangeEvent) {
const target = event.target as any;
this._allowedPermissions = target.selectedPermissions;
this.updateValue({ allowedVerbs: target.allowedVerbs });
}
render() {
@@ -44,10 +34,10 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle
<umb-body-layout headline=${this._headline}>
<uui-box>
${this._entityType
? html` <umb-entity-user-permission-settings-list
? html` <umb-input-entity-user-permission
.entityType=${this._entityType}
.selectedPermissions=${this._allowedPermissions}
@selection-change=${this.#onSelectedUserPermission}></umb-entity-user-permission-settings-list>`
.allowedVerbs=${this.value?.allowedVerbs ?? []}
@change=${this.#onPermissionChange}></umb-input-entity-user-permission>`
: nothing}
</uui-box>
@@ -58,7 +48,7 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle
color="positive"
look="primary"
label="Confirm"
@click=${this._handleConfirm}></uui-button>
@click=${this._submitModal}></uui-button>
</umb-body-layout>
`;
}

View File

@@ -0,0 +1,4 @@
export interface UmbUserPermissionModel {
$type: string;
verbs: Array<string>;
}

View File

@@ -34,40 +34,36 @@ export class UmbUserCollectionServerDataSource implements UmbCollectionDataSourc
async getCollection(filter: UmbUserCollectionFilterModel) {
const { data, error } = await tryExecuteAndNotify(this.#host, UserResource.getFilterUser(filter));
if (error) {
return { error };
if (data) {
const { items, total } = data;
const mappedItems: Array<UmbUserDetailModel> = items.map((item: UserResponseModel) => {
const userDetail: UmbUserDetailModel = {
entityType: UMB_USER_ENTITY_TYPE,
email: item.email,
userName: item.userName,
name: item.name,
userGroupUniques: item.userGroupIds,
unique: item.id,
languageIsoCode: item.languageIsoCode || null,
documentStartNodeUniques: item.documentStartNodeIds,
mediaStartNodeUniques: item.mediaStartNodeIds,
avatarUrls: item.avatarUrls,
state: item.state,
failedLoginAttempts: item.failedLoginAttempts,
createDate: item.createDate,
updateDate: item.updateDate,
lastLoginDate: item.lastLoginDate || null,
lastLockoutDate: item.lastLockoutDate || null,
lastPasswordChangeDate: item.lastPasswordChangeDate || null,
};
return userDetail;
});
return { data: { items: mappedItems, total } };
}
if (!data) {
return { data: { items: [], total: 0 } };
}
const { items, total } = data;
const mappedItems: Array<UmbUserDetailModel> = items.map((item: UserResponseModel) => {
const userDetail: UmbUserDetailModel = {
entityType: UMB_USER_ENTITY_TYPE,
email: item.email,
userName: item.userName,
name: item.name,
userGroupUniques: item.userGroupIds,
unique: item.id,
languageIsoCode: item.languageIsoCode || null,
documentStartNodeUniques: item.documentStartNodeIds,
mediaStartNodeUniques: item.mediaStartNodeIds,
avatarUrls: item.avatarUrls,
state: item.state,
failedLoginAttempts: item.failedLoginAttempts,
createDate: item.createDate,
updateDate: item.updateDate,
lastLoginDate: item.lastLoginDate || null,
lastLockoutDate: item.lastLockoutDate || null,
lastPasswordChangeDate: item.lastPasswordChangeDate || null,
};
return userDetail;
});
return { data: { items: mappedItems, total } };
return { error };
}
}

View File

@@ -1,7 +1,6 @@
import './user-input/user-input.element.js';
import './user-permission-setting/user-permission-setting.element.js';
import './user-document-start-node/user-document-start-node.element.js';
import './user-media-start-node/user-media-start-node.element.js';
export * from './user-input/user-input.element.js';
export * from './user-permission-setting/user-permission-setting.element.js';
export * from '../../user-permission/components/input-user-permission-verb/input-user-permission-verb.element.js';