From 651076a39bf15c7eb42ff5dfae7dea61ba9e8997 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 12 Dec 2024 13:47:03 -0500 Subject: [PATCH] doc: document permission scanning --- .../src/services/auth/PermissionService.js | 73 ++++++++++++++----- .../src/services/auth/VirtualGroupService.js | 8 ++ .../src/unstructured/permission-scan-lib.js | 11 +-- .../src/unstructured/permission-scanners.js | 47 +++++++++++- 4 files changed, 110 insertions(+), 29 deletions(-) diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js index d652060c..487101bd 100644 --- a/src/backend/src/services/auth/PermissionService.js +++ b/src/backend/src/services/auth/PermissionService.js @@ -37,10 +37,16 @@ const implicit_user_permissions = { /** -* The PermissionService class manages the core functionality for handling permissions within the Puter ecosystem. -* It provides methods for granting, revoking, and checking permissions for various entities such as users and applications. -* This service interacts with the database to store, retrieve, and audit permission changes, and also handles complex permission logic like rewriting, implication, and explosion of permissions. -*/ + * Permission rewriters are used to map one set of permission strings to another. + * These are invoked during permission scanning and when permissions are granted or revoked. + * + * For example, Puter's filesystem uses this to map 'fs:/some/path:mode' to + * 'fs:SOME-UUID:mode'. + * + * A rewriter is constructed using the static method PermissionRewriter.create({ matcher, rewriter }). + * The matcher is a function that takes a permission string and returns true if the rewriter should be applied. + * The rewriter is a function that takes a permission string and returns the rewritten permission string. + */ class PermissionRewriter { static create ({ id, matcher, rewriter }) { return new PermissionRewriter({ id, matcher, rewriter }); @@ -70,11 +76,17 @@ class PermissionRewriter { /** -* The PermissionImplicator class is used to manage implicit permissions. -* It defines methods to match and check if a given permission is implicitly granted to an actor. -* @class -* @name PermissionImplicator -*/ + * Permission implicators are used to manage implicit permissions. + * It defines a method to check if a given permission is implicitly granted to an actor. + * + * For example, Puter's filesystem uses this to grant permission to a file if the specified + * 'actor' is the owner of the file. + * + * An implicator is constructed using the static method PermissionImplicator.create({ matcher, checker }). + * `matcher is a function that takes a permission string and returns true if the implicator should be applied. + * `checker` is a function that takes an actor and a permission string and returns true if the permission is implied. + * The actor and permission are passed to checker({ actor, permission }) as an object. + */ class PermissionImplicator { static create ({ id, matcher, checker }) { return new PermissionImplicator({ id, matcher, checker }); @@ -108,12 +120,17 @@ class PermissionImplicator { /** -* The PermissionExploder class is responsible for expanding permissions into a set of more granular permissions. -* It uses a matcher function to determine if a permission should be exploded and an exploder function to perform the expansion. -* This class is part of the permission management system, allowing for dynamic and complex permission structures. -* -* @class PermissionExploder -*/ + * Permission exploders are used to map any permission to a list of permissions + * which are considered to imply the specified permission. + * + * It uses a matcher function to determine if a permission should be exploded + * and an exploder function to perform the expansion. + * + * The exploder is constructed using the static method PermissionExploder.create({ matcher, explode }). + * The `matcher` is a function that takes a permission string and returns true if the exploder should be applied. + * The `explode` is a function that takes an actor and a permission string and returns a list of implied permissions. + * The actor and permission are passed to explode({ actor, permission }) as an object. + */ class PermissionExploder { static create ({ id, matcher, exploder }) { return new PermissionExploder({ id, matcher, exploder }); @@ -952,14 +969,26 @@ class PermissionService extends BaseService { } - register_rewriter (translator) { - if ( ! (translator instanceof PermissionRewriter) ) { - throw new Error('translator must be a PermissionRewriter'); + /** + * Register a permission rewriter. For details see the documentation on the + * PermissionRewriter class. + * + * @param {PermissionRewriter} rewriter - The permission rewriter to register + */ + register_rewriter (rewriter) { + if ( ! (rewriter instanceof PermissionRewriter) ) { + throw new Error('rewriter must be a PermissionRewriter'); } - this._permission_rewriters.push(translator); + this._permission_rewriters.push(rewriter); } + /** + * Register a permission implicator. For details see the documentation on the + * PermissionImplicator class. + * + * @param {PermissionImplicator} implicator - The permission implicator to register + */ register_implicator (implicator) { if ( ! (implicator instanceof PermissionImplicator) ) { throw new Error('implicator must be a PermissionImplicator'); @@ -968,6 +997,12 @@ class PermissionService extends BaseService { this._permission_implicators.push(implicator); } + /** + * Register a permission exploder. For details see the documentation on the + * PermissionExploder class. + * + * @param {PermissionExploder} exploder - The permission exploder to register + */ register_exploder (exploder) { if ( ! (exploder instanceof PermissionExploder) ) { throw new Error('exploder must be a PermissionExploder'); diff --git a/src/backend/src/services/auth/VirtualGroupService.js b/src/backend/src/services/auth/VirtualGroupService.js index 3e0d36b7..607144f6 100644 --- a/src/backend/src/services/auth/VirtualGroupService.js +++ b/src/backend/src/services/auth/VirtualGroupService.js @@ -21,6 +21,14 @@ class VirtualGroupService extends BaseService { this.membership_implicators_ = []; } + /** + * Registers a function that reports one or more groups that an actor + * should be considered a member of. + * + * @note this only applies to virtual groups, not persistent groups. + * + * @param {*} implicator + */ register_membership_implicator (implicator) { this.membership_implicators_.push(implicator); } diff --git a/src/backend/src/unstructured/permission-scan-lib.js b/src/backend/src/unstructured/permission-scan-lib.js index 6a266b1d..2f333130 100644 --- a/src/backend/src/unstructured/permission-scan-lib.js +++ b/src/backend/src/unstructured/permission-scan-lib.js @@ -10,11 +10,7 @@ const remove_paths_through_user = ({ reading, user }) => { const no_cycle_reading = []; - for ( let i = 0 ; i < reading.length ; i++ ) { - const node = reading[i]; - - console.log('checking node...', node); - + for ( const node of reading ) { if ( node.$ === 'path' ) { if ( node.issuer_username === user.username @@ -33,14 +29,11 @@ const remove_paths_through_user = ({ reading, user }) => { no_cycle_reading.push(node); } - console.log('\x1B[36;1m ====', reading.length - no_cycle_reading.length, 'nodes filtered out ====\x1B[0m'); - return no_cycle_reading; }; const reading_has_terminal = ({ reading }) => { - for ( let i = 0 ; i < reading.length ; i++ ) { - const node = reading[i]; + for ( const node of reading ) { if ( node.has_terminal ) { return true; } diff --git a/src/backend/src/unstructured/permission-scanners.js b/src/backend/src/unstructured/permission-scanners.js index 124ef348..3bf20863 100644 --- a/src/backend/src/unstructured/permission-scanners.js +++ b/src/backend/src/unstructured/permission-scanners.js @@ -14,9 +14,26 @@ const { reading_has_terminal } = require("./permission-scan-lib"); "Ctrl+K, Ctrl+J" or "⌘K, ⌘J"; */ +/** + * Permission Scanners + * @usedBy scan-permission.js + * + * These are all the different ways an entity (user or app) can have a permission. + * This list of scanners is iterated over and invoked by scan-permission.js. + * + * Each `scan` function is passed a sequence scope. The instance attached to the + * sequence scope is PermissionService itself, so any `a.iget('something')` is + * accessing the member 'something' of the PermissionService instance. + */ const PERMISSION_SCANNERS = [ { name: 'implied', + documentation: ` + Scans for permissions that are implied by "permission implicators". + + Permission implicators are added by other services via + PermissionService's \`register_implicator\` method. + `, async scan (a) { const reading = a.get('reading'); const { actor, permission_options } = a.values(); @@ -46,6 +63,9 @@ const PERMISSION_SCANNERS = [ }, { name: 'user-user', + documentation: ` + User-to-User permissions are permission granted form one user to another. + `, async scan (a) { const { reading, actor, permission_options, state } = a.values(); if ( !(actor.type instanceof UserActorType) ) { @@ -96,7 +116,6 @@ const PERMISSION_SCANNERS = [ if ( should_continue ) continue; - // const issuer_perm = await this.check(issuer_actor, row.permission); const issuer_reading = await a.icall( 'scan', issuer_actor, row.permission, undefined, state); @@ -117,6 +136,13 @@ const PERMISSION_SCANNERS = [ }, { name: 'hc-user-group-user', + documentation: ` + These are user-to-group permissions that are defined in the + hardcoded_user_group_permissions section of "hardcoded-permissions.js". + + These are typically used to grant permissions from the system user to + the default groups: "admin", "user", and "temp". + `, async scan (a) { const { reading, actor, permission_options } = a.values(); if ( !(actor.type instanceof UserActorType) ) { @@ -167,6 +193,11 @@ const PERMISSION_SCANNERS = [ }, { name: 'user-group-user', + documentation: ` + This scans for permissions that are granted to the user because a + group they are a member of was granted this permission by another + user. + `, async scan (a) { const { reading, actor, permission_options } = a.values(); if ( !(actor.type instanceof UserActorType) ) { @@ -223,6 +254,16 @@ const PERMISSION_SCANNERS = [ }, { name: 'user-virtual-group-user', + documentation: ` + These are groups with computed membership. Permissions are not granted + to these groups; instead the groups are defined with a list of + permissions that are granted to the group members. + + Services can define "virtual groups" via the "virtual-group" service. + Services can also register membership implicators for virtual groups + which will compute on the fly whether or not an actor should be + considered a member of the group. + `, async scan (a) { const svc_virtualGroup = await a.iget('services').get('virtual-group'); const { reading, actor, permission_options } = a.values(); @@ -248,6 +289,10 @@ const PERMISSION_SCANNERS = [ }, { name: 'user-app', + documentation: ` + If the actor is an app, this scans for permissions granted to the app + because the user has the permission and granted it to the app. + `, async scan (a) { const { reading, actor, permission_options } = a.values(); if ( !(actor.type instanceof AppUnderUserActorType) ) {