Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Spaces and Roles] Fix rules on showing Permissions tab (#196442) #196937

Open
wants to merge 1 commit into
base: 8.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,14 @@ export const EditSpace: FC<PageProps> = ({
}) => {
const { state, dispatch } = useEditSpaceStore();
const { invokeClient } = useEditSpaceServices();
const { spacesManager, capabilities, serverBasePath, logger, notifications } =
useEditSpaceServices();
const {
spacesManager,
capabilities,
serverBasePath,
logger,
notifications,
isRoleManagementEnabled,
} = useEditSpaceServices();
const [space, setSpace] = useState<Space | null>(null);
const [userActiveSpace, setUserActiveSpace] = useState<Space | null>(null);
const [features, setFeatures] = useState<KibanaFeature[] | null>(null);
Expand All @@ -80,6 +86,7 @@ export const EditSpace: FC<PageProps> = ({
const [tabs, selectedTabContent] = useTabs({
space,
features,
isRoleManagementEnabled,
rolesCount: state.roles.size,
capabilities,
history,
Expand Down Expand Up @@ -139,10 +146,18 @@ export const EditSpace: FC<PageProps> = ({
setIsLoadingRoles(false);
};

if (!state.roles.size && !state.fetchRolesError) {
if (isRoleManagementEnabled && !state.roles.size && !state.fetchRolesError) {
getRoles();
}
}, [dispatch, invokeClient, spaceId, state.roles, state.fetchRolesError, logger]);
}, [
dispatch,
invokeClient,
spaceId,
logger,
state.roles,
state.fetchRolesError,
isRoleManagementEnabled,
]);

useEffect(() => {
const _getFeatures = async () => {
Expand All @@ -165,7 +180,7 @@ export const EditSpace: FC<PageProps> = ({
return null;
}

if (isLoadingSpace || isLoadingFeatures || isLoadingRoles) {
if (isLoadingSpace || isLoadingFeatures || (isRoleManagementEnabled && isLoadingRoles)) {
return (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const TestComponent: React.FC<React.PropsWithChildren> = ({ children }) => {
http={http}
notifications={notifications}
overlays={overlays}
getIsRoleManagementEnabled={() => Promise.resolve(() => undefined)}
getPrivilegesAPIClient={getPrivilegeAPIClient}
getSecurityLicense={getSecurityLicenseMock}
theme={theme}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe('EditSpaceSettings', () => {
http={http}
notifications={notifications}
overlays={overlays}
getIsRoleManagementEnabled={() => Promise.resolve(() => undefined)}
getPrivilegesAPIClient={getPrivilegeAPIClient}
getSecurityLicense={getSecurityLicenseMock}
theme={theme}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ describe('EditSpaceAssignedRolesTab', () => {
const loadRolesSpy = jest.spyOn(spacesManager, 'getRolesForSpace');
const toastErrorSpy = jest.spyOn(notifications.toasts, 'addError');

const TestComponent: React.FC<React.PropsWithChildren> = ({ children }) => {
const TestComponent: React.FC<
React.PropsWithChildren<{
getIsRoleManagementEnabled?: () => Promise<() => boolean | undefined>;
}>
> = ({ children, ...props }) => {
const getIsRoleManagementEnabled =
props.getIsRoleManagementEnabled ?? (() => Promise.resolve(() => undefined));

return (
<IntlProvider locale="en">
<EditSpaceProviderRoot
Expand All @@ -67,6 +74,7 @@ describe('EditSpaceAssignedRolesTab', () => {
http={http}
notifications={notifications}
overlays={overlays}
getIsRoleManagementEnabled={getIsRoleManagementEnabled}
getPrivilegesAPIClient={getPrivilegeAPIClient}
getSecurityLicense={getSecurityLicenseMock}
theme={theme}
Expand Down Expand Up @@ -118,4 +126,21 @@ describe('EditSpaceAssignedRolesTab', () => {
});
});
});

it('does not load roles if role management is not enabled', async () => {
const getIsRoleManagementEnabled = () => Promise.resolve(() => false);

act(() => {
render(
<TestComponent getIsRoleManagementEnabled={getIsRoleManagementEnabled}>
<EditSpaceAssignedRolesTab space={space} isReadOnly={false} features={[]} />
</TestComponent>
);
});

await waitFor(() => {
expect(loadRolesSpy).not.toHaveBeenCalled();
expect(toastErrorSpy).not.toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { render } from '@testing-library/react';

import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { scopedHistoryMock } from '@kbn/core-application-browser-mocks';
import { KibanaFeature } from '@kbn/features-plugin/common';

import type { GetTabsProps } from './edit_space_tabs';
import { getTabs } from './edit_space_tabs';

const space = {
id: 'my-space',
name: 'My Space',
disabledFeatures: [],
};
const features = [
new KibanaFeature({
id: 'feature-1',
name: 'feature 1',
app: [],
category: DEFAULT_APP_CATEGORIES.kibana,
privileges: null,
}),
];
const history = scopedHistoryMock.create();
const allowFeatureVisibility = true;
const allowSolutionVisibility = true;

const getCapabilities = (
options: Partial<GetTabsProps['capabilities']> = { roles: { save: false, view: true } }
) => ({
navLinks: {},
management: {},
catalogue: {},
spaces: { manage: true },
...options,
});

describe('Edit Space Tabs: getTabs', () => {
it('can include a Permissions tab', () => {
const isRoleManagementEnabled = true;
const capabilities = getCapabilities();

expect(
getTabs({
isRoleManagementEnabled,
capabilities,
space,
features,
history,
allowFeatureVisibility,
allowSolutionVisibility,
}).map(({ id, name }) => ({ name, id }))
).toEqual([
{ id: 'general', name: 'General settings' },
{ id: 'roles', name: 'Permissions' },
{ id: 'content', name: 'Content' },
]);
});

it('can include count of roles as a badge for Permissions tab', () => {
const isRoleManagementEnabled = true;
const capabilities = getCapabilities();

const rolesTab = getTabs({
rolesCount: 42,
isRoleManagementEnabled,
capabilities,
space,
features,
history,
allowFeatureVisibility,
allowSolutionVisibility,
}).find((tab) => tab.id === 'roles');

if (!rolesTab?.append) {
throw new Error('roles tab did not exist or did not have a badge!');
}
const { getByText } = render(rolesTab.append);

expect(getByText('42')).toBeInTheDocument();
});

it('hides Permissions tab when role management is not enabled', () => {
expect(
getTabs({
space,
isRoleManagementEnabled: false,
capabilities: getCapabilities(),
features,
history,
allowFeatureVisibility,
allowSolutionVisibility,
}).map(({ id, name }) => ({ name, id }))
).toEqual([
{ id: 'general', name: 'General settings' },
{ id: 'content', name: 'Content' },
]);
});

it('hides Permissions tab when role capabilities do not include "view"', () => {
expect(
getTabs({
space,
isRoleManagementEnabled: true,
capabilities: getCapabilities({ roles: { save: false, view: false } }),
features,
history,
allowFeatureVisibility,
allowSolutionVisibility,
}).map(({ id, name }) => ({ name, id }))
).toEqual([
{ id: 'general', name: 'General settings' },
{ id: 'content', name: 'Content' },
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export interface EditSpaceTab {

export interface GetTabsProps {
space: Space;
rolesCount: number;
rolesCount?: number;
isRoleManagementEnabled: boolean;
features: KibanaFeature[];
history: ScopedHistory;
capabilities: Capabilities & {
Expand Down Expand Up @@ -66,10 +67,9 @@ export const getTabs = ({
history,
capabilities,
rolesCount,
isRoleManagementEnabled,
...props
}: GetTabsProps): EditSpaceTab[] => {
const canUserViewRoles = Boolean(capabilities?.roles?.view);
const canUserModifyRoles = Boolean(capabilities?.roles?.save);
const reloadWindow = () => {
window.location.reload();
};
Expand All @@ -92,15 +92,17 @@ export const getTabs = ({
},
];

if (canUserViewRoles) {
const canUserViewRoles = Boolean(capabilities?.roles?.view);
const canUserModifyRoles = Boolean(capabilities?.roles?.save);
if (canUserViewRoles && isRoleManagementEnabled) {
tabsDefinition.push({
id: TAB_ID_ROLES,
name: i18n.translate('xpack.spaces.management.spaceDetails.contentTabs.roles.heading', {
defaultMessage: 'Permissions',
}),
append: (
<EuiNotificationBadge className="eui-alignCenter" color="subdued" size="m">
{rolesCount}
{rolesCount ?? 0}
</EuiNotificationBadge>
),
content: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type UseTabsProps = Pick<GetTabsProps, 'capabilities' | 'rolesCount'> & {
space: Space | null;
features: KibanaFeature[] | null;
currentSelectedTabId: string;
isRoleManagementEnabled: boolean;
history: ScopedHistory;
allowFeatureVisibility: boolean;
allowSolutionVisibility: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const SUTProvider = ({
spacesManager,
serverBasePath: '',
getUrlForApp: (_) => _,
getIsRoleManagementEnabled: () => Promise.resolve(() => undefined),
getRolesAPIClient: getRolesAPIClientMock,
getPrivilegesAPIClient: getPrivilegeAPIClientMock,
getSecurityLicense: getSecurityLicenseMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import React, {
useEffect,
useReducer,
useRef,
useState,
} from 'react';

import type { ApplicationStart } from '@kbn/core-application-browser';
Expand All @@ -41,6 +42,7 @@ export interface EditSpaceProviderRootProps
navigateToUrl: ApplicationStart['navigateToUrl'];
serverBasePath: string;
spacesManager: SpacesManager;
getIsRoleManagementEnabled: () => Promise<() => boolean | undefined>;
getRolesAPIClient: () => Promise<RolesAPIClient>;
getPrivilegesAPIClient: () => Promise<PrivilegesAPIClientPublicContract>;
getSecurityLicense: () => Promise<SecurityLicense>;
Expand All @@ -55,10 +57,14 @@ interface EditSpaceClients {
export interface EditSpaceServices
extends Omit<
EditSpaceProviderRootProps,
'getRolesAPIClient' | 'getPrivilegesAPIClient' | 'getSecurityLicense'
| 'getRolesAPIClient'
| 'getPrivilegesAPIClient'
| 'getSecurityLicense'
| 'getIsRoleManagementEnabled'
> {
invokeClient<R extends unknown>(arg: (clients: EditSpaceClients) => Promise<R>): Promise<R>;
license?: SecurityLicense;
isRoleManagementEnabled: boolean;
}

export interface EditSpaceStore {
Expand Down Expand Up @@ -101,8 +107,15 @@ export const EditSpaceProviderRoot = ({
children,
...services
}: PropsWithChildren<EditSpaceProviderRootProps>) => {
const { logger, getRolesAPIClient, getPrivilegesAPIClient, getSecurityLicense } = services;

const {
logger,
getRolesAPIClient,
getPrivilegesAPIClient,
getSecurityLicense,
getIsRoleManagementEnabled,
} = services;

const [isRoleManagementEnabled, setIsRoleManagementEnabled] = useState<boolean>(false);
const clients = useRef(Promise.all([getRolesAPIClient(), getPrivilegesAPIClient()]));
const license = useRef(getSecurityLicense);

Expand Down Expand Up @@ -162,9 +175,21 @@ export const EditSpaceProviderRoot = ({
[resolveAPIClients, services.spacesManager]
);

getIsRoleManagementEnabled().then((isEnabledFunction) => {
const result = isEnabledFunction();
setIsRoleManagementEnabled(typeof result === 'undefined' || result);
});

return (
<EditSpaceProvider
{...{ ...services, invokeClient, state, dispatch, license: licenseRef.current }}
{...{
...services,
invokeClient,
state,
dispatch,
license: licenseRef.current,
isRoleManagementEnabled,
}}
>
{children}
</EditSpaceProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const renderPrivilegeRolesForm = ({
getUrlForApp: jest.fn((_) => _),
navigateToUrl: jest.fn(),
license: licenseMock,
isRoleManagementEnabled: true,
capabilities: {
navLinks: {},
management: {},
Expand Down
Loading