Skip to content

Commit bc2ca42

Browse files
committed
fix(cubejs-server-core): fix schema caching in SQL API with DAP on
1 parent 7ef6282 commit bc2ca42

File tree

5 files changed

+235
-15
lines changed

5 files changed

+235
-15
lines changed

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import crypto from 'crypto';
22
import R from 'ramda';
33
import { createQuery, compile, queryClass, PreAggregations, QueryFactory } from '@cubejs-backend/schema-compiler';
4-
import { v4 as uuidv4 } from 'uuid';
4+
import { v4 as uuidv4, parse as uuidParse, stringify as uuidStringify } from 'uuid';
55
import { NativeInstance } from '@cubejs-backend/native';
66

77
export class CompilerApi {
@@ -428,7 +428,7 @@ export class CompilerApi {
428428
const { cubeEvaluator } = compilers;
429429

430430
if (!cubeEvaluator.isRbacEnabled()) {
431-
return cubes;
431+
return { cubes, visibilityMaskHash: null };
432432
}
433433

434434
for (const cube of cubes) {
@@ -481,39 +481,59 @@ export class CompilerApi {
481481
});
482482
};
483483

484-
return cubes
485-
.map((cube) => ({
486-
config: {
487-
...cube.config,
488-
measures: cube.config.measures?.map(visibilityPatcherForCube(cube)),
489-
dimensions: cube.config.dimensions?.map(visibilityPatcherForCube(cube)),
490-
segments: cube.config.segments?.map(visibilityPatcherForCube(cube)),
491-
hierarchies: cube.config.hierarchies?.map(visibilityPatcherForCube(cube)),
492-
},
493-
}));
484+
const visibiliyMask = JSON.stringify(isMemberVisibleInContext, Object.keys(isMemberVisibleInContext).sort());
485+
const visibilityMaskHash = crypto.createHash('md5').update(visibiliyMask).digest('hex').slice(0, 8);
486+
487+
return {
488+
cubes: cubes
489+
.map((cube) => ({
490+
config: {
491+
...cube.config,
492+
measures: cube.config.measures?.map(visibilityPatcherForCube(cube)),
493+
dimensions: cube.config.dimensions?.map(visibilityPatcherForCube(cube)),
494+
segments: cube.config.segments?.map(visibilityPatcherForCube(cube)),
495+
hierarchies: cube.config.hierarchies?.map(visibilityPatcherForCube(cube)),
496+
},
497+
})),
498+
visibilityMaskHash
499+
};
500+
}
501+
502+
mixInVisibilityMaskHash(compilerId, visibilityMaskHash) {
503+
const uuidBytes = uuidParse(compilerId);
504+
const hashBytes = Buffer.from(visibilityMaskHash, 'hex');
505+
// Mix hash into the first 4 bytes and the last 4 bytes in a way that doesn't change the UUID version
506+
for (let i = 0; i < 4; i++) {
507+
// eslint-disable-next-line no-bitwise
508+
uuidBytes[i] ^= hashBytes[i]; // First 4 bytes
509+
// eslint-disable-next-line no-bitwise
510+
uuidBytes[12 + i] ^= hashBytes[i + 4]; // Last 4 bytes
511+
}
512+
513+
return uuidStringify(uuidBytes);
494514
}
495515

496516
async metaConfig(requestContext, options = {}) {
497517
const { includeCompilerId, ...restOptions } = options;
498518
const compilers = await this.getCompilers(restOptions);
499519
const { cubes } = compilers.metaTransformer;
500-
const patchedCubes = await this.patchVisibilityByAccessPolicy(
520+
const { visibilityMaskHash, cubes: patchedCubes } = await this.patchVisibilityByAccessPolicy(
501521
compilers,
502522
requestContext,
503523
cubes
504524
);
505525
if (includeCompilerId) {
506526
return {
507527
cubes: patchedCubes,
508-
compilerId: compilers.compilerId,
528+
compilerId: visibilityMaskHash ? this.mixInVisibilityMaskHash(compilers.compilerId, visibilityMaskHash) : compilers.compilerId,
509529
};
510530
}
511531
return patchedCubes;
512532
}
513533

514534
async metaConfigExtended(requestContext, options) {
515535
const compilers = await this.getCompilers(options);
516-
const patchedCubes = await this.patchVisibilityByAccessPolicy(
536+
const { cubes: patchedCubes } = await this.patchVisibilityByAccessPolicy(
517537
compilers,
518538
requestContext,
519539
compilers.metaTransformer?.cubes

packages/cubejs-testing/birdbox-fixtures/rbac/cube.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
contextToRoles: async (context) => context.securityContext.auth?.roles || [],
3+
canSwitchSqlUser: async () => true,
34
checkSqlAuth: async (req, user, password) => {
45
if (user === 'admin') {
56
if (password && password !== 'admin_password') {
@@ -64,6 +65,27 @@ module.exports = {
6465
},
6566
};
6667
}
68+
if (user === 'restricted') {
69+
if (password && password !== 'restricted_password') {
70+
throw new Error(`Password doesn't match for ${user}`);
71+
}
72+
return {
73+
password,
74+
superuser: false,
75+
securityContext: {
76+
auth: {
77+
username: 'default',
78+
userAttributes: {
79+
region: 'CA',
80+
city: 'San Francisco',
81+
canHaveAdmin: true,
82+
minDefaultId: 20000,
83+
},
84+
roles: ['restricted'],
85+
},
86+
},
87+
};
88+
}
6789
throw new Error(`User "${user}" doesn't exist`);
6890
}
6991
};

packages/cubejs-testing/birdbox-fixtures/rbac/model/cubes/line_items.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ cube('line_items', {
6060
excludes: ['count', 'price', 'price_dim'],
6161
},
6262
},
63+
{
64+
role: 'restricted',
65+
memberLevel: {
66+
excludes: ['count', 'price', 'price_dim'],
67+
},
68+
},
6369
{
6470
role: 'admin',
6571
conditions: [

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

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,3 +770,153 @@ Array [
770770
`;
771771

772772
exports[`Cube RBAC Engine RBAC via SQL API manager SELECT * from line_items: line_items_manager 1`] = `Array []`;
773+
774+
exports[`Cube RBAC Engine RBAC via SQL changing users Switching user should allow more members to be visible: line_items 1`] = `
775+
Array [
776+
Object {
777+
"__cubeJoinField": null,
778+
"__user": null,
779+
"count": "1",
780+
"created_at": 2018-10-23T07:54:39.000Z,
781+
"price": 267,
782+
"quantity": 2,
783+
},
784+
Object {
785+
"__cubeJoinField": null,
786+
"__user": null,
787+
"count": "1",
788+
"created_at": 2018-01-01T13:50:20.000Z,
789+
"price": 263,
790+
"quantity": 7,
791+
},
792+
Object {
793+
"__cubeJoinField": null,
794+
"__user": null,
795+
"count": "1",
796+
"created_at": 2017-05-13T21:23:08.000Z,
797+
"price": 180,
798+
"quantity": 8,
799+
},
800+
Object {
801+
"__cubeJoinField": null,
802+
"__user": null,
803+
"count": "1",
804+
"created_at": 2018-04-10T22:51:15.000Z,
805+
"price": 169,
806+
"quantity": 6,
807+
},
808+
Object {
809+
"__cubeJoinField": null,
810+
"__user": null,
811+
"count": "1",
812+
"created_at": 2017-07-16T15:00:34.000Z,
813+
"price": 156,
814+
"quantity": 1,
815+
},
816+
Object {
817+
"__cubeJoinField": null,
818+
"__user": null,
819+
"count": "1",
820+
"created_at": 2019-05-23T04:25:27.000Z,
821+
"price": 36,
822+
"quantity": 5,
823+
},
824+
Object {
825+
"__cubeJoinField": null,
826+
"__user": null,
827+
"count": "1",
828+
"created_at": 2018-09-29T20:29:30.000Z,
829+
"price": 245,
830+
"quantity": 4,
831+
},
832+
Object {
833+
"__cubeJoinField": null,
834+
"__user": null,
835+
"count": "1",
836+
"created_at": 2019-04-17T03:32:54.000Z,
837+
"price": 232,
838+
"quantity": 8,
839+
},
840+
Object {
841+
"__cubeJoinField": null,
842+
"__user": null,
843+
"count": "1",
844+
"created_at": 2019-11-15T18:22:17.000Z,
845+
"price": 63,
846+
"quantity": 8,
847+
},
848+
Object {
849+
"__cubeJoinField": null,
850+
"__user": null,
851+
"count": "1",
852+
"created_at": 2019-12-16T08:09:36.000Z,
853+
"price": 68,
854+
"quantity": 6,
855+
},
856+
]
857+
`;
858+
859+
exports[`Cube RBAC Engine RBAC via SQL changing users Switching user should allow more members to be visible: line_items_default 1`] = `
860+
Array [
861+
Object {
862+
"__cubeJoinField": null,
863+
"__user": null,
864+
"created_at": 2018-10-23T07:54:39.000Z,
865+
"quantity": 2,
866+
},
867+
Object {
868+
"__cubeJoinField": null,
869+
"__user": null,
870+
"created_at": 2018-01-01T13:50:20.000Z,
871+
"quantity": 7,
872+
},
873+
Object {
874+
"__cubeJoinField": null,
875+
"__user": null,
876+
"created_at": 2017-05-13T21:23:08.000Z,
877+
"quantity": 8,
878+
},
879+
Object {
880+
"__cubeJoinField": null,
881+
"__user": null,
882+
"created_at": 2018-04-10T22:51:15.000Z,
883+
"quantity": 6,
884+
},
885+
Object {
886+
"__cubeJoinField": null,
887+
"__user": null,
888+
"created_at": 2017-07-16T15:00:34.000Z,
889+
"quantity": 1,
890+
},
891+
Object {
892+
"__cubeJoinField": null,
893+
"__user": null,
894+
"created_at": 2019-05-23T04:25:27.000Z,
895+
"quantity": 5,
896+
},
897+
Object {
898+
"__cubeJoinField": null,
899+
"__user": null,
900+
"created_at": 2018-09-29T20:29:30.000Z,
901+
"quantity": 4,
902+
},
903+
Object {
904+
"__cubeJoinField": null,
905+
"__user": null,
906+
"created_at": 2019-04-17T03:32:54.000Z,
907+
"quantity": 8,
908+
},
909+
Object {
910+
"__cubeJoinField": null,
911+
"__user": null,
912+
"created_at": 2019-11-15T18:22:17.000Z,
913+
"quantity": 8,
914+
},
915+
Object {
916+
"__cubeJoinField": null,
917+
"__user": null,
918+
"created_at": 2019-12-16T08:09:36.000Z,
919+
"quantity": 6,
920+
},
921+
]
922+
`;

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,28 @@ describe('Cube RBAC Engine', () => {
198198
});
199199
});
200200

201+
describe('RBAC via SQL changing users', () => {
202+
let connection: PgClient;
203+
204+
beforeAll(async () => {
205+
connection = await createPostgresClient('restricted', 'restricted_password');
206+
});
207+
208+
afterAll(async () => {
209+
await connection.end();
210+
}, JEST_AFTER_ALL_DEFAULT_TIMEOUT);
211+
212+
test('Switching user should allow more members to be visible', async () => {
213+
const resDefault = await connection.query('SELECT * FROM line_items limit 10');
214+
expect(resDefault.rows).toMatchSnapshot('line_items_default');
215+
216+
await connection.query('SET USER=admin');
217+
218+
const resAdmin = await connection.query('SELECT * FROM line_items limit 10');
219+
expect(resAdmin.rows).toMatchSnapshot('line_items');
220+
});
221+
});
222+
201223
describe('RBAC via REST API', () => {
202224
let client: CubeApi;
203225
let defaultClient: CubeApi;

0 commit comments

Comments
 (0)