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 { 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<IPermission>) => {
splash: {},
});
};
const userIsMemberOfProjects = (projects: string[]) => {
userHasPermissions(
projects.map(project => ({
project,
environment: 'irrelevant',
permission: 'irrelevant',
}))
);
};
const featureEnvironments = (
feature: string,
@ -156,6 +174,7 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
path,
pathTemplate,
}) => (
<SWRConfig value={{ provider: () => new Map() }}>
<UIProviderContainer>
<AccessProvider>
<MemoryRouter initialEntries={[path]}>
@ -169,6 +188,7 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
</MemoryRouter>
</AccessProvider>
</UIProviderContainer>
</SWRConfig>
);
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(
<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 {
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 = (