From 54471fada946a70eaa0df6bfceae995bc4e5848c Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Tue, 23 Jul 2024 01:27:39 -0400 Subject: [PATCH] feat: add server command to scan permissions --- .../src/services/auth/PermissionService.js | 43 ++++++ .../sequence/scan-user-permission.js | 56 ++++++++ .../src/unstructured/permission-scanners.js | 124 ++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 src/backend/src/structured/sequence/scan-user-permission.js create mode 100644 src/backend/src/unstructured/permission-scanners.js diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js index e913ddb0b..bd3fc358e 100644 --- a/src/backend/src/services/auth/PermissionService.js +++ b/src/backend/src/services/auth/PermissionService.js @@ -243,7 +243,33 @@ class PermissionService extends BaseService { return res; }); } + + async scan (actor, permission) { + const reading = []; + { + const old_perm = permission; + permission = await this._rewrite_permission(permission); + if ( permission !== old_perm ) { + reading.push({ + $: 'rewrite', + from: old_perm, + to: permission, + }); + } + } + + + await require('../../structured/sequence/scan-user-permission') + .call(this, { + actor, + permission, + reading, + }); + + return reading; + } + async check__ (actor, permission) { permission = await this._rewrite_permission(permission); @@ -856,6 +882,23 @@ class PermissionService extends BaseService { await this.grant_user_app_permission(actor, app_uid, permission, extra); } + }, + { + id: 'scan', + handler: async (args, ctx) => { + const [ username, permission ] = args; + + // actor from username + const actor = new Actor({ + type: new UserActorType({ + user: await get_user({ username }), + }), + }) + + const reading = await this.scan(actor, permission); + const util = require('node:util'); + ctx.log(JSON.stringify(reading, undefined, ' ')); + } } ]); } diff --git a/src/backend/src/structured/sequence/scan-user-permission.js b/src/backend/src/structured/sequence/scan-user-permission.js new file mode 100644 index 000000000..ebcabe354 --- /dev/null +++ b/src/backend/src/structured/sequence/scan-user-permission.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +const { Sequence } = require("../../codex/Sequence"); +const { get_user } = require("../../helpers"); +const { Actor, UserActorType } = require("../../services/auth/Actor"); +const { PERMISSION_SCANNERS } = require("../../unstructured/permission-scanners"); + +module.exports = new Sequence([ + async function grant_if_system (a) { + const reading = a.get('reading'); + const { actor } = a.values(); + if ( actor.type.user.username === 'system' ) { + reading.push({ + $: 'option', + source: 'implied', + data: {} + }) + return a.stop({}); + } + }, + 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 run_scanners (a) { + const scanners = PERMISSION_SCANNERS; + const ps = []; + for ( const scanner of scanners ) { + ps.push(scanner.scan(a)); + } + await Promise.all(ps); + }, +]); diff --git a/src/backend/src/unstructured/permission-scanners.js b/src/backend/src/unstructured/permission-scanners.js new file mode 100644 index 000000000..334b7f8d9 --- /dev/null +++ b/src/backend/src/unstructured/permission-scanners.js @@ -0,0 +1,124 @@ +const { get_user } = require("../helpers"); +const { Actor, UserActorType } = require("../services/auth/Actor"); + +const PERMISSION_SCANNERS = [ + { + name: 'implied', + async scan (a) { + const reading = a.get('reading'); + 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, + }); + if ( implied ) { + reading.push({ + $: 'option', + source: 'implied', + data: implied, + }); + } + } + } + }, + { + name: 'user-user', + async scan (a) { + const reading = a.get('reading'); + const db = a.iget('db'); + const { actor, permission_options } = a.values(); + + let sql_perm = permission_options.map(perm => { + return `\`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_reading = await a.icall('scan', issuer_actor, row.permission); + reading.push({ + $: 'path', + via: 'user', + // issuer: issuer_actor, + issuer_username: issuer_actor.type.user.username, + reading: issuer_reading, + }); + } + } + }, + { + name: 'user-group-user', + async scan (a) { + const reading = a.get('reading'); + const { actor, permission_options } = a.values(); + const db = a.iget('db'); + + let sql_perm = permission_options.map((perm) => + `p.permission = ?`).join(' OR '); + + if ( permission_options.length > 1 ) { + sql_perm = '(' + sql_perm + ')'; + } + const rows = await db.read( + 'SELECT p.permission, p.user_id, p.group_id, p.extra FROM `user_to_group_permissions` p ' + + 'JOIN `jct_user_group` ug ON p.group_id = ug.group_id ' + + 'WHERE ug.user_id = ? AND ' + sql_perm, + [ + actor.type.user.id, + ...permission_options, + ] + ); + + for ( const row of rows ) { + const issuer_actor = new Actor({ + type: new UserActorType({ + user: await get_user({ id: row.user_id }), + }), + }); + + const issuer_reading = await a.icall('scan', issuer_actor, row.permission); + + reading.push({ + $: 'path', + via: 'user-group', + // issuer: issuer_actor, + issuer_username: issuer_actor.type.user.username, + reading: issuer_reading, + group_id: row.group_id, + }); + } + } + } +]; + +module.exports = { + PERMISSION_SCANNERS, +};