doc: document permission scanning

This commit is contained in:
KernelDeimos 2024-12-12 13:47:03 -05:00
parent 4e47c8bf9d
commit 651076a39b
4 changed files with 110 additions and 29 deletions

View File

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

View File

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

View File

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

View File

@ -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) ) {