Skip to content

Commit 0fc80f1

Browse files
committed
fix(api-gateway): fix DAP RLS issue with denied queries and python conf
1 parent 59dc373 commit 0fc80f1

File tree

6 files changed

+146
-26
lines changed

6 files changed

+146
-26
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,16 +1207,16 @@ class ApiGateway {
12071207
}
12081208

12091209
// First apply cube/view level security policies
1210-
const queryWithRlsFilters = await compilerApi.applyRowLevelSecurity(
1210+
const { query: queryWithRlsFilters, denied } = await compilerApi.applyRowLevelSecurity(
12111211
normalizedQuery,
12121212
evaluatedQuery,
12131213
context
12141214
);
12151215
// Then apply user-supplied queryRewrite
1216-
let rewrittenQuery = await this.queryRewrite(
1216+
let rewrittenQuery = !denied ? await this.queryRewrite(
12171217
queryWithRlsFilters,
12181218
context
1219-
);
1219+
) : queryWithRlsFilters;
12201220

12211221
// applyRowLevelSecurity may add new filters which may contain raw member expressions
12221222
// if that's the case, we should run an extra pass of parsing here to make sure

packages/cubejs-server-core/src/core/CompilerApi.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export class CompilerApi {
322322
name: 'rlsAccessDenied',
323323
});
324324
// If we hit this condition there's no need to evaluate the rest of the policy
325-
break;
325+
return { query, denied: true };
326326
}
327327
}
328328
}
@@ -334,7 +334,7 @@ export class CompilerApi {
334334
);
335335
query.filters = query.filters || [];
336336
query.filters.push(rlsFilter);
337-
return query;
337+
return { query, denied: false };
338338
}
339339

340340
buildFinalRlsFilter(cubeFiltersPerCubePerRole, viewFiltersPerCubePerRole, hasAllowAllForCube) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cubes:
2+
- name: products
3+
sql_table: products
4+
5+
measures:
6+
- name: count
7+
type: count
8+
9+
dimensions:
10+
- name: id
11+
sql: ID
12+
type: number
13+
primary_key: true
14+
15+
- name: product_category
16+
sql: PRODUCT_CATEGORY
17+
type: string
18+
19+
- name: name
20+
sql: NAME
21+
type: string
22+
23+
- name: created_at
24+
sql: CREATED_AT
25+
type: time
26+
27+
access_policy:
28+
- role: some_role
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cubes:
2+
- name: products
3+
sql_table: products
4+
5+
measures:
6+
- name: count
7+
type: count
8+
9+
dimensions:
10+
- name: id
11+
sql: ID
12+
type: number
13+
primary_key: true
14+
15+
- name: product_category
16+
sql: PRODUCT_CATEGORY
17+
type: string
18+
19+
- name: name
20+
sql: NAME
21+
type: string
22+
23+
- name: created_at
24+
sql: CREATED_AT
25+
type: time
26+
27+
access_policy:
28+
- role: some_role

packages/cubejs-testing/test/__snapshots__/smoke-rbac.test.ts.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ Array [
88
]
99
`;
1010

11+
exports[`Cube RBAC Engine [Python config][dev mode] products with no matching policy: products_no_policy_python 1`] = `
12+
Array [
13+
Object {
14+
"products.count": "0",
15+
},
16+
]
17+
`;
18+
19+
exports[`Cube RBAC Engine [dev mode] products with no matching policy: products_no_policy 1`] = `
20+
Array [
21+
Object {
22+
"products.count": "0",
23+
},
24+
]
25+
`;
26+
1127
exports[`Cube RBAC Engine RBAC via REST API line_items hidden price_dim: line_items_view_no_policy_rest 1`] = `
1228
Array [
1329
Object {

packages/cubejs-testing/test/smoke-rbac.test.ts

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ import {
1616
const PG_PORT = 5656;
1717
let connectionId = 0;
1818

19+
const DEFAULT_API_TOKEN = sign({
20+
auth: {
21+
username: 'nobody',
22+
userAttributes: {},
23+
roles: [],
24+
},
25+
}, DEFAULT_CONFIG.CUBEJS_API_SECRET, {
26+
expiresIn: '2 days'
27+
});
28+
1929
async function createPostgresClient(user: string, password: string) {
2030
connectionId++;
2131
const currentConnId = connectionId;
@@ -96,7 +106,6 @@ describe('Cube RBAC Engine', () => {
96106
expect(res.rows).toMatchSnapshot('line_items');
97107
});
98108

99-
// ???
100109
test('SELECT * from line_items_view_no_policy', async () => {
101110
const res = await connection.query('SELECT * FROM line_items_view_no_policy limit 10');
102111
// This should query the line_items cube through the view that should
@@ -202,16 +211,6 @@ describe('Cube RBAC Engine', () => {
202211
expiresIn: '2 days'
203212
});
204213

205-
const DEFAULT_API_TOKEN = sign({
206-
auth: {
207-
username: 'nobody',
208-
userAttributes: {},
209-
roles: [],
210-
},
211-
}, DEFAULT_CONFIG.CUBEJS_API_SECRET, {
212-
expiresIn: '2 days'
213-
});
214-
215214
beforeAll(async () => {
216215
client = cubejs(async () => ADMIN_API_TOKEN, {
217216
apiUrl: birdbox.configuration.apiUrl,
@@ -289,16 +288,6 @@ describe('Cube RBAC Engine [dev mode]', () => {
289288
let birdbox: BirdBox;
290289
let client: CubeApi;
291290

292-
const DEFAULT_API_TOKEN = sign({
293-
auth: {
294-
username: 'nobody',
295-
userAttributes: {},
296-
roles: [],
297-
},
298-
}, DEFAULT_CONFIG.CUBEJS_API_SECRET, {
299-
expiresIn: '2 days'
300-
});
301-
302291
const pgPort = 5656;
303292

304293
beforeAll(async () => {
@@ -344,6 +333,15 @@ describe('Cube RBAC Engine [dev mode]', () => {
344333
expect(dim.public).toBe(false);
345334
}
346335
});
336+
337+
test('products with no matching policy', async () => {
338+
const result = await client.load({
339+
measures: ['products.count'],
340+
});
341+
342+
// Querying a cube with no matching access policy should return no data
343+
expect(result.rawData()).toMatchSnapshot('products_no_policy');
344+
});
347345
});
348346

349347
describe('Cube RBAC Engine [Python config]', () => {
@@ -402,3 +400,53 @@ describe('Cube RBAC Engine [Python config]', () => {
402400
});
403401
});
404402
});
403+
404+
describe('Cube RBAC Engine [Python config][dev mode]', () => {
405+
jest.setTimeout(60 * 5 * 1000);
406+
let db: StartedTestContainer;
407+
let birdbox: BirdBox;
408+
let client: CubeApi;
409+
410+
beforeAll(async () => {
411+
db = await PostgresDBRunner.startContainer({});
412+
await PostgresDBRunner.loadEcom(db);
413+
birdbox = await getBirdbox(
414+
'postgres',
415+
{
416+
...DEFAULT_CONFIG,
417+
CUBEJS_DEV_MODE: 'true',
418+
NODE_ENV: 'dev',
419+
//
420+
CUBEJS_DB_TYPE: 'postgres',
421+
CUBEJS_DB_HOST: db.getHost(),
422+
CUBEJS_DB_PORT: `${db.getMappedPort(5432)}`,
423+
CUBEJS_DB_NAME: 'test',
424+
CUBEJS_DB_USER: 'test',
425+
CUBEJS_DB_PASS: 'test',
426+
//
427+
CUBEJS_PG_SQL_PORT: `${PG_PORT}`,
428+
},
429+
{
430+
schemaDir: 'rbac-python/model',
431+
cubejsConfig: 'rbac-python/cube.py',
432+
}
433+
);
434+
client = cubejs(async () => DEFAULT_API_TOKEN, {
435+
apiUrl: birdbox.configuration.apiUrl,
436+
});
437+
}, JEST_BEFORE_ALL_DEFAULT_TIMEOUT);
438+
439+
afterAll(async () => {
440+
await birdbox.stop();
441+
await db.stop();
442+
}, JEST_AFTER_ALL_DEFAULT_TIMEOUT);
443+
444+
test('products with no matching policy', async () => {
445+
const result = await client.load({
446+
measures: ['products.count'],
447+
});
448+
449+
// Querying a cube with no matching access policy should return no data
450+
expect(result.rawData()).toMatchSnapshot('products_no_policy_python');
451+
});
452+
});

0 commit comments

Comments
 (0)