mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[RAM] Enable read-only users to access rules (#167003)
Closes #166613 ## Summary Enables users with read privilege on `Stack rules` to see the rules table and the rule detail page without editing capabilities. Before:  After:  In case of empty rules lists, the prompt "Create your first rule" was shown, even to users without create permissions:  To avoid confusion, read-only users now see the empty table instead:  In the rule detail page, users without access to `Actions and Connectors` now see a missing privileges message under `Actions` in the details panel instead of `No actions` and a `Forbidden` error toast.  Finally, the original missing authorization prompt now shows "read" instead of "create":  ## To test - Create an Elasticsearch query rule - Create a Role with read privilege granted in `Stack rules` (under Kibana > Management) and assign it to a user - Create a test user with the created role - Log in as the test user - Navigate to Stack Management > Rules - Check that the rules table is visible, with create and update actions disabled ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) --------- Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
This commit is contained in:
parent
ea0a1a073e
commit
e49628acab
10 changed files with 74 additions and 16 deletions
|
@ -38306,7 +38306,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.monthsLabel": "mois",
|
||||
"xpack.triggersActionsUI.sections.rulesList.multipleTitle": "règles",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateDescription": "Contactez votre administrateur système.",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateTitle": "Aucune autorisation pour créer des règles",
|
||||
"xpack.triggersActionsUI.sections.rulesList.previousSnooze": "Précédent",
|
||||
"xpack.triggersActionsUI.sections.rulesList.refreshRulesButtonLabel": "Actualiser",
|
||||
"xpack.triggersActionsUI.sections.rulesList.remainingSnoozeIndefinite": "Indéfiniment",
|
||||
|
|
|
@ -38297,7 +38297,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.monthsLabel": "月",
|
||||
"xpack.triggersActionsUI.sections.rulesList.multipleTitle": "ルール",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateDescription": "システム管理者にお問い合わせください。",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateTitle": "ルールを作成する権限がありません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.previousSnooze": "前へ",
|
||||
"xpack.triggersActionsUI.sections.rulesList.refreshRulesButtonLabel": "更新",
|
||||
"xpack.triggersActionsUI.sections.rulesList.remainingSnoozeIndefinite": "永久",
|
||||
|
|
|
@ -38291,7 +38291,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.monthsLabel": "个月",
|
||||
"xpack.triggersActionsUI.sections.rulesList.multipleTitle": "规则",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateDescription": "请联系您的系统管理员。",
|
||||
"xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateTitle": "没有创建规则的权限",
|
||||
"xpack.triggersActionsUI.sections.rulesList.previousSnooze": "上一步",
|
||||
"xpack.triggersActionsUI.sections.rulesList.refreshRulesButtonLabel": "刷新",
|
||||
"xpack.triggersActionsUI.sections.rulesList.remainingSnoozeIndefinite": "无限期",
|
||||
|
|
|
@ -16,8 +16,8 @@ export const NoPermissionPrompt = () => (
|
|||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.rulesList.noPermissionToCreateTitle"
|
||||
defaultMessage="No permissions to create rules"
|
||||
id="xpack.triggersActionsUI.sections.rulesList.noPermissionToReadTitle"
|
||||
defaultMessage="No permissions to read rules"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ export const useLoadRuleTypesQuery = (props: UseLoadRuleTypesQueryProps) => {
|
|||
const authorizedToCreateAnyRules = authorizedRuleTypes.some(
|
||||
(ruleType) => ruleType.authorizedConsumers[ALERTS_FEATURE_ID]?.all
|
||||
);
|
||||
const authorizedToReadAnyRules =
|
||||
authorizedToCreateAnyRules ||
|
||||
authorizedRuleTypes.some((ruleType) => ruleType.authorizedConsumers[ALERTS_FEATURE_ID]?.read);
|
||||
|
||||
return {
|
||||
ruleTypesState: {
|
||||
|
@ -73,6 +76,7 @@ export const useLoadRuleTypesQuery = (props: UseLoadRuleTypesQueryProps) => {
|
|||
},
|
||||
hasAnyAuthorizedRuleType,
|
||||
authorizedRuleTypes,
|
||||
authorizedToReadAnyRules,
|
||||
authorizedToCreateAnyRules,
|
||||
isSuccess,
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { isEmpty } from 'lodash';
|
|||
import { RulesListFilters } from '../../types';
|
||||
|
||||
interface UseUiProps {
|
||||
authorizedToReadAnyRules: boolean;
|
||||
authorizedToCreateAnyRules: boolean;
|
||||
filters: RulesListFilters;
|
||||
hasDefaultRuleTypesFiltersOn: boolean;
|
||||
|
@ -37,6 +38,7 @@ const getFilterApplied = ({ hasEmptyTypesFilter, filters }: GetFilterAppliedProp
|
|||
};
|
||||
|
||||
export const useRulesListUiState = ({
|
||||
authorizedToReadAnyRules,
|
||||
authorizedToCreateAnyRules,
|
||||
filters,
|
||||
hasDefaultRuleTypesFiltersOn,
|
||||
|
@ -56,8 +58,9 @@ export const useRulesListUiState = ({
|
|||
const isInitialLoading = isInitialLoadingRuleTypes || isInitialLoadingRules;
|
||||
const isLoading = isLoadingRuleTypes || isLoadingRules;
|
||||
|
||||
const showNoAuthPrompt = !isInitialLoadingRuleTypes && !authorizedToCreateAnyRules;
|
||||
const showCreateFirstRulePrompt = !isLoading && !hasData && !isFilterApplied;
|
||||
const showNoAuthPrompt = !isInitialLoadingRuleTypes && !authorizedToReadAnyRules;
|
||||
const showCreateFirstRulePrompt =
|
||||
!isLoading && !hasData && !isFilterApplied && authorizedToCreateAnyRules;
|
||||
const showSpinner =
|
||||
isInitialLoading && (isLoadingRuleTypes || (!showNoAuthPrompt && isLoadingRules));
|
||||
const showRulesList = !showSpinner && !showCreateFirstRulePrompt && !showNoAuthPrompt;
|
||||
|
|
|
@ -23,6 +23,7 @@ jest.mock('./rule_actions', () => ({
|
|||
jest.mock('../../../lib/capabilities', () => ({
|
||||
hasAllPrivilege: jest.fn(() => true),
|
||||
hasSaveRulesCapability: jest.fn(() => true),
|
||||
hasShowActionsCapability: jest.fn(() => true),
|
||||
hasExecuteActionsCapability: jest.fn(() => true),
|
||||
hasManageApiKeysCapability: jest.fn(() => true),
|
||||
}));
|
||||
|
|
|
@ -21,7 +21,11 @@ import { formatDuration } from '@kbn/alerting-plugin/common';
|
|||
import { RuleDefinitionProps } from '../../../../types';
|
||||
import { RuleType, useLoadRuleTypes } from '../../../..';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities';
|
||||
import {
|
||||
hasAllPrivilege,
|
||||
hasExecuteActionsCapability,
|
||||
hasShowActionsCapability,
|
||||
} from '../../../lib/capabilities';
|
||||
import { RuleActions } from './rule_actions';
|
||||
import { RuleEdit } from '../../rule_form';
|
||||
|
||||
|
@ -60,6 +64,7 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
values: { numberOfConditions },
|
||||
});
|
||||
};
|
||||
const canReadActions = hasShowActionsCapability(capabilities);
|
||||
const canExecuteActions = hasExecuteActionsCapability(capabilities);
|
||||
const canSaveRule =
|
||||
rule &&
|
||||
|
@ -209,11 +214,21 @@ export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({
|
|||
})}
|
||||
</ItemTitleRuleSummary>
|
||||
<EuiFlexItem grow={3}>
|
||||
<RuleActions
|
||||
ruleActions={rule.actions}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
legacyNotifyWhen={rule.notifyWhen}
|
||||
/>
|
||||
{canReadActions ? (
|
||||
<RuleActions
|
||||
ruleActions={rule.actions}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
legacyNotifyWhen={rule.notifyWhen}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.triggersActionsUI.ruleDetails.cannotReadActions', {
|
||||
defaultMessage: 'Connector feature privileges are required to view actions',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -243,6 +243,7 @@ export const RulesList = ({
|
|||
ruleTypesState,
|
||||
hasAnyAuthorizedRuleType,
|
||||
authorizedRuleTypes,
|
||||
authorizedToReadAnyRules,
|
||||
authorizedToCreateAnyRules,
|
||||
isSuccess: isLoadRuleTypesSuccess,
|
||||
} = useLoadRuleTypesQuery({ filteredRuleTypes });
|
||||
|
@ -285,6 +286,7 @@ export const RulesList = ({
|
|||
});
|
||||
|
||||
const { showSpinner, showRulesList, showNoAuthPrompt, showCreateFirstRulePrompt } = useUiState({
|
||||
authorizedToReadAnyRules,
|
||||
authorizedToCreateAnyRules,
|
||||
filters,
|
||||
hasDefaultRuleTypesFiltersOn,
|
||||
|
|
|
@ -297,9 +297,42 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
'Create rule button',
|
||||
async () => await testSubjects.exists('createRuleButton')
|
||||
);
|
||||
await retry.waitFor(
|
||||
'Create rule button is enabled',
|
||||
async () => await testSubjects.isEnabled('createRuleButton')
|
||||
);
|
||||
});
|
||||
|
||||
it(`shows the no permission prompt when the user has no permissions`, async () => {
|
||||
// We kept this test to make sure that the stack management rule page
|
||||
// is showing the right prompt corresponding to the right privileges.
|
||||
// Knowing that o11y alert page won't come up if you do not have any
|
||||
// kind of privileges to o11y
|
||||
await observability.users.setTestUserRole({
|
||||
elasticsearch: {
|
||||
cluster: [],
|
||||
indices: [],
|
||||
run_as: [],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: [],
|
||||
feature: {
|
||||
discover: ['read'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
await observability.alerts.common.navigateToRulesPage();
|
||||
await retry.waitFor(
|
||||
'No permissions prompt',
|
||||
async () => await testSubjects.exists('noPermissionPrompt')
|
||||
);
|
||||
await observability.users.restoreDefaultTestUserRole();
|
||||
});
|
||||
|
||||
it(`shows the rules list in read-only mode when the user only has read permissions`, async () => {
|
||||
await observability.users.setTestUserRole(
|
||||
observability.users.defineBasicObservabilityRole({
|
||||
logs: ['read'],
|
||||
|
@ -307,10 +340,13 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
);
|
||||
await observability.alerts.common.navigateToRulesPage();
|
||||
await retry.waitFor(
|
||||
'No permissions prompt',
|
||||
async () => await testSubjects.exists('noPermissionPrompt')
|
||||
'Read-only rules list is visible',
|
||||
async () => await testSubjects.exists('rulesList')
|
||||
);
|
||||
await retry.waitFor(
|
||||
'Create rule button is disabled',
|
||||
async () => !(await testSubjects.isEnabled('createRuleButton'))
|
||||
);
|
||||
|
||||
await observability.users.restoreDefaultTestUserRole();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue