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

feat: verify project collaboration mode in CR permissions for buttons (#3349)

This commit is contained in:
Mateusz Kwasniewski 2023-03-20 14:50:50 +01:00 committed by GitHub
parent 2909360e7c
commit acc05a4b84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 34 deletions

View File

@ -8,9 +8,14 @@ import { testServerRoute, testServerSetup } from '../../utils/testServer';
import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer'; import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer';
import { FC } from 'react'; import { FC } from 'react';
import { IPermission } from '../../interfaces/user'; import { IPermission } from '../../interfaces/user';
import { ProjectMode } from '../project/Project/hooks/useProjectForm';
import { SWRConfig } from 'swr';
const server = testServerSetup(); const server = testServerSetup();
const projectWithCollaborationMode = (mode: ProjectMode) =>
testServerRoute(server, '/api/admin/projects/default', { mode });
const changeRequestsEnabledIn = ( const changeRequestsEnabledIn = (
env: 'development' | 'production' | 'custom' env: 'development' | 'production' | 'custom'
) => ) =>
@ -57,7 +62,6 @@ const setupOtherRoutes = (feature: string) => {
'api/admin/projects/default/change-requests/pending', 'api/admin/projects/default/change-requests/pending',
[] []
); );
testServerRoute(server, '/api/admin/projects/default', {});
testServerRoute(server, `api/admin/client-metrics/features/${feature}`, { testServerRoute(server, `api/admin/client-metrics/features/${feature}`, {
version: 1, version: 1,
maturity: 'stable', maturity: 'stable',
@ -69,6 +73,11 @@ const setupOtherRoutes = (feature: string) => {
version: 1, version: 1,
tags: [], tags: [],
}); });
testServerRoute(server, `api/admin/tags/simple`, {
version: 1,
tags: [],
});
testServerRoute(server, `api/admin/strategies`, { testServerRoute(server, `api/admin/strategies`, {
version: 1, version: 1,
strategies: [ strategies: [
@ -119,6 +128,15 @@ const userHasPermissions = (permissions: Array<IPermission>) => {
splash: {}, splash: {},
}); });
}; };
const userIsMemberOfProjects = (projects: string[]) => {
userHasPermissions(
projects.map(project => ({
project,
environment: 'irrelevant',
permission: 'irrelevant',
}))
);
};
const featureEnvironments = ( const featureEnvironments = (
feature: string, feature: string,
@ -156,19 +174,21 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
path, path,
pathTemplate, pathTemplate,
}) => ( }) => (
<UIProviderContainer> <SWRConfig value={{ provider: () => new Map() }}>
<AccessProvider> <UIProviderContainer>
<MemoryRouter initialEntries={[path]}> <AccessProvider>
<ThemeProvider> <MemoryRouter initialEntries={[path]}>
<AnnouncerProvider> <ThemeProvider>
<Routes> <AnnouncerProvider>
<Route path={pathTemplate} element={children} /> <Routes>
</Routes> <Route path={pathTemplate} element={children} />
</AnnouncerProvider> </Routes>
</ThemeProvider> </AnnouncerProvider>
</MemoryRouter> </ThemeProvider>
</AccessProvider> </MemoryRouter>
</UIProviderContainer> </AccessProvider>
</UIProviderContainer>
</SWRConfig>
); );
const strategiesAreDisplayed = async ( 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 copyButtonsActiveInOtherEnv = async () => {
const copyButtons = screen.getAllByTestId('STRATEGY_FORM_COPY_ID'); const copyButtons = screen.getAllByTestId('STRATEGY_FORM_COPY_ID');
expect(copyButtons.length).toBe(2); expect(copyButtons.length).toBe(2);
@ -209,7 +246,7 @@ const copyButtonsActiveInOtherEnv = async () => {
expect(customEnvStrategyCopyButton).not.toBeDisabled(); 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 project = 'default';
const featureName = 'test'; const featureName = 'test';
featureEnvironments(featureName, [ featureEnvironments(featureName, [
@ -217,14 +254,9 @@ test('user without priviledges can only perform change requests actions', async
{ name: 'production', strategies: ['userWithId'] }, { name: 'production', strategies: ['userWithId'] },
{ name: 'custom', strategies: ['default'] }, { name: 'custom', strategies: ['default'] },
]); ]);
userHasPermissions([ userIsMemberOfProjects([]);
{
project,
environment: 'production',
permission: 'APPLY_CHANGE_REQUEST',
},
]);
changeRequestsEnabledIn('production'); changeRequestsEnabledIn('production');
projectWithCollaborationMode('open');
uiConfigForEnterprise(); uiConfigForEnterprise();
setupOtherRoutes(featureName); setupOtherRoutes(featureName);
@ -241,3 +273,59 @@ test('user without priviledges can only perform change requests actions', async
await deleteButtonsActiveInChangeRequestEnv(); await deleteButtonsActiveInChangeRequestEnv();
await copyButtonsActiveInOtherEnv(); 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(
<UnleashUiSetup
pathTemplate="/projects/:projectId/features/:featureId/*"
path={`/projects/${project}/features/${featureName}`}
>
<FeatureView />
</UnleashUiSetup>
);
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(
<UnleashUiSetup
pathTemplate="/projects/:projectId/features/:featureId/*"
path={`/projects/${project}/features/${featureName}`}
>
<FeatureView />
</UnleashUiSetup>
);
await strategiesAreDisplayed('UserIDs', 'Standard');
await deleteButtonsInactiveInChangeRequestEnv();
await copyButtonsActiveInOtherEnv();
});

View File

@ -3,11 +3,13 @@ import AccessContext from '../contexts/AccessContext';
import { useChangeRequestsEnabled } from './useChangeRequestsEnabled'; import { useChangeRequestsEnabled } from './useChangeRequestsEnabled';
import { import {
CREATE_FEATURE_STRATEGY, CREATE_FEATURE_STRATEGY,
UPDATE_FEATURE_STRATEGY,
DELETE_FEATURE_STRATEGY, DELETE_FEATURE_STRATEGY,
UPDATE_FEATURE_ENVIRONMENT, UPDATE_FEATURE_ENVIRONMENT,
UPDATE_FEATURE_ENVIRONMENT_VARIANTS, UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
UPDATE_FEATURE_STRATEGY,
} from '../component/providers/AccessProvider/permissions'; } 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. * 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; 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 * 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 * If you know what you're doing you can skip that check and call useCheckProjectAccess
@ -87,19 +128,14 @@ export const useHasProjectEnvironmentAccess = (
projectId: string, projectId: string,
environmentId: string environmentId: string
) => { ) => {
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const checkAccess = useCheckProjectPermissions(projectId); const checkAccess = useCheckProjectPermissions(projectId);
const changeRequestMode = isChangeRequestConfigured(environmentId); const isAllowedForChangeRequest = useIsAllowedForChangeRequest(
const emptyArray: string[] = []; permission,
projectId,
return ( environmentId
(changeRequestMode &&
intersect(
ALLOWED_CHANGE_REQUEST_PERMISSIONS,
emptyArray.concat(permission)
)) ||
checkAccess(permission, environmentId)
); );
return isAllowedForChangeRequest || checkAccess(permission, environmentId);
}; };
export const useHasRootAccess = ( export const useHasRootAccess = (