From 0cf90ee39af6548d271dec45ed8ee9e6df1cd14d Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 24 Jun 2024 16:59:28 -0400 Subject: [PATCH] fix: fix issues with apps in /share endpoint --- doc/devmeta/track-comments.md | 5 + packages/backend/src/routers/share.js | 181 +++++++++++++++++--------- 2 files changed, 126 insertions(+), 60 deletions(-) diff --git a/doc/devmeta/track-comments.md b/doc/devmeta/track-comments.md index 91e43bf6..94233250 100644 --- a/doc/devmeta/track-comments.md +++ b/doc/devmeta/track-comments.md @@ -46,3 +46,8 @@ Comments beginning with `// track:`. See then the null check should result in a early return of null; this code with the track comment may have additional logic for the null/undefined case. +- `track: manual safe object` + This code manually creates a new "client-safe" version of + some object that's in scope. This could be either to pass + onto the browser or to pass to something like the + notification service. \ No newline at end of file diff --git a/packages/backend/src/routers/share.js b/packages/backend/src/routers/share.js index a648894f..34b96fb3 100644 --- a/packages/backend/src/routers/share.js +++ b/packages/backend/src/routers/share.js @@ -235,6 +235,7 @@ const v0_2 = async (req, res) => { recipients_work.clear_invalid(); + // Check: users specified by username exist for ( const item of recipients_work.list() ) { if ( item.type !== 'username' ) continue; @@ -270,9 +271,11 @@ const v0_2 = async (req, res) => { } shares_work.lockin(); + // Check: all share items are a type-tagged-object + // with one of these types: fs-share, app-share. for ( const item of shares_work.list() ) { - const { i } = item; - let { value } = item; + const { i } = item; + let { value } = item; const thing = lib_typeTagged.process(value); if ( thing.$ === 'error' ) { @@ -286,59 +289,92 @@ const v0_2 = async (req, res) => { const allowed_things = ['fs-share', 'app-share']; if ( ! allowed_things.includes(thing.$) ) { - APIError.create('disallowed_thing', null, { - thing: thing.$, - accepted: allowed_things, - }) - } - - if ( thing.$ === 'fs-share' ) { - item.type = 'fs'; - const errors = []; - if ( ! thing.path ) { - errors.push('`path` is required'); - } - let access = thing.access; - if ( access ) { - if ( ! ['read','write'].includes(access) ) { - errors.push('`access` should be `read` or `write`'); - } - } else access = 'read'; - - if ( errors.length ) { - item.invalid = true; - result.shares[item.i] = - APIError.create('field_errors', null, { - key: `shares[${item.i}]`, - errors - }); - continue; - } - - item.path = thing.path; - item.permission = PermissionUtil.join('fs', thing.path, access); - } - - if ( thing.$ === 'app-share' ) { - item.type = 'app'; - const errors = []; - if ( ! thing.uid && thing.name ) { - errors.push('`uid` or `name` is required'); - } - - if ( errors.length ) { - item.invalid = true; - result.shares[item.i] = - APIError.create('field_errors', null, { - key: `shares[${item.i}]`, - errors - }); - continue; - } - - item.permission = PermissionUtil.join('app', thing.path, 'access'); + item.invalid = true; + result.shares[i] = + APIError.create('disallowed_thing', null, { + thing: thing.$, + accepted: allowed_things, + }); continue; } + + item.thing = thing; + } + + shares_work.clear_invalid(); + + // Process: create $share-intent:file for file items + for ( const item of shares_work.list() ) { + const { thing } = item; + if ( thing.$ !== 'fs-share' ) continue; + + item.type = 'fs'; + const errors = []; + if ( ! thing.path ) { + errors.push('`path` is required'); + } + let access = thing.access; + if ( access ) { + if ( ! ['read','write'].includes(access) ) { + errors.push('`access` should be `read` or `write`'); + } + } else access = 'read'; + + if ( errors.length ) { + item.invalid = true; + result.shares[item.i] = + APIError.create('field_errors', null, { + key: `shares[${item.i}]`, + errors + }); + continue; + } + + item.path = thing.path; + item.share_intent = { + $: 'share-intent:file', + permissions: [PermissionUtil.join('fs', thing.path, access)], + }; + } + + shares_work.clear_invalid(); + + // Process: create $share-intent:app for app items + for ( const item of shares_work.list() ) { + const { thing } = item; + if ( thing.$ !== 'app-share' ) continue; + + item.type = 'app'; + const errors = []; + if ( ! thing.uid && ! thing.name ) { + errors.push('`uid` or `name` is required'); + } + + if ( errors.length ) { + item.invalid = true; + result.shares[item.i] = + APIError.create('field_errors', null, { + key: `shares[${item.i}]`, + errors + }); + continue; + } + + const app_selector = thing.uid + ? `uid#${thing.uid}` : thing.name; + + item.share_intent = { + $: 'share-intent:app', + permissions: [ + PermissionUtil.join('app', app_selector, 'access') + ] + } + continue; + } + + // Process: conditionally add permission for subdomain + for ( const item of shares_work.list() ) { + // NEXT } shares_work.clear_invalid(); @@ -372,6 +408,13 @@ const v0_2 = async (req, res) => { item.email_link = email_link; } + shares_work.clear_invalid(); + + for ( const item of shares_work.list() ) { + if ( item.type !== 'app' ) continue; + const app = await get_app({}); + } + shares_work.clear_invalid(); // Mark files as successful; further errors will be @@ -408,11 +451,14 @@ const v0_2 = async (req, res) => { const username = recipient_item.user.username; for ( const share_item of shares_work.list() ) { - await svc_permission.grant_user_user_permission( - actor, - username, - share_item.permission, - ); + const permissions = share_item.share_intent.permissions; + for ( const perm of permissions ) { + await svc_permission.grant_user_user_permission( + actor, + username, + perm, + ); + } } // TODO: Need to re-work this for multiple files @@ -433,12 +479,27 @@ const v0_2 = async (req, res) => { */ const files = []; { - for ( const path_item of shares_work.list() ) { + for ( const item of shares_work.list() ) { + if ( item.type !== 'file' ) continue; files.push( - await path_item.node.getSafeEntry(), + await item.node.getSafeEntry(), ); } } + + const apps = []; { + for ( const item of shares_work.list() ) { + if ( item.type !== 'app' ) continue; + // TODO: is there a general way to create a + // client-safe app right now without + // going through entity storage? + // track: manual safe object + apps.push(item.name + ? item.name : await get_app({ + uid: item.uid, + })); + } + } svc_notification.notify(UsernameNotifSelector(username), { source: 'sharing',