Skip to content

Commit

Permalink
Added vertical roles for oss (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
stankis authored Jul 7, 2024
1 parent d290b87 commit 03e0312
Show file tree
Hide file tree
Showing 22 changed files with 414 additions and 47 deletions.
1 change: 1 addition & 0 deletions jest/int/setup-after-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// test environment the max listeners limitation is lifted.
require('events').EventEmitter.defaultMaxListeners = 1000;

require('../../dist/server/tests/int/mocks');
require('../../dist/server');

const {db} = require('../../dist/server/db');
Expand Down
1 change: 1 addition & 0 deletions src/components/middlewares/auth-zitadel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const authZitadel = async (req: Request, res: Response, next: NextFunctio
res.locals.userId = r1.userId;
res.locals.login = r1.username;
res.locals.serviceUser = r2.username;
res.locals.zitadelUserRole = r1.role;
return next();
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/middlewares/ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const ctx = async (req: Request, res: Response, next: NextFunction) => {
onlyPublic,
projectId,
serviceUser,
zitadelUserRole,
} = res.locals;

const privatePermissions = resolvePrivatePermissions(
Expand All @@ -30,6 +31,7 @@ export const ctx = async (req: Request, res: Response, next: NextFunction) => {
privatePermissions,
projectId: projectId || null,
serviceUser,
zitadelUserRole,
});

next();
Expand Down
2 changes: 1 addition & 1 deletion src/configs/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default {
tenantIdOverride: 'common',

dlsEnabled: false,
accessServiceEnabled: false,
accessServiceEnabled: Utils.isTrueArg(Utils.getEnvVariable('ZITADEL')),
accessBindingsServiceEnabled: false,

masterToken: Utils.getEnvTokenVariable('MASTER_TOKEN'),
Expand Down
7 changes: 6 additions & 1 deletion src/configs/int-testing.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {AuthPolicy} from '@gravity-ui/expresskit';
import {AppConfig} from '@gravity-ui/nodekit';

export default {} as Partial<AppConfig>;
export default {
zitadelEnabled: true,
accessServiceEnabled: true,
appAuthPolicy: AuthPolicy.required,
} as Partial<AppConfig>;
60 changes: 58 additions & 2 deletions src/registry/common/components/iam/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import {AppContext, AppError} from '@gravity-ui/nodekit';
import type {CheckOrganizationPermission, CheckProjectPermission} from './types';
import {OrganizationPermission, ProjectPermission} from '../../../../components/iam';
import {US_ERRORS} from '../../../../const';
import {ZitadelUserRole} from '../../../../types/zitadel';

export const checkOrganizationPermission: CheckOrganizationPermission = async () => {};
const throwAccessServicePermissionDenied = () => {
throw new AppError(US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED, {
code: US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED,
});
};

export const checkProjectPermission: CheckProjectPermission = async () => {};
export const checkOrganizationPermission: CheckOrganizationPermission = async (args: {
ctx: AppContext;
permission: OrganizationPermission;
}) => {
const {ctx, permission} = args;
const {zitadelUserRole: role} = ctx.get('info');

switch (permission) {
case OrganizationPermission.UseInstance:
break;

case OrganizationPermission.ManageInstance:
if (role !== ZitadelUserRole.Admin) {
throwAccessServicePermissionDenied();
}
break;

case OrganizationPermission.CreateCollectionInRoot:
case OrganizationPermission.CreateWorkbookInRoot:
if (role !== ZitadelUserRole.Editor && role !== ZitadelUserRole.Admin) {
throwAccessServicePermissionDenied();
}
break;

default:
throwAccessServicePermissionDenied();
}
};

export const checkProjectPermission: CheckProjectPermission = async (args: {
ctx: AppContext;
permission: ProjectPermission;
}) => {
const {ctx, permission} = args;

const {zitadelUserRole: role} = ctx.get('info');

switch (permission) {
case ProjectPermission.CreateCollectionInRoot:
case ProjectPermission.CreateWorkbookInRoot:
if (role !== ZitadelUserRole.Editor && role !== ZitadelUserRole.Admin) {
throwAccessServicePermissionDenied();
}
break;

default:
throwAccessServicePermissionDenied();
}
};
50 changes: 45 additions & 5 deletions src/registry/common/entities/collection/collection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type {AppContext} from '@gravity-ui/nodekit';
import type {CollectionModel} from '../../../../db/models/new/collection';
import {AppError} from '@gravity-ui/nodekit';
import {CollectionConstructor, CollectionInstance} from './types';
import {Permissions} from '../../../../entities/collection/types';
import {CollectionPermission, Permissions} from '../../../../entities/collection/types';
import {US_ERRORS} from '../../../../const';
import {ZitadelUserRole} from '../../../../types/zitadel';

export const Collection: CollectionConstructor = class Collection implements CollectionInstance {
ctx: AppContext;
Expand All @@ -13,14 +16,42 @@ export const Collection: CollectionConstructor = class Collection implements Col
this.model = model;
}

private getAllPermissions() {
const {zitadelUserRole: role} = this.ctx.get('info');

const isEditorOrAdmin = role === ZitadelUserRole.Editor || role === ZitadelUserRole.Admin;

const permissions = {
listAccessBindings: true,
updateAccessBindings: isEditorOrAdmin,
createCollection: isEditorOrAdmin,
createWorkbook: isEditorOrAdmin,
limitedView: true,
view: true,
update: isEditorOrAdmin,
copy: isEditorOrAdmin,
move: isEditorOrAdmin,
delete: isEditorOrAdmin,
};

return permissions;
}

async register() {}

async checkPermission() {}
async checkPermission(args: {
parentIds: string[];
permission: CollectionPermission;
}): Promise<void> {
const permissions = this.getAllPermissions();

async fetchAllPermissions() {}
if (permissions[args.permission] === false) {
throw new AppError(US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED, {
code: US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED,
});
}

setPermissions(permissions: Permissions) {
this.permissions = permissions;
return Promise.resolve();
}

enableAllPermissions() {
Expand All @@ -37,4 +68,13 @@ export const Collection: CollectionConstructor = class Collection implements Col
delete: true,
};
}

setPermissions(permissions: Permissions) {
this.permissions = permissions;
}

async fetchAllPermissions() {
this.permissions = this.getAllPermissions();
return Promise.resolve();
}
};
4 changes: 2 additions & 2 deletions src/registry/common/entities/collection/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export interface CollectionInstance {

checkPermission(args: {parentIds: string[]; permission: CollectionPermission}): Promise<void>;

fetchAllPermissions(args: {parentIds: string[]}): Promise<void>;

setPermissions(permissions: Permissions): void;

enableAllPermissions(): void;

fetchAllPermissions(args: {parentIds: string[]}): Promise<void>;
}

export type BulkFetchCollectionsAllPermissions = (
Expand Down
6 changes: 5 additions & 1 deletion src/registry/common/entities/collection/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export const bulkFetchCollectionsAllPermissions: BulkFetchCollectionsAllPermissi
) => {
return items.map(({model}) => {
const collection = new Collection({ctx, model});
collection.enableAllPermissions();
if (ctx.config.accessServiceEnabled) {
collection.fetchAllPermissions({parentIds: []});
} else {
collection.enableAllPermissions();
}
return collection;
});
};
6 changes: 5 additions & 1 deletion src/registry/common/entities/workbook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export const bulkFetchWorkbooksAllPermissions: BulkFetchWorkbooksAllPermissions
) => {
return items.map(({model}) => {
const workbook = new Workbook({ctx, model});
workbook.enableAllPermissions();
if (ctx.config.accessServiceEnabled) {
workbook.fetchAllPermissions({parentIds: []});
} else {
workbook.enableAllPermissions();
}
return workbook;
});
};
50 changes: 46 additions & 4 deletions src/registry/common/entities/workbook/workbook.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type {AppContext} from '@gravity-ui/nodekit';
import type {WorkbookModel} from '../../../../db/models/new/workbook';
import {AppError} from '@gravity-ui/nodekit';
import {WorkbookConstructor, WorkbookInstance} from './types';
import {Permissions} from '../../../../entities/workbook/types';
import {Permissions, WorkbookPermission} from '../../../../entities/workbook/types';
import {US_ERRORS} from '../../../../const';
import {ZitadelUserRole} from '../../../../types/zitadel';

export const Workbook: WorkbookConstructor<WorkbookInstance> = class Workbook
implements WorkbookInstance
Expand All @@ -15,11 +18,50 @@ export const Workbook: WorkbookConstructor<WorkbookInstance> = class Workbook
this.model = model;
}

async register() {}
private getAllPermissions() {
const {zitadelUserRole: role} = this.ctx.get('info');

async checkPermission() {}
const isEditorOrAdmin = role === ZitadelUserRole.Editor || role === ZitadelUserRole.Admin;

async fetchAllPermissions() {}
const permissions = {
listAccessBindings: true,
updateAccessBindings: isEditorOrAdmin,
limitedView: true,
view: true,
update: isEditorOrAdmin,
copy: isEditorOrAdmin,
move: isEditorOrAdmin,
publish: isEditorOrAdmin,
embed: isEditorOrAdmin,
delete: isEditorOrAdmin,
};

return permissions;
}

async register(_args: {parentIds: string[]}): Promise<unknown> {
return Promise.resolve();
}

async checkPermission(args: {
parentIds: string[];
permission: WorkbookPermission;
}): Promise<void> {
const permissions = this.getAllPermissions();

if (permissions[args.permission] === false) {
throw new AppError(US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED, {
code: US_ERRORS.ACCESS_SERVICE_PERMISSION_DENIED,
});
}

return Promise.resolve();
}

async fetchAllPermissions(): Promise<void> {
this.permissions = this.getAllPermissions();
return Promise.resolve();
}

setPermissions(permissions: Permissions) {
this.permissions = permissions;
Expand Down
2 changes: 0 additions & 2 deletions src/services/new/workbook/get-workbooks-list-by-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ export const getWorkbooksListByIds = async (
if (includePermissionsInfo) {
return workbookList.map((model) => {
const workbook = new Workbook({ctx, model});

workbook.enableAllPermissions();

return workbook;
});
}
Expand Down
2 changes: 0 additions & 2 deletions src/services/new/workbook/get-workbooks-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,9 @@ export const getWorkbooksList = async (
} else {
workbooks = workbooksPage.results.map((model) => {
const workbook = new Workbook({ctx, model});

if (includePermissionsInfo) {
workbook.enableAllPermissions();
}

return workbook;
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/tests/int/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export const testUserLogin = 'unknown';

export const testTenantId = 'common';
export const testProjectId = null;

export const ZITADEL_USER_ROLE_HEADER = 'zitadel-user-role';
17 changes: 17 additions & 0 deletions src/tests/int/mocks/auth-zitadel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Request, Response, NextFunction} from '@gravity-ui/expresskit';

jest.mock('../../../components/middlewares/auth-zitadel', () => {
const originalModule = jest.requireActual('../../../components/middlewares/auth-zitadel');

return {
...originalModule,

authZitadel: jest.fn((req: Request, res: Response, next: NextFunction) => {
const {ZITADEL_USER_ROLE_HEADER} = require('../constants');
const {ZitadelUserRole} = require('../../../types/zitadel');
const role = req.headers[ZITADEL_USER_ROLE_HEADER];
res.locals.zitadelUserRole = role ?? ZitadelUserRole.Viewer;
return next();
}),
};
});
1 change: 1 addition & 0 deletions src/tests/int/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './auth-zitadel';
Loading

0 comments on commit 03e0312

Please sign in to comment.