Skip to content

Commit f2963e0

Browse files
Add extra validations for endpoints that do not use principal to get the resources (#2112)
Fixes OPS-3895. --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 9eb2644 commit f2963e0

11 files changed

Lines changed: 193 additions & 82 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
ApplicationError,
3+
ErrorCode,
4+
Flow,
5+
FlowVersion,
6+
PopulatedFlow,
7+
} from '@openops/shared';
8+
import { flowService } from '../flow/flow.service';
9+
10+
export async function assertFlowVersionBelongsToProject(
11+
flowVersion: FlowVersion,
12+
projectId: string,
13+
): Promise<void> {
14+
const flow = await flowService.getOne({
15+
id: flowVersion.flowId,
16+
projectId,
17+
});
18+
19+
if (flow == null) {
20+
throw new ApplicationError({
21+
code: ErrorCode.AUTHORIZATION,
22+
params: {
23+
message: 'The flow and version are not associated with the project',
24+
},
25+
});
26+
}
27+
}
28+
29+
export function assertFlowBelongsToProject(
30+
flow: PopulatedFlow | Flow,
31+
projectId: string,
32+
): void {
33+
if (flow.projectId !== projectId) {
34+
throw new ApplicationError({
35+
code: ErrorCode.AUTHORIZATION,
36+
params: {
37+
message: 'The flow is not associated with the project',
38+
},
39+
});
40+
}
41+
}

packages/server/api/src/app/flows/common/flow-version-validation.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

packages/server/api/src/app/flows/flow-run/flow-run-controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const flowRunController: FastifyPluginCallbackTypebox = (
8383
const flowRun = await flowRunService.retry({
8484
flowRunId: req.params.id,
8585
strategy: req.body.strategy,
86+
projectId: req.principal.projectId,
8687
});
8788

8889
if (isNil(flowRun)) {

packages/server/api/src/app/flows/flow-run/flow-run-service.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,11 @@ export const flowRunService = {
260260

261261
return query.getCount();
262262
},
263-
async retry({ flowRunId, strategy }: RetryParams): Promise<FlowRun | null> {
263+
async retry({
264+
flowRunId,
265+
strategy,
266+
projectId,
267+
}: RetryParams): Promise<FlowRun | null> {
264268
if (strategy !== FlowRetryStrategy.ON_LATEST_VERSION) {
265269
logger.warn(`The provided strategy (${strategy}) is not valid.`, {
266270
flowRunId,
@@ -271,7 +275,7 @@ export const flowRunService = {
271275

272276
const flowRun = await flowRunService.getOnePopulatedOrThrow({
273277
id: flowRunId,
274-
projectId: undefined,
278+
projectId,
275279
});
276280

277281
const flowVersion = await flowVersionService.getLatestLockedVersionOrThrow(
@@ -684,6 +688,7 @@ type PauseParams = {
684688
};
685689

686690
type RetryParams = {
691+
projectId: ProjectId;
687692
flowRunId: FlowRunId;
688693
strategy: FlowRetryStrategy;
689694
};

packages/server/api/src/app/flows/flow/flow-version.controller.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,17 @@ import {
44
} from '@fastify/type-provider-typebox';
55
import {
66
ApplicationError,
7-
ErrorCode,
87
FlowVersionState,
98
MinimalFlow,
109
OpenOpsId,
1110
Permission,
1211
PrincipalType,
1312
SERVICE_KEY_SECURITY_OPENAPI,
14-
StepOutputWithData,
1513
UpdateFlowVersionRequest,
1614
} from '@openops/shared';
1715
import { StatusCodes } from 'http-status-codes';
1816
import { getProjectScopedRoutePolicy } from '../../core/security/route-policies/route-security-policy-factory';
19-
import { validateFlowVersionBelongsToProject } from '../common/flow-version-validation';
17+
import { assertFlowVersionBelongsToProject } from '../common/flow-validations';
2018
import { flowVersionService } from '../flow-version/flow-version.service';
2119
import { flowStepTestOutputService } from '../step-test-output/flow-step-test-output.service';
2220

@@ -62,16 +60,11 @@ export const flowVersionController: FastifyPluginAsyncTypebox = async (
6260
});
6361
}
6462

65-
const isValid = await validateFlowVersionBelongsToProject(
63+
await assertFlowVersionBelongsToProject(
6664
flowVersion,
6765
request.principal.projectId,
68-
reply,
6966
);
7067

71-
if (!isValid) {
72-
return;
73-
}
74-
7568
if (flowVersion.state === FlowVersionState.LOCKED) {
7669
await reply.status(StatusCodes.BAD_REQUEST).send({
7770
success: false,
@@ -103,6 +96,10 @@ export const flowVersionController: FastifyPluginAsyncTypebox = async (
10396
message: newFlowVersion.updated,
10497
});
10598
} catch (error) {
99+
if (error instanceof ApplicationError) {
100+
throw error;
101+
}
102+
106103
await reply.status(StatusCodes.INTERNAL_SERVER_ERROR).send({
107104
success: false,
108105
message: (error as Error).message,
@@ -147,16 +144,23 @@ export const flowVersionController: FastifyPluginAsyncTypebox = async (
147144
}),
148145
},
149146
},
150-
async (request): Promise<Record<OpenOpsId, StepOutputWithData>> => {
147+
async (request) => {
151148
const { stepIds } = request.query;
152149
const { flowVersionId } = request.params;
153150

151+
const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId);
152+
await assertFlowVersionBelongsToProject(
153+
flowVersion,
154+
request.principal.projectId,
155+
);
156+
154157
const flowStepTestOutputs = await flowStepTestOutputService.listDecrypted(
155158
{
156159
stepIds,
157160
flowVersionId,
158161
},
159162
);
163+
160164
return Object.fromEntries(
161165
flowStepTestOutputs.map((flowStepTestOutput) => [
162166
flowStepTestOutput.stepId as OpenOpsId,
@@ -203,8 +207,11 @@ export const flowVersionController: FastifyPluginAsyncTypebox = async (
203207
message: Type.String(),
204208
}),
205209
[StatusCodes.NOT_FOUND]: Type.Object({
206-
success: Type.Boolean(),
207-
message: Type.String(),
210+
code: Type.String(),
211+
params: Type.Object({
212+
entityId: Type.String(),
213+
entityType: Type.String(),
214+
}),
208215
}),
209216
},
210217
},
@@ -216,40 +223,33 @@ export const flowVersionController: FastifyPluginAsyncTypebox = async (
216223
const flowVersion = await flowVersionService.getOneOrThrow(
217224
flowVersionId,
218225
);
219-
const isValid = await validateFlowVersionBelongsToProject(
226+
227+
await assertFlowVersionBelongsToProject(
220228
flowVersion,
221229
request.principal.projectId,
222-
reply,
223230
);
224-
if (!isValid) {
225-
return;
226-
}
231+
227232
const saved = await flowStepTestOutputService.save({
228233
stepId,
229234
flowVersionId,
230235
input,
231236
output,
232237
success,
233238
});
239+
234240
await reply.status(StatusCodes.OK).send({
235241
success: true,
236242
id: saved.id,
237243
});
238244
} catch (error) {
239-
if (
240-
error instanceof ApplicationError &&
241-
error.error.code === ErrorCode.ENTITY_NOT_FOUND
242-
) {
243-
await reply.status(StatusCodes.NOT_FOUND).send({
244-
success: false,
245-
message: 'The defined flow version was not found',
246-
});
247-
} else {
248-
await reply.status(StatusCodes.BAD_REQUEST).send({
249-
success: false,
250-
message: (error as Error).message,
251-
});
245+
if (error instanceof ApplicationError) {
246+
throw error;
252247
}
248+
249+
await reply.status(StatusCodes.BAD_REQUEST).send({
250+
success: false,
251+
message: (error as Error).message,
252+
});
253253
}
254254
},
255255
);

packages/server/api/src/app/flows/flow/flow.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ const DeleteFlowRequestOptions = {
418418
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE],
419419
security: getProjectScopedRoutePolicy({
420420
allowedPrincipals: [PrincipalType.USER, PrincipalType.SERVICE],
421-
permission: Permission.WRITE_FLOW,
421+
permission: Permission.DELETE_FLOW,
422422
}),
423423
},
424424
schema: {

packages/server/api/src/app/flows/flow/form/form.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const formController: FastifyPluginAsyncTypebox = async (app) => {
1515
app.get('/:flowId', GetFormRequest, async (request) => {
1616
return formService.getFormByFlowIdOrThrow(
1717
request.params.flowId,
18+
request.principal.projectId,
1819
request.query.useDraft ?? false,
1920
);
2021
});

packages/server/api/src/app/flows/flow/form/form.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isNil,
99
PopulatedFlow,
1010
} from '@openops/shared';
11+
import { assertFlowBelongsToProject } from '../../common/flow-validations';
1112
import { flowVersionService } from '../../flow-version/flow-version.service';
1213
import { flowRepo } from '../flow.repo';
1314

@@ -30,6 +31,7 @@ const FORMS_TRIGGER_NAMES = [FORM_TRIIGGER, FILE_TRIGGER];
3031
export const formService = {
3132
getFormByFlowIdOrThrow: async (
3233
flowId: string,
34+
projectId: string,
3335
useDraft: boolean,
3436
): Promise<FormResponse> => {
3537
const flow = await getPopulatedFlowById(flowId, useDraft);
@@ -48,6 +50,8 @@ export const formService = {
4850
},
4951
});
5052
}
53+
54+
assertFlowBelongsToProject(flow, projectId);
5155
logger.info(flow.version.trigger.settings);
5256
const triggerName = flow.version.trigger.settings.triggerName;
5357
return {

packages/server/api/src/app/flows/test/test.controller.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '@openops/shared';
1212
import { StatusCodes } from 'http-status-codes';
1313
import { getProjectScopedRoutePolicy } from '../../core/security/route-policies/route-security-policy-factory';
14-
import { validateFlowVersionBelongsToProject } from '../common/flow-version-validation';
14+
import { assertFlowVersionBelongsToProject } from '../common/flow-validations';
1515
import { flowRunService } from '../flow-run/flow-run-service';
1616
import { flowVersionService } from '../flow-version/flow-version.service';
1717
import { stepRunService } from '../step-run/step-run-service';
@@ -24,15 +24,7 @@ export const testController: FastifyPluginAsyncTypebox = async (fastify) => {
2424

2525
const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId);
2626

27-
const isValid = await validateFlowVersionBelongsToProject(
28-
flowVersion,
29-
projectId,
30-
reply,
31-
);
32-
33-
if (!isValid) {
34-
return;
35-
}
27+
await assertFlowVersionBelongsToProject(flowVersion, projectId);
3628

3729
const step = flowHelper
3830
.getAllSteps(flowVersion.trigger)
@@ -76,15 +68,8 @@ export const testController: FastifyPluginAsyncTypebox = async (fastify) => {
7668
try {
7769
const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId);
7870

79-
const isValid = await validateFlowVersionBelongsToProject(
80-
flowVersion,
81-
projectId,
82-
reply,
83-
);
71+
await assertFlowVersionBelongsToProject(flowVersion, projectId);
8472

85-
if (!isValid) {
86-
return;
87-
}
8873
const flowRun = await flowRunService.test({
8974
projectId,
9075
flowVersionId: flowVersion.id,

packages/server/api/test/integration/ce/flows/flow-version.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ describe('Flow Version API', () => {
150150

151151
expect(response?.statusCode).toBe(StatusCodes.FORBIDDEN);
152152
const responseBody = JSON.parse(response?.body || '{}');
153-
expect(responseBody?.message).toBe(
153+
expect(responseBody?.params.message).toBe(
154154
'The flow and version are not associated with the project',
155155
);
156156
});
@@ -368,10 +368,7 @@ describe('POST to update test-output', () => {
368368
});
369369

370370
expect(response?.statusCode).toBe(StatusCodes.NOT_FOUND);
371-
const responseBody = response?.json();
372-
expect(responseBody).toMatchObject({
373-
success: false,
374-
message: 'The defined flow version was not found',
375-
});
371+
const responseBody = JSON.parse(response?.body || '{}');
372+
expect(responseBody?.params.entityType).toBe('FlowVersion');
376373
});
377374
});

0 commit comments

Comments
 (0)