Skip to content

Commit 651076a

Browse files
committed
doc: document permission scanning
1 parent 4e47c8b commit 651076a

File tree

4 files changed

+110
-29
lines changed

4 files changed

+110
-29
lines changed

src/backend/src/services/auth/PermissionService.js

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ const implicit_user_permissions = {
3737

3838

3939
/**
40-
* The PermissionService class manages the core functionality for handling permissions within the Puter ecosystem.
41-
* It provides methods for granting, revoking, and checking permissions for various entities such as users and applications.
42-
* 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.
43-
*/
40+
* Permission rewriters are used to map one set of permission strings to another.
41+
* These are invoked during permission scanning and when permissions are granted or revoked.
42+
*
43+
* For example, Puter's filesystem uses this to map 'fs:/some/path:mode' to
44+
* 'fs:SOME-UUID:mode'.
45+
*
46+
* A rewriter is constructed using the static method PermissionRewriter.create({ matcher, rewriter }).
47+
* The matcher is a function that takes a permission string and returns true if the rewriter should be applied.
48+
* The rewriter is a function that takes a permission string and returns the rewritten permission string.
49+
*/
4450
class PermissionRewriter {
4551
static create ({ id, matcher, rewriter }) {
4652
return new PermissionRewriter({ id, matcher, rewriter });
@@ -70,11 +76,17 @@ class PermissionRewriter {
7076

7177

7278
/**
73-
* The PermissionImplicator class is used to manage implicit permissions.
74-
* It defines methods to match and check if a given permission is implicitly granted to an actor.
75-
* @class
76-
* @name PermissionImplicator
77-
*/
79+
* Permission implicators are used to manage implicit permissions.
80+
* It defines a method to check if a given permission is implicitly granted to an actor.
81+
*
82+
* For example, Puter's filesystem uses this to grant permission to a file if the specified
83+
* 'actor' is the owner of the file.
84+
*
85+
* An implicator is constructed using the static method PermissionImplicator.create({ matcher, checker }).
86+
* `matcher is a function that takes a permission string and returns true if the implicator should be applied.
87+
* `checker` is a function that takes an actor and a permission string and returns true if the permission is implied.
88+
* The actor and permission are passed to checker({ actor, permission }) as an object.
89+
*/
7890
class PermissionImplicator {
7991
static create ({ id, matcher, checker }) {
8092
return new PermissionImplicator({ id, matcher, checker });
@@ -108,12 +120,17 @@ class PermissionImplicator {
108120

109121

110122
/**
111-
* The PermissionExploder class is responsible for expanding permissions into a set of more granular permissions.
112-
* It uses a matcher function to determine if a permission should be exploded and an exploder function to perform the expansion.
113-
* This class is part of the permission management system, allowing for dynamic and complex permission structures.
114-
*
115-
* @class PermissionExploder
116-
*/
123+
* Permission exploders are used to map any permission to a list of permissions
124+
* which are considered to imply the specified permission.
125+
*
126+
* It uses a matcher function to determine if a permission should be exploded
127+
* and an exploder function to perform the expansion.
128+
*
129+
* The exploder is constructed using the static method PermissionExploder.create({ matcher, explode }).
130+
* The `matcher` is a function that takes a permission string and returns true if the exploder should be applied.
131+
* The `explode` is a function that takes an actor and a permission string and returns a list of implied permissions.
132+
* The actor and permission are passed to explode({ actor, permission }) as an object.
133+
*/
117134
class PermissionExploder {
118135
static create ({ id, matcher, exploder }) {
119136
return new PermissionExploder({ id, matcher, exploder });
@@ -952,14 +969,26 @@ class PermissionService extends BaseService {
952969
}
953970

954971

955-
register_rewriter (translator) {
956-
if ( ! (translator instanceof PermissionRewriter) ) {
957-
throw new Error('translator must be a PermissionRewriter');
972+
/**
973+
* Register a permission rewriter. For details see the documentation on the
974+
* PermissionRewriter class.
975+
*
976+
* @param {PermissionRewriter} rewriter - The permission rewriter to register
977+
*/
978+
register_rewriter (rewriter) {
979+
if ( ! (rewriter instanceof PermissionRewriter) ) {
980+
throw new Error('rewriter must be a PermissionRewriter');
958981
}
959982

960-
this._permission_rewriters.push(translator);
983+
this._permission_rewriters.push(rewriter);
961984
}
962985

986+
/**
987+
* Register a permission implicator. For details see the documentation on the
988+
* PermissionImplicator class.
989+
*
990+
* @param {PermissionImplicator} implicator - The permission implicator to register
991+
*/
963992
register_implicator (implicator) {
964993
if ( ! (implicator instanceof PermissionImplicator) ) {
965994
throw new Error('implicator must be a PermissionImplicator');
@@ -968,6 +997,12 @@ class PermissionService extends BaseService {
968997
this._permission_implicators.push(implicator);
969998
}
970999

1000+
/**
1001+
* Register a permission exploder. For details see the documentation on the
1002+
* PermissionExploder class.
1003+
*
1004+
* @param {PermissionExploder} exploder - The permission exploder to register
1005+
*/
9711006
register_exploder (exploder) {
9721007
if ( ! (exploder instanceof PermissionExploder) ) {
9731008
throw new Error('exploder must be a PermissionExploder');

src/backend/src/services/auth/VirtualGroupService.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ class VirtualGroupService extends BaseService {
2121
this.membership_implicators_ = [];
2222
}
2323

24+
/**
25+
* Registers a function that reports one or more groups that an actor
26+
* should be considered a member of.
27+
*
28+
* @note this only applies to virtual groups, not persistent groups.
29+
*
30+
* @param {*} implicator
31+
*/
2432
register_membership_implicator (implicator) {
2533
this.membership_implicators_.push(implicator);
2634
}

src/backend/src/unstructured/permission-scan-lib.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
const remove_paths_through_user = ({ reading, user }) => {
1111
const no_cycle_reading = [];
1212

13-
for ( let i = 0 ; i < reading.length ; i++ ) {
14-
const node = reading[i];
15-
16-
console.log('checking node...', node);
17-
13+
for ( const node of reading ) {
1814
if ( node.$ === 'path' ) {
1915
if (
2016
node.issuer_username === user.username
@@ -33,14 +29,11 @@ const remove_paths_through_user = ({ reading, user }) => {
3329
no_cycle_reading.push(node);
3430
}
3531

36-
console.log('\x1B[36;1m ====', reading.length - no_cycle_reading.length, 'nodes filtered out ====\x1B[0m');
37-
3832
return no_cycle_reading;
3933
};
4034

4135
const reading_has_terminal = ({ reading }) => {
42-
for ( let i = 0 ; i < reading.length ; i++ ) {
43-
const node = reading[i];
36+
for ( const node of reading ) {
4437
if ( node.has_terminal ) {
4538
return true;
4639
}

src/backend/src/unstructured/permission-scanners.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,26 @@ const { reading_has_terminal } = require("./permission-scan-lib");
1414
"Ctrl+K, Ctrl+J" or "⌘K, ⌘J";
1515
*/
1616

17+
/**
18+
* Permission Scanners
19+
* @usedBy scan-permission.js
20+
*
21+
* These are all the different ways an entity (user or app) can have a permission.
22+
* This list of scanners is iterated over and invoked by scan-permission.js.
23+
*
24+
* Each `scan` function is passed a sequence scope. The instance attached to the
25+
* sequence scope is PermissionService itself, so any `a.iget('something')` is
26+
* accessing the member 'something' of the PermissionService instance.
27+
*/
1728
const PERMISSION_SCANNERS = [
1829
{
1930
name: 'implied',
31+
documentation: `
32+
Scans for permissions that are implied by "permission implicators".
33+
34+
Permission implicators are added by other services via
35+
PermissionService's \`register_implicator\` method.
36+
`,
2037
async scan (a) {
2138
const reading = a.get('reading');
2239
const { actor, permission_options } = a.values();
@@ -46,6 +63,9 @@ const PERMISSION_SCANNERS = [
4663
},
4764
{
4865
name: 'user-user',
66+
documentation: `
67+
User-to-User permissions are permission granted form one user to another.
68+
`,
4969
async scan (a) {
5070
const { reading, actor, permission_options, state } = a.values();
5171
if ( !(actor.type instanceof UserActorType) ) {
@@ -96,7 +116,6 @@ const PERMISSION_SCANNERS = [
96116

97117
if ( should_continue ) continue;
98118

99-
// const issuer_perm = await this.check(issuer_actor, row.permission);
100119
const issuer_reading = await a.icall(
101120
'scan', issuer_actor, row.permission, undefined, state);
102121

@@ -117,6 +136,13 @@ const PERMISSION_SCANNERS = [
117136
},
118137
{
119138
name: 'hc-user-group-user',
139+
documentation: `
140+
These are user-to-group permissions that are defined in the
141+
hardcoded_user_group_permissions section of "hardcoded-permissions.js".
142+
143+
These are typically used to grant permissions from the system user to
144+
the default groups: "admin", "user", and "temp".
145+
`,
120146
async scan (a) {
121147
const { reading, actor, permission_options } = a.values();
122148
if ( !(actor.type instanceof UserActorType) ) {
@@ -167,6 +193,11 @@ const PERMISSION_SCANNERS = [
167193
},
168194
{
169195
name: 'user-group-user',
196+
documentation: `
197+
This scans for permissions that are granted to the user because a
198+
group they are a member of was granted this permission by another
199+
user.
200+
`,
170201
async scan (a) {
171202
const { reading, actor, permission_options } = a.values();
172203
if ( !(actor.type instanceof UserActorType) ) {
@@ -223,6 +254,16 @@ const PERMISSION_SCANNERS = [
223254
},
224255
{
225256
name: 'user-virtual-group-user',
257+
documentation: `
258+
These are groups with computed membership. Permissions are not granted
259+
to these groups; instead the groups are defined with a list of
260+
permissions that are granted to the group members.
261+
262+
Services can define "virtual groups" via the "virtual-group" service.
263+
Services can also register membership implicators for virtual groups
264+
which will compute on the fly whether or not an actor should be
265+
considered a member of the group.
266+
`,
226267
async scan (a) {
227268
const svc_virtualGroup = await a.iget('services').get('virtual-group');
228269
const { reading, actor, permission_options } = a.values();
@@ -248,6 +289,10 @@ const PERMISSION_SCANNERS = [
248289
},
249290
{
250291
name: 'user-app',
292+
documentation: `
293+
If the actor is an app, this scans for permissions granted to the app
294+
because the user has the permission and granted it to the app.
295+
`,
251296
async scan (a) {
252297
const { reading, actor, permission_options } = a.values();
253298
if ( !(actor.type instanceof AppUnderUserActorType) ) {

0 commit comments

Comments
 (0)