Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cubejs-backend-native/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ export interface PyConfiguration {
scheduledRefreshContexts?: (ctx: unknown) => Promise<string[]>
scheduledRefreshTimeZones?: (ctx: unknown) => Promise<string[]>
contextToRoles?: (ctx: unknown) => Promise<string[]>
contextToGroups?: (ctx: unknown) => Promise<string[]>
}

function simplifyExpressRequest(req: ExpressRequest) {
Expand Down
2 changes: 2 additions & 0 deletions packages/cubejs-backend-native/python/cube/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class Configuration:
pre_aggregations_schema: Union[Callable[[RequestContext], str], str]
orchestrator_options: Union[Dict, Callable[[RequestContext], Dict]]
context_to_roles: Callable[[RequestContext], list[str]]
context_to_groups: Callable[[RequestContext], list[str]]
fast_reload: bool

def __init__(self):
Expand Down Expand Up @@ -128,6 +129,7 @@ def __init__(self):
self.pre_aggregations_schema = None
self.orchestrator_options = None
self.context_to_roles = None
self.context_to_groups = None
self.fast_reload = None

def __call__(self, func):
Expand Down
1 change: 1 addition & 0 deletions packages/cubejs-backend-native/src/python/cube_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl CubeConfigPy {
"context_to_orchestrator_id",
"context_to_cube_store_router_id",
"context_to_roles",
"context_to_groups",
"db_type",
"driver_factory",
"extend_context",
Expand Down
10 changes: 10 additions & 0 deletions packages/cubejs-backend-native/test/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,13 @@ def context_to_roles(ctx):
return [
"admin",
]


@config
def context_to_groups(ctx):
print("[python] context_to_groups", ctx)

return [
"dev",
"analytics",
]
12 changes: 12 additions & 0 deletions packages/cubejs-backend-native/test/old-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@ def logger(msg, params):
print('[python] logger msg', msg, 'params=', params)

settings.logger = logger

def context_to_roles(ctx):
print('[python] context_to_roles', ctx)
return ['admin']

settings.context_to_roles = context_to_roles

def context_to_groups(ctx):
print('[python] context_to_groups', ctx)
return ['dev', 'analytics']

settings.context_to_groups = context_to_groups
10 changes: 10 additions & 0 deletions packages/cubejs-backend-native/test/python.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ suite('Python Config', () => {
repositoryFactory: expect.any(Function),
schemaVersion: expect.any(Function),
contextToRoles: expect.any(Function),
contextToGroups: expect.any(Function),
scheduledRefreshContexts: expect.any(Function),
scheduledRefreshTimeZones: expect.any(Function),
});
Expand Down Expand Up @@ -99,6 +100,14 @@ suite('Python Config', () => {
expect(await config.contextToRoles({})).toEqual(['admin']);
});

test('context_to_groups', async () => {
if (!config.contextToGroups) {
throw new Error('contextToGroups was not defined in config.py');
}

expect(await config.contextToGroups({})).toEqual(['dev', 'analytics']);
});

test('context_to_api_scopes', async () => {
if (!config.contextToApiScopes) {
throw new Error('contextToApiScopes was not defined in config.py');
Expand Down Expand Up @@ -243,6 +252,7 @@ darwinSuite('Old Python Config', () => {
repositoryFactory: expect.any(Function),
schemaVersion: expect.any(Function),
contextToRoles: expect.any(Function),
contextToGroups: expect.any(Function),
scheduledRefreshContexts: expect.any(Function),
scheduledRefreshTimeZones: expect.any(Function),
});
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ export type Filter =
};

export type AccessPolicyDefinition = {
role?: string;
group?: string;
groups?: string[];
rowLevel?: {
filters: Filter[];
};
Expand Down
10 changes: 8 additions & 2 deletions packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,13 +765,19 @@ const RowLevelPolicySchema = Joi.object().keys({
}).xor('filters', 'allowAll');

const RolePolicySchema = Joi.object().keys({
role: Joi.string().required(),
role: Joi.string(),
group: Joi.string(),
groups: Joi.array().items(Joi.string()),
memberLevel: MemberLevelPolicySchema,
rowLevel: RowLevelPolicySchema,
conditions: Joi.array().items(Joi.object().keys({
if: Joi.func().required(),
})),
});
})
.nand('group', 'groups') // Cannot have both group and groups
.nand('role', 'group') // Cannot have both role and group
.nand('role', 'groups') // Cannot have both role and groups
.or('role', 'group', 'groups'); // Must have at least one

/* *****************************
* ATTENTION:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import {
TranspilerSymbolResolver,
TraverseObject
} from './transpiler.interface';
import type { CubeSymbols } from '../CubeSymbols';
import type { CubeDictionary } from '../CubeDictionary';

/* this list was generated by getTransformPatterns() with additional variants for snake_case */
export const transpiledFieldsPatterns: Array<RegExp> = [
Expand Down
126 changes: 126 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1166,4 +1166,130 @@ describe('Cube Validation', () => {
}
});
});

describe('Access Policy group/groups support:', () => {
const cubeValidator = new CubeValidator(new CubeSymbols());

it('should allow group instead of role', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
group: 'admin',
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeFalsy();
});

it('should allow groups as array', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
groups: ['admin', 'user'],
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeFalsy();
});

it('should allow role as single string (existing behavior)', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
role: 'admin',
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeFalsy();
});

it('should allow group: "*" syntax', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
group: '*',
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeFalsy();
});

it('should reject role and group together', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
role: 'admin',
group: 'admin',
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeTruthy();
});

it('should reject role and groups together', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
role: 'admin',
groups: ['user'],
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeTruthy();
});

it('should reject group and groups together', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
group: 'admin',
groups: ['user'],
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeTruthy();
});

it('should reject access policy without role/group/groups', () => {
const cube = {
name: 'TestCube',
fileName: 'test.js',
sql: () => 'SELECT * FROM test',
accessPolicy: [{
rowLevel: { allowAll: true }
}]
};

const result = cubeValidator.validate(cube, new ConsoleErrorReporter());
expect(result.error).toBeTruthy();
});
});
});
Loading
Loading