mirror of
https://github.com/HeyPuter/puter.git
synced 2026-01-08 14:10:41 -06:00
feat: add server command to scan permissions
This commit is contained in:
@@ -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, ' '));
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
56
src/backend/src/structured/sequence/scan-user-permission.js
Normal file
56
src/backend/src/structured/sequence/scan-user-permission.js
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
},
|
||||
]);
|
||||
124
src/backend/src/unstructured/permission-scanners.js
Normal file
124
src/backend/src/unstructured/permission-scanners.js
Normal file
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user