diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js index 38e5b88c..27b4bb4b 100644 --- a/src/backend/src/services/auth/PermissionService.js +++ b/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(); diff --git a/src/backend/src/unstructured/permission-scan-lib.js b/src/backend/src/unstructured/permission-scan-lib.js new file mode 100644 index 00000000..6a266b1d --- /dev/null +++ b/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, +}; diff --git a/src/backend/src/unstructured/permission-scanners.js b/src/backend/src/unstructured/permission-scanners.js index 4dc4ee88..06adc8f8 100644 --- a/src/backend/src/unstructured/permission-scanners.js +++ b/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,