diff --git a/packages/backend/src/codex/Sequence.js b/packages/backend/src/codex/Sequence.js index f20a58ac..86e981a8 100644 --- a/packages/backend/src/codex/Sequence.js +++ b/packages/backend/src/codex/Sequence.js @@ -216,6 +216,17 @@ class Sequence { return this.thisArg?.[k]; } + // Instance call: call a method on the instance + icall(k, ...args) { + return this.thisArg?.[k]?.call(this.thisArg, ...args); + } + + // Instance dynamic call: call a method on the instance, + // passing the sequence state as the first argument + idcall(k, ...args) { + return this.thisArg?.[k]?.call(this.thisArg, this, ...args); + } + get log () { return this.iget('log'); } diff --git a/packages/backend/src/services/auth/PermissionService.js b/packages/backend/src/services/auth/PermissionService.js index 0f45f9b3..823d42cd 100644 --- a/packages/backend/src/services/auth/PermissionService.js +++ b/packages/backend/src/services/auth/PermissionService.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ const { get_user, get_app } = require("../../helpers"); +const { AssignableMethodsTrait } = require("../../traits/AssignableMethodsTrait"); const { Context } = require("../../util/context"); const BaseService = require("../BaseService"); const { DB_WRITE } = require("../database/consts"); @@ -306,60 +307,14 @@ 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 = await this.get_higher_permissions(permission); - - // Check implicit permissions - for ( const parent_perm of parent_perms ) { - if ( implicit_user_permissions[parent_perm] ) { - return implicit_user_permissions[parent_perm]; - } - } - - for ( const implicator of this._permission_implicators ) { - if ( ! implicator.matches(permission) ) continue; - const implied = await implicator.check({ + return await require('../../structured/sequence/check-user-permission') + .call(this, { + // passed actor, permission, - recurse: this.check.bind(this), + // constants + implicit_user_permissions, }); - if ( implied ) return implied; - } - - // Check permissions granted by other users - let sql_perm = parent_perms.map((perm) => - `\`permission\` = ?`).join(' OR '); - if ( parent_perms.length > 1 ) sql_perm = '(' + sql_perm + ')'; - - // SELECT permission - const rows = await this.db.read( - 'SELECT * FROM `user_to_user_permissions` ' + - 'WHERE `holder_user_id` = ? AND ' + - sql_perm, - [ - actor.type.user.id, - ...parent_perms, - ] - ); - - // Return the first matching permission where the - // issuer also has the permission granted - for ( const row of rows ) { - const issuer_actor = new Actor({ - type: new UserActorType({ - user: await get_user({ id: row.issuer_user_id }), - }), - }); - - const issuer_perm = await this.check(issuer_actor, row.permission); - - if ( ! issuer_perm ) continue; - - return row.extra; - } - - return undefined; } async check_access_token_permission (authorizer, token, permission) { diff --git a/packages/backend/src/structured/sequence/check-user-permission.js b/packages/backend/src/structured/sequence/check-user-permission.js new file mode 100644 index 00000000..1ed1e7e2 --- /dev/null +++ b/packages/backend/src/structured/sequence/check-user-permission.js @@ -0,0 +1,89 @@ +const { Sequence } = require("../../codex/Sequence"); +const { Actor, UserActorType } = require("../../services/auth/Actor"); + + + +module.exports = new Sequence([ + async function rewrite_permission (a) { + let { permission } = a.values(); + permission = await a.icall('_rewrite_permission', permission); + a.values({ permission }); + }, + async function explode_permission (a) { + const { permission } = a.values(); + const permission_options = + await a.icall('get_higher_permissions', permission); + a.values({ permission_options }); + }, + async function try_hardcoded_permission (a) { + const { + permission_options, + implicit_user_permissions + } = a.values(); + + for ( const perm of permission_options ) { + if ( implicit_user_permissions[perm] ) { + return a.stop(implicit_user_permissions[perm]); + } + } + }, + async function try_permission_implicators (a) { + // NOTE: it's really weird that we check `permission` only and not + // the `permission_options` list here. I haven't changed this + // to avoid regressions but it's something to consider. + const { actor, permission } = a.values(); + + const _permission_implicators = a.iget('_permission_implicators'); + + for ( const implicator of _permission_implicators ) { + if ( ! implicator.matches(permission) ) continue; + const implied = await implicator.check({ + actor, + permission, + recurse: this.check.bind(this), + }); + if ( implied ) { + return a.stop(implied); + } + } + }, + async function try_user_to_user_permissions (a) { + const { actor, permission_options } = a.values(); + const db = a.iget('db'); + + let sql_perm = permission_options.map((perm) => + `\`permission\` = ?`).join(' OR '); + + if ( permission_options.length > 1 ) { + sql_perm = '(' + sql_perm + ')'; + } + + // SELECT permission + const rows = await db.read( + 'SELECT * FROM `user_to_user_permissions` ' + + 'WHERE `holder_user_id` = ? AND ' + + sql_perm, + [ + actor.type.user.id, + ...permission_options, + ] + ); + + // Return the first matching permission where the + // issuer also has the permission granted + for ( const row of rows ) { + const issuer_actor = new Actor({ + type: new UserActorType({ + user: await get_user({ id: row.issuer_user_id }), + }), + }); + + // const issuer_perm = await this.check(issuer_actor, row.permission); + const issuer_perm = await a.icall('check', issuer_actor, row.permission); + + if ( ! issuer_perm ) continue; + + return a.stop(row.extra); + } + } +]);