2023-01-27 05:56:56 -08:00
import type { INode , Workflow } from 'n8n-workflow' ;
2022-12-21 07:42:07 -08:00
import {
NodeOperationError ,
SubworkflowOperationError ,
WorkflowOperationError ,
} from 'n8n-workflow' ;
2023-01-27 05:56:56 -08:00
import type { FindOptionsWhere } from 'typeorm' ;
import { In } from 'typeorm' ;
2022-11-11 02:14:45 -08:00
import * as Db from '@/Db' ;
2022-11-22 05:24:29 -08:00
import config from '@/config' ;
import type { SharedCredentials } from '@db/entities/SharedCredentials' ;
2023-08-02 23:58:36 -07:00
import { isSharingEnabled } from './UserManagementHelper' ;
2022-12-21 07:42:07 -08:00
import { WorkflowsService } from '@/workflows/workflows.services' ;
2023-08-22 06:58:05 -07:00
import { UserService } from '@/services/user.service' ;
2023-07-31 02:37:09 -07:00
import { OwnershipService } from '@/services/ownership.service' ;
import Container from 'typedi' ;
2023-08-02 23:58:36 -07:00
import { RoleService } from '@/services/role.service' ;
2022-11-11 02:14:45 -08:00
export class PermissionChecker {
/ * *
* Check if a user is permitted to execute a workflow .
* /
static async check ( workflow : Workflow , userId : string ) {
// allow if no nodes in this workflow use creds
const credIdsToNodes = PermissionChecker . mapCredIdsToNodes ( workflow ) ;
const workflowCredIds = Object . keys ( credIdsToNodes ) ;
if ( workflowCredIds . length === 0 ) return ;
// allow if requesting user is instance owner
2023-01-13 09:12:22 -08:00
const user = await Db . collections . User . findOneOrFail ( {
where : { id : userId } ,
2022-11-11 02:14:45 -08:00
relations : [ 'globalRole' ] ,
} ) ;
if ( user . globalRole . name === 'owner' ) return ;
// allow if all creds used in this workflow are a subset of
// all creds accessible to users who have access to this workflow
2022-11-22 05:24:29 -08:00
let workflowUserIds = [ userId ] ;
2022-11-21 23:37:52 -08:00
2022-12-21 07:42:07 -08:00
if ( workflow . id && isSharingEnabled ( ) ) {
2022-11-21 23:37:52 -08:00
const workflowSharings = await Db . collections . SharedWorkflow . find ( {
relations : [ 'workflow' ] ,
2023-01-02 08:42:32 -08:00
where : { workflowId : workflow.id } ,
2023-05-23 00:40:38 -07:00
select : [ 'userId' ] ,
2022-11-21 23:37:52 -08:00
} ) ;
workflowUserIds = workflowSharings . map ( ( s ) = > s . userId ) ;
}
2022-11-11 02:14:45 -08:00
2023-01-13 09:12:22 -08:00
const credentialsWhere : FindOptionsWhere < SharedCredentials > = { userId : In ( workflowUserIds ) } ;
2022-11-22 05:24:29 -08:00
2022-12-21 07:42:07 -08:00
if ( ! isSharingEnabled ( ) ) {
2023-08-02 23:58:36 -07:00
const role = await Container . get ( RoleService ) . findCredentialOwnerRole ( ) ;
2022-11-22 05:24:29 -08:00
// If credential sharing is not enabled, get only credentials owned by this user
2023-08-02 23:58:36 -07:00
credentialsWhere . roleId = role . id ;
2022-11-22 05:24:29 -08:00
}
2022-12-27 07:09:43 -08:00
const credentialSharings = await Db . collections . SharedCredentials . find ( {
where : credentialsWhere ,
} ) ;
2022-11-11 02:14:45 -08:00
2023-01-02 08:42:32 -08:00
const accessibleCredIds = credentialSharings . map ( ( s ) = > s . credentialsId ) ;
2022-11-11 02:14:45 -08:00
const inaccessibleCredIds = workflowCredIds . filter ( ( id ) = > ! accessibleCredIds . includes ( id ) ) ;
if ( inaccessibleCredIds . length === 0 ) return ;
// if disallowed, flag only first node using first inaccessible cred
const nodeToFlag = credIdsToNodes [ inaccessibleCredIds [ 0 ] ] [ 0 ] ;
throw new NodeOperationError ( nodeToFlag , 'Node has no access to credential' , {
description : 'Please recreate the credential or ask its owner to share it with you.' ,
2023-05-15 06:54:48 -07:00
severity : 'warning' ,
2022-11-11 02:14:45 -08:00
} ) ;
}
2022-12-21 07:42:07 -08:00
static async checkSubworkflowExecutePolicy (
subworkflow : Workflow ,
userId : string ,
parentWorkflowId? : string ,
) {
/ * *
* Important considerations : both the current workflow and the parent can have empty IDs .
* This happens when a user is executing an unsaved workflow manually running a workflow
* loaded from a file or code , for instance .
* This is an important topic to keep in mind for all security checks
* /
if ( ! subworkflow . id ) {
// It's a workflow from code and not loaded from DB
// No checks are necessary since it doesn't have any sort of settings
return ;
}
let policy =
subworkflow . settings ? . callerPolicy ? ? config . getEnv ( 'workflows.callerPolicyDefaultOption' ) ;
if ( ! isSharingEnabled ( ) ) {
// Community version allows only same owner workflows
policy = 'workflowsFromSameOwner' ;
}
2023-07-31 02:37:09 -07:00
const subworkflowOwner = await Container . get ( OwnershipService ) . getWorkflowOwnerCached (
subworkflow . id ,
) ;
2022-12-21 07:42:07 -08:00
const errorToThrow = new SubworkflowOperationError (
` Target workflow ID ${ subworkflow . id ? ? '' } may not be called ` ,
subworkflowOwner . id === userId
? 'Change the settings of the sub-workflow so it can be called by this one.'
: ` ${ subworkflowOwner . firstName } ( ${ subworkflowOwner . email } ) can make this change. You may need to tell them the ID of this workflow, which is ${ subworkflow . id } ` ,
) ;
if ( policy === 'none' ) {
throw errorToThrow ;
}
if ( policy === 'workflowsFromAList' ) {
if ( parentWorkflowId === undefined ) {
throw errorToThrow ;
}
2023-03-24 05:11:48 -07:00
const allowedCallerIds = subworkflow . settings . callerIds
2022-12-21 07:42:07 -08:00
? . split ( ',' )
. map ( ( id ) = > id . trim ( ) )
. filter ( ( id ) = > id !== '' ) ;
if ( ! allowedCallerIds ? . includes ( parentWorkflowId ) ) {
throw errorToThrow ;
}
}
if ( policy === 'workflowsFromSameOwner' ) {
2023-08-22 06:58:05 -07:00
const user = await Container . get ( UserService ) . findOne ( { where : { id : userId } } ) ;
2022-12-21 07:42:07 -08:00
if ( ! user ) {
throw new WorkflowOperationError (
'Fatal error: user not found. Please contact the system administrator.' ,
) ;
}
const sharing = await WorkflowsService . getSharing ( user , subworkflow . id , [ 'role' , 'user' ] ) ;
if ( ! sharing || sharing . role . name !== 'owner' ) {
throw errorToThrow ;
}
}
}
2022-11-11 02:14:45 -08:00
private static mapCredIdsToNodes ( workflow : Workflow ) {
return Object . values ( workflow . nodes ) . reduce < { [ credentialId : string ] : INode [ ] } > (
( map , node ) = > {
if ( node . disabled || ! node . credentials ) return map ;
Object . values ( node . credentials ) . forEach ( ( cred ) = > {
if ( ! cred . id ) {
throw new NodeOperationError ( node , 'Node uses invalid credential' , {
description : 'Please recreate the credential.' ,
} ) ;
}
map [ cred . id ] = map [ cred . id ] ? [ . . . map [ cred . id ] , node ] : [ node ] ;
} ) ;
return map ;
} ,
{ } ,
) ;
}
}