refactor: move check-user-permission to Sequence

This commit is contained in:
KernelDeimos 2024-06-25 18:46:49 -04:00 committed by Eric Dubé
parent fbc02a5f62
commit e132a203d1
3 changed files with 106 additions and 51 deletions

View File

@ -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');
}

View File

@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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) {

View File

@ -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);
}
}
]);