diff --git a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx index 002d4784a9..bfb3c63958 100644 --- a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx @@ -8,9 +8,14 @@ import { testServerRoute, testServerSetup } from '../../utils/testServer'; import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer'; import { FC } from 'react'; import { IPermission } from '../../interfaces/user'; +import { ProjectMode } from '../project/Project/hooks/useProjectForm'; +import { SWRConfig } from 'swr'; const server = testServerSetup(); +const projectWithCollaborationMode = (mode: ProjectMode) => + testServerRoute(server, '/api/admin/projects/default', { mode }); + const changeRequestsEnabledIn = ( env: 'development' | 'production' | 'custom' ) => @@ -57,7 +62,6 @@ const setupOtherRoutes = (feature: string) => { 'api/admin/projects/default/change-requests/pending', [] ); - testServerRoute(server, '/api/admin/projects/default', {}); testServerRoute(server, `api/admin/client-metrics/features/${feature}`, { version: 1, maturity: 'stable', @@ -69,6 +73,11 @@ const setupOtherRoutes = (feature: string) => { version: 1, tags: [], }); + testServerRoute(server, `api/admin/tags/simple`, { + version: 1, + tags: [], + }); + testServerRoute(server, `api/admin/strategies`, { version: 1, strategies: [ @@ -119,6 +128,15 @@ const userHasPermissions = (permissions: Array) => { splash: {}, }); }; +const userIsMemberOfProjects = (projects: string[]) => { + userHasPermissions( + projects.map(project => ({ + project, + environment: 'irrelevant', + permission: 'irrelevant', + })) + ); +}; const featureEnvironments = ( feature: string, @@ -156,19 +174,21 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({ path, pathTemplate, }) => ( - - - - - - - - - - - - - + new Map() }}> + + + + + + + + + + + + + + ); const strategiesAreDisplayed = async ( @@ -196,6 +216,23 @@ const deleteButtonsActiveInChangeRequestEnv = async () => { }); }; +const deleteButtonsInactiveInChangeRequestEnv = async () => { + const deleteButtons = screen.getAllByTestId('STRATEGY_FORM_REMOVE_ID'); + expect(deleteButtons.length).toBe(2); + + // wait for change request config to be loaded + await waitFor(() => { + // production + const productionStrategyDeleteButton = deleteButtons[0]; + expect(productionStrategyDeleteButton).toBeDisabled(); + }); + await waitFor(() => { + // custom env + const customEnvStrategyDeleteButton = deleteButtons[1]; + expect(customEnvStrategyDeleteButton).toBeDisabled(); + }); +}; + const copyButtonsActiveInOtherEnv = async () => { const copyButtons = screen.getAllByTestId('STRATEGY_FORM_COPY_ID'); expect(copyButtons.length).toBe(2); @@ -209,7 +246,7 @@ const copyButtonsActiveInOtherEnv = async () => { expect(customEnvStrategyCopyButton).not.toBeDisabled(); }; -test('user without priviledges can only perform change requests actions', async () => { +test('open mode + non-project member can perform basic change request actions', async () => { const project = 'default'; const featureName = 'test'; featureEnvironments(featureName, [ @@ -217,14 +254,9 @@ test('user without priviledges can only perform change requests actions', async { name: 'production', strategies: ['userWithId'] }, { name: 'custom', strategies: ['default'] }, ]); - userHasPermissions([ - { - project, - environment: 'production', - permission: 'APPLY_CHANGE_REQUEST', - }, - ]); + userIsMemberOfProjects([]); changeRequestsEnabledIn('production'); + projectWithCollaborationMode('open'); uiConfigForEnterprise(); setupOtherRoutes(featureName); @@ -241,3 +273,59 @@ test('user without priviledges can only perform change requests actions', async await deleteButtonsActiveInChangeRequestEnv(); await copyButtonsActiveInOtherEnv(); }); + +test('protected mode + project member can perform basic change request actions', async () => { + const project = 'default'; + const featureName = 'test'; + featureEnvironments(featureName, [ + { name: 'development', strategies: [] }, + { name: 'production', strategies: ['userWithId'] }, + { name: 'custom', strategies: ['default'] }, + ]); + userIsMemberOfProjects([project]); + changeRequestsEnabledIn('production'); + projectWithCollaborationMode('protected'); + uiConfigForEnterprise(); + setupOtherRoutes(featureName); + + render( + + + + ); + + await strategiesAreDisplayed('UserIDs', 'Standard'); + await deleteButtonsActiveInChangeRequestEnv(); + await copyButtonsActiveInOtherEnv(); +}); + +test('protected mode + non-project member cannot perform basic change request actions', async () => { + const project = 'default'; + const featureName = 'test'; + featureEnvironments(featureName, [ + { name: 'development', strategies: [] }, + { name: 'production', strategies: ['userWithId'] }, + { name: 'custom', strategies: ['default'] }, + ]); + userIsMemberOfProjects([]); + changeRequestsEnabledIn('production'); + projectWithCollaborationMode('protected'); + uiConfigForEnterprise(); + setupOtherRoutes(featureName); + + render( + + + + ); + + await strategiesAreDisplayed('UserIDs', 'Standard'); + await deleteButtonsInactiveInChangeRequestEnv(); + await copyButtonsActiveInOtherEnv(); +}); diff --git a/frontend/src/hooks/useHasAccess.ts b/frontend/src/hooks/useHasAccess.ts index 1a802c0ea8..87c65650b3 100644 --- a/frontend/src/hooks/useHasAccess.ts +++ b/frontend/src/hooks/useHasAccess.ts @@ -3,11 +3,13 @@ import AccessContext from '../contexts/AccessContext'; import { useChangeRequestsEnabled } from './useChangeRequestsEnabled'; import { CREATE_FEATURE_STRATEGY, - UPDATE_FEATURE_STRATEGY, DELETE_FEATURE_STRATEGY, UPDATE_FEATURE_ENVIRONMENT, UPDATE_FEATURE_ENVIRONMENT_VARIANTS, + UPDATE_FEATURE_STRATEGY, } from '../component/providers/AccessProvider/permissions'; +import { useAuthPermissions } from './api/getters/useAuth/useAuthPermissions'; +import useProject from './api/getters/useProject/useProject'; /** * This is for features not integrated with change request. @@ -78,6 +80,45 @@ const intersect = (array1: string[], array2: string[]) => { return array1.filter(value => array2.includes(value)).length > 0; }; +const useIsProjectMember = (projectId: string) => { + const { permissions } = useAuthPermissions(); + const isProjectMember = permissions + ? permissions.find(permission => permission.project === projectId) + : false; + return isProjectMember; +}; + +const useIsAllowedUser = (projectId: string) => { + const isProjectMember = useIsProjectMember(projectId); + const { project } = useProject(projectId); + const isProtectedProject = project.mode === 'protected'; + return isProtectedProject ? isProjectMember : true; +}; + +const isChangeRequestPermission = (permission: string | string[]) => { + const emptyArray: string[] = []; + return intersect( + ALLOWED_CHANGE_REQUEST_PERMISSIONS, + emptyArray.concat(permission) + ); +}; + +const useIsAllowedForChangeRequest = ( + permission: string | string[], + projectId: string, + environmentId: string +) => { + const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); + const isChangeRequestMode = isChangeRequestConfigured(environmentId); + const isAllowedMember = useIsAllowedUser(projectId); + + return ( + isChangeRequestMode && + isAllowedMember && + isChangeRequestPermission(permission) + ); +}; + /** * This methods does the same as useCheckProjectAccess but also checks if the permission is in ALLOWED_CHANGE_REQUEST_PERMISSIONS * If you know what you're doing you can skip that check and call useCheckProjectAccess @@ -87,19 +128,14 @@ export const useHasProjectEnvironmentAccess = ( projectId: string, environmentId: string ) => { - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const checkAccess = useCheckProjectPermissions(projectId); - const changeRequestMode = isChangeRequestConfigured(environmentId); - const emptyArray: string[] = []; - - return ( - (changeRequestMode && - intersect( - ALLOWED_CHANGE_REQUEST_PERMISSIONS, - emptyArray.concat(permission) - )) || - checkAccess(permission, environmentId) + const isAllowedForChangeRequest = useIsAllowedForChangeRequest( + permission, + projectId, + environmentId ); + + return isAllowedForChangeRequest || checkAccess(permission, environmentId); }; export const useHasRootAccess = (