mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Resolves #126328 and #126314.
(cherry picked from commit 18a5344082
)
Co-authored-by: Yara Tercero <yctercero@users.noreply.github.com>
This commit is contained in:
parent
eb178a860b
commit
c2d8771925
13 changed files with 175 additions and 62 deletions
|
@ -72,24 +72,7 @@ describe('Detections > Callouts', () => {
|
|||
});
|
||||
});
|
||||
|
||||
context('On Rules Management page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
|
||||
});
|
||||
|
||||
it('We show one primary callout', () => {
|
||||
waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary');
|
||||
});
|
||||
|
||||
context('When a user clicks Dismiss on the callout', () => {
|
||||
it('We hide it and persist the dismissal', () => {
|
||||
waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary');
|
||||
dismissCallOut(MISSING_PRIVILEGES_CALLOUT);
|
||||
reloadPage();
|
||||
getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
// FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts
|
||||
|
||||
context('On Rule Details page', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { ROLES } from '../../../common/test';
|
||||
import { getNewRule } from '../../objects/rule';
|
||||
import {
|
||||
COLLAPSED_ACTION_BTN,
|
||||
RULE_CHECKBOX,
|
||||
RULE_NAME,
|
||||
} from '../../screens/alerts_detection_rules';
|
||||
import { PAGE_TITLE } from '../../screens/common/page';
|
||||
import { waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
|
||||
import { createCustomRule } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { dismissCallOut, getCallOut, waitForCallOutToBeShown } from '../../tasks/common/callouts';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
|
||||
|
||||
const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges';
|
||||
|
||||
describe('All rules - read only', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
createCustomRule(getNewRule(), '1');
|
||||
loginAndWaitForPageWithoutDateRange(SECURITY_DETECTIONS_RULES_URL, ROLES.reader);
|
||||
waitForRulesTableToBeLoaded();
|
||||
cy.get(RULE_NAME).should('have.text', getNewRule().name);
|
||||
});
|
||||
|
||||
it('Does not display select boxes for rules', () => {
|
||||
cy.get(RULE_CHECKBOX).should('not.exist');
|
||||
});
|
||||
|
||||
it('Does not display action options', () => {
|
||||
// These are the 3 dots at the end of the row that opens up
|
||||
// options to take action on the rule
|
||||
cy.get(COLLAPSED_ACTION_BTN).should('not.exist');
|
||||
});
|
||||
|
||||
it('Displays missing privileges primary callout', () => {
|
||||
waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary');
|
||||
});
|
||||
|
||||
context('When a user clicks Dismiss on the callouts', () => {
|
||||
it('We hide them and persist the dismissal', () => {
|
||||
waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary');
|
||||
|
||||
dismissCallOut(MISSING_PRIVILEGES_CALLOUT);
|
||||
cy.reload();
|
||||
cy.get(PAGE_TITLE).should('be.visible');
|
||||
cy.get(RULE_NAME).should('have.text', getNewRule().name);
|
||||
|
||||
getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { ALERTS_PATH, SecurityPageName } from '../../../../common/constants';
|
||||
|
@ -13,9 +13,8 @@ import { NotFoundPage } from '../../../app/404';
|
|||
import * as i18n from './translations';
|
||||
import { TrackApplicationView } from '../../../../../../../src/plugins/usage_collection/public';
|
||||
import { DetectionEnginePage } from '../../pages/detection_engine/detection_engine';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { SpyRoute } from '../../../common/utils/route/spy_routes';
|
||||
import { useAlertsPrivileges } from '../../containers/detection_engine/alerts/use_alerts_privileges';
|
||||
import { useReadonlyHeader } from '../../../use_readonly_header';
|
||||
|
||||
const AlertsRoute = () => (
|
||||
<TrackApplicationView viewId={SecurityPageName.alerts}>
|
||||
|
@ -25,24 +24,7 @@ const AlertsRoute = () => (
|
|||
);
|
||||
|
||||
const AlertsContainerComponent: React.FC = () => {
|
||||
const { chrome } = useKibana().services;
|
||||
const { hasIndexRead, hasIndexWrite } = useAlertsPrivileges();
|
||||
|
||||
useEffect(() => {
|
||||
// if the user is read only then display the glasses badge in the global navigation header
|
||||
if (!hasIndexWrite && hasIndexRead) {
|
||||
chrome.setBadge({
|
||||
text: i18n.READ_ONLY_BADGE_TEXT,
|
||||
tooltip: i18n.READ_ONLY_BADGE_TOOLTIP,
|
||||
iconType: 'glasses',
|
||||
});
|
||||
}
|
||||
|
||||
// remove the icon after the component unmounts
|
||||
return () => {
|
||||
chrome.setBadge();
|
||||
};
|
||||
}, [chrome, hasIndexRead, hasIndexWrite]);
|
||||
useReadonlyHeader(i18n.READ_ONLY_BADGE_TOOLTIP);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const READ_ONLY_BADGE_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.alerts.badge.readOnly.text',
|
||||
{
|
||||
defaultMessage: 'Read only',
|
||||
}
|
||||
);
|
||||
|
||||
export const READ_ONLY_BADGE_TOOLTIP = i18n.translate(
|
||||
'xpack.securitySolution.alerts.badge.readOnly.tooltip',
|
||||
{
|
||||
|
|
|
@ -390,7 +390,7 @@ export const RulesTables = React.memo<RulesTableProps>(
|
|||
onChange={tableOnChangeCallback}
|
||||
pagination={paginationMemo}
|
||||
ref={tableRef}
|
||||
selection={euiBasicTableSelectionProps}
|
||||
selection={hasPermissions ? euiBasicTableSelectionProps : undefined}
|
||||
sorting={{
|
||||
sort: {
|
||||
// EuiBasicTable has incorrect `sort.field` types which accept only `keyof Item` and reject fields in dot notation
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public';
|
||||
import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { ExceptionListsTable } from '../detections/pages/detection_engine/rules/all/exceptions/exceptions_table';
|
||||
import { SpyRoute } from '../common/utils/route/spy_routes';
|
||||
import { NotFoundPage } from '../app/404';
|
||||
import { useReadonlyHeader } from '../use_readonly_header';
|
||||
|
||||
const ExceptionsRoutes = () => {
|
||||
return (
|
||||
|
@ -22,7 +24,9 @@ const ExceptionsRoutes = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const renderExceptionsRoutes = () => {
|
||||
const ExceptionsContainerComponent: React.FC = () => {
|
||||
useReadonlyHeader(i18n.READ_ONLY_BADGE_TOOLTIP);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={EXCEPTIONS_PATH} exact component={ExceptionsRoutes} />
|
||||
|
@ -31,6 +35,10 @@ const renderExceptionsRoutes = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const Exceptions = React.memo(ExceptionsContainerComponent);
|
||||
|
||||
const renderExceptionsRoutes = () => <Exceptions />;
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
path: EXCEPTIONS_PATH,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const READ_ONLY_BADGE_TOOLTIP = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.badge.readOnly.tooltip',
|
||||
{
|
||||
defaultMessage: 'Unable to create, edit or delete exceptions',
|
||||
}
|
||||
);
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public';
|
||||
import { RULES_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { NotFoundPage } from '../app/404';
|
||||
|
@ -14,6 +15,7 @@ import { RulesPage } from '../detections/pages/detection_engine/rules';
|
|||
import { CreateRulePage } from '../detections/pages/detection_engine/rules/create';
|
||||
import { RuleDetailsPage } from '../detections/pages/detection_engine/rules/details';
|
||||
import { EditRulePage } from '../detections/pages/detection_engine/rules/edit';
|
||||
import { useReadonlyHeader } from '../use_readonly_header';
|
||||
|
||||
const RulesSubRoutes = [
|
||||
{
|
||||
|
@ -38,18 +40,26 @@ const RulesSubRoutes = [
|
|||
},
|
||||
];
|
||||
|
||||
const renderRulesRoutes = () => (
|
||||
<TrackApplicationView viewId={SecurityPageName.rules}>
|
||||
<Switch>
|
||||
{RulesSubRoutes.map((route, index) => (
|
||||
<Route key={`rules-route-${route.path}`} path={route.path} exact={route?.exact ?? false}>
|
||||
<route.main />
|
||||
</Route>
|
||||
))}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</TrackApplicationView>
|
||||
);
|
||||
const RulesContainerComponent: React.FC = () => {
|
||||
useReadonlyHeader(i18n.READ_ONLY_BADGE_TOOLTIP);
|
||||
|
||||
return (
|
||||
<TrackApplicationView viewId={SecurityPageName.rules}>
|
||||
<Switch>
|
||||
{RulesSubRoutes.map((route, index) => (
|
||||
<Route key={`rules-route-${route.path}`} path={route.path} exact={route?.exact ?? false}>
|
||||
<route.main />
|
||||
</Route>
|
||||
))}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</TrackApplicationView>
|
||||
);
|
||||
};
|
||||
|
||||
const Rules = React.memo(RulesContainerComponent);
|
||||
|
||||
const renderRulesRoutes = () => <Rules />;
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const READ_ONLY_BADGE_TOOLTIP = i18n.translate(
|
||||
'xpack.securitySolution.rules.badge.readOnly.tooltip',
|
||||
{
|
||||
defaultMessage: 'Unable to create, edit or delete rules',
|
||||
}
|
||||
);
|
12
x-pack/plugins/security_solution/public/translations.ts
Normal file
12
x-pack/plugins/security_solution/public/translations.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const READ_ONLY_BADGE_TEXT = i18n.translate('xpack.securitySolution.badge.readOnly.text', {
|
||||
defaultMessage: 'Read only',
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { useKibana } from './common/lib/kibana';
|
||||
import { useAlertsPrivileges } from './detections/containers/detection_engine/alerts/use_alerts_privileges';
|
||||
|
||||
/**
|
||||
* This component places a read-only icon badge in the header
|
||||
* if user only has read *Kibana* privileges, not individual data index
|
||||
* privileges
|
||||
*/
|
||||
export function useReadonlyHeader(tooltip: string) {
|
||||
const { hasKibanaREAD, hasKibanaCRUD } = useAlertsPrivileges();
|
||||
const chrome = useKibana().services.chrome;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasKibanaREAD && !hasKibanaCRUD) {
|
||||
chrome.setBadge({
|
||||
text: i18n.READ_ONLY_BADGE_TEXT,
|
||||
tooltip,
|
||||
iconType: 'glasses',
|
||||
});
|
||||
}
|
||||
|
||||
// remove the icon after the component unmounts
|
||||
return () => {
|
||||
chrome.setBadge();
|
||||
};
|
||||
}, [chrome, hasKibanaREAD, hasKibanaCRUD, tooltip]);
|
||||
}
|
|
@ -22845,7 +22845,6 @@
|
|||
"xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす",
|
||||
"xpack.securitySolution.alertDetails.summary.readMore": "続きを読む",
|
||||
"xpack.securitySolution.alertDetails.threatIntel": "Threat Intel",
|
||||
"xpack.securitySolution.alerts.badge.readOnly.text": "読み取り専用",
|
||||
"xpack.securitySolution.alerts.badge.readOnly.tooltip": "アラートを更新できません",
|
||||
"xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "このルールで生成されたすべてのアラートのリスクスコアを選択します。",
|
||||
"xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "デフォルトリスクスコア",
|
||||
|
|
|
@ -22874,7 +22874,6 @@
|
|||
"xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容",
|
||||
"xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容",
|
||||
"xpack.securitySolution.alertDetails.threatIntel": "威胁情报",
|
||||
"xpack.securitySolution.alerts.badge.readOnly.text": "只读",
|
||||
"xpack.securitySolution.alerts.badge.readOnly.tooltip": "无法更新告警",
|
||||
"xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "选择此规则生成的所有告警的风险分数。",
|
||||
"xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "默认风险分数",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue