From 5bc8be81e31aa6ea7f799d729b65350d66c403d9 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:47:52 +0200 Subject: [PATCH] add action to check if the current logged-in user is allowed to perform actions on the selected user --- .../utils/is-current-user.function.ts | 20 +-------- .../user/user/conditions/manifests.ts | 2 + .../user-allow-action-base.condition.ts | 11 ++++- .../user-allow-mfa-action.condition.ts | 14 +----- .../user-can-perform-actions.condition.ts | 44 +++++++++++++++++++ 5 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-can-perform-actions.condition.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts index 7992138140..2f088841db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts @@ -4,32 +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); - let currentUserContext = await ctrl.asPromise(); + const currentUserContext = await ctrl.asPromise(); ctrl.destroy(); - const controller = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT, (context) => { - currentUserContext = context; - }); - - await controller.asPromise(); - - controller.destroy(); - return currentUserContext!.isUserCurrentUser(userUnique); }; export const isCurrentUserAnAdmin = async (host: UmbControllerHost) => { const ctrl = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT); - let currentUserContext = await ctrl.asPromise(); + const currentUserContext = await ctrl.asPromise(); ctrl.destroy(); - const controller = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT, (context) => { - currentUserContext = context; - }); - - await controller.asPromise(); - - controller.destroy(); - return currentUserContext!.isCurrentUserAdmin(); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts index e085874d5e..de7909943d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/manifests.ts @@ -3,6 +3,7 @@ import { manifest as userAllowEnableActionManifest } from './user-allow-enable-a import { manifest as userAllowUnlockActionManifest } from './user-allow-unlock-action.condition.js'; import { manifest as userAllowMfaActionManifest } from './user-allow-mfa-action.condition.js'; import { manifest as userAllowDeleteActionManifest } from './user-allow-delete-action.condition.js'; +import { manifest as userCanPerformActionsManifest } from './user-can-perform-actions.condition.js'; export const manifests = [ userAllowDisableActionManifest, @@ -10,4 +11,5 @@ export const manifests = [ userAllowUnlockActionManifest, userAllowMfaActionManifest, userAllowDeleteActionManifest, + userCanPerformActionsManifest, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-action-base.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-action-base.condition.ts index fe26878e80..f8f2bcc7c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-action-base.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-action-base.condition.ts @@ -1,3 +1,4 @@ +import { isUserAdmin } from '../utils/index.js'; import type { UmbUserStateEnum } from '../types.js'; import { UMB_USER_WORKSPACE_CONTEXT } from '../workspace/user-workspace.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -56,10 +57,18 @@ export abstract class UmbUserActionConditionBase * Check if the current user is an admin * @protected */ - protected async isCurrentUserAdmin() { + protected isCurrentUserAdmin() { return isCurrentUserAnAdmin(this._host); } + /** + * Check if the selected user is an admin + * @protected + */ + protected async isUserAdmin() { + return this.userUnique ? isUserAdmin(this._host, this.userUnique) : false; + } + /** * Called when the user data changes * @protected diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-mfa-action.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-mfa-action.condition.ts index d9731e89bc..f5a47432b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-mfa-action.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-allow-mfa-action.condition.ts @@ -4,20 +4,10 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr export class UmbUserAllowMfaActionCondition extends UmbUserActionConditionBase { async _onUserDataChange() { - const isCurrentUser = await this.isCurrentUser(); - let isAdmin = false; - - // If it's not the current user being clicked, we need to check if the current logged-in user is an admin - if (!isCurrentUser) { - isAdmin = await this.isCurrentUserAdmin(); - } - - const isCurrentUserOrAdmin = isCurrentUser || isAdmin; - - // Check if there are any MFA providers available and if the user is allowed to use them + // Check if there are any MFA providers available this.observe( umbExtensionsRegistry.byType('mfaLoginProvider'), - (exts) => (this.permitted = isCurrentUserOrAdmin && exts.length > 0), + (exts) => (this.permitted = exts.length > 0), '_userAllowMfaActionConditionProviders', ); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-can-perform-actions.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-can-perform-actions.condition.ts new file mode 100644 index 0000000000..9a885aa7aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/conditions/user-can-perform-actions.condition.ts @@ -0,0 +1,44 @@ +import { UmbUserActionConditionBase } from './user-allow-action-base.condition.js'; +import type { ManifestCondition } from '@umbraco-cms/backoffice/extension-api'; + +/** + * Condition that checks if the current user is allowed to perform actions on the selected user. + * The logic is generally laid out so that admins can perform actions on any user, while users can only perform actions on other users. + */ +export class UmbUserCanPerformActionsCondition extends UmbUserActionConditionBase { + async _onUserDataChange() { + // Must have selected a user + if (this.userUnique === undefined) { + this.permitted = false; + return; + } + + // If the user is the current user, they can perform actions + if (await this.isCurrentUser()) { + this.permitted = true; + return; + } + + // If the current user is an admin, they can perform actions on any user + if (await this.isCurrentUserAdmin()) { + this.permitted = true; + return; + } + + // Otherwise, the current user can only perform actions on other users + if (await this.isUserAdmin()) { + this.permitted = false; + return; + } + + // The current user seems to be able to perform actions on the selected user + this.permitted = true; + } +} + +export const manifest: ManifestCondition = { + type: 'condition', + name: 'User Can Perform Actions Condition', + alias: 'Umb.Condition.User.CanPerformActions', + api: UmbUserCanPerformActionsCondition, +};