@eclipse-scout/core
Version:
Eclipse Scout runtime
246 lines (220 loc) • 8.94 kB
text/typescript
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {FullModelOf, InitModelOf, ObjectModel, ObjectOrModel, objects, Permission, PermissionLevel, PropertyChangeEvent, PropertyEventEmitter, PropertyEventMap, scout} from '../index';
export class PermissionCollection extends PropertyEventEmitter implements PermissionCollectionModel {
declare self: PermissionCollection;
declare model: PermissionCollectionModel;
declare initModel: PermissionCollectionModel;
declare eventMap: PermissionCollectionEventMap;
permissions: PermissionMap;
type: PermissionCollectionType;
constructor() {
super();
this.permissions = new Map();
this.type = PermissionCollectionType.DEFAULT;
}
override init(model: InitModelOf<this>) {
model = model || {} as InitModelOf<this>;
this.permissions = scout.nvl(model.permissions, this.permissions);
this.type = scout.nvl(model.type, this.type);
this._setPermissions(this.permissions);
this._setType(this.type);
}
setPermissions(permissions: PermissionModelMapModel) {
this.setProperty('permissions', permissions);
}
protected _setPermissions(permissions: PermissionModelMapModel) {
permissions = PermissionCollection._ensurePermissionMap(permissions);
this._setProperty('permissions', permissions);
}
setType(type: PermissionCollectionType) {
this.setProperty('type', type);
}
protected _setType(type: PermissionCollectionType) {
if (!Object.values(PermissionCollectionType).includes(type)) {
type = PermissionCollectionType.DEFAULT;
}
this._setProperty('type', type);
}
/**
* Check if the given {@link Permission} is implied (see {@link PermissionCollectionType}).
* Quick implies is executed synchronously while non-quick implies is executed asynchronously.
*/
implies(permission: Permission, quick: true): boolean;
implies(permission: Permission, quick?: false): JQuery.Promise<boolean>;
implies(permission: Permission, quick?: boolean): boolean | JQuery.Promise<boolean>;
implies(permission: Permission, quick?: boolean): boolean | JQuery.Promise<boolean> {
if (!permission) {
return quick ? false : $.resolvedPromise(false);
}
switch (this.type) {
case PermissionCollectionType.DEFAULT: {
const permissions = this.permissions.get(permission.id);
if (!permissions || !permissions.size) {
return quick ? false : $.resolvedPromise(false);
}
if (quick) {
// check if at least one of the permissions implies permission
for (const p of permissions) {
if (p.implies(permission, quick)) {
return true;
}
}
return false;
}
const deferred = $.Deferred();
// collect all promises
const impliedPromises: JQuery.Promise<void>[] = [];
for (const p of permissions) {
impliedPromises.push(p.implies(permission, false)
.then(implies => {
if (implies) {
// resolve with true if first one implies
deferred.resolve(true);
}
}));
}
// resolve with false if not already resolved (i.e. not implied by any permission)
$.promiseAll(impliedPromises).then(values => deferred.resolve(false));
return deferred.promise();
}
case PermissionCollectionType.ALL:
return quick ? true : $.resolvedPromise(true);
case PermissionCollectionType.NONE:
return quick ? false : $.resolvedPromise(false);
}
}
/**
* Returns the granted {@link PermissionLevel} for a given permission instance `permission`.
* - {@link Permission.Level.UNDEFINED} if `permission` is `null` or in general 'not an {@link Permission}'
* - {@link Permission.Level.NONE} if no level at all is granted to `permission`
* - {@link PermissionLevel} if the level can be determined exactly.
* - {@link Permission.Level.UNDEFINED} if there are multiple granted permission levels possible and there is not enough data in the `permission` contained to determine the result closer.
*/
getGrantedPermissionLevel(permission: Permission): PermissionLevel {
if (!permission || !(permission instanceof Permission)) {
return Permission.Level.UNDEFINED;
}
switch (this.type) {
case PermissionCollectionType.DEFAULT: {
const permissions = this.permissions.get(permission.id);
if (!permissions || !permissions.size) {
return Permission.Level.NONE;
}
const grantedLevels = new Set<PermissionLevel>();
for (const p of permissions) {
if (p.matches(permission)) {
grantedLevels.add(p.level);
}
}
switch (grantedLevels.size) {
case 0:
return Permission.Level.NONE;
case 1:
return grantedLevels.values().next().value;
default:
return Permission.Level.UNDEFINED;
}
}
case PermissionCollectionType.ALL:
return Permission.Level.ALL;
case PermissionCollectionType.NONE:
return Permission.Level.NONE;
}
}
/**
* Ensures that the given `permissionCollection` is of type {@link PermissionCollection}. If a model is provided, a new {@link PermissionCollection} will be created.
*/
static ensure<T extends PermissionCollection = PermissionCollection>(permissionCollection: ObjectOrModel<T>): T {
if (!permissionCollection) {
return permissionCollection as T;
}
if (permissionCollection instanceof PermissionCollection) {
return permissionCollection;
}
// May return a specialized subclass of PermissionCollection
if (!permissionCollection.objectType) {
permissionCollection.objectType = PermissionCollection;
}
return scout.create(permissionCollection as FullModelOf<T>);
}
/**
* Ensures that the given `permissionModelMapModel` is of type {@link PermissionMap}.
*
* @param permissionModelMapModel map/object of {@link Permission}s or their model grouped by `id`.
*/
protected static _ensurePermissionMap(permissionModelMapModel: PermissionModelMapModel): PermissionMap {
// permissionModelMapModel is a map or an object
if (objects.isPojo(permissionModelMapModel)) {
// permissionModelMapModel is an object, create a map containing the entries of the object
permissionModelMapModel = new Map(Object.entries(permissionModelMapModel));
}
if (!(permissionModelMapModel instanceof Map)) {
return new Map();
}
const permissionMap: PermissionMap = new Map();
// permissionModelMapModel is a map and its values are sets or arrays of Permission's or their model
for (const [name, modelSetOrArray] of permissionModelMapModel) {
if (!name || !modelSetOrArray) {
continue;
}
if (Array.isArray(modelSetOrArray) && !modelSetOrArray.length) {
continue;
}
if (modelSetOrArray instanceof Set && !modelSetOrArray.size) {
continue;
}
// transform set or array of Permission's or their model into a set of Permission's
const permissionSet: Set<Permission> = new Set();
for (const model of modelSetOrArray) {
// ensure that element is a Permission
const permission = Permission.ensure(model);
if (permission) {
permissionSet.add(permission);
}
}
// only add if set has elements
if (permissionSet.size) {
permissionMap.set(name, permissionSet);
}
}
return permissionMap;
}
}
export interface PermissionCollectionModel extends ObjectModel<PermissionCollection> {
/**
* {@link Permission}s grouped by `id`.
*/
permissions?: PermissionModelMapModel;
/**
* The type of the {@link PermissionCollection} (see {@link PermissionCollectionType}).
*/
type?: PermissionCollectionType;
}
export type PermissionMap = Map<string, Set<Permission>>;
export type PermissionModelMapModel = Map<string, Set<ObjectOrModel<Permission>> | ObjectOrModel<Permission>[]> | Record<string, Set<ObjectOrModel<Permission>> | ObjectOrModel<Permission>[]>;
export interface PermissionCollectionEventMap extends PropertyEventMap {
'propertyChange:permissions': PropertyChangeEvent<PermissionMap>;
'propertyChange:type': PropertyChangeEvent<PermissionCollectionType>;
}
export enum PermissionCollectionType {
/**
* Check if {@link Permission} is contained {@link PermissionCollection}.
*/
DEFAULT = 'DEFAULT',
/**
* All {@link Permission}s are implied.
*/
ALL = 'ALL',
/**
* No {@link Permission} is implied.
*/
NONE = 'NONE'
}