1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

fix: project tokens can now be created with the correct permissions (#4165)

This commit is contained in:
Simon Hornby 2023-07-06 15:47:03 +02:00 committed by GitHub
parent d7b7d93533
commit 79dd508485
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 6 deletions

View File

@ -7,6 +7,7 @@ import {
ADMIN, ADMIN,
CREATE_FRONTEND_API_TOKEN, CREATE_FRONTEND_API_TOKEN,
CREATE_CLIENT_API_TOKEN, CREATE_CLIENT_API_TOKEN,
CREATE_PROJECT_API_TOKEN,
} from '@server/types/permissions'; } from '@server/types/permissions';
import { useHasRootAccess } from 'hooks/useHasAccess'; import { useHasRootAccess } from 'hooks/useHasAccess';
import { SelectOption } from './TokenTypeSelector/TokenTypeSelector'; import { SelectOption } from './TokenTypeSelector/TokenTypeSelector';
@ -17,17 +18,28 @@ export const useApiTokenForm = (project?: string) => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const initialEnvironment = environments?.find(e => e.enabled)?.name; const initialEnvironment = environments?.find(e => e.enabled)?.name;
const hasCreateTokenPermission = useHasRootAccess(CREATE_CLIENT_API_TOKEN);
const hasCreateProjectTokenPermission = useHasRootAccess(
CREATE_PROJECT_API_TOKEN,
project
);
const apiTokenTypes: SelectOption[] = [ const apiTokenTypes: SelectOption[] = [
{ {
key: TokenType.CLIENT, key: TokenType.CLIENT,
label: `Server-side SDK (${TokenType.CLIENT})`, label: `Server-side SDK (${TokenType.CLIENT})`,
title: 'Connect server-side SDK or Unleash Proxy', title: 'Connect server-side SDK or Unleash Proxy',
enabled: useHasRootAccess(CREATE_CLIENT_API_TOKEN), enabled:
hasCreateTokenPermission || hasCreateProjectTokenPermission,
}, },
]; ];
const hasAdminAccess = useHasRootAccess(ADMIN); const hasAdminAccess = useHasRootAccess(ADMIN);
const hasCreateFrontendAccess = useHasRootAccess(CREATE_FRONTEND_API_TOKEN); const hasCreateFrontendAccess = useHasRootAccess(CREATE_FRONTEND_API_TOKEN);
const hasCreateFrontendTokenAccess = useHasRootAccess(
CREATE_PROJECT_API_TOKEN,
project
);
if (!project) { if (!project) {
apiTokenTypes.push({ apiTokenTypes.push({
key: TokenType.ADMIN, key: TokenType.ADMIN,
@ -42,7 +54,7 @@ export const useApiTokenForm = (project?: string) => {
key: TokenType.FRONTEND, key: TokenType.FRONTEND,
label: `Client-side SDK (${TokenType.FRONTEND})`, label: `Client-side SDK (${TokenType.FRONTEND})`,
title: 'Connect web and mobile SDK directly to Unleash', title: 'Connect web and mobile SDK directly to Unleash',
enabled: hasCreateFrontendAccess, enabled: hasCreateFrontendAccess || hasCreateFrontendTokenAccess,
}); });
} }

View File

@ -34,7 +34,6 @@ import { Response } from 'express';
import { timingSafeEqual } from 'crypto'; import { timingSafeEqual } from 'crypto';
import { createApiToken } from '../../../schema/api-token-schema'; import { createApiToken } from '../../../schema/api-token-schema';
import { OperationDeniedError } from '../../../error'; import { OperationDeniedError } from '../../../error';
import { tokenTypeToCreatePermission } from '../api-token';
interface ProjectTokenParam { interface ProjectTokenParam {
token: string; token: string;
@ -159,9 +158,7 @@ export class ProjectApiTokenController extends Controller {
): Promise<any> { ): Promise<any> {
const createToken = await createApiToken.validateAsync(req.body); const createToken = await createApiToken.validateAsync(req.body);
const { projectId } = req.params; const { projectId } = req.params;
const permissionRequired = tokenTypeToCreatePermission( const permissionRequired = CREATE_PROJECT_API_TOKEN;
createToken.type,
);
const hasPermission = await this.accessService.hasPermission( const hasPermission = await this.accessService.hasPermission(
req.user, req.user,
permissionRequired, permissionRequired,

View File

@ -5,6 +5,7 @@ import { ApiTokenType } from '../../../../lib/types/models/api-token';
import { RoleName } from '../../../../lib/types/model'; import { RoleName } from '../../../../lib/types/model';
import { import {
CREATE_CLIENT_API_TOKEN, CREATE_CLIENT_API_TOKEN,
CREATE_PROJECT_API_TOKEN,
DELETE_CLIENT_API_TOKEN, DELETE_CLIENT_API_TOKEN,
READ_CLIENT_API_TOKEN, READ_CLIENT_API_TOKEN,
READ_FRONTEND_API_TOKEN, READ_FRONTEND_API_TOKEN,
@ -171,6 +172,60 @@ test('Token-admin should be allowed to create token', async () => {
await destroy(); await destroy();
}); });
test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', async () => {
expect.assertions(0);
const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.VIEWER);
const user = await userService.createUser({
email: 'powerpuffgirls_viewer@example.com',
rootRole: role.id,
});
req.user = user;
const createClientApiTokenRole = await accessService.createRole({
name: 'project_client_token_creator',
description: 'Can create client tokens',
permissions: [],
type: 'root-custom',
});
await accessService.addPermissionToRole(
role.id,
CREATE_PROJECT_API_TOKEN,
);
await accessService.addUserToRole(
user.id,
createClientApiTokenRole.id,
'default',
);
req.user = await userService.createUser({
email: 'someguyinplaces@example.com',
rootRole: role.id,
});
next();
});
};
const { request, destroy } = await setupAppWithCustomAuth(stores, preHook, {
experimental: {
flags: {
customRootRoles: true,
},
},
});
await request
.post('/api/admin/projects/default/api-tokens')
.send({
username: 'client-token-maker',
type: 'client',
projects: ['default'],
})
.set('Content-Type', 'application/json')
.expect(201);
await destroy();
});
describe('Fine grained API token permissions', () => { describe('Fine grained API token permissions', () => {
describe('A role with access to CREATE_CLIENT_API_TOKEN', () => { describe('A role with access to CREATE_CLIENT_API_TOKEN', () => {
test('should be allowed to create client tokens', async () => { test('should be allowed to create client tokens', async () => {