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

feat: add CREATE_TAG_TYPE permission (#5386)

https://linear.app/unleash/issue/2-1164/update-tag-type-covers-both-creation-and-update

Adds a new `CREATE_TAG_TYPE` permission instead of using
`UPDATE_TAG_TYPE` for both actions.
This commit is contained in:
Nuno Góis 2023-11-22 10:20:19 +00:00 committed by GitHub
parent fac2578922
commit 5dc3e830a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 63 additions and 13 deletions

View File

@ -224,7 +224,7 @@ export const ManageBulkTagsDialog: VFC<IManageBulkTagsDialogProps> = ({
open={open} open={open}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
primaryButtonText='Save tags' primaryButtonText='Save tags'
title='Update tags to feature toggle' title='Update feature toggle tags'
onClick={() => onSubmit(payload)} onClick={() => onSubmit(payload)}
disabledPrimaryButton={ disabledPrimaryButton={
payload.addedTags.length === 0 && payload.addedTags.length === 0 &&

View File

@ -248,7 +248,7 @@ export const ManageTagsDialog = ({ open, setOpen }: IManageTagsProps) => {
open={open} open={open}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
primaryButtonText='Save tags' primaryButtonText='Save tags'
title='Update tags to feature toggle' title='Update feature toggle tags'
onClick={onSubmit} onClick={onSubmit}
disabledPrimaryButton={loading || differenceCount === 0} disabledPrimaryButton={loading || differenceCount === 0}
onClose={onCancel} onClose={onCancel}

View File

@ -13,8 +13,9 @@ export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
export const CREATE_PROJECT = 'CREATE_PROJECT'; export const CREATE_PROJECT = 'CREATE_PROJECT';
export const UPDATE_PROJECT = 'UPDATE_PROJECT'; export const UPDATE_PROJECT = 'UPDATE_PROJECT';
export const DELETE_PROJECT = 'DELETE_PROJECT'; export const DELETE_PROJECT = 'DELETE_PROJECT';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE'; export const CREATE_TAG_TYPE = 'CREATE_TAG_TYPE';
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE'; export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
export const CREATE_ADDON = 'CREATE_ADDON'; export const CREATE_ADDON = 'CREATE_ADDON';
export const UPDATE_ADDON = 'UPDATE_ADDON'; export const UPDATE_ADDON = 'UPDATE_ADDON';
export const DELETE_ADDON = 'DELETE_ADDON'; export const DELETE_ADDON = 'DELETE_ADDON';

View File

@ -3,7 +3,7 @@ import useTagTypeForm from '../TagTypeForm/useTagTypeForm';
import TagTypeForm from '../TagTypeForm/TagTypeForm'; import TagTypeForm from '../TagTypeForm/TagTypeForm';
import { CreateButton } from 'component/common/CreateButton/CreateButton'; import { CreateButton } from 'component/common/CreateButton/CreateButton';
import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { UPDATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions'; import { CREATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions';
import useTagTypesApi from 'hooks/api/actions/useTagTypesApi/useTagTypesApi'; import useTagTypesApi from 'hooks/api/actions/useTagTypesApi/useTagTypesApi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
@ -78,7 +78,7 @@ const CreateTagType = () => {
clearErrors={clearErrors} clearErrors={clearErrors}
validateNameUniqueness={validateNameUniqueness} validateNameUniqueness={validateNameUniqueness}
> >
<CreateButton name='type' permission={UPDATE_TAG_TYPE} /> <CreateButton name='type' permission={CREATE_TAG_TYPE} />
</TagTypeForm> </TagTypeForm>
</FormTemplate> </FormTemplate>
); );

View File

@ -1,7 +1,7 @@
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions'; import { CREATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions';
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -18,14 +18,14 @@ export const AddTagTypeButton = () => {
<PermissionIconButton <PermissionIconButton
onClick={() => navigate('/tag-types/create')} onClick={() => navigate('/tag-types/create')}
size='large' size='large'
permission={UPDATE_TAG_TYPE} permission={CREATE_TAG_TYPE}
> >
<Add /> <Add />
</PermissionIconButton> </PermissionIconButton>
} }
elseShow={ elseShow={
<PermissionButton <PermissionButton
permission={UPDATE_TAG_TYPE} permission={CREATE_TAG_TYPE}
onClick={() => navigate('/tag-types/create')} onClick={() => navigate('/tag-types/create')}
> >
New tag type New tag type

View File

@ -373,11 +373,11 @@ test('validate import data', async () => {
affectedItems: [ affectedItems: [
'Create feature toggles', 'Create feature toggles',
'Update feature toggles', 'Update feature toggles',
'Update tag types',
'Create context fields', 'Create context fields',
'Create activation strategies', 'Create activation strategies',
'Delete activation strategies', 'Delete activation strategies',
'Update variants', 'Update variants',
'Create tag types',
], ],
}, },
], ],

View File

@ -11,6 +11,7 @@ import {
UPDATE_FEATURE, UPDATE_FEATURE,
UPDATE_FEATURE_ENVIRONMENT_VARIANTS, UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
UPDATE_TAG_TYPE, UPDATE_TAG_TYPE,
CREATE_TAG_TYPE,
} from '../../types'; } from '../../types';
import { PermissionError } from '../../error'; import { PermissionError } from '../../error';
@ -94,7 +95,7 @@ export class ImportPermissionsService {
]); ]);
const permissions = [UPDATE_FEATURE]; const permissions = [UPDATE_FEATURE];
if (newTagTypes.length > 0) { if (newTagTypes.length > 0) {
permissions.push(UPDATE_TAG_TYPE); permissions.push(CREATE_TAG_TYPE);
} }
if (Array.isArray(newContextFields) && newContextFields.length > 0) { if (Array.isArray(newContextFields) && newContextFields.length > 0) {
permissions.push(CREATE_CONTEXT_FIELD); permissions.push(CREATE_CONTEXT_FIELD);

View File

@ -329,6 +329,34 @@ test('Does not double check permission if not changing project when updating tog
); );
}); });
test('CREATE_TAG_TYPE does not need projectId', async () => {
const accessService = {
hasPermission: jest.fn().mockReturnValue(true),
};
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new User({ username: 'user', id: 1 }),
params: {},
body: { name: 'new-tag-type', description: 'New tag type for testing' },
};
func(req, undefined, cb);
await req.checkRbac(perms.CREATE_TAG_TYPE);
expect(accessService.hasPermission).toHaveBeenCalledTimes(1);
expect(accessService.hasPermission).toHaveBeenCalledWith(
req.user,
[perms.CREATE_TAG_TYPE],
undefined,
undefined,
);
});
test('UPDATE_TAG_TYPE does not need projectId', async () => { test('UPDATE_TAG_TYPE does not need projectId', async () => {
const accessService = { const accessService = {
hasPermission: jest.fn().mockReturnValue(true), hasPermission: jest.fn().mockReturnValue(true),

View File

@ -2,6 +2,7 @@ import { Request, Response } from 'express';
import Controller from '../controller'; import Controller from '../controller';
import { import {
CREATE_TAG_TYPE,
DELETE_TAG_TYPE, DELETE_TAG_TYPE,
NONE, NONE,
UPDATE_TAG_TYPE, UPDATE_TAG_TYPE,
@ -72,7 +73,7 @@ class TagTypeController extends Controller {
method: 'post', method: 'post',
path: '', path: '',
handler: this.createTagType, handler: this.createTagType,
permission: UPDATE_TAG_TYPE, permission: CREATE_TAG_TYPE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['Tags'], tags: ['Tags'],
@ -91,7 +92,7 @@ class TagTypeController extends Controller {
method: 'post', method: 'post',
path: '/validate', path: '/validate',
handler: this.validateTagType, handler: this.validateTagType,
permission: UPDATE_TAG_TYPE, permission: NONE,
middleware: [ middleware: [
openApiService.validPath({ openApiService.validPath({
tags: ['Tags'], tags: ['Tags'],

View File

@ -37,6 +37,7 @@ export const CREATE_STRATEGY = 'CREATE_STRATEGY';
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY'; export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
export const DELETE_STRATEGY = 'DELETE_STRATEGY'; export const DELETE_STRATEGY = 'DELETE_STRATEGY';
export const CREATE_TAG_TYPE = 'CREATE_TAG_TYPE';
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE'; export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE'; export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
@ -112,6 +113,6 @@ export const ROOT_PERMISSION_CATEGORIES = [
}, },
{ {
label: 'Tag type', label: 'Tag type',
permissions: [UPDATE_TAG_TYPE, DELETE_TAG_TYPE], permissions: [CREATE_TAG_TYPE, UPDATE_TAG_TYPE, DELETE_TAG_TYPE],
}, },
]; ];

View File

@ -0,0 +1,18 @@
exports.up = function (db, cb) {
db.runSql(
`
INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_TAG_TYPE', 'Create tag types', 'root');
SELECT assign_unleash_permission_to_role('CREATE_TAG_TYPE', 'Editor');
`,
cb
);
};
exports.down = function (db, cb) {
db.runSql(
`
DELETE FROM permissions WHERE permission = 'CREATE_TAG_TYPE';
`,
cb
);
};