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 { 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();
|
||||||
|
});
|
||||||
|
@ -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 = (
|
||||||
|
Loading…
Reference in New Issue
Block a user