From 3f0e7659f37250e65e93621d80f82cf7cde6df7e Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 6 Jul 2025 15:49:19 -0400 Subject: [PATCH] dev: add support to imply shortcut permissions Permission implicators with the "shortcut" option will be checked prior to concurrent permission scanning. If any of these permissions are granted, then the concurrent phase of permission scanning will not be invoked; instead, the user will only see the shortcut permission. It may be useful to add an option in the future for a full permission scan, which could be useful in such things as a user interface to help with permission management. --- .../src/services/auth/PermissionService.js | 7 ++-- .../structured/sequence/scan-permission.js | 36 +++++++++++++++++++ .../src/unstructured/permission-scanners.js | 9 +++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js index b5f2d39f4..9195117ed 100644 --- a/src/backend/src/services/auth/PermissionService.js +++ b/src/backend/src/services/auth/PermissionService.js @@ -77,14 +77,15 @@ class PermissionRewriter { * The actor and permission are passed to checker({ actor, permission }) as an object. */ class PermissionImplicator { - static create ({ id, matcher, checker }) { - return new PermissionImplicator({ id, matcher, checker }); + static create ({ id, matcher, checker, ...options }) { + return new PermissionImplicator({ id, matcher, checker, options }); } - constructor ({ id, matcher, checker }) { + constructor ({ id, matcher, checker, options }) { this.id = id; this.matcher = matcher; this.checker = checker; + this.options = options; } matches (permission) { diff --git a/src/backend/src/structured/sequence/scan-permission.js b/src/backend/src/structured/sequence/scan-permission.js index be3e93ae1..573574952 100644 --- a/src/backend/src/structured/sequence/scan-permission.js +++ b/src/backend/src/structured/sequence/scan-permission.js @@ -75,6 +75,42 @@ module.exports = new Sequence([ } a.set('permission_options', permission_options.flat()); }, + async function handle_shortcuts (a) { + const reading = a.get('reading'); + const { actor, permission_options } = a.values(); + + const _permission_implicators = a.iget('_permission_implicators'); + + for ( const permission of permission_options ) + for ( const implicator of _permission_implicators ) { + if ( ! implicator.options?.shortcut ) continue; + + // TODO: is it possible to DRY this with concurrent implicators in permission-scanners.js? + if ( ! implicator.matches(permission) ) { + continue; + } + const implied = await implicator.check({ + actor, + permission, + }); + if ( implied ) { + reading.push({ + $: 'option', + permission, + source: 'implied', + by: implicator.id, + data: implied, + ...((!!actor.type.user) + ? { holder_username: actor.type.user.username } + : {}), + }); + if ( implicator.options?.shortcut ) { + a.stop(); + return; + } + } + } + }, async function run_scanners (a) { const scanners = PERMISSION_SCANNERS; const ps = []; diff --git a/src/backend/src/unstructured/permission-scanners.js b/src/backend/src/unstructured/permission-scanners.js index 6e3c2b88e..907c16561 100644 --- a/src/backend/src/unstructured/permission-scanners.js +++ b/src/backend/src/unstructured/permission-scanners.js @@ -61,6 +61,8 @@ const PERMISSION_SCANNERS = [ for ( const permission of permission_options ) for ( const implicator of _permission_implicators ) { + if ( implicator.options?.shortcut ) continue; + if ( ! implicator.matches(permission) ) { continue; } @@ -75,7 +77,14 @@ const PERMISSION_SCANNERS = [ source: 'implied', by: implicator.id, data: implied, + ...((!!actor.type.user) + ? { holder_username: actor.type.user.username } + : {}), }); + if ( implicator.options?.shortcut ) { + a.stop(); + return; + } } } }