From de5886698e1eae2b250baac174b57029f3244e96 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Tue, 18 Jun 2024 03:56:19 -0400 Subject: [PATCH] fix: fix permission cascade properly this time --- .../src/filesystem/FilesystemService.js | 44 +++++----- .../backend/src/services/auth/ACLService.js | 3 +- .../src/services/auth/PermissionService.js | 81 ++++++++++++++++++- 3 files changed, 99 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/filesystem/FilesystemService.js b/packages/backend/src/filesystem/FilesystemService.js index 25c8ad73..e69dae3a 100644 --- a/packages/backend/src/filesystem/FilesystemService.js +++ b/packages/backend/src/filesystem/FilesystemService.js @@ -34,7 +34,7 @@ const APIError = require('../api/APIError.js'); const { LLMkdir } = require('./ll_operations/ll_mkdir.js'); const { LLCWrite, LLOWrite } = require('./ll_operations/ll_write.js'); const { LLCopy } = require('./ll_operations/ll_copy.js'); -const { PermissionUtil, PermissionRewriter, PermissionImplicator } = require('../services/auth/PermissionService.js'); +const { PermissionUtil, PermissionRewriter, PermissionImplicator, PermissionExploder } = require('../services/auth/PermissionService.js'); const { DB_WRITE } = require("../services/database/consts"); const { UserActorType } = require('../services/auth/Actor'); const { get_user } = require('../helpers'); @@ -165,38 +165,34 @@ class FilesystemService extends AdvancedBase { return undefined; }, })); - svc_permission.register_implicator(PermissionImplicator.create({ + svc_permission.register_exploder(PermissionExploder.create({ matcher: permission => { - return permission.startsWith('fs:'); + return permission.startsWith('fs:') && + PermissionUtil.split(permission).length >= 3; }, - checker: async ({ actor, permission, recurse }) => { + exploder: async ({ permission }) => { + const permissions = [permission]; const parts = PermissionUtil.split(permission); - if ( parts.length < 3 ) return undefined; const specified_mode = parts[2]; - const mode = { - write: 'read', - read: 'list', - list: 'see', - }[specified_mode]; + const rules = { + see: ['list', 'read', 'write'], + list: ['read', 'write'], + read: ['write'], + }; - if ( ! mode ) return undefined; - - const perm = await recurse(actor, - PermissionUtil.join( - parts[0], - parts[1], - mode, - ...parts.slice(3), - ) - ) - if ( perm ) { - console.log('RETURNING IT!', perm); - return perm; + if ( rules.hasOwnProperty(specified_mode) ) { + permissions.push(...rules[specified_mode].map( + mode => PermissionUtil.join( + parts[0], parts[1], + mode, + ...parts.slice(3), + ) + )); } - return undefined; + return permissions; }, })); } diff --git a/packages/backend/src/services/auth/ACLService.js b/packages/backend/src/services/auth/ACLService.js index b5c55f32..16304863 100644 --- a/packages/backend/src/services/auth/ACLService.js +++ b/packages/backend/src/services/auth/ACLService.js @@ -108,7 +108,8 @@ class ACLService extends BaseService { const svc_permission = await context.get('services').get('permission'); - const modes = this._higher_modes(mode); + // const modes = this._higher_modes(mode); + const modes = [mode]; let perm_fsNode = fsNode; while ( ! await perm_fsNode.get('is-root') ) { for ( const mode of modes ) { diff --git a/packages/backend/src/services/auth/PermissionService.js b/packages/backend/src/services/auth/PermissionService.js index df801eb9..16a11636 100644 --- a/packages/backend/src/services/auth/PermissionService.js +++ b/packages/backend/src/services/auth/PermissionService.js @@ -135,6 +135,32 @@ class PermissionImplicator { } } +class PermissionExploder { + static create ({ id, matcher, exploder }) { + return new PermissionExploder({ id, matcher, exploder }); + } + + constructor ({ id, matcher, exploder }) { + this.id = id; + this.matcher = matcher; + this.exploder = exploder; + } + + matches (permission) { + return this.matcher(permission); + } + + /** + * Check if the permission is implied by this implicator + * @param {Actor} actor + * @param {string} permission + * @returns + */ + async explode ({ actor, permission }) { + return await this.exploder({ actor, permission }); + } +} + class PermissionUtil { static unescape_permission_component (component) { let unescaped_str = ''; @@ -194,6 +220,7 @@ class PermissionService extends BaseService { this._permission_rewriters = []; this._permission_implicators = []; + this._permission_exploders = []; } async _rewrite_permission (permission) { @@ -220,6 +247,17 @@ class PermissionService extends BaseService { actor: actor.uid, permission, }); + + // for ( const implicator of this._permission_implicators ) { + // if ( ! implicator.matches(permission) ) continue; + // const implied = await implicator.check({ + // actor, + // permission, + // recurse: this.check.bind(this), + // }); + // if ( implied ) return implied; + // } + // For now we're only checking driver permissions, and users have all of them if ( actor.type instanceof UserActorType ) { return await this.check_user_permission(actor, permission); @@ -240,8 +278,17 @@ class PermissionService extends BaseService { // NEXT: const app_uid = actor.type.app.uid; const user_actor = actor.get_related_actor(UserActorType); - const user_has_permission = await this.check_user_permission(user_actor, permission); + // const user_has_permission = await this.check_user_permission(user_actor, permission); + const user_has_permission = await this.check__( + user_actor, permission, + ); if ( ! user_has_permission ) return undefined; + + // This was a useful log so I'm keeping it here + // console.log('\x1B[36;1m>=== THIS IS HERE ===<\x1B[0m', + // app_uid, + // permission, + // ) return await this.check_user_app_permission(actor, app_uid, permission); } @@ -252,7 +299,8 @@ class PermissionService extends BaseService { // TODO: context meta for cycle detection async check_user_permission (actor, permission) { permission = await this._rewrite_permission(permission); - const parent_perms = this.get_parent_permissions(permission); + // const parent_perms = this.get_parent_permissions(permission); + const parent_perms = await this.get_higher_permissions(permission); // Check implicit permissions for ( const parent_perm of parent_perms ) { @@ -329,7 +377,8 @@ class PermissionService extends BaseService { if ( ! app ) app = await get_app({ name: app_uid }); const app_id = app.id; - const parent_perms = this.get_parent_permissions(permission); + // const parent_perms = this.get_parent_permissions(permission); + const parent_perms = await this.get_higher_permissions(permission); for ( const permission of parent_perms ) { // Check hardcoded permissions @@ -348,7 +397,7 @@ class PermissionService extends BaseService { return implicit_permissions[permission]; } } - + // My biggest gripe with SQL is doing string manipulation for queries. // If the grammar for SQL was simpler we could model it, write this as // data, and even implement macros for common patterns. @@ -665,6 +714,21 @@ class PermissionService extends BaseService { return retval; } + + async get_higher_permissions (permission) { + const higher_perms = []; + const parent_perms = this.get_parent_permissions(permission); + for ( const parent_perm of parent_perms ) { + for ( const exploder of this._permission_exploders ) { + if ( ! exploder.matches(parent_perm) ) continue; + const perms = await exploder.explode({ + permission: parent_perm, + }); + higher_perms.push(...perms); + } + } + return higher_perms; + } get_parent_permissions (permission) { const parent_perms = []; @@ -699,6 +763,14 @@ class PermissionService extends BaseService { this._permission_implicators.push(implicator); } + register_exploder (exploder) { + if ( ! (exploder instanceof PermissionExploder) ) { + throw new Error('exploder must be a PermissionExploder'); + } + + this._permission_exploders.push(exploder); + } + _register_commands (commands) { commands.registerCommands('perms', [ { @@ -723,6 +795,7 @@ class PermissionService extends BaseService { module.exports = { PermissionRewriter, PermissionImplicator, + PermissionExploder, PermissionUtil, PermissionService, };