mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor(core): Store projectId on additionalData independent of dataStore context (no-changelog) (#19093)
This commit is contained in:
@@ -10,7 +10,6 @@ import type { INodePropertyOptions, NodeParameterValueType } from 'n8n-workflow'
|
|||||||
|
|
||||||
import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service';
|
import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service';
|
||||||
import { getBase } from '@/workflow-execute-additional-data';
|
import { getBase } from '@/workflow-execute-additional-data';
|
||||||
import { userHasScopes } from '@/permissions.ee/check-access';
|
|
||||||
|
|
||||||
@RestController('/dynamic-node-parameters')
|
@RestController('/dynamic-node-parameters')
|
||||||
export class DynamicNodeParametersController {
|
export class DynamicNodeParametersController {
|
||||||
@@ -22,6 +21,8 @@ export class DynamicNodeParametersController {
|
|||||||
_res: Response,
|
_res: Response,
|
||||||
@Body payload: OptionsRequestDto,
|
@Body payload: OptionsRequestDto,
|
||||||
): Promise<INodePropertyOptions[]> {
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
await this.service.scrubInaccessibleProjectId(req.user, payload);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
credentials,
|
credentials,
|
||||||
currentNodeParameters,
|
currentNodeParameters,
|
||||||
@@ -33,17 +34,7 @@ export class DynamicNodeParametersController {
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
additionalData.dataTableProjectId = projectId;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodName) {
|
if (methodName) {
|
||||||
return await this.service.getOptionsViaMethodName(
|
return await this.service.getOptionsViaMethodName(
|
||||||
@@ -75,6 +66,8 @@ export class DynamicNodeParametersController {
|
|||||||
_res: Response,
|
_res: Response,
|
||||||
@Body payload: ResourceLocatorRequestDto,
|
@Body payload: ResourceLocatorRequestDto,
|
||||||
) {
|
) {
|
||||||
|
await this.service.scrubInaccessibleProjectId(req.user, payload);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
path,
|
path,
|
||||||
methodName,
|
methodName,
|
||||||
@@ -87,17 +80,7 @@ export class DynamicNodeParametersController {
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
additionalData.dataTableProjectId = projectId;
|
||||||
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(
|
return await this.service.getResourceLocatorResults(
|
||||||
methodName,
|
methodName,
|
||||||
@@ -117,21 +100,13 @@ export class DynamicNodeParametersController {
|
|||||||
_res: Response,
|
_res: Response,
|
||||||
@Body payload: ResourceMapperFieldsRequestDto,
|
@Body payload: ResourceMapperFieldsRequestDto,
|
||||||
) {
|
) {
|
||||||
|
await this.service.scrubInaccessibleProjectId(req.user, payload);
|
||||||
|
|
||||||
const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion, projectId } =
|
const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion, projectId } =
|
||||||
payload;
|
payload;
|
||||||
|
|
||||||
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
const additionalData = await getBase(req.user.id, currentNodeParameters);
|
||||||
|
additionalData.dataTableProjectId = projectId;
|
||||||
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.getResourceMappingFields(
|
return await this.service.getResourceMappingFields(
|
||||||
methodName,
|
methodName,
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ export class DataStoreProxyService implements DataStoreProxyProvider {
|
|||||||
async getDataStoreAggregateProxy(
|
async getDataStoreAggregateProxy(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
dataStoreProjectId?: string,
|
projectId?: string,
|
||||||
): Promise<IDataStoreProjectAggregateService> {
|
): Promise<IDataStoreProjectAggregateService> {
|
||||||
this.validateRequest(node);
|
this.validateRequest(node);
|
||||||
const projectId = dataStoreProjectId ?? (await this.getProjectId(workflow));
|
projectId = projectId ?? (await this.getProjectId(workflow));
|
||||||
|
|
||||||
return this.makeAggregateOperations(projectId);
|
return this.makeAggregateOperations(projectId);
|
||||||
}
|
}
|
||||||
@@ -61,10 +61,10 @@ export class DataStoreProxyService implements DataStoreProxyProvider {
|
|||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
dataStoreId: string,
|
dataStoreId: string,
|
||||||
dataStoreProjectId?: string,
|
projectId?: string,
|
||||||
): Promise<IDataStoreProjectService> {
|
): Promise<IDataStoreProjectService> {
|
||||||
this.validateRequest(node);
|
this.validateRequest(node);
|
||||||
const projectId = dataStoreProjectId ?? (await this.getProjectId(workflow));
|
projectId = projectId ?? (await this.getProjectId(workflow));
|
||||||
|
|
||||||
return this.makeDataStoreOperations(projectId, dataStoreId);
|
return this.makeDataStoreOperations(projectId, dataStoreId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ import { Workflow, UnexpectedError } from 'n8n-workflow';
|
|||||||
import { NodeTypes } from '@/node-types';
|
import { NodeTypes } from '@/node-types';
|
||||||
|
|
||||||
import { WorkflowLoaderService } from './workflow-loader.service';
|
import { WorkflowLoaderService } from './workflow-loader.service';
|
||||||
|
import { User } from '@n8n/db';
|
||||||
|
import { userHasScopes } from '@/permissions.ee/check-access';
|
||||||
|
import { Logger } from '@n8n/backend-common';
|
||||||
|
|
||||||
type LocalResourceMappingMethod = (
|
type LocalResourceMappingMethod = (
|
||||||
this: ILocalLoadOptionsFunctions,
|
this: ILocalLoadOptionsFunctions,
|
||||||
@@ -52,10 +55,29 @@ type NodeMethod =
|
|||||||
@Service()
|
@Service()
|
||||||
export class DynamicNodeParametersService {
|
export class DynamicNodeParametersService {
|
||||||
constructor(
|
constructor(
|
||||||
|
private logger: Logger,
|
||||||
private nodeTypes: NodeTypes,
|
private nodeTypes: NodeTypes,
|
||||||
private workflowLoaderService: WorkflowLoaderService,
|
private workflowLoaderService: WorkflowLoaderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async scrubInaccessibleProjectId(user: User, payload: { projectId?: string }) {
|
||||||
|
// We want to avoid relying on generic project:read permissions to enable
|
||||||
|
// a future with fine-grained permission control dependent on the respective resource
|
||||||
|
// For now we use the dataStore:listProject scope as this is the existing consumer of
|
||||||
|
// the project id
|
||||||
|
if (
|
||||||
|
payload.projectId &&
|
||||||
|
!(await userHasScopes(user, ['dataStore:listProject'], false, {
|
||||||
|
projectId: payload.projectId,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Scrubbed inaccessible projectId ${payload.projectId} from DynamicNodeParameters request`,
|
||||||
|
);
|
||||||
|
payload.projectId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the available options via a predefined method */
|
/** Returns the available options via a predefined method */
|
||||||
async getOptionsViaMethodName(
|
async getOptionsViaMethodName(
|
||||||
methodName: string,
|
methodName: string,
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ declare module 'n8n-workflow' {
|
|||||||
hooks?: ExecutionLifecycleHooks;
|
hooks?: ExecutionLifecycleHooks;
|
||||||
externalSecretsProxy: ExternalSecretsProxy;
|
externalSecretsProxy: ExternalSecretsProxy;
|
||||||
'data-table'?: { dataStoreProxyProvider: DataStoreProxyProvider };
|
'data-table'?: { dataStoreProxyProvider: DataStoreProxyProvider };
|
||||||
dataStoreProjectId?: string;
|
// Project ID is currently only added on the additionalData if the user
|
||||||
|
// has data table listing permission for that project. We should consider
|
||||||
|
// that only data tables belonging to their respective projects are shown.
|
||||||
|
dataTableProjectId?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ export function getDataStoreHelperFunctions(
|
|||||||
await dataStoreProxyProvider.getDataStoreAggregateProxy(
|
await dataStoreProxyProvider.getDataStoreAggregateProxy(
|
||||||
workflow,
|
workflow,
|
||||||
node,
|
node,
|
||||||
additionalData.dataStoreProjectId,
|
additionalData.dataTableProjectId,
|
||||||
),
|
),
|
||||||
getDataStoreProxy: async (dataStoreId: string) =>
|
getDataStoreProxy: async (dataStoreId: string) =>
|
||||||
await dataStoreProxyProvider.getDataStoreProxy(
|
await dataStoreProxyProvider.getDataStoreProxy(
|
||||||
workflow,
|
workflow,
|
||||||
node,
|
node,
|
||||||
dataStoreId,
|
dataStoreId,
|
||||||
additionalData.dataStoreProjectId,
|
additionalData.dataTableProjectId,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -924,13 +924,13 @@ export type DataStoreProxyProvider = {
|
|||||||
getDataStoreAggregateProxy(
|
getDataStoreAggregateProxy(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
dataStoreProjectId?: string,
|
projectId?: string,
|
||||||
): Promise<IDataStoreProjectAggregateService>;
|
): Promise<IDataStoreProjectAggregateService>;
|
||||||
getDataStoreProxy(
|
getDataStoreProxy(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
dataStoreId: string,
|
dataStoreId: string,
|
||||||
dataStoreProjectId?: string,
|
projectId?: string,
|
||||||
): Promise<IDataStoreProjectService>;
|
): Promise<IDataStoreProjectService>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user