Skip to content

Commit

Permalink
Hide remote indices role configuration when not supported (#155490)
Browse files Browse the repository at this point in the history
Resolves #155389

## Summary

Adds feature flag to automatically hide remote index privileges section
when not supported by cluster.
  • Loading branch information
thomheymann authored Apr 21, 2023
1 parent f95ebdf commit cfc01d5
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type { HttpStart } from '@kbn/core/public';

import type { RoleMapping } from '../../../common/model';

interface CheckRoleMappingFeaturesResponse {
export interface CheckRoleMappingFeaturesResponse {
canManageRoleMappings: boolean;
canUseInlineScripts: boolean;
canUseStoredScripts: boolean;
hasCompatibleRealms: boolean;
canUseRemoteIndices: boolean;
}

type DeleteRoleMappingsResponse = Array<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,13 @@ function getProps({
role,
canManageSpaces = true,
spacesEnabled = true,
canUseRemoteIndices = true,
}: {
action: 'edit' | 'clone';
role?: Role;
canManageSpaces?: boolean;
spacesEnabled?: boolean;
canUseRemoteIndices?: boolean;
}) {
const rolesAPIClient = rolesAPIClientMock.create();
rolesAPIClient.getRole.mockResolvedValue(role);
Expand All @@ -171,12 +173,15 @@ function getProps({
const { fatalErrors } = coreMock.createSetup();
const { http, docLinks, notifications } = coreMock.createStart();
http.get.mockImplementation(async (path: any) => {
if (!spacesEnabled) {
throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
}
if (path === '/api/spaces/space') {
if (!spacesEnabled) {
throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
}
return buildSpaces();
}
if (path === '/internal/security/_check_role_mapping_features') {
return { canUseRemoteIndices };
}
});

return {
Expand Down Expand Up @@ -265,6 +270,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});

Expand All @@ -291,6 +298,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});

Expand All @@ -308,6 +317,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});

Expand Down Expand Up @@ -480,6 +491,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});

Expand Down Expand Up @@ -507,6 +520,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});

Expand All @@ -524,6 +539,8 @@ describe('<EditRolePage />', () => {
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});

Expand Down Expand Up @@ -612,6 +629,19 @@ describe('<EditRolePage />', () => {
});
});

it('hides remote index privileges section when not supported', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage {...getProps({ action: 'edit', canUseRemoteIndices: false })} />
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
});

it('registers fatal error if features endpoint fails unexpectedly', async () => {
const error = { response: { status: 500 } };
const getFeatures = jest.fn().mockRejectedValue(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@elastic/eui';
import type { ChangeEvent, FocusEvent, FunctionComponent, HTMLProps } from 'react';
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';

import type { IHttpFetchError } from '@kbn/core-http-browser';
import type {
Expand Down Expand Up @@ -56,6 +57,7 @@ import {
prepareRoleClone,
} from '../../../../common/model';
import { useCapabilities } from '../../../components/use_capabilities';
import type { CheckRoleMappingFeaturesResponse } from '../../role_mappings/role_mappings_api_client';
import type { UserAPIClient } from '../../users';
import type { IndicesAPIClient } from '../indices_api_client';
import { KibanaPrivileges } from '../model';
Expand Down Expand Up @@ -86,6 +88,12 @@ interface Props {
spacesApiUi?: SpacesApiUi;
}

function useFeatureCheck(http: HttpStart) {
return useAsync(() =>
http.get<CheckRoleMappingFeaturesResponse>('/internal/security/_check_role_mapping_features')
);
}

function useRunAsUsers(
userAPIClient: PublicMethodsOf<UserAPIClient>,
fatalErrors: FatalErrorsSetup
Expand Down Expand Up @@ -311,6 +319,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
const privileges = usePrivileges(privilegesAPIClient, fatalErrors);
const spaces = useSpaces(http, fatalErrors);
const features = useFeatures(getFeatures, fatalErrors);
const featureCheckState = useFeatureCheck(http);
const [role, setRole] = useRole(
rolesAPIClient,
fatalErrors,
Expand All @@ -329,7 +338,15 @@ export const EditRolePage: FunctionComponent<Props> = ({
}
}, [hasReadOnlyPrivileges, isEditingExistingRole]); // eslint-disable-line react-hooks/exhaustive-deps

if (!role || !runAsUsers || !indexPatternsTitles || !privileges || !spaces || !features) {
if (
!role ||
!runAsUsers ||
!indexPatternsTitles ||
!privileges ||
!spaces ||
!features ||
!featureCheckState.value
) {
return null;
}

Expand Down Expand Up @@ -457,6 +474,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
builtinESPrivileges={builtInESPrivileges}
license={license}
docLinks={docLinks}
canUseRemoteIndices={featureCheckState.value?.canUseRemoteIndices}
/>
</div>
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ test('it renders index privileges section', () => {
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
});

test('it renders remote index privileges section', () => {
test('it does not render remote index privileges section by default', () => {
const wrapper = shallowWithIntl(<ElasticsearchPrivileges {...getProps()} />);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
});

test('it renders remote index privileges section when `canUseRemoteIndices` is enabled', () => {
const wrapper = shallowWithIntl(<ElasticsearchPrivileges {...getProps()} canUseRemoteIndices />);
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface Props {
validator: RoleValidator;
builtinESPrivileges: BuiltinESPrivileges;
indexPatterns: string[];
canUseRemoteIndices?: boolean;
}

export class ElasticsearchPrivileges extends Component<Props, {}> {
Expand All @@ -62,6 +63,7 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
indexPatterns,
license,
builtinESPrivileges,
canUseRemoteIndices,
} = this.props;

return (
Expand Down Expand Up @@ -170,37 +172,42 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
availableIndexPrivileges={builtinESPrivileges.index}
editable={editable}
/>
<EuiSpacer />
<EuiSpacer />

<EuiTitle size="xs">
<h3>
<FormattedMessage
id="xpack.security.management.editRole.elasticSearchPrivileges.remoteIndexPrivilegesTitle"
defaultMessage="Remote index privileges"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToRemoteClusterDataDescription"
defaultMessage="Control access to the data in remote clusters. "
{canUseRemoteIndices && (
<>
<EuiSpacer />
<EuiSpacer />

<EuiTitle size="xs">
<h3>
<FormattedMessage
id="xpack.security.management.editRole.elasticSearchPrivileges.remoteIndexPrivilegesTitle"
defaultMessage="Remote index privileges"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.security.management.editRole.elasticSearchPrivileges.controlAccessToRemoteClusterDataDescription"
defaultMessage="Control access to the data in remote clusters. "
/>
{this.learnMore(docLinks.links.security.indicesPrivileges)}
</p>
</EuiText>
<IndexPrivileges
indexType="remote_indices"
role={role}
indicesAPIClient={indicesAPIClient}
validator={validator}
license={license}
onChange={onChange}
availableIndexPrivileges={builtinESPrivileges.index}
editable={editable}
/>
{this.learnMore(docLinks.links.security.indicesPrivileges)}
</p>
</EuiText>
<IndexPrivileges
indexType="remote_indices"
role={role}
indicesAPIClient={indicesAPIClient}
validator={validator}
license={license}
onChange={onChange}
availableIndexPrivileges={builtinESPrivileges.index}
editable={editable}
/>
</>
)}
</Fragment>
);
};
Expand Down
Loading

0 comments on commit cfc01d5

Please sign in to comment.