From 31d4eb090efb340fdfb7cb6b751145e859624eeb Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 16 Jun 2024 21:09:04 -0400 Subject: [PATCH] feat: implicit access from apps to shared appdata dirs --- .../backend/src/filesystem/FSNodeContext.js | 21 +++++++++++++------ .../src/filesystem/hl_operations/hl_write.js | 20 ++++++++++++++---- .../backend/src/services/auth/ACLService.js | 19 ++++++++++++++++- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/backend/src/filesystem/FSNodeContext.js b/packages/backend/src/filesystem/FSNodeContext.js index 8061a315..a7677157 100644 --- a/packages/backend/src/filesystem/FSNodeContext.js +++ b/packages/backend/src/filesystem/FSNodeContext.js @@ -165,17 +165,26 @@ module.exports = class FSNodeContext { return ! this.entry.parent_uid; } - async getUserPart () { - if ( this.isRoot ) return; + async getPathComponents () { + if ( this.isRoot ) return []; let path = await this.get('path'); if ( path.startsWith('/') ) path = path.slice(1); - const components = path.split('/'); - const userpart = components[0]; - - return userpart; + return path.split('/'); + } + + async getUserPart () { + if ( this.isRoot ) return; + const components = await this.getPathComponents(); + return components[0]; } + async getPathSize () { + if ( this.isRoot ) return; + const components = await this.getPathComponents(); + return components.length; + } + async exists (fetch_options = {}) { await this.fetchEntry(); if ( ! this.found ) { diff --git a/packages/backend/src/filesystem/hl_operations/hl_write.js b/packages/backend/src/filesystem/hl_operations/hl_write.js index c36787f5..dfccef46 100644 --- a/packages/backend/src/filesystem/hl_operations/hl_write.js +++ b/packages/backend/src/filesystem/hl_operations/hl_write.js @@ -31,6 +31,7 @@ const { RootNodeSelector, NodePathSelector } = require("../node/selectors"); const { is_valid_node_name } = require("../validation"); const { HLFilesystemOperation } = require("./definitions"); const { MkTree } = require("./hl_mkdir"); +const { Actor } = require("../../services/auth/Actor"); class WriteCommonTrait { install_in_instance (instance) { @@ -186,10 +187,6 @@ class HLWrite extends HLFilesystemOperation { throw APIError.create('cannot_write_to_root'); } - if ( values.user && ! await chkperm(await parent.get('entry'), values.user.id, 'write') ) { - throw APIError.create('forbidden'); - } - try { // old validator is kept here to avoid changing the // error messages; eventually is_valid_node_name @@ -222,6 +219,21 @@ class HLWrite extends HLFilesystemOperation { if ( values.offset !== undefined && ! dest_exists ) { throw APIError.create('offset_without_existing_file'); } + + // The correct ACL check here depends on context. + // ll_write checks ACL, but we need to shortcut it here + // or else we might send the user too much information. + { + const node_to_check = + ( dest_exists && overwrite && ! dedupe_name ) + ? destination : parent; + + const actor = values.actor ?? Actor.adapt(values.user); + const svc_acl = context.get('services').get('acl'); + if ( ! await svc_acl.check(actor, node_to_check, 'write') ) { + throw await svc_acl.get_safe_acl_error(actor, node_to_check, 'write'); + } + } if ( dest_exists ) { console.log('DESTINATION EXISTS', dedupe_name) diff --git a/packages/backend/src/services/auth/ACLService.js b/packages/backend/src/services/auth/ACLService.js index 8b4d761e..b5c55f32 100644 --- a/packages/backend/src/services/auth/ACLService.js +++ b/packages/backend/src/services/auth/ACLService.js @@ -78,7 +78,7 @@ class ACLService extends BaseService { return true; } } - + // app-under-user only works if the user also has permission if ( actor.type instanceof AppUnderUserActorType ) { const user_actor = new Actor({ @@ -88,6 +88,23 @@ class ACLService extends BaseService { if ( ! user_perm ) return false; } + + // Hard rule: if app-under-user is accessing appdata directory + // under a **different user**, allow, + // IFF that appdata directory is shared with user + // (by "user also has permission" check above) + if (await (async () => { + if ( ! (actor.type instanceof AppUnderUserActorType) ) { + return false; + } + if ( await fsNode.getUserPart() === actor.type.user.username ) { + return false; + } + const components = await fsNode.getPathComponents(); + if ( components[1] !== 'AppData' ) return false; + if ( components[2] !== actor.type.app.uid ) return false; + return true; + })()) return true; const svc_permission = await context.get('services').get('permission');