Skip to content

Commit

Permalink
[Ingest node pipelines] Privileges (#63850)
Browse files Browse the repository at this point in the history
* Create privileges check for ingest pipelines app

Also moved the public side logic for checking and rendering
privilege related messages to es_ui_shared/public following the
new __packages_do_not_import__ convention.

* Add ,

* Fix import paths

* Address PR feedback

Fix i18n strings (remove reference to snapshot and restore) and
fix copy referencing snapshot and restore - all copy-pasta errors.

Also remove unused field from missing privileges object.

* Fix issue from resolving merge conflicts

* Add missing app privilege

* Use non-deprecated privilige name
  • Loading branch information
jloleysens authored Apr 23, 2020
1 parent 34cb91a commit eba5305
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 22 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/ingest_pipelines/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
94 changes: 83 additions & 11 deletions x-pack/plugins/ingest_pipelines/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<HashRouter>
<AppWithoutRouter />
</HashRouter>
);
};
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 = () => (
<Switch>
Expand All @@ -25,3 +28,72 @@ export const AppWithoutRouter = () => (
<Route exact path={`${BASE_PATH}/edit/:name`} component={PipelinesEdit} />
</Switch>
);

export const App: FunctionComponent = () => {
const { apiError } = useAuthorizationContext();

if (apiError) {
return (
<SectionError
title={
<FormattedMessage
id="xpack.ingestPipelines.app.checkingPrivilegesErrorMessage"
defaultMessage="Error fetching user privileges from the server."
/>
}
error={apiError}
/>
);
}

return (
<WithPrivileges
privileges={APP_CLUSTER_REQUIRED_PRIVILEGES.map(privilege => `cluster.${privilege}`)}
>
{({ isLoading, hasPrivileges, privilegesMissing }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.ingestPipelines.app.checkingPrivilegesDescription"
defaultMessage="Checking privileges…"
/>
</SectionLoading>
);
}

if (!hasPrivileges) {
return (
<EuiPageContent>
<NotAuthorizedSection
title={
<FormattedMessage
id="xpack.ingestPipelines.app.deniedPrivilegeTitle"
defaultMessage="You're missing cluster privileges"
/>
}
message={
<FormattedMessage
id="xpack.ingestPipelines.app.deniedPrivilegeDescription"
defaultMessage="To use Ingest Pipelines, you must have {privilegesCount,
plural, one {this cluster privilege} other {these cluster privileges}}: {missingPrivileges}."
values={{
missingPrivileges: privilegesMissing.cluster!.join(', '),
privilegesCount: privilegesMissing.cluster!.length,
}}
/>
}
/>
</EuiPageContent>
);
}

return (
<HashRouter>
<AppWithoutRouter />
</HashRouter>
);
}}
</WithPrivileges>
);
};
27 changes: 21 additions & 6 deletions x-pack/plugins/ingest_pipelines/public/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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(
<I18nContext>
<KibanaContextProvider services={services}>
<App />
</KibanaContextProvider>
</I18nContext>,
<AuthorizationProvider
privilegesEndpoint={`${API_BASE_PATH}/privileges`}
httpClient={coreServices.http}
>
<I18nContext>
<KibanaContextProvider services={services}>
<App />
</KibanaContextProvider>
</I18nContext>
</AuthorizationProvider>,
element
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 });
}
11 changes: 10 additions & 1 deletion x-pack/plugins/ingest_pipelines/public/shared_imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AppServices>();
2 changes: 2 additions & 0 deletions x-pack/plugins/ingest_pipelines/server/routes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export { registerCreateRoute } from './create';

export { registerUpdateRoute } from './update';

export { registerPrivilegesRoute } from './privileges';

export { registerDeleteRoute } from './delete';
62 changes: 62 additions & 0 deletions x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts
Original file line number Diff line number Diff line change
@@ -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);
}
})
);
};
2 changes: 2 additions & 0 deletions x-pack/plugins/ingest_pipelines/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
registerGetRoutes,
registerCreateRoute,
registerUpdateRoute,
registerPrivilegesRoute,
registerDeleteRoute,
} from './api';

Expand All @@ -18,6 +19,7 @@ export class ApiRoutes {
registerGetRoutes(dependencies);
registerCreateRoute(dependencies);
registerUpdateRoute(dependencies);
registerPrivilegesRoute(dependencies);
registerDeleteRoute(dependencies);
}
}

0 comments on commit eba5305

Please sign in to comment.