ソースを参照

fix: prevent permission cycles

KernelDeimos 6 ヶ月 前
コミット
e0128aa88c

+ 10 - 1
src/backend/src/services/auth/PermissionService.js

@@ -23,6 +23,7 @@ const {
 
 const { get_user, get_app } = require("../../helpers");
 const { AssignableMethodsFeature } = require("../../traits/AssignableMethodsFeature");
+const { remove_paths_through_user } = require("../../unstructured/permission-scan-lib");
 const { Context } = require("../../util/context");
 const { get_a_letter, cylog } = require("../../util/debugutil");
 const BaseService = require("../BaseService");
@@ -180,6 +181,7 @@ class PermissionUtil {
                 });
             }
             if ( finding.$ === 'path' ) {
+                if ( finding.has_terminal === false ) continue;
                 const new_extras = ( finding.data ) ? [
                     finding.data,
                     ...extras,
@@ -223,8 +225,14 @@ class PermissionService extends BaseService {
         return options.length > 0;
     }
 
-    async scan (actor, permission_options) {
+    async scan (actor, permission_options, _reserved, state) {
         const reading = [];
+
+        if ( ! state ) {
+            state = {
+                anti_cycle_actors: [actor],
+            };
+        }
         
         if ( ! Array.isArray(permission_options) ) {
             permission_options = [permission_options];
@@ -240,6 +248,7 @@ class PermissionService extends BaseService {
                 actor,
                 permission_options,
                 reading,
+                state,
             });
         const end_ts = Date.now();
         

+ 58 - 0
src/backend/src/unstructured/permission-scan-lib.js

@@ -0,0 +1,58 @@
+/**
+ * Filters a permission reading so that it does not contain paths through the
+ * specified user. This operation is performed recursively on all paths in the
+ * reading.
+ * 
+ * This does not prevent all possible cycles. To prevent all cycles, this filter
+ * must by applied on each reading for a permission holder, specifying the
+ * permission issuer as the user to filter out.
+ */
+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);
+
+        if ( node.$ === 'path' ) {
+            if (
+                node.issuer_username === user.username
+            ) {
+                console.log('filtered out one');
+                // process.exit(0);
+                continue;
+            }
+
+            node.reading = remove_paths_through_user({
+                reading: node.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];
+        if ( node.has_terminal ) {
+            return true;
+        }
+        if ( node.$ === 'option' ) {
+            return true;
+        }
+    }
+
+    return false;
+};
+
+module.exports = {
+    remove_paths_through_user,
+    reading_has_terminal,
+};

+ 31 - 2
src/backend/src/unstructured/permission-scanners.js

@@ -5,6 +5,7 @@ const {
 } = require("../data/hardcoded-permissions");
 const { get_user } = require("../helpers");
 const { Actor, UserActorType, AppUnderUserActorType } = require("../services/auth/Actor");
+const { reading_has_terminal } = require("./permission-scan-lib");
 
 /*
     OPTIMAL FOLD LEVEL: 3
@@ -46,7 +47,7 @@ const PERMISSION_SCANNERS = [
     {
         name: 'user-user',
         async scan (a) {
-            const { reading, actor, permission_options } = a.values();
+            const { reading, actor, permission_options, state } = a.values();
             if ( !(actor.type instanceof UserActorType)  ) {
                 return;
             }
@@ -85,11 +86,26 @@ const PERMISSION_SCANNERS = [
                     }),
                 });
 
+                let should_continue = false;
+                for ( const seen_actor of state.anti_cycle_actors ) {
+                    if ( seen_actor.type.user.id === issuer_actor.type.user.id ) {
+                        should_continue = true;
+                        break;
+                    }
+                }
+
+                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);
+                const issuer_reading = await a.icall(
+                    'scan', issuer_actor, row.permission, undefined, state);
+
+                const has_terminal = reading_has_terminal({ reading: issuer_reading });
+
                 reading.push({
                     $: 'path',
                     via: 'user',
+                    has_terminal,
                     permission: row.permission,
                     data: row.extra,
                     holder_username: actor.type.user.username,
@@ -134,9 +150,13 @@ const PERMISSION_SCANNERS = [
                         if ( ! issuer_group.hasOwnProperty(permission) ) continue;
                         const issuer_reading =
                             await a.icall('scan', issuer_actor, permission)
+
+                        const has_terminal = reading_has_terminal({ reading: issuer_reading });
+
                         reading.push({
                             $: 'path',
                             via: 'hc-user-group',
+                            has_terminal,
                             permission,
                             data: issuer_group[permission],
                             holder_username: actor.type.user.username,
@@ -188,9 +208,12 @@ const PERMISSION_SCANNERS = [
 
                 const issuer_reading = await a.icall('scan', issuer_actor, row.permission);
 
+                const has_terminal = reading_has_terminal({ reading: issuer_reading });
+
                 reading.push({
                     $: 'path',
                     via: 'user-group',
+                    has_terminal,
                     // issuer: issuer_actor,
                     permission: row.permission,
                     data: row.extra,
@@ -240,6 +263,8 @@ const PERMISSION_SCANNERS = [
             
             const issuer_actor = actor.get_related_actor(UserActorType);
             const issuer_reading = await a.icall('scan', issuer_actor, permission_options);
+
+            const has_terminal = reading_has_terminal({ reading: issuer_reading });
             
             for ( const permission of permission_options ) {
                 {
@@ -249,6 +274,7 @@ const PERMISSION_SCANNERS = [
                         reading.push({
                             $: 'path',
                             permission,
+                            has_terminal,
                             source: 'user-app-implied',
                             by: 'user-app-hc-1',
                             data: implied,
@@ -267,6 +293,7 @@ const PERMISSION_SCANNERS = [
                         reading.push({
                             $: 'path',
                             permission,
+                            has_terminal,
                             source: 'user-app-implied',
                             by: 'user-app-hc-2',
                             data: implicit_permissions[permission],
@@ -301,10 +328,12 @@ const PERMISSION_SCANNERS = [
                 })();
                 const issuer_actor = actor.get_related_actor(UserActorType);
                 const issuer_reading = await a.icall('scan', issuer_actor, row.permission);
+                const has_terminal = reading_has_terminal({ reading: issuer_reading });
                 reading.push({
                     $: 'path',
                     via: 'user-app',
                     permission: row.permission,
+                    has_terminal,
                     data: row.extra,
                     issuer_username: actor.type.user.username,
                     reading: issuer_reading,