mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-03 07:48:46 +08:00
doc: document permission scanning
This commit is contained in:
parent
4e47c8bf9d
commit
651076a39b
@ -37,9 +37,15 @@ const implicit_user_permissions = {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PermissionService class manages the core functionality for handling permissions within the Puter ecosystem.
|
* Permission rewriters are used to map one set of permission strings to another.
|
||||||
* It provides methods for granting, revoking, and checking permissions for various entities such as users and applications.
|
* These are invoked during permission scanning and when permissions are granted or revoked.
|
||||||
* 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.
|
*
|
||||||
|
* 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 {
|
class PermissionRewriter {
|
||||||
static create ({ id, matcher, rewriter }) {
|
static create ({ id, matcher, rewriter }) {
|
||||||
@ -70,10 +76,16 @@ class PermissionRewriter {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PermissionImplicator class is used to manage implicit permissions.
|
* Permission implicators are used to manage implicit permissions.
|
||||||
* It defines methods to match and check if a given permission is implicitly granted to an actor.
|
* It defines a method to check if a given permission is implicitly granted to an actor.
|
||||||
* @class
|
*
|
||||||
* @name PermissionImplicator
|
* 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 {
|
class PermissionImplicator {
|
||||||
static create ({ id, matcher, checker }) {
|
static create ({ id, matcher, checker }) {
|
||||||
@ -108,11 +120,16 @@ class PermissionImplicator {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PermissionExploder class is responsible for expanding permissions into a set of more granular permissions.
|
* Permission exploders are used to map any permission to a list of permissions
|
||||||
* It uses a matcher function to determine if a permission should be exploded and an exploder function to perform the expansion.
|
* which are considered to imply the specified permission.
|
||||||
* This class is part of the permission management system, allowing for dynamic and complex permission structures.
|
|
||||||
*
|
*
|
||||||
* @class PermissionExploder
|
* 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 {
|
class PermissionExploder {
|
||||||
static create ({ id, matcher, exploder }) {
|
static create ({ id, matcher, exploder }) {
|
||||||
@ -952,14 +969,26 @@ class PermissionService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
register_rewriter (translator) {
|
/**
|
||||||
if ( ! (translator instanceof PermissionRewriter) ) {
|
* Register a permission rewriter. For details see the documentation on the
|
||||||
throw new Error('translator must be a PermissionRewriter');
|
* 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) {
|
register_implicator (implicator) {
|
||||||
if ( ! (implicator instanceof PermissionImplicator) ) {
|
if ( ! (implicator instanceof PermissionImplicator) ) {
|
||||||
throw new Error('implicator must be a PermissionImplicator');
|
throw new Error('implicator must be a PermissionImplicator');
|
||||||
@ -968,6 +997,12 @@ class PermissionService extends BaseService {
|
|||||||
this._permission_implicators.push(implicator);
|
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) {
|
register_exploder (exploder) {
|
||||||
if ( ! (exploder instanceof PermissionExploder) ) {
|
if ( ! (exploder instanceof PermissionExploder) ) {
|
||||||
throw new Error('exploder must be a PermissionExploder');
|
throw new Error('exploder must be a PermissionExploder');
|
||||||
|
@ -21,6 +21,14 @@ class VirtualGroupService extends BaseService {
|
|||||||
this.membership_implicators_ = [];
|
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) {
|
register_membership_implicator (implicator) {
|
||||||
this.membership_implicators_.push(implicator);
|
this.membership_implicators_.push(implicator);
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,7 @@
|
|||||||
const remove_paths_through_user = ({ reading, user }) => {
|
const remove_paths_through_user = ({ reading, user }) => {
|
||||||
const no_cycle_reading = [];
|
const no_cycle_reading = [];
|
||||||
|
|
||||||
for ( let i = 0 ; i < reading.length ; i++ ) {
|
for ( const node of reading ) {
|
||||||
const node = reading[i];
|
|
||||||
|
|
||||||
console.log('checking node...', node);
|
|
||||||
|
|
||||||
if ( node.$ === 'path' ) {
|
if ( node.$ === 'path' ) {
|
||||||
if (
|
if (
|
||||||
node.issuer_username === user.username
|
node.issuer_username === user.username
|
||||||
@ -33,14 +29,11 @@ const remove_paths_through_user = ({ reading, user }) => {
|
|||||||
no_cycle_reading.push(node);
|
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;
|
return no_cycle_reading;
|
||||||
};
|
};
|
||||||
|
|
||||||
const reading_has_terminal = ({ reading }) => {
|
const reading_has_terminal = ({ reading }) => {
|
||||||
for ( let i = 0 ; i < reading.length ; i++ ) {
|
for ( const node of reading ) {
|
||||||
const node = reading[i];
|
|
||||||
if ( node.has_terminal ) {
|
if ( node.has_terminal ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,26 @@ const { reading_has_terminal } = require("./permission-scan-lib");
|
|||||||
"Ctrl+K, Ctrl+J" or "⌘K, ⌘J";
|
"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 = [
|
const PERMISSION_SCANNERS = [
|
||||||
{
|
{
|
||||||
name: 'implied',
|
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) {
|
async scan (a) {
|
||||||
const reading = a.get('reading');
|
const reading = a.get('reading');
|
||||||
const { actor, permission_options } = a.values();
|
const { actor, permission_options } = a.values();
|
||||||
@ -46,6 +63,9 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-user',
|
name: 'user-user',
|
||||||
|
documentation: `
|
||||||
|
User-to-User permissions are permission granted form one user to another.
|
||||||
|
`,
|
||||||
async scan (a) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options, state } = a.values();
|
const { reading, actor, permission_options, state } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -96,7 +116,6 @@ const PERMISSION_SCANNERS = [
|
|||||||
|
|
||||||
if ( should_continue ) continue;
|
if ( should_continue ) continue;
|
||||||
|
|
||||||
// const issuer_perm = await this.check(issuer_actor, row.permission);
|
|
||||||
const issuer_reading = await a.icall(
|
const issuer_reading = await a.icall(
|
||||||
'scan', issuer_actor, row.permission, undefined, state);
|
'scan', issuer_actor, row.permission, undefined, state);
|
||||||
|
|
||||||
@ -117,6 +136,13 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'hc-user-group-user',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -167,6 +193,11 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-group-user',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -223,6 +254,16 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-virtual-group-user',
|
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) {
|
async scan (a) {
|
||||||
const svc_virtualGroup = await a.iget('services').get('virtual-group');
|
const svc_virtualGroup = await a.iget('services').get('virtual-group');
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
@ -248,6 +289,10 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-app',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof AppUnderUserActorType) ) {
|
if ( !(actor.type instanceof AppUnderUserActorType) ) {
|
||||||
|
Loading…
Reference in New Issue
Block a user