diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index 13cece33838df..edf681c276a84 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -14,3 +14,5 @@ export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; export const BASE_PATH = '/management/elasticsearch/ingest_pipelines'; export const API_BASE_PATH = '/api/ingest_pipelines'; + +export const APP_CLUSTER_REQUIRED_PRIVILEGES = ['manage_pipeline', 'cluster:monitor/nodes/info']; diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index 1027d08c133db..2ec72267701d7 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -3,19 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPageContent } from '@elastic/eui'; +import React, { FunctionComponent } from 'react'; import { HashRouter, Switch, Route } from 'react-router-dom'; -import { BASE_PATH } from '../../common/constants'; -import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections'; -export const App = () => { - return ( - - - - ); -}; +import { BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants'; + +import { + SectionError, + useAuthorizationContext, + WithPrivileges, + SectionLoading, + NotAuthorizedSection, +} from '../shared_imports'; + +import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections'; export const AppWithoutRouter = () => ( @@ -25,3 +28,72 @@ export const AppWithoutRouter = () => ( ); + +export const App: FunctionComponent = () => { + const { apiError } = useAuthorizationContext(); + + if (apiError) { + return ( + + } + error={apiError} + /> + ); + } + + return ( + `cluster.${privilege}`)} + > + {({ isLoading, hasPrivileges, privilegesMissing }) => { + if (isLoading) { + return ( + + + + ); + } + + if (!hasPrivileges) { + return ( + + + } + message={ + + } + /> + + ); + } + + return ( + + + + ); + }} + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index 778ce0c873e66..e43dba4689b44 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HttpSetup } from 'kibana/public'; import React, { ReactNode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { NotificationsSetup } from 'kibana/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { API_BASE_PATH } from '../../common/constants'; + +import { AuthorizationProvider } from '../shared_imports'; + import { App } from './app'; import { DocumentationService, UiMetricService, ApiService, BreadcrumbService } from './services'; @@ -20,17 +25,27 @@ export interface AppServices { notifications: NotificationsSetup; } +export interface CoreServices { + http: HttpSetup; +} + export const renderApp = ( element: HTMLElement, I18nContext: ({ children }: { children: ReactNode }) => JSX.Element, - services: AppServices + services: AppServices, + coreServices: CoreServices ) => { render( - - - - - , + + + + + + + , element ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index 9b950a54096c3..e36f27cbf5f62 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -10,11 +10,11 @@ import { documentationService, uiMetricService, apiService, breadcrumbService } import { renderApp } from '.'; export async function mountManagementSection( - coreSetup: CoreSetup, + { http, getStartServices, notifications }: CoreSetup, params: ManagementAppMountParams ) { const { element, setBreadcrumbs } = params; - const [coreStart] = await coreSetup.getStartServices(); + const [coreStart] = await getStartServices(); const { docLinks, i18n: { Context: I18nContext }, @@ -28,8 +28,8 @@ export async function mountManagementSection( metric: uiMetricService, documentation: documentationService, api: apiService, - notifications: coreSetup.notifications, + notifications, }; - return renderApp(element, I18nContext, services); + return renderApp(element, I18nContext, services, { http }); } diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index 1035a1d8fc864..5127821f3820c 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -45,6 +45,15 @@ export { isJSON, isEmptyString, } from '../../../../src/plugins/es_ui_shared/static/validators/string'; -export { SectionLoading } from '../../../../src/plugins/es_ui_shared/public'; + +export { + SectionLoading, + WithPrivileges, + AuthorizationProvider, + SectionError, + Error, + useAuthorizationContext, + NotAuthorizedSection, +} from '../../../../src/plugins/es_ui_shared/public'; export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts index 9992f56512c01..37819b9bf6889 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/index.ts @@ -10,4 +10,6 @@ export { registerCreateRoute } from './create'; export { registerUpdateRoute } from './update'; +export { registerPrivilegesRoute } from './privileges'; + export { registerDeleteRoute } from './delete'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts new file mode 100644 index 0000000000000..2e1c11928959f --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { RouteDependencies } from '../../types'; +import { API_BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../../common/constants'; +import { Privileges } from '../../../../../../src/plugins/es_ui_shared/public'; + +const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } = {}): string[] => + Object.keys(privilegesObject).reduce((privileges: string[], privilegeName: string): string[] => { + if (!privilegesObject[privilegeName]) { + privileges.push(privilegeName); + } + return privileges; + }, []); + +export const registerPrivilegesRoute = ({ license, router }: RouteDependencies) => { + router.get( + { + path: `${API_BASE_PATH}/privileges`, + validate: false, + }, + license.guardApiRoute(async (ctx, req, res) => { + const { + core: { + elasticsearch: { dataClient }, + }, + } = ctx; + + const privilegesResult: Privileges = { + hasAllPrivileges: true, + missingPrivileges: { + cluster: [], + }, + }; + + try { + const { has_all_requested: hasAllPrivileges, cluster } = await dataClient.callAsCurrentUser( + 'transport.request', + { + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, + }, + } + ); + + if (!hasAllPrivileges) { + privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); + } + + privilegesResult.hasAllPrivileges = hasAllPrivileges; + + return res.ok({ body: privilegesResult }); + } catch (e) { + return res.internalError(e); + } + }) + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/index.ts index 419525816f217..8cfcc1054ca4e 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/index.ts @@ -10,6 +10,7 @@ import { registerGetRoutes, registerCreateRoute, registerUpdateRoute, + registerPrivilegesRoute, registerDeleteRoute, } from './api'; @@ -18,6 +19,7 @@ export class ApiRoutes { registerGetRoutes(dependencies); registerCreateRoute(dependencies); registerUpdateRoute(dependencies); + registerPrivilegesRoute(dependencies); registerDeleteRoute(dependencies); } }