Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SECURITY SOLUTION] [CASES] Allow cases to be there when security solutions privileges is none #113573

Merged
merged 25 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7b2ce23
allow case to itself when security solutions privileges is none
XavierM Sep 30, 2021
60609ba
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 14, 2021
ddeb0b7
bring back the right owner for cases
XavierM Oct 14, 2021
dc8e77f
bring no privilege msg when needed it
XavierM Oct 14, 2021
0081e9f
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 15, 2021
e950051
fix types
XavierM Oct 15, 2021
dd2fa10
fix test
XavierM Oct 15, 2021
44d54b2
adding test
XavierM Oct 16, 2021
0575e04
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 16, 2021
a689200
merge and type fix
semd Oct 18, 2021
e4c7f01
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 18, 2021
b4ac98c
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 20, 2021
266904a
review
XavierM Oct 20, 2021
d4c87a5
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 20, 2021
11e89a3
deepLinks generation fixed
semd Oct 21, 2021
bc24a21
register home solution with old app id
semd Oct 21, 2021
3548d79
fix get deep links
semd Oct 21, 2021
74a3871
fix home link
XavierM Oct 21, 2021
516a654
Merge branch 'cases-top-feature' of github.com:XavierM/kibana into ca…
XavierM Oct 21, 2021
1dcccff
fix unit test
XavierM Oct 21, 2021
d6cce9d
add test
XavierM Oct 21, 2021
0fd969a
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 21, 2021
ce015c9
fix telemetry
XavierM Oct 21, 2021
2953dce
Merge branch 'master' of github.com:elastic/kibana into cases-top-fea…
XavierM Oct 21, 2021
401ba0f
Merge branch 'master' into cases-top-feature
kibanamachine Oct 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ENABLE_CASE_CONNECTOR } from '../../cases/common';
import { METADATA_TRANSFORMS_PATTERN } from './endpoint/constants';

export const APP_ID = 'securitySolution';
export const APP_UI_ID = 'securitySolutionUI';
export const CASES_FEATURE_ID = 'securitySolutionCases';
export const SERVER_APP_ID = 'siem';
export const APP_NAME = 'Security';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getDeepLinks, PREMIUM_DEEP_LINK_IDS } from '.';
import { AppDeepLink, Capabilities } from '../../../../../../src/core/public';
import { SecurityPageName } from '../types';
import { mockGlobalState } from '../../common/mock';
import { CASES_FEATURE_ID } from '../../../common/constants';
import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../../common/constants';

const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null =>
deepLinks.reduce((deepLinkFound: AppDeepLink | null, deepLink) => {
Expand Down Expand Up @@ -60,15 +60,16 @@ describe('deepLinks', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
[SERVER_APP_ID]: { show: true },
} as unknown as Capabilities);

expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy();
});

it('should return case links with NO deepLinks for basic license with only read_cases capabilities', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
[SERVER_APP_ID]: { show: true },
} as unknown as Capabilities);
expect(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length === 0).toBeTruthy();
});
Expand All @@ -77,6 +78,7 @@ describe('deepLinks', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: true },
[SERVER_APP_ID]: { show: true },
} as unknown as Capabilities);

expect(
Expand All @@ -88,6 +90,7 @@ describe('deepLinks', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
[CASES_FEATURE_ID]: { read_cases: false, crud_cases: false },
[SERVER_APP_ID]: { show: true },
} as unknown as Capabilities);

expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeFalsy();
Expand Down
30 changes: 7 additions & 23 deletions x-pack/plugins/security_solution/public/app/deep_links/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@
*/

import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import { isEmpty } from 'lodash';

import { LicenseType } from '../../../../licensing/common/types';
import { SecurityPageName } from '../types';
import {
AppDeepLink,
ApplicationStart,
AppNavLinkStatus,
AppUpdater,
} from '../../../../../../src/core/public';
import { AppDeepLink, ApplicationStart, AppNavLinkStatus } from '../../../../../../src/core/public';
import {
OVERVIEW,
DETECT,
Expand Down Expand Up @@ -50,6 +45,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 @@ -368,7 +364,10 @@ export function getDeepLinks(
if (deepLink.id === SecurityPageName.ueba) {
return enableExperimental.uebaEnabled;
}
return true;
if (!isEmpty(deepLink.deepLinks)) {
return true;
}
return capabilities == null || capabilities[SERVER_APP_ID].show === true;
})
.map((deepLink) => {
if (
Expand Down Expand Up @@ -402,18 +401,3 @@ export function isPremiumLicense(licenseType?: LicenseType): boolean {
licenseType === 'trial'
);
}

export function updateGlobalNavigation({
capabilities,
updater$,
enableExperimental,
}: {
capabilities: ApplicationStart['capabilities'];
updater$: Subject<AppUpdater>;
enableExperimental: ExperimentalFeatures;
}) {
updater$.next(() => ({
navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent showing main nav link
deepLinks: getDeepLinks(enableExperimental, undefined, capabilities),
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionPageWrapp
const showEmptyState = useShowPagesWithEmptyView();
const emptyStateProps = showEmptyState ? NO_DATA_PAGE_TEMPLATE_PROPS : {};

// StyledKibanaPageTemplate is a styled EuiPageTemplate. Security solution currently passes the header and page content as the children of StyledKibanaPageTemplate, as opposed to using the pageHeader prop, which may account for any style discrepancies, such as the bottom border not extending the full width of the page, between EuiPageTemplate and the security solution pages.

/*
* StyledKibanaPageTemplate is a styled EuiPageTemplate. Security solution currently passes the header
* and page content as the children of StyledKibanaPageTemplate, as opposed to using the pageHeader prop,
* which may account for any style discrepancies, such as the bottom border not extending the full width of the page,
* between EuiPageTemplate and the security solution pages.
*/
return (
<StyledKibanaPageTemplate
$isTimelineBottomBarVisible={isTimelineBottomBarVisible}
Expand Down
21 changes: 4 additions & 17 deletions x-pack/plugins/security_solution/public/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const renderApp = ({
services,
store,
usageCollection,
subPlugins,
subPluginRoutes,
}: RenderAppProps): (() => void) => {
const ApplicationUsageTrackingProvider =
usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
Expand All @@ -36,22 +36,9 @@ export const renderApp = ({
>
<ApplicationUsageTrackingProvider>
<Switch>
{[
...subPlugins.overview.routes,
...subPlugins.alerts.routes,
...subPlugins.rules.routes,
...subPlugins.exceptions.routes,
...subPlugins.hosts.routes,
...subPlugins.network.routes,
// will be undefined if enabledExperimental.uebaEnabled === false
...(subPlugins.ueba != null ? subPlugins.ueba.routes : []),
...subPlugins.timelines.routes,
...subPlugins.cases.routes,
...subPlugins.management.routes,
].map((route, index) => (
<Route key={`route-${index}`} {...route} />
))}

{subPluginRoutes.map((route, index) => {
return <Route key={`route-${index}`} {...route} />;
})}
<Route path="" exact>
<Redirect to={OVERVIEW_PATH} />
</Route>
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 @@ -15,7 +15,7 @@ import {
} from '../../../common/components/link_to';
import { SecurityPageName } from '../../../app/types';
import { useKibana } from '../../../common/lib/kibana';
import { APP_ID } from '../../../../common/constants';
import { APP_ID, APP_UI_ID } from '../../../../common/constants';

export interface AllCasesNavProps {
detailName: string;
Expand All @@ -36,7 +36,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
const goToCreateCase = useCallback(
async (ev) => {
ev.preventDefault();
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getCreateCaseUrl(urlSearch),
});
Expand All @@ -47,7 +47,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
const goToCaseConfigure = useCallback(
async (ev) => {
ev.preventDefault();
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getConfigureCasesUrl(urlSearch),
});
Expand All @@ -61,7 +61,7 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId }));
},
onClick: async ({ detailName, subCaseId, search }: AllCasesNavProps) => {
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getCaseDetailsUrl({ id: detailName, search, subCaseId }),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Case, CaseViewRefreshPropInterface } from '../../../../../cases/common'
import { TimelineId } from '../../../../common/types/timeline';
import { SecurityPageName } from '../../../app/types';
import { useKibana } from '../../../common/lib/kibana';
import { APP_ID } from '../../../../common/constants';
import { APP_UI_ID } from '../../../../common/constants';
import { timelineActions } from '../../../timelines/store/timeline';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
Expand Down Expand Up @@ -153,7 +153,7 @@ export const CaseView = React.memo(
if (e) {
e.preventDefault();
}
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: allCasesLink,
});
Expand All @@ -165,7 +165,7 @@ export const CaseView = React.memo(
if (e) {
e.preventDefault();
}
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getCaseDetailsUrl({ id: caseId }),
});
Expand All @@ -178,7 +178,7 @@ export const CaseView = React.memo(
if (e) {
e.preventDefault();
}
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getConfigureCasesUrl(search),
});
Expand All @@ -193,7 +193,7 @@ export const CaseView = React.memo(
if (e) {
e.preventDefault();
}
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
path: getEndpointDetailsPath({
name: 'endpointActivityLog',
selected_endpoint: endpointId,
Expand All @@ -207,7 +207,7 @@ export const CaseView = React.memo(
if (e) {
e.preventDefault();
}
return navigateToApp(APP_ID, {
return navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.rules,
path: getRuleDetailsUrl(ruleId ?? ''),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Create } from '.';
import { useKibana } from '../../../common/lib/kibana';
import { Case } from '../../../../../cases/public/containers/types';
import { basicCase } from '../../../../../cases/public/containers/mock';
import { APP_ID, SecurityPageName } from '../../../../common/constants';
import { APP_ID, APP_UI_ID, SecurityPageName } from '../../../../common/constants';
import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';

jest.mock('../use_insert_timeline');
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('Create case', () => {
);

await waitFor(() =>
expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, {
expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, {
path: `?${mockRes}`,
deepLinkId: SecurityPageName.case,
})
Expand All @@ -96,7 +96,7 @@ describe('Create case', () => {
);

await waitFor(() =>
expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_ID, {
expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_UI_ID, {
path: `/basic-case-id?${mockRes}`,
deepLinkId: SecurityPageName.case,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_t
import { useKibana } from '../../../common/lib/kibana';
import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline';
import { useInsertTimeline } from '../use_insert_timeline';
import { APP_ID } from '../../../../common/constants';
import { APP_ID, APP_UI_ID } from '../../../../common/constants';
import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
import { navTabs } from '../../../app/home/home_navigations';
import { SecurityPageName } from '../../../app/types';
Expand All @@ -25,15 +25,15 @@ export const Create = React.memo(() => {
const search = useGetUrlSearch(navTabs.case);
const onSuccess = useCallback(
async ({ id }) =>
navigateToApp(APP_ID, {
navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getCaseDetailsUrl({ id, search }),
}),
[navigateToApp, search]
);
const handleSetIsCancel = useCallback(
async () =>
navigateToApp(APP_ID, {
navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.case,
path: getCaseUrl(search),
}),
Expand Down
Loading