mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
refactor: token permissions, drop admin-like permissions (#4050)
https://linear.app/unleash/issue/2-1155/refactor-permissions - Our `rbac-middleware` now supports multiple OR permissions; - Drops non-specific permissions (e.g. CRUD API token permissions without specifying the token type); - Makes our permission descriptions consistent; - Drops our higher-level permissions that basically mean ADMIN (e.g. ADMIN token permissions) in favor of `ADMIN` permission in order to avoid privilege escalations; This PR may help with https://linear.app/unleash/issue/2-1144/discover-potential-privilege-escalations as it may prevent privilege escalations altogether. There's some UI permission logic around this, but in the future https://linear.app/unleash/issue/2-1156/adapt-api-tokens-creation-ui-to-new-permissions could take it a bit further by adapting the creation of tokens as well. --------- Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
parent
24e9cf7c8f
commit
7e9069e390
@ -1,11 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import {
|
||||
CREATE_API_TOKEN,
|
||||
DELETE_API_TOKEN,
|
||||
READ_API_TOKEN,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
|
||||
import { ApiTokenTable } from 'component/common/ApiTokenTable/ApiTokenTable';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
@ -18,6 +13,13 @@ import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import { CopyApiTokenButton } from 'component/common/ApiTokenTable/CopyApiTokenButton/CopyApiTokenButton';
|
||||
import { RemoveApiTokenButton } from 'component/common/ApiTokenTable/RemoveApiTokenButton/RemoveApiTokenButton';
|
||||
import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||
import {
|
||||
ADMIN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
} from '@server/types/permissions';
|
||||
|
||||
export const ApiTokenPage = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
@ -34,26 +36,45 @@ export const ApiTokenPage = () => {
|
||||
setGlobalFilter,
|
||||
setHiddenColumns,
|
||||
columns,
|
||||
} = useApiTokenTable(tokens, props => (
|
||||
<ActionCell>
|
||||
<CopyApiTokenButton
|
||||
token={props.row.original}
|
||||
permission={READ_API_TOKEN}
|
||||
/>
|
||||
<RemoveApiTokenButton
|
||||
token={props.row.original}
|
||||
permission={DELETE_API_TOKEN}
|
||||
onRemove={async () => {
|
||||
await deleteToken(props.row.original.secret);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
</ActionCell>
|
||||
));
|
||||
} = useApiTokenTable(tokens, props => {
|
||||
const READ_PERMISSION =
|
||||
props.row.original.type === 'client'
|
||||
? READ_CLIENT_API_TOKEN
|
||||
: props.row.original.type === 'frontend'
|
||||
? READ_FRONTEND_API_TOKEN
|
||||
: ADMIN;
|
||||
const DELETE_PERMISSION =
|
||||
props.row.original.type === 'client'
|
||||
? DELETE_CLIENT_API_TOKEN
|
||||
: props.row.original.type === 'frontend'
|
||||
? DELETE_FRONTEND_API_TOKEN
|
||||
: ADMIN;
|
||||
|
||||
return (
|
||||
<ActionCell>
|
||||
<CopyApiTokenButton
|
||||
token={props.row.original}
|
||||
permission={READ_PERMISSION}
|
||||
/>
|
||||
<RemoveApiTokenButton
|
||||
token={props.row.original}
|
||||
permission={DELETE_PERMISSION}
|
||||
onRemove={async () => {
|
||||
await deleteToken(props.row.original.secret);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
</ActionCell>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={hasAccess(READ_API_TOKEN)}
|
||||
condition={hasAccess([
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
ADMIN,
|
||||
])}
|
||||
show={() => (
|
||||
<PageContent
|
||||
header={
|
||||
@ -67,7 +88,7 @@ export const ApiTokenPage = () => {
|
||||
/>
|
||||
<PageHeader.Divider />
|
||||
<CreateApiTokenButton
|
||||
permission={CREATE_API_TOKEN}
|
||||
permission={ADMIN}
|
||||
path="/admin/api/create-token"
|
||||
/>
|
||||
</>
|
||||
|
@ -7,7 +7,6 @@ import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useApiTokenForm } from 'component/admin/apiToken/ApiTokenForm/useApiTokenForm';
|
||||
import { CREATE_API_TOKEN } from 'component/providers/AccessProvider/permissions';
|
||||
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
|
||||
import { scrollToTop } from 'component/common/util';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
@ -18,6 +17,7 @@ import { TokenInfo } from '../ApiTokenForm/TokenInfo/TokenInfo';
|
||||
import { TokenTypeSelector } from '../ApiTokenForm/TokenTypeSelector/TokenTypeSelector';
|
||||
import { ProjectSelector } from '../ApiTokenForm/ProjectSelector/ProjectSelector';
|
||||
import { EnvironmentSelector } from '../ApiTokenForm/EnvironmentSelector/EnvironmentSelector';
|
||||
import { ADMIN } from '@server/types/permissions';
|
||||
|
||||
const pageTitle = 'Create API token';
|
||||
interface ICreateApiTokenProps {
|
||||
@ -52,8 +52,6 @@ export const CreateApiToken = ({ modal = false }: ICreateApiTokenProps) => {
|
||||
|
||||
const PATH = `api/admin/api-tokens`;
|
||||
|
||||
const permission = CREATE_API_TOKEN;
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!isValid()) {
|
||||
@ -107,7 +105,7 @@ export const CreateApiToken = ({ modal = false }: ICreateApiTokenProps) => {
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
mode="Create"
|
||||
actions={<CreateButton name="token" permission={permission} />}
|
||||
actions={<CreateButton name="token" permission={ADMIN} />}
|
||||
>
|
||||
<TokenInfo
|
||||
username={username}
|
||||
|
@ -15,7 +15,6 @@ import { Search } from 'component/common/Search/Search';
|
||||
import theme from 'themes/theme';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { UPDATE_ROLE } from '@server/types/permissions';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import { IRole } from 'interfaces/role';
|
||||
|
||||
@ -146,7 +145,7 @@ export const Roles = () => {
|
||||
}}
|
||||
maxWidth={`${theme.breakpoints.values['sm']}px`}
|
||||
Icon={Add}
|
||||
permission={UPDATE_ROLE}
|
||||
permission={ADMIN}
|
||||
>
|
||||
New {type} role
|
||||
</ResponsiveButton>
|
||||
|
@ -40,16 +40,22 @@ export const checkAdmin = (permissions: IPermission[] | undefined): boolean => {
|
||||
|
||||
export const hasAccess = (
|
||||
permissions: IPermission[] | undefined,
|
||||
permission: string,
|
||||
permission: string | string[],
|
||||
project?: string,
|
||||
environment?: string
|
||||
): boolean => {
|
||||
if (!permissions) {
|
||||
return false;
|
||||
}
|
||||
return permissions.some(p => {
|
||||
return checkPermission(p, permission, project, environment);
|
||||
});
|
||||
const permissionsToCheck = Array.isArray(permission)
|
||||
? permission
|
||||
: [permission];
|
||||
|
||||
return permissions.some(p =>
|
||||
permissionsToCheck.some(permissionToCheck =>
|
||||
checkPermission(p, permissionToCheck, project, environment)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const checkPermission = (
|
||||
|
@ -17,10 +17,6 @@ export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
|
||||
export const CREATE_ADDON = 'CREATE_ADDON';
|
||||
export const UPDATE_ADDON = 'UPDATE_ADDON';
|
||||
export const DELETE_ADDON = 'DELETE_ADDON';
|
||||
export const CREATE_API_TOKEN = 'CREATE_API_TOKEN';
|
||||
export const UPDATE_API_TOKEN = 'UPDATE_API_TOKEN';
|
||||
export const DELETE_API_TOKEN = 'DELETE_API_TOKEN';
|
||||
export const READ_API_TOKEN = 'READ_API_TOKEN';
|
||||
export const DELETE_ENVIRONMENT = 'DELETE_ENVIRONMENT';
|
||||
export const UPDATE_ENVIRONMENT = 'UPDATE_ENVIRONMENT';
|
||||
export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY';
|
||||
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||
export interface IAccessContext {
|
||||
isAdmin: boolean;
|
||||
hasAccess: (
|
||||
permission: string,
|
||||
permission: string | string[],
|
||||
project?: string,
|
||||
environment?: string
|
||||
) => boolean;
|
||||
|
@ -1,22 +1,33 @@
|
||||
import { ApiErrorSchema, UnleashError } from './unleash-error';
|
||||
|
||||
class NoAccessError extends UnleashError {
|
||||
permission: string;
|
||||
type Permission = string | string[];
|
||||
|
||||
class NoAccessError extends UnleashError {
|
||||
permissions: Permission;
|
||||
|
||||
constructor(permission: Permission = [], environment?: string) {
|
||||
const permissions = Array.isArray(permission)
|
||||
? permission
|
||||
: [permission];
|
||||
|
||||
const permissionsMessage =
|
||||
permissions.length === 1
|
||||
? `the ${permissions[0]} permission`
|
||||
: `any of the following permissions: ${permissions.join(', ')}`;
|
||||
|
||||
constructor(permission: string, environment?: string) {
|
||||
const message =
|
||||
`You don't have the required permissions to perform this operation. You need the "${permission}" permission to perform this action` +
|
||||
`You don't have the required permissions to perform this operation. You need ${permissionsMessage}" to perform this action` +
|
||||
(environment ? ` in the "${environment}" environment.` : `.`);
|
||||
|
||||
super(message);
|
||||
|
||||
this.permission = permission;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
toJSON(): ApiErrorSchema {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
permission: this.permission,
|
||||
permissions: this.permissions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -417,12 +417,20 @@ describe('Error serialization special cases', () => {
|
||||
expect(json).toMatchObject(config);
|
||||
});
|
||||
|
||||
it('NoAccessError: adds `permission`', () => {
|
||||
it('NoAccessError: adds `permissions`', () => {
|
||||
const permission = 'x';
|
||||
const error = new NoAccessError(permission);
|
||||
const json = error.toJSON();
|
||||
|
||||
expect(json.permission).toBe(permission);
|
||||
expect(json.permissions).toStrictEqual([permission]);
|
||||
});
|
||||
|
||||
it('NoAccessError: supports multiple permissions', () => {
|
||||
const permission = ['x', 'y', 'z'];
|
||||
const error = new NoAccessError(permission);
|
||||
const json = error.toJSON();
|
||||
|
||||
expect(json.permissions).toStrictEqual(permission);
|
||||
});
|
||||
|
||||
it('BadDataError: adds `details` with error details', () => {
|
||||
|
@ -152,7 +152,7 @@ test('should verify permission for root resource', async () => {
|
||||
expect(accessService.hasPermission).toHaveBeenCalledTimes(1);
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.ADMIN,
|
||||
[perms.ADMIN],
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
@ -182,7 +182,7 @@ test('should lookup projectId from params', async () => {
|
||||
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.UPDATE_PROJECT,
|
||||
[perms.UPDATE_PROJECT],
|
||||
req.params.projectId,
|
||||
undefined,
|
||||
);
|
||||
@ -217,7 +217,7 @@ test('should lookup projectId from feature toggle', async () => {
|
||||
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.UPDATE_FEATURE,
|
||||
[perms.UPDATE_FEATURE],
|
||||
projectId,
|
||||
undefined,
|
||||
);
|
||||
@ -252,7 +252,7 @@ test('should lookup projectId from data', async () => {
|
||||
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.CREATE_FEATURE,
|
||||
[perms.CREATE_FEATURE],
|
||||
projectId,
|
||||
undefined,
|
||||
);
|
||||
@ -279,7 +279,7 @@ test('Does not double check permission if not changing project when updating tog
|
||||
expect(accessService.hasPermission).toHaveBeenCalledTimes(1);
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.UPDATE_FEATURE,
|
||||
[perms.UPDATE_FEATURE],
|
||||
oldProjectId,
|
||||
undefined,
|
||||
);
|
||||
@ -303,7 +303,7 @@ test('UPDATE_TAG_TYPE does not need projectId', async () => {
|
||||
expect(accessService.hasPermission).toHaveBeenCalledTimes(1);
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.UPDATE_TAG_TYPE,
|
||||
[perms.UPDATE_TAG_TYPE],
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
@ -327,7 +327,7 @@ test('DELETE_TAG_TYPE does not need projectId', async () => {
|
||||
expect(accessService.hasPermission).toHaveBeenCalledTimes(1);
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.DELETE_TAG_TYPE,
|
||||
[perms.DELETE_TAG_TYPE],
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
@ -360,7 +360,7 @@ test('should not expect featureName for UPDATE_FEATURE when projectId specified'
|
||||
|
||||
expect(accessService.hasPermission).toHaveBeenCalledWith(
|
||||
req.user,
|
||||
perms.UPDATE_FEATURE,
|
||||
[perms.UPDATE_FEATURE],
|
||||
projectId,
|
||||
undefined,
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ import User from '../types/user';
|
||||
interface PermissionChecker {
|
||||
hasPermission(
|
||||
user: User,
|
||||
permission: string,
|
||||
permissions: string[],
|
||||
projectId?: string,
|
||||
environment?: string,
|
||||
): Promise<boolean>;
|
||||
@ -38,7 +38,11 @@ const rbacMiddleware = (
|
||||
logger.debug('Enabling RBAC middleware');
|
||||
|
||||
return (req, res, next) => {
|
||||
req.checkRbac = async (permission: string) => {
|
||||
req.checkRbac = async (permissions: string | string[]) => {
|
||||
const permissionsArray = Array.isArray(permissions)
|
||||
? permissions
|
||||
: [permissions];
|
||||
|
||||
const { user, params } = req;
|
||||
|
||||
if (!user) {
|
||||
@ -65,21 +69,26 @@ const rbacMiddleware = (
|
||||
// will be removed in Unleash v5.0
|
||||
if (
|
||||
!projectId &&
|
||||
[DELETE_FEATURE, UPDATE_FEATURE].includes(permission)
|
||||
permissionsArray.some((permission) =>
|
||||
[DELETE_FEATURE, UPDATE_FEATURE].includes(permission),
|
||||
)
|
||||
) {
|
||||
const { featureName } = params;
|
||||
projectId = await featureToggleStore.getProjectId(featureName);
|
||||
} else if (
|
||||
projectId === undefined &&
|
||||
(permission == CREATE_FEATURE ||
|
||||
permission.endsWith('FEATURE_STRATEGY'))
|
||||
permissionsArray.some(
|
||||
(permission) =>
|
||||
permission == CREATE_FEATURE ||
|
||||
permission.endsWith('FEATURE_STRATEGY'),
|
||||
)
|
||||
) {
|
||||
projectId = 'default';
|
||||
}
|
||||
|
||||
return accessService.hasPermission(
|
||||
user,
|
||||
permission,
|
||||
permissionsArray,
|
||||
projectId,
|
||||
environment,
|
||||
);
|
||||
|
@ -3,20 +3,12 @@ import { Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import {
|
||||
ADMIN,
|
||||
CREATE_ADMIN_API_TOKEN,
|
||||
CREATE_API_TOKEN,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
CREATE_FRONTEND_API_TOKEN,
|
||||
DELETE_ADMIN_API_TOKEN,
|
||||
DELETE_API_TOKEN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
READ_ADMIN_API_TOKEN,
|
||||
READ_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
UPDATE_ADMIN_API_TOKEN,
|
||||
UPDATE_API_TOKEN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
UPDATE_FRONTEND_API_TOKEN,
|
||||
} from '../../types/permissions';
|
||||
@ -58,7 +50,7 @@ const tokenTypeToCreatePermission: (tokenType: ApiTokenType) => string = (
|
||||
) => {
|
||||
switch (tokenType) {
|
||||
case ApiTokenType.ADMIN:
|
||||
return CREATE_ADMIN_API_TOKEN;
|
||||
return ADMIN;
|
||||
case ApiTokenType.CLIENT:
|
||||
return CREATE_CLIENT_API_TOKEN;
|
||||
case ApiTokenType.FRONTEND:
|
||||
@ -87,14 +79,7 @@ const permissionToTokenType: (
|
||||
].includes(permission)
|
||||
) {
|
||||
return ApiTokenType.CLIENT;
|
||||
} else if (
|
||||
[
|
||||
READ_ADMIN_API_TOKEN,
|
||||
CREATE_ADMIN_API_TOKEN,
|
||||
DELETE_ADMIN_API_TOKEN,
|
||||
UPDATE_ADMIN_API_TOKEN,
|
||||
].includes(permission)
|
||||
) {
|
||||
} else if (ADMIN === permission) {
|
||||
return ApiTokenType.ADMIN;
|
||||
} else {
|
||||
return undefined;
|
||||
@ -106,7 +91,7 @@ const tokenTypeToUpdatePermission: (tokenType: ApiTokenType) => string = (
|
||||
) => {
|
||||
switch (tokenType) {
|
||||
case ApiTokenType.ADMIN:
|
||||
return UPDATE_ADMIN_API_TOKEN;
|
||||
return ADMIN;
|
||||
case ApiTokenType.CLIENT:
|
||||
return UPDATE_CLIENT_API_TOKEN;
|
||||
case ApiTokenType.FRONTEND:
|
||||
@ -119,7 +104,7 @@ const tokenTypeToDeletePermission: (tokenType: ApiTokenType) => string = (
|
||||
) => {
|
||||
switch (tokenType) {
|
||||
case ApiTokenType.ADMIN:
|
||||
return DELETE_ADMIN_API_TOKEN;
|
||||
return ADMIN;
|
||||
case ApiTokenType.CLIENT:
|
||||
return DELETE_CLIENT_API_TOKEN;
|
||||
case ApiTokenType.FRONTEND:
|
||||
@ -164,7 +149,7 @@ export class ApiTokenController extends Controller {
|
||||
method: 'get',
|
||||
path: '',
|
||||
handler: this.getAllApiTokens,
|
||||
permission: READ_API_TOKEN,
|
||||
permission: [ADMIN, READ_CLIENT_API_TOKEN, READ_FRONTEND_API_TOKEN],
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['API tokens'],
|
||||
@ -180,7 +165,11 @@ export class ApiTokenController extends Controller {
|
||||
method: 'post',
|
||||
path: '',
|
||||
handler: this.createApiToken,
|
||||
permission: CREATE_API_TOKEN,
|
||||
permission: [
|
||||
ADMIN,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
CREATE_FRONTEND_API_TOKEN,
|
||||
],
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['API tokens'],
|
||||
@ -197,7 +186,11 @@ export class ApiTokenController extends Controller {
|
||||
method: 'put',
|
||||
path: '/:token',
|
||||
handler: this.updateApiToken,
|
||||
permission: UPDATE_API_TOKEN,
|
||||
permission: [
|
||||
ADMIN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
UPDATE_FRONTEND_API_TOKEN,
|
||||
],
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['API tokens'],
|
||||
@ -215,7 +208,11 @@ export class ApiTokenController extends Controller {
|
||||
path: '/:token',
|
||||
handler: this.deleteApiToken,
|
||||
acceptAnyContentType: true,
|
||||
permission: DELETE_API_TOKEN,
|
||||
permission: [
|
||||
ADMIN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
],
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['API tokens'],
|
||||
@ -355,7 +352,7 @@ export class ApiTokenController extends Controller {
|
||||
);
|
||||
|
||||
const allowedTokenTypes = [
|
||||
READ_ADMIN_API_TOKEN,
|
||||
ADMIN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
]
|
||||
|
@ -18,9 +18,11 @@ interface IRequestHandler<
|
||||
): Promise<void> | void;
|
||||
}
|
||||
|
||||
type Permission = string | string[];
|
||||
|
||||
interface IRouteOptionsBase {
|
||||
path: string;
|
||||
permission: string;
|
||||
permission: Permission;
|
||||
middleware?: RequestHandler[];
|
||||
handler: IRequestHandler;
|
||||
acceptedContentTypes?: string[];
|
||||
@ -37,15 +39,21 @@ interface IRouteOptionsNonGet extends IRouteOptionsBase {
|
||||
|
||||
type IRouteOptions = IRouteOptionsNonGet | IRouteOptionsGet;
|
||||
|
||||
const checkPermission = (permission) => async (req, res, next) => {
|
||||
if (!permission || permission === NONE) {
|
||||
return next();
|
||||
}
|
||||
if (req.checkRbac && (await req.checkRbac(permission))) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json(new NoAccessError(permission)).end();
|
||||
};
|
||||
const checkPermission =
|
||||
(permission: Permission = []) =>
|
||||
async (req, res, next) => {
|
||||
const permissions = (
|
||||
Array.isArray(permission) ? permission : [permission]
|
||||
).filter((p) => p !== NONE);
|
||||
|
||||
if (!permissions.length) {
|
||||
return next();
|
||||
}
|
||||
if (req.checkRbac && (await req.checkRbac(permissions))) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json(new NoAccessError(permissions)).end();
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for Controllers to standardize binding to express Router.
|
||||
@ -97,7 +105,11 @@ export default class Controller {
|
||||
);
|
||||
}
|
||||
|
||||
get(path: string, handler: IRequestHandler, permission?: string): void {
|
||||
get(
|
||||
path: string,
|
||||
handler: IRequestHandler,
|
||||
permission: Permission = NONE,
|
||||
): void {
|
||||
this.route({
|
||||
method: 'get',
|
||||
path,
|
||||
@ -109,7 +121,7 @@ export default class Controller {
|
||||
post(
|
||||
path: string,
|
||||
handler: IRequestHandler,
|
||||
permission: string,
|
||||
permission: Permission = NONE,
|
||||
...acceptedContentTypes: string[]
|
||||
): void {
|
||||
this.route({
|
||||
@ -124,7 +136,7 @@ export default class Controller {
|
||||
put(
|
||||
path: string,
|
||||
handler: IRequestHandler,
|
||||
permission: string,
|
||||
permission: Permission = NONE,
|
||||
...acceptedContentTypes: string[]
|
||||
): void {
|
||||
this.route({
|
||||
@ -139,7 +151,7 @@ export default class Controller {
|
||||
patch(
|
||||
path: string,
|
||||
handler: IRequestHandler,
|
||||
permission: string,
|
||||
permission: Permission = NONE,
|
||||
...acceptedContentTypes: string[]
|
||||
): void {
|
||||
this.route({
|
||||
@ -151,7 +163,11 @@ export default class Controller {
|
||||
});
|
||||
}
|
||||
|
||||
delete(path: string, handler: IRequestHandler, permission: string): void {
|
||||
delete(
|
||||
path: string,
|
||||
handler: IRequestHandler,
|
||||
permission: Permission = NONE,
|
||||
): void {
|
||||
this.route({
|
||||
method: 'delete',
|
||||
path,
|
||||
@ -165,7 +181,7 @@ export default class Controller {
|
||||
path: string,
|
||||
filehandler: IRequestHandler,
|
||||
handler: Function,
|
||||
permission: string,
|
||||
permission: Permission = NONE,
|
||||
): void {
|
||||
this.app.post(
|
||||
path,
|
||||
|
@ -120,12 +120,21 @@ export class AccessService {
|
||||
*/
|
||||
async hasPermission(
|
||||
user: Pick<IUser, 'id' | 'permissions' | 'isAPI'>,
|
||||
permission: string,
|
||||
permission: string | string[],
|
||||
projectId?: string,
|
||||
environment?: string,
|
||||
): Promise<boolean> {
|
||||
const permissionsArray = Array.isArray(permission)
|
||||
? permission
|
||||
: [permission];
|
||||
|
||||
const permissionLogInfo =
|
||||
permissionsArray.length === 1
|
||||
? `permission=${permissionsArray[0]}`
|
||||
: `permissions=[${permissionsArray.join(',')}]`;
|
||||
|
||||
this.logger.info(
|
||||
`Checking permission=${permission}, userId=${user.id}, projectId=${projectId}, environment=${environment}`,
|
||||
`Checking ${permissionLogInfo}, userId=${user.id}, projectId=${projectId}, environment=${environment}`,
|
||||
);
|
||||
|
||||
try {
|
||||
@ -145,11 +154,12 @@ export class AccessService {
|
||||
)
|
||||
.some(
|
||||
(p) =>
|
||||
p.permission === permission || p.permission === ADMIN,
|
||||
permissionsArray.includes(p.permission) ||
|
||||
p.permission === ADMIN,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
`Error checking permission=${permission}, userId=${user.id} projectId=${projectId}`,
|
||||
`Error checking ${permissionLogInfo}, userId=${user.id} projectId=${projectId}`,
|
||||
e,
|
||||
);
|
||||
return Promise.resolve(false);
|
||||
|
@ -1,40 +1,13 @@
|
||||
//Special
|
||||
// Special
|
||||
export const ADMIN = 'ADMIN';
|
||||
export const CLIENT = 'CLIENT';
|
||||
export const FRONTEND = 'FRONTEND';
|
||||
export const NONE = 'NONE';
|
||||
|
||||
export const CREATE_FEATURE = 'CREATE_FEATURE';
|
||||
export const UPDATE_FEATURE = 'UPDATE_FEATURE';
|
||||
export const DELETE_FEATURE = 'DELETE_FEATURE';
|
||||
export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY';
|
||||
export const UPDATE_FEATURE_STRATEGY = 'UPDATE_FEATURE_STRATEGY';
|
||||
export const DELETE_FEATURE_STRATEGY = 'DELETE_FEATURE_STRATEGY';
|
||||
export const UPDATE_FEATURE_ENVIRONMENT = 'UPDATE_FEATURE_ENVIRONMENT';
|
||||
export const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
||||
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||
export const CREATE_CONTEXT_FIELD = 'CREATE_CONTEXT_FIELD';
|
||||
export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||
export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
|
||||
export const CREATE_PROJECT = 'CREATE_PROJECT';
|
||||
export const UPDATE_PROJECT = 'UPDATE_PROJECT';
|
||||
export const DELETE_PROJECT = 'DELETE_PROJECT';
|
||||
// Root
|
||||
export const CREATE_ADDON = 'CREATE_ADDON';
|
||||
export const UPDATE_ADDON = 'UPDATE_ADDON';
|
||||
export const DELETE_ADDON = 'DELETE_ADDON';
|
||||
export const READ_ROLE = 'READ_ROLE';
|
||||
export const UPDATE_ROLE = 'UPDATE_ROLE';
|
||||
export const UPDATE_API_TOKEN = 'UPDATE_API_TOKEN';
|
||||
export const CREATE_API_TOKEN = 'CREATE_API_TOKEN';
|
||||
export const DELETE_API_TOKEN = 'DELETE_API_TOKEN';
|
||||
export const READ_API_TOKEN = 'READ_API_TOKEN';
|
||||
|
||||
export const UPDATE_ADMIN_API_TOKEN = 'UPDATE_ADMIN_API_TOKEN';
|
||||
export const CREATE_ADMIN_API_TOKEN = 'CREATE_ADMIN_API_TOKEN';
|
||||
export const DELETE_ADMIN_API_TOKEN = 'DELETE_ADMIN_API_TOKEN';
|
||||
export const READ_ADMIN_API_TOKEN = 'READ_ADMIN_API_TOKEN';
|
||||
|
||||
export const UPDATE_CLIENT_API_TOKEN = 'UPDATE_CLIENT_API_TOKEN';
|
||||
export const CREATE_CLIENT_API_TOKEN = 'CREATE_CLIENT_API_TOKEN';
|
||||
@ -46,22 +19,49 @@ export const CREATE_FRONTEND_API_TOKEN = 'CREATE_FRONTEND_API_TOKEN';
|
||||
export const DELETE_FRONTEND_API_TOKEN = 'DELETE_FRONTEND_API_TOKEN';
|
||||
export const READ_FRONTEND_API_TOKEN = 'READ_FRONTEND_API_TOKEN';
|
||||
|
||||
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
|
||||
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
|
||||
export const UPDATE_FEATURE_VARIANTS = 'UPDATE_FEATURE_VARIANTS';
|
||||
export const UPDATE_FEATURE_ENVIRONMENT_VARIANTS =
|
||||
'UPDATE_FEATURE_ENVIRONMENT_VARIANTS';
|
||||
export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE';
|
||||
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||
|
||||
export const CREATE_CONTEXT_FIELD = 'CREATE_CONTEXT_FIELD';
|
||||
export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||
export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
|
||||
|
||||
export const CREATE_PROJECT = 'CREATE_PROJECT';
|
||||
|
||||
export const READ_ROLE = 'READ_ROLE';
|
||||
|
||||
export const CREATE_SEGMENT = 'CREATE_SEGMENT';
|
||||
export const UPDATE_SEGMENT = 'UPDATE_SEGMENT';
|
||||
export const DELETE_SEGMENT = 'DELETE_SEGMENT';
|
||||
export const UPDATE_PROJECT_SEGMENT = 'UPDATE_PROJECT_SEGMENT';
|
||||
export const APPROVE_CHANGE_REQUEST = 'APPROVE_CHANGE_REQUEST';
|
||||
export const APPLY_CHANGE_REQUEST = 'APPLY_CHANGE_REQUEST';
|
||||
export const SKIP_CHANGE_REQUEST = 'SKIP_CHANGE_REQUEST';
|
||||
|
||||
export const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
||||
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||
|
||||
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
|
||||
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
|
||||
|
||||
// Project
|
||||
export const CREATE_FEATURE = 'CREATE_FEATURE';
|
||||
export const UPDATE_FEATURE = 'UPDATE_FEATURE';
|
||||
export const DELETE_FEATURE = 'DELETE_FEATURE';
|
||||
export const UPDATE_PROJECT = 'UPDATE_PROJECT';
|
||||
export const DELETE_PROJECT = 'DELETE_PROJECT';
|
||||
export const UPDATE_FEATURE_VARIANTS = 'UPDATE_FEATURE_VARIANTS';
|
||||
export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE';
|
||||
export const READ_PROJECT_API_TOKEN = 'READ_PROJECT_API_TOKEN';
|
||||
export const CREATE_PROJECT_API_TOKEN = 'CREATE_PROJECT_API_TOKEN';
|
||||
export const DELETE_PROJECT_API_TOKEN = 'DELETE_PROJECT_API_TOKEN';
|
||||
export const UPDATE_PROJECT_SEGMENT = 'UPDATE_PROJECT_SEGMENT';
|
||||
|
||||
export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY';
|
||||
export const UPDATE_FEATURE_STRATEGY = 'UPDATE_FEATURE_STRATEGY';
|
||||
export const DELETE_FEATURE_STRATEGY = 'DELETE_FEATURE_STRATEGY';
|
||||
export const UPDATE_FEATURE_ENVIRONMENT_VARIANTS =
|
||||
'UPDATE_FEATURE_ENVIRONMENT_VARIANTS';
|
||||
export const UPDATE_FEATURE_ENVIRONMENT = 'UPDATE_FEATURE_ENVIRONMENT';
|
||||
export const APPROVE_CHANGE_REQUEST = 'APPROVE_CHANGE_REQUEST';
|
||||
export const APPLY_CHANGE_REQUEST = 'APPLY_CHANGE_REQUEST';
|
||||
export const SKIP_CHANGE_REQUEST = 'SKIP_CHANGE_REQUEST';
|
||||
|
||||
export const ROOT_PERMISSION_CATEGORIES = [
|
||||
{
|
||||
@ -71,10 +71,14 @@ export const ROOT_PERMISSION_CATEGORIES = [
|
||||
{
|
||||
label: 'API token',
|
||||
permissions: [
|
||||
READ_API_TOKEN,
|
||||
CREATE_API_TOKEN,
|
||||
UPDATE_API_TOKEN,
|
||||
DELETE_API_TOKEN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
UPDATE_FRONTEND_API_TOKEN,
|
||||
CREATE_FRONTEND_API_TOKEN,
|
||||
DELETE_FRONTEND_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -95,7 +99,7 @@ export const ROOT_PERMISSION_CATEGORIES = [
|
||||
},
|
||||
{
|
||||
label: 'Role',
|
||||
permissions: [READ_ROLE, UPDATE_ROLE],
|
||||
permissions: [READ_ROLE],
|
||||
},
|
||||
{
|
||||
label: 'Segment',
|
||||
|
@ -0,0 +1,24 @@
|
||||
exports.up = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
UPDATE permissions SET display_name = 'Create CLIENT API tokens' WHERE permission = 'CREATE_CLIENT_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Update CLIENT API tokens' WHERE permission = 'UPDATE_CLIENT_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Delete CLIENT API tokens' WHERE permission = 'DELETE_CLIENT_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Read CLIENT API tokens' WHERE permission = 'READ_CLIENT_API_TOKEN';
|
||||
|
||||
UPDATE permissions SET display_name = 'Create FRONTEND API tokens' WHERE permission = 'CREATE_FRONTEND_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Update FRONTEND API tokens' WHERE permission = 'UPDATE_FRONTEND_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Delete FRONTEND API tokens' WHERE permission = 'DELETE_FRONTEND_API_TOKEN';
|
||||
UPDATE permissions SET display_name = 'Read FRONTEND API tokens' WHERE permission = 'READ_FRONTEND_API_TOKEN';
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
@ -4,15 +4,10 @@ import getLogger from '../../../fixtures/no-logger';
|
||||
import { ApiTokenType } from '../../../../lib/types/models/api-token';
|
||||
import { RoleName } from '../../../../lib/types/model';
|
||||
import {
|
||||
CREATE_API_TOKEN,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
DELETE_API_TOKEN,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
READ_ADMIN_API_TOKEN,
|
||||
READ_API_TOKEN,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
UPDATE_API_TOKEN,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
} from '../../../../lib/types';
|
||||
import { addDays } from 'date-fns';
|
||||
@ -196,10 +191,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
@ -251,10 +242,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
@ -306,10 +293,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
role.id,
|
||||
CREATE_CLIENT_API_TOKEN,
|
||||
@ -364,10 +347,6 @@ describe('Fine grained API token permissions', () => {
|
||||
type: 'root-custom',
|
||||
},
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
readFrontendApiToken.id,
|
||||
READ_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
readFrontendApiToken.id,
|
||||
READ_FRONTEND_API_TOKEN,
|
||||
@ -437,10 +416,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
readClientTokenRole.id,
|
||||
READ_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
readClientTokenRole.id,
|
||||
READ_CLIENT_API_TOKEN,
|
||||
@ -490,49 +465,23 @@ describe('Fine grained API token permissions', () => {
|
||||
});
|
||||
await destroy();
|
||||
});
|
||||
test('READ_ADMIN_API_TOKEN should be able to see ADMIN tokens', async () => {
|
||||
test('Admin users should be able to see all tokens', async () => {
|
||||
const preHook = (app, config, { userService, accessService }) => {
|
||||
app.use('/api/admin/', async (req, res, next) => {
|
||||
const role = await accessService.getRootRole(
|
||||
RoleName.VIEWER,
|
||||
RoleName.ADMIN,
|
||||
);
|
||||
const user = await userService.createUser({
|
||||
email: 'read_admin_token@example.com',
|
||||
rootRole: role.id,
|
||||
});
|
||||
req.user = user;
|
||||
const readAdminApiToken = await accessService.createRole({
|
||||
name: 'admin_token_reader',
|
||||
description: 'Can read admin tokens',
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
readAdminApiToken.id,
|
||||
READ_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
readAdminApiToken.id,
|
||||
READ_ADMIN_API_TOKEN,
|
||||
);
|
||||
await accessService.addUserToRole(
|
||||
user.id,
|
||||
readAdminApiToken.id,
|
||||
'default',
|
||||
);
|
||||
next();
|
||||
});
|
||||
};
|
||||
const { request, destroy } = await setupAppWithCustomAuth(
|
||||
stores,
|
||||
preHook,
|
||||
{
|
||||
experimental: {
|
||||
flags: {
|
||||
customRootRoles: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
await stores.apiTokenStore.insert({
|
||||
username: 'client',
|
||||
@ -555,8 +504,54 @@ describe('Fine grained API token permissions', () => {
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.tokens).toHaveLength(1);
|
||||
expect(res.body.tokens[0].type).toBe(ApiTokenType.ADMIN);
|
||||
expect(res.body.tokens).toHaveLength(3);
|
||||
});
|
||||
await destroy();
|
||||
});
|
||||
test('Editor users should be able to see all tokens except ADMIN tokens', async () => {
|
||||
const preHook = (app, config, { userService, accessService }) => {
|
||||
app.use('/api/admin/', async (req, res, next) => {
|
||||
const role = await accessService.getRootRole(
|
||||
RoleName.EDITOR,
|
||||
);
|
||||
const user = await userService.createUser({
|
||||
email: 'standard-editor-reads-tokens@example.com',
|
||||
rootRole: role.id,
|
||||
});
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
const { request, destroy } = await setupAppWithCustomAuth(
|
||||
stores,
|
||||
preHook,
|
||||
);
|
||||
await stores.apiTokenStore.insert({
|
||||
username: 'client',
|
||||
secret: 'client_secret_4321',
|
||||
type: ApiTokenType.CLIENT,
|
||||
});
|
||||
await stores.apiTokenStore.insert({
|
||||
username: 'admin',
|
||||
secret: 'admin_secret_4321',
|
||||
type: ApiTokenType.ADMIN,
|
||||
});
|
||||
await stores.apiTokenStore.insert({
|
||||
username: 'frontender',
|
||||
secret: 'frontend_secret_4321',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
});
|
||||
await request
|
||||
.get('/api/admin/api-tokens')
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.tokens).toHaveLength(2);
|
||||
expect(
|
||||
res.body.tokens.filter(
|
||||
({ type }) => type === ApiTokenType.ADMIN,
|
||||
),
|
||||
).toHaveLength(0);
|
||||
});
|
||||
await destroy();
|
||||
});
|
||||
@ -585,10 +580,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
@ -645,10 +636,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
@ -706,10 +693,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
UPDATE_CLIENT_API_TOKEN,
|
||||
@ -770,10 +753,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
@ -830,10 +809,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
@ -890,10 +865,6 @@ describe('Fine grained API token permissions', () => {
|
||||
permissions: [],
|
||||
type: 'root-custom',
|
||||
});
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_API_TOKEN,
|
||||
);
|
||||
await accessService.addPermissionToRole(
|
||||
updateClientApiExpiry.id,
|
||||
DELETE_CLIENT_API_TOKEN,
|
||||
|
Loading…
Reference in New Issue
Block a user