mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Spaces] Show callout on spaces permission tab when security is disabled (#210961)
## Summary This PR updates the behavior of `Permissions` tab in `Space Management` when `xpack.security.enabled` is set to `false` to show a callout with a meaningful explanation.  Closes: #210241 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4cd9ef318b
commit
46132d8c3e
15 changed files with 108 additions and 4 deletions
|
@ -677,6 +677,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
|
|||
mappingRolesFieldRules: `${ELASTICSEARCH_DOCS}role-mapping-resources.html#mapping-roles-rule-field`,
|
||||
runAsPrivilege: `${ELASTICSEARCH_DOCS}security-privileges.html#_run_as_privilege`,
|
||||
deprecatedV1Endpoints: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-199656`,
|
||||
enableElasticSearchSecurityFeatures: `${ELASTICSEARCH_DOCS}security-minimal-setup.html#_enable_elasticsearch_security_features`,
|
||||
},
|
||||
spaces: {
|
||||
kibanaLegacyUrlAliases: `${KIBANA_DOCS}legacy-url-aliases.html`,
|
||||
|
|
|
@ -458,6 +458,7 @@ export interface DocLinks {
|
|||
mappingRolesFieldRules: string;
|
||||
runAsPrivilege: string;
|
||||
deprecatedV1Endpoints: string;
|
||||
enableElasticSearchSecurityFeatures: string;
|
||||
}>;
|
||||
readonly spaces: Readonly<{
|
||||
kibanaLegacyUrlAliases: string;
|
||||
|
|
|
@ -75,6 +75,8 @@ export const EditSpace: FC<PageProps> = ({
|
|||
logger,
|
||||
notifications,
|
||||
isRoleManagementEnabled,
|
||||
license,
|
||||
enableSecurityLink,
|
||||
} = useEditSpaceServices();
|
||||
const [space, setSpace] = useState<Space | null>(null);
|
||||
const [userActiveSpace, setUserActiveSpace] = useState<Space | null>(null);
|
||||
|
@ -83,6 +85,7 @@ export const EditSpace: FC<PageProps> = ({
|
|||
const [isLoadingFeatures, setIsLoadingFeatures] = useState(true);
|
||||
const [isLoadingRoles, setIsLoadingRoles] = useState(true);
|
||||
const selectedTabId = getSelectedTabId(Boolean(capabilities?.roles?.view), _selectedTabId);
|
||||
const isSecurityEnabled = Boolean(license?.isEnabled());
|
||||
const [tabs, selectedTabContent] = useTabs({
|
||||
space,
|
||||
features,
|
||||
|
@ -91,6 +94,8 @@ export const EditSpace: FC<PageProps> = ({
|
|||
capabilities,
|
||||
history,
|
||||
currentSelectedTabId: selectedTabId,
|
||||
isSecurityEnabled,
|
||||
enableSecurityLink,
|
||||
...props,
|
||||
});
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ const TestComponent: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|||
userProfile={userProfile}
|
||||
i18n={i18n}
|
||||
logger={logger}
|
||||
enableSecurityLink=""
|
||||
>
|
||||
{children}
|
||||
</EditSpaceProviderRoot>
|
||||
|
|
|
@ -89,6 +89,7 @@ describe('EditSpaceSettings', () => {
|
|||
theme={theme}
|
||||
i18n={i18n}
|
||||
logger={logger}
|
||||
enableSecurityLink=""
|
||||
>
|
||||
{children}
|
||||
</EditSpaceProviderRoot>
|
||||
|
|
|
@ -83,6 +83,7 @@ describe('EditSpaceAssignedRolesTab', () => {
|
|||
theme={theme}
|
||||
i18n={i18n}
|
||||
logger={logger}
|
||||
enableSecurityLink=""
|
||||
>
|
||||
{children}
|
||||
</EditSpaceProviderRoot>
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import React from '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 { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
|
||||
import type { GetTabsProps } from './edit_space_tabs';
|
||||
import { getTabs } from './edit_space_tabs';
|
||||
|
@ -45,6 +46,7 @@ const getCapabilities = (
|
|||
describe('Edit Space Tabs: getTabs', () => {
|
||||
it('can include a Permissions tab', () => {
|
||||
const isRoleManagementEnabled = true;
|
||||
const isSecurityEnabled = true;
|
||||
const capabilities = getCapabilities();
|
||||
|
||||
expect(
|
||||
|
@ -56,6 +58,8 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
history,
|
||||
allowFeatureVisibility,
|
||||
allowSolutionVisibility,
|
||||
isSecurityEnabled,
|
||||
enableSecurityLink: '',
|
||||
}).map(({ id, name }) => ({ name, id }))
|
||||
).toEqual([
|
||||
{ id: 'general', name: 'General settings' },
|
||||
|
@ -66,6 +70,7 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
|
||||
it('can include count of roles as a badge for Permissions tab', () => {
|
||||
const isRoleManagementEnabled = true;
|
||||
const isSecurityEnabled = true;
|
||||
const capabilities = getCapabilities();
|
||||
|
||||
const rolesTab = getTabs({
|
||||
|
@ -77,6 +82,8 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
history,
|
||||
allowFeatureVisibility,
|
||||
allowSolutionVisibility,
|
||||
isSecurityEnabled,
|
||||
enableSecurityLink: '',
|
||||
}).find((tab) => tab.id === 'roles');
|
||||
|
||||
if (!rolesTab?.append) {
|
||||
|
@ -87,6 +94,32 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
expect(getByText('42')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show a warning callout when security is disabled', () => {
|
||||
const isRoleManagementEnabled = true;
|
||||
const isSecurityEnabled = false;
|
||||
const capabilities = getCapabilities();
|
||||
|
||||
const rolesTab = getTabs({
|
||||
rolesCount: 0,
|
||||
isRoleManagementEnabled,
|
||||
capabilities,
|
||||
space,
|
||||
features,
|
||||
history,
|
||||
allowFeatureVisibility,
|
||||
allowSolutionVisibility,
|
||||
isSecurityEnabled,
|
||||
enableSecurityLink: '',
|
||||
}).find((tab) => tab.id === 'roles');
|
||||
|
||||
if (!rolesTab?.content) {
|
||||
throw new Error('roles tab did not exist!');
|
||||
}
|
||||
const { getByTestId } = render(<IntlProvider locale="en">{rolesTab.content}</IntlProvider>);
|
||||
|
||||
expect(getByTestId('securityDisabledCallout')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides Permissions tab when role management is not enabled', () => {
|
||||
expect(
|
||||
getTabs({
|
||||
|
@ -97,6 +130,8 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
history,
|
||||
allowFeatureVisibility,
|
||||
allowSolutionVisibility,
|
||||
isSecurityEnabled: true,
|
||||
enableSecurityLink: '',
|
||||
}).map(({ id, name }) => ({ name, id }))
|
||||
).toEqual([
|
||||
{ id: 'general', name: 'General settings' },
|
||||
|
@ -114,6 +149,8 @@ describe('Edit Space Tabs: getTabs', () => {
|
|||
history,
|
||||
allowFeatureVisibility,
|
||||
allowSolutionVisibility,
|
||||
isSecurityEnabled: true,
|
||||
enableSecurityLink: '',
|
||||
}).map(({ id, name }) => ({ name, id }))
|
||||
).toEqual([
|
||||
{ id: 'general', name: 'General settings' },
|
|
@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
|
||||
import { TAB_ID_CONTENT, TAB_ID_GENERAL, TAB_ID_ROLES } from './constants';
|
||||
import { SecurityDisabledCallout } from './security_disabled_callout';
|
||||
import type { Space } from '../../../common';
|
||||
|
||||
export interface EditSpaceTab {
|
||||
|
@ -35,6 +36,8 @@ export interface GetTabsProps {
|
|||
};
|
||||
allowFeatureVisibility: boolean;
|
||||
allowSolutionVisibility: boolean;
|
||||
isSecurityEnabled: boolean;
|
||||
enableSecurityLink: string;
|
||||
}
|
||||
|
||||
const SuspenseEditSpaceSettingsTab = withSuspense(
|
||||
|
@ -68,6 +71,8 @@ export const getTabs = ({
|
|||
capabilities,
|
||||
rolesCount,
|
||||
isRoleManagementEnabled,
|
||||
isSecurityEnabled,
|
||||
enableSecurityLink,
|
||||
...props
|
||||
}: GetTabsProps): EditSpaceTab[] => {
|
||||
const reloadWindow = () => {
|
||||
|
@ -105,12 +110,14 @@ export const getTabs = ({
|
|||
{rolesCount ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
),
|
||||
content: (
|
||||
content: isSecurityEnabled ? (
|
||||
<SuspenseEditSpaceAssignedRolesTab
|
||||
space={space}
|
||||
features={features}
|
||||
isReadOnly={!canUserModifyRoles}
|
||||
/>
|
||||
) : (
|
||||
<SecurityDisabledCallout enableSecurityLink={enableSecurityLink} />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ type UseTabsProps = Pick<GetTabsProps, 'capabilities' | 'rolesCount'> & {
|
|||
history: ScopedHistory;
|
||||
allowFeatureVisibility: boolean;
|
||||
allowSolutionVisibility: boolean;
|
||||
isSecurityEnabled: boolean;
|
||||
enableSecurityLink: string;
|
||||
};
|
||||
|
||||
export const useTabs = ({
|
||||
|
|
|
@ -70,6 +70,7 @@ const SUTProvider = ({
|
|||
getSecurityLicense: getSecurityLicenseMock,
|
||||
navigateToUrl: jest.fn(),
|
||||
capabilities,
|
||||
enableSecurityLink: '',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -49,6 +49,7 @@ export interface EditSpaceProviderRootProps
|
|||
getRolesAPIClient: () => Promise<RolesAPIClient>;
|
||||
getPrivilegesAPIClient: () => Promise<PrivilegesAPIClientPublicContract>;
|
||||
getSecurityLicense: () => Promise<SecurityLicense>;
|
||||
enableSecurityLink: string;
|
||||
}
|
||||
|
||||
interface EditSpaceClients {
|
||||
|
|
|
@ -113,6 +113,7 @@ const renderPrivilegeRolesForm = ({
|
|||
fetchRolesError: false,
|
||||
},
|
||||
invokeClient: spacesClientsInvocatorMock,
|
||||
enableSecurityLink: '',
|
||||
}}
|
||||
>
|
||||
<PrivilegesRolesForm
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
interface Props {
|
||||
enableSecurityLink: string;
|
||||
}
|
||||
|
||||
export const SecurityDisabledCallout = ({ enableSecurityLink }: Props) => (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
data-test-subj="securityDisabledCallout"
|
||||
title={i18n.translate(
|
||||
'xpack.spaces.management.spaceDetails.contentTabs.securityDisabledTitle',
|
||||
{
|
||||
defaultMessage: 'Security features are disabled',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.spaceDetails.contentTabs.securityDisabledDescription"
|
||||
defaultMessage="To manage space permissions, you must enable security features first. {learnMoreLink}"
|
||||
values={{
|
||||
learnMoreLink: (
|
||||
<EuiLink href={enableSecurityLink} target="_blank" external={true}>
|
||||
<FormattedMessage
|
||||
id="xpack.spaces.management.spaceDetails.contentTabs.securityDisabledLearnMore"
|
||||
defaultMessage="Learn more"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
|
@ -189,7 +189,7 @@ describe('spacesManagementApp', () => {
|
|||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
data-test-subj="kbnRedirectAppLink"
|
||||
>
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"userProfile":{},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true}
|
||||
Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"userProfile":{},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true,"enableSecurityLink":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/security-minimal-setup.html#_enable_elasticsearch_security_features"}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
|
|
@ -82,7 +82,8 @@ export const spacesManagementApp = Object.freeze({
|
|||
text: title,
|
||||
href: `/`,
|
||||
};
|
||||
const { notifications, application, chrome, http, overlays } = coreStart;
|
||||
const { notifications, application, chrome, http, overlays, docLinks } = coreStart;
|
||||
const enableSecurityLink = docLinks.links.security.enableElasticSearchSecurityFeatures;
|
||||
|
||||
chrome.docTitle.change(title);
|
||||
|
||||
|
@ -174,6 +175,7 @@ export const spacesManagementApp = Object.freeze({
|
|||
allowFeatureVisibility={config.allowFeatureVisibility}
|
||||
allowSolutionVisibility={config.allowSolutionVisibility}
|
||||
getPrivilegesAPIClient={getPrivilegesAPIClient}
|
||||
enableSecurityLink={enableSecurityLink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue