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:
parent
2909360e7c
commit
acc05a4b84
@ -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();
|
||||
});
|
||||
|
@ -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 = (
|
||||
|
Loading…
Reference in New Issue
Block a user