Skip to content

Commit

Permalink
bring no privilege msg when needed it
Browse files Browse the repository at this point in the history
  • Loading branch information
XavierM committed Oct 15, 2021
1 parent ddeb0b7 commit dc8e77f
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
UEBA_PATH,
CASES_FEATURE_ID,
HOST_ISOLATION_EXCEPTIONS_PATH,
SERVER_APP_ID,
} from '../../../common/constants';
import { ExperimentalFeatures } from '../../../common/experimental_features';

Expand Down Expand Up @@ -365,7 +366,7 @@ export function getDeepLinks(
if (deepLink.id === SecurityPageName.investigate) {
return true;
}
return capabilities?.siem.show ?? false;
return capabilities == null || capabilities[SERVER_APP_ID].show === true;
})
.map((deepLink) => {
if (
Expand Down
8 changes: 2 additions & 6 deletions x-pack/plugins/security_solution/public/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,8 @@ export const renderApp = ({
services,
store,
usageCollection,
subPlugins,
subPluginRoutes,
}: RenderAppProps): (() => void) => {
const allRoutes = Object.entries(subPlugins).reduce<RouteProps[]>(
(acc, [, value]) => [...acc, ...value.routes],
[]
);
const ApplicationUsageTrackingProvider =
usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
render(
Expand All @@ -40,7 +36,7 @@ export const renderApp = ({
>
<ApplicationUsageTrackingProvider>
<Switch>
{allRoutes.map((route, index) => {
{subPluginRoutes.map((route, index) => {
return <Route key={`route-${index}`} {...route} />;
})}
<Route path="" exact>
Expand Down
47 changes: 47 additions & 0 deletions x-pack/plugins/security_solution/public/app/no_privileges.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 React, { useMemo } from 'react';

import { EuiPageTemplate } from '@elastic/eui';
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
import { EmptyPage } from '../common/components/empty_page';
import { useKibana } from '../common/lib/kibana';
import * as i18n from './translations';

interface NoPrivilegesPageProps {
subPluginKey: string;
}

export const NoPrivilegesPage = React.memo<NoPrivilegesPageProps>(({ subPluginKey }) => {
const { docLinks } = useKibana().services;
const emptyPageActions = useMemo(
() => ({
feature: {
icon: 'documents',
label: i18n.GO_TO_DOCUMENTATION,
url: `${docLinks.links.siem.privileges}`,
target: '_blank',
},
}),
[docLinks]
);
return (
<SecuritySolutionPageWrapper>
<EuiPageTemplate template="centeredContent">
<EmptyPage
actions={emptyPageActions}
message={i18n.NO_PERMISSIONS_MSG(subPluginKey)}
data-test-subj="no_feature_permissions-alerts"
title={i18n.NO_PERMISSIONS_TITLE}
/>
</EuiPageTemplate>
</SecuritySolutionPageWrapper>
);
});

NoPrivilegesPage.displayName = 'NoPrivilegePage';
18 changes: 18 additions & 0 deletions x-pack/plugins/security_solution/public/app/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,21 @@ export const INVESTIGATE = i18n.translate('xpack.securitySolution.navigation.inv
export const MANAGE = i18n.translate('xpack.securitySolution.navigation.manage', {
defaultMessage: 'Manage',
});

export const GO_TO_DOCUMENTATION = i18n.translate(
'xpack.securitySolution.goToDocumentationButton',
{
defaultMessage: 'View documentation',
}
);

export const NO_PERMISSIONS_MSG = (subPluginKey: string) =>
i18n.translate('xpack.securitySolution.noPermissionsMessage', {
values: { subPluginKey },
defaultMessage:
'To view {subPluginKey}, you must update privileges. For more information, contact your Kibana administrator.',
});

export const NO_PERMISSIONS_TITLE = i18n.translate('xpack.securitySolution.noPermissionsTitle', {
defaultMessage: 'Privileges required',
});
4 changes: 2 additions & 2 deletions x-pack/plugins/security_solution/public/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import {
import { RouteProps } from 'react-router-dom';
import { AppMountParameters } from '../../../../../src/core/public';
import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public';
import { StartedSubPlugins, StartServices } from '../types';
import { StartServices } from '../types';

/**
* The React properties used to render `SecurityApp` as well as the `element` to render it into.
*/
export interface RenderAppProps extends AppMountParameters {
services: StartServices;
store: Store<State, Action>;
subPlugins: StartedSubPlugins;
subPluginRoutes: RouteProps[];
usageCollection?: UsageCollectionSetup;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@
*/

import { isEmpty } from 'lodash/fp';
import { matchPath } from 'react-router-dom';
import React from 'react';
import { matchPath, RouteProps } from 'react-router-dom';

import { CoreStart } from '../../../../src/core/public';
import { Capabilities, CoreStart } from '../../../../src/core/public';
import {
ALERTS_PATH,
APP_UI_ID,
EXCEPTIONS_PATH,
RULES_PATH,
UEBA_PATH,
RISKY_HOSTS_INDEX_PREFIX,
SERVER_APP_ID,
CASES_FEATURE_ID,
} from '../common/constants';
import {
FactoryQueryTypes,
StrategyResponseType,
} from '../common/search_strategy/security_solution';
import { TimelineEqlResponse } from '../common/search_strategy/timeline';
import { NoPrivilegesPage } from './app/no_privileges';
import { SecurityPageName } from './app/types';
import { InspectResponse } from './types';
import { CASES_SUB_PLUGIN_KEY, InspectResponse, StartedSubPlugins } from './types';

export const parseRoute = (location: Pick<Location, 'hash' | 'pathname' | 'search'>) => {
if (!isEmpty(location.hash)) {
Expand Down Expand Up @@ -158,3 +162,28 @@ export const isDetectionsPath = (pathname: string): boolean => {
export const getHostRiskIndex = (spaceId: string): string => {
return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`;
};

export const getSubPluginRoutesByCapabilities = (
subPlugins: StartedSubPlugins,
capabilities: Capabilities
): RouteProps[] => {
return Object.entries(subPlugins).reduce<RouteProps[]>((acc, [key, value]) => {
if (isSubPluginAvailable(key, capabilities)) {
return [...acc, ...value.routes];
}
return [
...acc,
...value.routes.map((route: RouteProps) => ({
path: route.path,
component: <NoPrivilegesPage subPluginKey={key} />,
})),
];
}, []);
};

const isSubPluginAvailable = (pluginKey: string, capabilities: Capabilities): boolean => {
if (CASES_SUB_PLUGIN_KEY === pluginKey) {
return capabilities[CASES_FEATURE_ID].read_cases === true;
}
return capabilities[SERVER_APP_ID].show === true;
};
73 changes: 6 additions & 67 deletions x-pack/plugins/security_solution/public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,11 @@
* 2.0.
*/

import React, { createContext, useContext, useEffect, useState } from 'react';
import { DeepReadonly } from 'utility-types';
import { PluginInitializerContext } from '../../../../src/core/public';
import { Plugin } from './plugin';
import { PluginSetup } from './types';
export type { TimelineModel } from './timelines/store/timeline/model';

import { Capabilities } from '../../../../../../../src/core/public';
import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges';
import { useFetchListPrivileges } from '../../../detections/components/user_privileges/use_fetch_list_privileges';
import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges';
export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context);

import { SERVER_APP_ID } from '../../../../common/constants';
export interface UserPrivilegesState {
listPrivileges: ReturnType<typeof useFetchListPrivileges>;
detectionEnginePrivileges: ReturnType<typeof useFetchDetectionEnginePrivileges>;
endpointPrivileges: EndpointPrivileges;
kibanaSecuritySolutionsPrivileges: { crud: boolean; read: boolean };
}

export const initialUserPrivilegesState = (): UserPrivilegesState => ({
listPrivileges: { loading: false, error: undefined, result: undefined },
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false },
kibanaSecuritySolutionsPrivileges: { crud: false, read: false },
});

const UserPrivilegesContext = createContext<UserPrivilegesState>(initialUserPrivilegesState());

interface UserPrivilegesProviderProps {
kibanaCapabilities: Capabilities;
children: React.ReactNode;
}

export const UserPrivilegesProvider = ({
kibanaCapabilities,
children,
}: UserPrivilegesProviderProps) => {
const listPrivileges = useFetchListPrivileges();
const detectionEnginePrivileges = useFetchDetectionEnginePrivileges();
const endpointPrivileges = useEndpointPrivileges();
const [kibanaSecuritySolutionsPrivileges, setKibanaSecuritySolutionsPrivileges] = useState({
crud: false,
read: false,
});
const crud: boolean = kibanaCapabilities[SERVER_APP_ID].crud === true;
const read: boolean = kibanaCapabilities[SERVER_APP_ID].show === true;

useEffect(() => {
setKibanaSecuritySolutionsPrivileges((currPrivileges) => {
if (currPrivileges.read !== read || currPrivileges.crud !== crud) {
return { read, crud };
}
return currPrivileges;
});
}, [crud, read]);

return (
<UserPrivilegesContext.Provider
value={{
listPrivileges,
detectionEnginePrivileges,
endpointPrivileges,
kibanaSecuritySolutionsPrivileges,
}}
>
{children}
</UserPrivilegesContext.Provider>
);
};

export const useUserPrivileges = (): DeepReadonly<UserPrivilegesState> =>
useContext(UserPrivilegesContext);
export { Plugin, PluginSetup };
15 changes: 7 additions & 8 deletions x-pack/plugins/security_solution/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
} from '../common/constants';

import { getDeepLinks } from './app/deep_links';
import { manageOldSiemRoutes } from './helpers';
import { getSubPluginRoutesByCapabilities, manageOldSiemRoutes } from './helpers';
import {
IndexFieldsStrategyRequest,
IndexFieldsStrategyResponse,
Expand Down Expand Up @@ -153,7 +153,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
services: await startServices,
store: await this.store(coreStart, startPlugins, subPlugins),
usageCollection: plugins.usageCollection,
subPlugins,
subPluginRoutes: getSubPluginRoutesByCapabilities(
subPlugins,
coreStart.application.capabilities
),
});
},
});
Expand Down Expand Up @@ -382,9 +385,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
...subPlugins.exceptions.storageTimelines!.timelineById,
...subPlugins.hosts.storageTimelines!.timelineById,
...subPlugins.network.storageTimelines!.timelineById,
...(this.experimentalFeatures.uebaEnabled && subPlugins.ueba != null
? subPlugins.ueba.storageTimelines!.timelineById
: {}),
...subPlugins.ueba.storageTimelines!.timelineById,
},
},
};
Expand Down Expand Up @@ -415,9 +416,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
{
...subPlugins.hosts.store.reducer,
...subPlugins.network.store.reducer,
...(this.experimentalFeatures.uebaEnabled && subPlugins.ueba != null
? subPlugins.ueba.store.reducer
: {}),
...subPlugins.ueba.store.reducer,
timeline: timelineReducer,
...subPlugins.management.store.reducer,
...tGridReducer,
Expand Down
5 changes: 3 additions & 2 deletions x-pack/plugins/security_solution/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ export interface AppObservableLibs {

export type InspectResponse = Inspect & { response: string[] };

export const CASES_SUB_PLUGIN_KEY = 'cases';
export interface SubPlugins {
alerts: Detections;
rules: Rules;
exceptions: Exceptions;
cases: Cases;
[CASES_SUB_PLUGIN_KEY]: Cases;
hosts: Hosts;
network: Network;
ueba: Ueba;
Expand All @@ -107,7 +108,7 @@ export interface StartedSubPlugins {
alerts: ReturnType<Detections['start']>;
rules: ReturnType<Rules['start']>;
exceptions: ReturnType<Exceptions['start']>;
cases: ReturnType<Cases['start']>;
[CASES_SUB_PLUGIN_KEY]: ReturnType<Cases['start']>;
hosts: ReturnType<Hosts['start']>;
network: ReturnType<Network['start']>;
ueba: ReturnType<Ueba['start']>;
Expand Down

0 comments on commit dc8e77f

Please sign in to comment.