Skip to content

Commit d79d3aa

Browse files
authored
feat(cubejs-api-gateway): Add scope check to sql runner (#6053)
1 parent 5c84467 commit d79d3aa

File tree

2 files changed

+32
-10
lines changed

2 files changed

+32
-10
lines changed

packages/cubejs-api-gateway/src/gateway.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,12 @@ class ApiGateway {
354354
this.preAggregationsJobs.bind(this),
355355
);
356356

357+
const sqlRunnerMiddlewares = [...systemMiddlewares, this.checkSqlRunnerScope];
358+
357359
app.post(
358360
'/cubejs-system/v1/sql-runner',
359361
jsonParser,
360-
systemMiddlewares,
362+
sqlRunnerMiddlewares,
361363
async (req: Request, res: Response) => {
362364
await this.sqlRunner({
363365
query: req.body.query,
@@ -370,7 +372,7 @@ class ApiGateway {
370372
app.get(
371373
'/cubejs-system/v1/data-sources',
372374
jsonParser,
373-
systemMiddlewares,
375+
sqlRunnerMiddlewares,
374376
async (req: Request, res: Response) => {
375377
await this.dataSources({
376378
context: req.context,
@@ -2040,6 +2042,24 @@ class ApiGateway {
20402042
protected checkAuthSystemMiddleware: RequestHandler = async (req, res, next) => {
20412043
await this.checkAuthWrapper(this.checkAuthSystemFn, req, res, next);
20422044
};
2045+
2046+
protected checkSqlRunnerScope: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
2047+
await this.checkAuthWrapper(
2048+
async () => {
2049+
if (
2050+
!getEnv('devMode') &&
2051+
(!req.context?.securityContext?.scope ||
2052+
!Array.isArray(req.context?.securityContext?.scope) ||
2053+
!req.context?.securityContext?.scope.includes('sql-runner'))
2054+
) {
2055+
throw new CubejsHandlerError(403, 'Forbidden', 'Sql-runner scope is missing.');
2056+
}
2057+
},
2058+
req,
2059+
res,
2060+
next
2061+
);
2062+
};
20432063

20442064
protected requestContextMiddleware: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
20452065
req.context = await this.contextByReq(req, req.securityContext, getRequestIdFromRequest(req));

packages/cubejs-api-gateway/test/index.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ describe('API Gateway', () => {
447447

448448
const scheduledRefreshTimeZonesFactory = () => (['UTC', 'America/Los_Angeles']);
449449

450-
const appPrepareFactory = () => {
450+
const appPrepareFactory = (scope?: string[]) => {
451451
const playgroundAuthSecret = 'test12345';
452452
const { app } = createApiGateway(
453453
new AdapterApiMock(),
@@ -460,7 +460,7 @@ describe('API Gateway', () => {
460460
scheduledRefreshTimeZones: scheduledRefreshTimeZonesFactory()
461461
}
462462
);
463-
const token = generateAuthToken({ uid: 5, }, {}, playgroundAuthSecret);
463+
const token = generateAuthToken({ uid: 5, scope }, {}, playgroundAuthSecret);
464464
const tokenUser = generateAuthToken({ uid: 5, }, {}, API_SECRET);
465465

466466
return { app, token, tokenUser };
@@ -490,8 +490,8 @@ describe('API Gateway', () => {
490490
.expect(404);
491491
};
492492

493-
const successTestFactory = ({ route, method = 'get', successBody = {}, successResult }) => async () => {
494-
const { app, token } = appPrepareFactory();
493+
const successTestFactory = ({ route, method = 'get', successBody = {}, successResult, scope = [''] }) => async () => {
494+
const { app, token } = appPrepareFactory(scope);
495495

496496
const req = request(app)[method](`/cubejs-system/v1/${route}`)
497497
.set('Content-type', 'application/json')
@@ -504,18 +504,19 @@ describe('API Gateway', () => {
504504
expect(res.body).toMatchObject(successResult);
505505
};
506506

507-
const wrongPayloadsTestFactory = ({ route, wrongPayloads }: {
507+
const wrongPayloadsTestFactory = ({ route, wrongPayloads, scope }: {
508508
route: string,
509509
method: string,
510+
scope?: string[],
510511
wrongPayloads: {
511512
result: {
512513
status: number,
513514
error: string
514515
},
515-
body: {}
516+
body: {},
516517
}[]
517518
}) => async () => {
518-
const { app, token } = appPrepareFactory();
519+
const { app, token } = appPrepareFactory(scope);
519520

520521
for (const payload of wrongPayloads) {
521522
const req = request(app).post(`/cubejs-system/v1/${route}`)
@@ -551,6 +552,7 @@ describe('API Gateway', () => {
551552
},
552553
{
553554
route: 'sql-runner',
555+
scope: ['sql-runner'],
554556
method: 'post',
555557
successBody: {
556558
query: {
@@ -592,7 +594,7 @@ describe('API Gateway', () => {
592594
}
593595
}]
594596
},
595-
{ route: 'data-sources', successResult: { dataSources: [{ dataSource: 'default', dbType: 'postgres' }] } },
597+
{ route: 'data-sources', scope: ['sql-runner'], successResult: { dataSources: [{ dataSource: 'default', dbType: 'postgres' }] } },
596598
];
597599

598600
testConfigs.forEach((config) => {

0 commit comments

Comments
 (0)