diff --git a/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx index a9675b592aa39..e9d93e6baf631 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/create_enrich_policy/create_enrich_policy.test.tsx @@ -57,10 +57,6 @@ describe('Create enrich policy', () => { beforeEach(async () => { httpRequestsMockHelpers.setGetMatchingIndices(getMatchingIndices()); - httpRequestsMockHelpers.setGetPrivilegesResponse({ - hasAllPrivileges: true, - missingPrivileges: { cluster: [] }, - }); httpRequestsMockHelpers.setGetMatchingDataStreams(getMatchingDataStreams()); await act(async () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index 0504f9bf40b7a..a9f1dac15ed1f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -188,9 +188,6 @@ const registerHttpRequestMockHelpers = ( error ); - const setGetPrivilegesResponse = (response?: HttpResponse, error?: ResponseError) => - mockResponse('GET', `${INTERNAL_API_BASE_PATH}/enrich_policies/privileges`, response, error); - const setCreateEnrichPolicy = (response?: HttpResponse, error?: ResponseError) => mockResponse('POST', `${INTERNAL_API_BASE_PATH}/enrich_policies`, response, error); @@ -253,7 +250,6 @@ const registerHttpRequestMockHelpers = ( setCreateIndexResponse, setGetMatchingIndices, setGetFieldsFromIndices, - setGetPrivilegesResponse, setCreateEnrichPolicy, setInferenceModels, setGetMatchingDataStreams, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 633ed96ec8225..24f641ae2833f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -91,6 +91,11 @@ const appDependencies = { overlays: { openConfirm: jest.fn(), }, + privs: { + monitor: true, + manageEnrich: true, + monitorEnrich: true, + }, } as any; export const kibanaVersion = new SemVer(MAJOR_VERSION); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx index 77c7757fe9202..040602a058e25 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx @@ -38,11 +38,6 @@ describe('Enrich policies tab', () => { describe('empty states', () => { beforeEach(async () => { setDelayResponse(false); - - httpRequestsMockHelpers.setGetPrivilegesResponse({ - hasAllPrivileges: true, - missingPrivileges: { cluster: [] }, - }); }); test('displays a loading prompt', async () => { @@ -82,24 +77,6 @@ describe('Enrich policies tab', () => { }); }); - describe('permissions check', () => { - it('shows a permissions error when the user does not have sufficient privileges', async () => { - httpRequestsMockHelpers.setGetPrivilegesResponse({ - hasAllPrivileges: false, - missingPrivileges: { cluster: ['manage_enrich'] }, - }); - - testBed = await setup(httpSetup); - await act(async () => { - testBed.actions.goToEnrichPoliciesTab(); - }); - - testBed.component.update(); - - expect(testBed.exists('enrichPoliciesInsuficientPrivileges')).toBe(true); - }); - }); - describe('policies list', () => { let testPolicy: ReturnType; beforeEach(async () => { @@ -110,11 +87,6 @@ describe('Enrich policies tab', () => { createTestEnrichPolicy('policy-range', 'range'), ]); - httpRequestsMockHelpers.setGetPrivilegesResponse({ - hasAllPrivileges: true, - missingPrivileges: { cluster: [] }, - }); - testBed = await setup(httpSetup); await act(async () => { testBed.actions.goToEnrichPoliciesTab(); diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 2ae162b421001..0a59fef7be6e6 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -168,6 +168,11 @@ describe('index table', () => { enableIndexActions: true, enableIndexStats: true, }, + privs: { + monitor: true, + manageEnrich: true, + monitorEnrich: true, + }, }; component = ( diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 6df01a109a036..3573ae33812d9 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -80,6 +80,11 @@ export interface AppDependencies { kibanaVersion: SemVer; overlays: OverlayStart; canUseSyntheticSource: boolean; + privs: { + monitor: boolean; + manageEnrich: boolean; + monitorEnrich: boolean; + }; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/components/enrich_policies/index.ts b/x-pack/plugins/index_management/public/application/components/enrich_policies/index.ts deleted file mode 100644 index de8fa4ebf5ac7..0000000000000 --- a/x-pack/plugins/index_management/public/application/components/enrich_policies/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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. - */ - -export { EnrichPoliciesWithPrivileges } from './with_privileges'; -export { EnrichPoliciesAuthProvider } from './auth_provider'; diff --git a/x-pack/plugins/index_management/public/application/components/enrich_policies/with_privileges.tsx b/x-pack/plugins/index_management/public/application/components/enrich_policies/with_privileges.tsx deleted file mode 100644 index 09a79343b2210..0000000000000 --- a/x-pack/plugins/index_management/public/application/components/enrich_policies/with_privileges.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { FormattedMessage } from '@kbn/i18n-react'; -import React, { FunctionComponent } from 'react'; - -import { - PageLoading, - PageError, - useAuthorizationContext, - WithPrivileges, - NotAuthorizedSection, -} from '../../../shared_imports'; -import { ENRICH_POLICIES_REQUIRED_PRIVILEGES } from '../../constants'; - -export const EnrichPoliciesWithPrivileges: FunctionComponent<{ - children?: React.ReactNode; -}> = ({ children }) => { - const { apiError } = useAuthorizationContext(); - - if (apiError) { - return ( - - } - error={apiError} - /> - ); - } - - return ( - `cluster.${privilege}`)} - > - {({ isLoading, hasPrivileges, privilegesMissing }) => { - if (isLoading) { - return ( - - - - ); - } - - if (!hasPrivileges) { - return ( - - } - message={ - - } - /> - ); - } - - return <>{children}; - }} - - ); -}; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index 48a45579a01fc..83cf620a0e8e0 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -73,6 +73,8 @@ export function getIndexManagementDependencies({ }): AppDependencies { const { docLinks, application, uiSettings, settings } = core; const { url } = startDependencies.share; + const { monitor, manageEnrich, monitorEnrich } = application.capabilities.index_management; + return { core: { getUrlForApp: application.getUrlForApp, @@ -103,6 +105,11 @@ export function getIndexManagementDependencies({ kibanaVersion, overlays: core.overlays, canUseSyntheticSource, + privs: { + monitor: !!monitor, + manageEnrich: !!manageEnrich, + monitorEnrich: !!monitorEnrich, + }, }; } diff --git a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx index f7ef260b14651..321dee68ae8aa 100644 --- a/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx +++ b/x-pack/plugins/index_management/public/application/sections/enrich_policy_create/enrich_policy_create.tsx @@ -14,12 +14,8 @@ import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/bre import { CreatePolicyWizard } from './create_policy_wizard'; import { CreatePolicyContextProvider } from './create_policy_context'; -import { - EnrichPoliciesAuthProvider, - EnrichPoliciesWithPrivileges, -} from '../../components/enrich_policies'; -const CreateView: React.FunctionComponent = () => { +export const EnrichPolicyCreate: React.FunctionComponent = () => { useEffect(() => { breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.enrichPoliciesCreate); }, []); @@ -64,11 +60,3 @@ const CreateView: React.FunctionComponent = () => { ); }; - -export const EnrichPolicyCreate: React.FunctionComponent = (props) => ( - - - - - -); diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx index a036273ebdde9..eeba7b9e03fd8 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/enrich_policies_list.tsx @@ -17,10 +17,6 @@ import { APP_WRAPPER_CLASS, useExecutionContext } from '../../../../shared_impor import { useAppContext } from '../../../app_context'; import { useRedirectPath } from '../../../hooks/redirect_path'; -import { - EnrichPoliciesAuthProvider, - EnrichPoliciesWithPrivileges, -} from '../../../components/enrich_policies'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs'; import { documentationService } from '../../../services/documentation'; import { useLoadEnrichPolicies } from '../../../services/api'; @@ -34,9 +30,13 @@ const getEnrichPolicyNameFromLocation = (location: Location) => { return policy; }; -const ListView: React.FunctionComponent = ({ history, location }) => { +export const EnrichPoliciesList: React.FunctionComponent = ({ + history, + location, +}) => { const { core: { executionContext }, + privs, } = useAppContext(); const redirectTo = useRedirectPath(history); @@ -79,7 +79,7 @@ const ListView: React.FunctionComponent = ({ history, locat return ; } - if (policies?.length === 0) { + if (privs.manageEnrich && policies?.length === 0) { return ; } @@ -151,11 +151,3 @@ const ListView: React.FunctionComponent = ({ history, locat ); }; - -export const EnrichPoliciesList: React.FunctionComponent = (props) => ( - - - - - -); diff --git a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx index e78e08b829997..61574a70fa545 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/enrich_policies_list/policies_table/policies_table.tsx @@ -33,43 +33,84 @@ export const PoliciesTable: FunctionComponent = ({ onDeletePolicyClick, onExecutePolicyClick, }) => { - const { history } = useAppContext(); + const { history, privs } = useAppContext(); - const renderToolsRight = () => { - return [ - - - , - - - , - ]; - }; + const createBtn = ( + + + + ); + + const toolsRight = [ + + + , + ]; + + if (privs.manageEnrich) { + toolsRight.push(createBtn); + } const search: EuiSearchBarProps = { - toolsRight: renderToolsRight(), + toolsRight, box: { incremental: true, }, }; + const actions: EuiBasicTableColumn = { + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.actionsField', { + defaultMessage: 'Actions', + }), + actions: [ + { + isPrimary: true, + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeAction', { + defaultMessage: 'Execute', + }), + description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeDescription', { + defaultMessage: 'Execute this enrich policy', + }), + type: 'icon', + icon: 'play', + 'data-test-subj': 'executePolicyButton', + onClick: ({ name }) => onExecutePolicyClick(name), + }, + { + isPrimary: true, + name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteAction', { + defaultMessage: 'Delete', + }), + description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteDescription', { + defaultMessage: 'Delete this enrich policy', + }), + type: 'icon', + icon: 'trash', + color: 'danger', + 'data-test-subj': 'deletePolicyButton', + onClick: ({ name }) => onDeletePolicyClick(name), + }, + ], + }; + const columns: Array> = [ { field: 'name', @@ -120,42 +161,12 @@ export const PoliciesTable: FunctionComponent = ({ truncateText: true, render: (fields: string[]) => {fields.join(', ')}, }, - { - name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.actionsField', { - defaultMessage: 'Actions', - }), - actions: [ - { - isPrimary: true, - name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeAction', { - defaultMessage: 'Execute', - }), - description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeDescription', { - defaultMessage: 'Execute this enrich policy', - }), - type: 'icon', - icon: 'play', - 'data-test-subj': 'executePolicyButton', - onClick: ({ name }) => onExecutePolicyClick(name), - }, - { - isPrimary: true, - name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteAction', { - defaultMessage: 'Delete', - }), - description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteDescription', { - defaultMessage: 'Delete this enrich policy', - }), - type: 'icon', - icon: 'trash', - color: 'danger', - 'data-test-subj': 'deletePolicyButton', - onClick: ({ name }) => onDeletePolicyClick(name), - }, - ], - }, ]; + if (privs.manageEnrich) { + columns.push(actions); + } + const { pageSize, sorting, onTableChange } = useEuiTablePersist({ tableId: 'enrichPolicies', initialPageSize: 50, diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index e7de896fa4b38..2d0fc7d4ec108 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -41,6 +41,7 @@ export const IndexManagementHome: React.FunctionComponent { const { plugins: { console: consolePlugin }, + privs, } = useAppContext(); const tabs = [ { @@ -74,7 +75,10 @@ export const IndexManagementHome: React.FunctionComponent ), }, - { + ]; + + if (privs.monitorEnrich) { + tabs.push({ id: Section.EnrichPolicies, name: ( ), - }, - ]; + }); + } const onSectionChange = (newSection: Section) => { history.push(`/${newSection}`); @@ -143,7 +147,9 @@ export const IndexManagementHome: React.FunctionComponent - + {privs.monitorEnrich && ( + + )} {consolePlugin?.EmbeddableConsole ? : null} diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 82ba6505696aa..06adcc75d80b3 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { Subject } from 'rxjs'; import SemVer from 'semver/classes/semver'; import { @@ -14,6 +15,7 @@ import { Plugin, PluginInitializerContext, ScopedHistory, + Capabilities, } from '@kbn/core/public'; import { IndexManagementPluginSetup, @@ -61,6 +63,8 @@ export class IndexMgmtUIPlugin private canUseSyntheticSource: boolean = false; private licensingSubscription?: Subscription; + private capabilities$ = new Subject(); + constructor(ctx: PluginInitializerContext) { // Temporary hack to provide the service instances in module files in order to avoid a big refactor // For the selectors we should expose them through app dependencies and read them from there on each container component. @@ -98,29 +102,34 @@ export class IndexMgmtUIPlugin coreSetup: CoreSetup, plugins: SetupDependencies ): IndexManagementPluginSetup { - if (this.config.isIndexManagementUiEnabled) { - const { fleet, usageCollection, management, cloud } = plugins; + const { fleet, usageCollection, management, cloud } = plugins; - management.sections.section.data.registerApp({ - id: PLUGIN.id, - title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), - order: 0, - mount: async (params) => { - const { mountManagementSection } = await import('./application/mount_management_section'); - return mountManagementSection({ - coreSetup, - usageCollection, - params, - extensionsService: this.extensionsService, - isFleetEnabled: Boolean(fleet), - kibanaVersion: this.kibanaVersion, - config: this.config, - cloud, - canUseSyntheticSource: this.canUseSyntheticSource, - }); - }, - }); - } + this.capabilities$.subscribe((capabilities) => { + const { monitor, manageEnrich, monitorEnrich } = capabilities.index_management; + if (this.config.isIndexManagementUiEnabled && (monitor || manageEnrich || monitorEnrich)) { + management.sections.section.data.registerApp({ + id: PLUGIN.id, + title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), + order: 0, + mount: async (params) => { + const { mountManagementSection } = await import( + './application/mount_management_section' + ); + return mountManagementSection({ + coreSetup, + usageCollection, + params, + extensionsService: this.extensionsService, + isFleetEnabled: Boolean(fleet), + kibanaVersion: this.kibanaVersion, + config: this.config, + cloud, + canUseSyntheticSource: this.canUseSyntheticSource, + }); + }, + }); + } + }); this.locator = plugins.share.url.locators.create( new IndexManagementLocatorDefinition({ @@ -138,6 +147,8 @@ export class IndexMgmtUIPlugin public start(coreStart: CoreStart, plugins: StartDependencies): IndexManagementPluginStart { const { fleet, usageCollection, cloud, share, console, ml, licensing } = plugins; + this.capabilities$.next(coreStart.application.capabilities); + this.licensingSubscription = licensing?.license$.subscribe((next) => { this.canUseSyntheticSource = next.hasAtLeast('enterprise'); }); diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 0ba15c463d4ec..ab6b058cddc78 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -37,15 +37,20 @@ export class IndexMgmtServerPlugin implements Plugin { describe('Enrich policies tab', function () { before(async () => { await log.debug('Creating required index and enrich policy'); + + try { + await es.indices.delete({ index: ENRICH_INDEX_NAME }); + // eslint-disable-next-line no-empty + } catch (e) {} + try { await es.indices.create({ index: ENRICH_INDEX_NAME, @@ -94,7 +100,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(await successToast.getVisibleText()).to.contain(`Executed ${ENRICH_POLICY_NAME}`); }); + it('read only access', async () => { + await security.testUser.setRoles(['index_management_monitor_enrich_only']); + await pageObjects.common.navigateToApp('indexManagement'); + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.missingOrFail('createPolicyButton'); + await testSubjects.missingOrFail('deletePolicyButton'); + }); + it('can delete a policy', async () => { + await security.testUser.setRoles(['index_management_user']); + await pageObjects.common.navigateToApp('indexManagement'); + // Navigate to the enrich policies tab + await pageObjects.indexManagement.changeTabs('enrich_policiesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); // Since we disabled wait_for_completion in the server request, we dont know when // a given policy will finish executing. Until that happens the policy cannot // be deleted. 2s seems to be plenty enough to guarantee that, at least for this @@ -104,8 +125,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.indexManagement.clickDeleteEnrichPolicyAt(0); await pageObjects.indexManagement.clickConfirmModalButton(); - const successToast = await toasts.getElementByIndex(2); + const successToast = await toasts.getElementByIndex(1); expect(await successToast.getVisibleText()).to.contain(`Deleted ${ENRICH_POLICY_NAME}`); }); + + it('no access', async () => { + await security.testUser.setRoles(['index_management_monitor_only']); + await pageObjects.common.navigateToApp('indexManagement'); + await testSubjects.missingOrFail('enrich_policiesTab'); + }); }); };