diff --git a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts index ac8e8df274..ca2236566a 100644 --- a/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts +++ b/packages/@n8n/api-types/src/dto/dynamic-node-parameters/resource-locator-request.dto.ts @@ -6,4 +6,5 @@ export class ResourceLocatorRequestDto extends BaseDynamicParametersRequestDto.e methodName: z.string(), filter: z.string().optional(), paginationToken: z.string().optional(), + projectId: z.string().optional(), }) {} diff --git a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts index e3246bdb07..6dfaa512bc 100644 --- a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts +++ b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts @@ -10,6 +10,7 @@ import type { INodePropertyOptions, NodeParameterValueType } from 'n8n-workflow' import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service'; import { getBase } from '@/workflow-execute-additional-data'; +import { userHasScopes } from '@/permissions.ee/check-access'; @RestController('/dynamic-node-parameters') export class DynamicNodeParametersController { @@ -70,10 +71,22 @@ export class DynamicNodeParametersController { credentials, currentNodeParameters, nodeTypeAndVersion, + projectId, } = payload; const additionalData = await getBase(req.user.id, currentNodeParameters); + if (projectId) { + if (await userHasScopes(req.user, ['dataStore:listProject'], false, { projectId })) { + // Project ID is currently only added on the additionalData if the user + // has data store listing permission for that project. We should consider + // turning this into a more general check, but as of now data stores are + // the only nodes with project specific resource locators where we want to ensure + // that only data stores belonging to their respective projects are shown. + additionalData.dataStoreProjectId = projectId; + } + } + return await this.service.getResourceLocatorResults( methodName, path, diff --git a/packages/cli/src/modules/data-table/data-store-proxy.service.ts b/packages/cli/src/modules/data-table/data-store-proxy.service.ts index 7a05ef4a78..b5e9afbaa7 100644 --- a/packages/cli/src/modules/data-table/data-store-proxy.service.ts +++ b/packages/cli/src/modules/data-table/data-store-proxy.service.ts @@ -47,9 +47,10 @@ export class DataStoreProxyService implements DataStoreProxyProvider { async getDataStoreAggregateProxy( workflow: Workflow, node: INode, + dataStoreProjectId?: string, ): Promise { this.validateRequest(node); - const projectId = await this.getProjectId(workflow); + const projectId = dataStoreProjectId ?? (await this.getProjectId(workflow)); return this.makeAggregateOperations(projectId); } @@ -58,9 +59,10 @@ export class DataStoreProxyService implements DataStoreProxyProvider { workflow: Workflow, node: INode, dataStoreId: string, + dataStoreProjectId?: string, ): Promise { this.validateRequest(node); - const projectId = await this.getProjectId(workflow); + const projectId = dataStoreProjectId ?? (await this.getProjectId(workflow)); return this.makeDataStoreOperations(projectId, dataStoreId); } diff --git a/packages/core/src/execution-engine/index.ts b/packages/core/src/execution-engine/index.ts index 1b944b60fc..ed82afbac5 100644 --- a/packages/core/src/execution-engine/index.ts +++ b/packages/core/src/execution-engine/index.ts @@ -8,6 +8,7 @@ declare module 'n8n-workflow' { hooks?: ExecutionLifecycleHooks; externalSecretsProxy: ExternalSecretsProxy; dataStoreProxyProvider?: DataStoreProxyProvider; + dataStoreProjectId?: string; } } diff --git a/packages/core/src/execution-engine/node-execution-context/utils/data-store-helper-functions.ts b/packages/core/src/execution-engine/node-execution-context/utils/data-store-helper-functions.ts index adb09de992..fc8e027b18 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/data-store-helper-functions.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/data-store-helper-functions.ts @@ -14,8 +14,17 @@ export function getDataStoreHelperFunctions( const dataStoreProxyProvider = additionalData.dataStoreProxyProvider; return { getDataStoreAggregateProxy: async () => - await dataStoreProxyProvider.getDataStoreAggregateProxy(workflow, node), + await dataStoreProxyProvider.getDataStoreAggregateProxy( + workflow, + node, + additionalData.dataStoreProjectId, + ), getDataStoreProxy: async (dataStoreId: string) => - await dataStoreProxyProvider.getDataStoreProxy(workflow, node, dataStoreId), + await dataStoreProxyProvider.getDataStoreProxy( + workflow, + node, + dataStoreId, + additionalData.dataStoreProjectId, + ), }; } diff --git a/packages/frontend/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/frontend/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 1da3c0c4ef..da91187df8 100644 --- a/packages/frontend/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/frontend/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -55,6 +55,7 @@ import { } from '../../utils/fromAIOverrideUtils'; import { N8nNotice } from '@n8n/design-system'; import { completeExpressionSyntax } from '@/utils/expressions'; +import { useProjectsStore } from '@/stores/projects.store'; /** * Regular expression to check if the error message contains credential-related phrases. @@ -144,6 +145,7 @@ const ndvStore = useNDVStore(); const rootStore = useRootStore(); const uiStore = useUIStore(); const workflowsStore = useWorkflowsStore(); +const projectsStore = useProjectsStore(); const appName = computed(() => { if (!props.node) { @@ -270,6 +272,7 @@ const currentRequestParams = computed(() => { parameters: props.node?.parameters ?? {}, credentials: props.node?.credentials ?? {}, filter: searchFilter.value, + projectId: projectsStore.currentProjectId, }; }); @@ -722,6 +725,7 @@ async function loadResources() { methodName: loadOptionsMethod, currentNodeParameters: resolvedNodeParameters, credentials: props.node.credentials, + projectId: projectsStore.currentProjectId, }; if (params.filter) { diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index eb920602e9..e0b3d73108 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -924,11 +924,13 @@ export type DataStoreProxyProvider = { getDataStoreAggregateProxy( workflow: Workflow, node: INode, + dataStoreProjectId?: string, ): Promise; getDataStoreProxy( workflow: Workflow, node: INode, dataStoreId: string, + dataStoreProjectId?: string, ): Promise; };