Alerting: Fix folder permissions (#48189)

* Use the folder view permission in alerting

* Add tests
This commit is contained in:
Konrad Lalik 2022-04-26 15:57:00 +02:00 committed by GitHub
parent 3b4d237ade
commit 5f594addbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 135 additions and 8 deletions

View File

@ -8,20 +8,23 @@ import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'
import { locationService, setDataSourceSrv } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
import RuleList from './RuleList';
import { fetchBuildInfo } from './api/buildInfo';
import { fetchRules } from './api/prometheus';
import { fetchRulerRules, deleteRulerRulesGroup, deleteNamespace, setRulerRuleGroup } from './api/ruler';
import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
import {
enableRBAC,
grantUserPermissions,
mockDataSource,
MockDataSourceSrv,
mockPromAlert,
mockPromAlertingRule,
mockPromRecordingRule,
mockPromRuleGroup,
mockPromRuleNamespace,
MockDataSourceSrv,
somePromRules,
someRulerRules,
} from './mocks';
@ -101,6 +104,8 @@ const ui = {
moreErrorsButton: byRole('button', { name: /more errors/ }),
editCloudGroupIcon: byTestId('edit-group'),
newRuleButton: byRole('link', { name: 'New alert rule' }),
editGroupModal: {
namespaceInput: byLabelText('Namespace'),
ruleGroupInput: byLabelText('Rule group'),
@ -631,4 +636,105 @@ describe('RuleList', () => {
);
});
});
describe('RBAC Enabled', () => {
describe('Grafana Managed Alerts', () => {
it('New alert button should be visible when the user has alert rule create and folder read permissions and no rules exists', async () => {
enableRBAC();
grantUserPermissions([
AccessControlAction.FoldersRead,
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleRead,
]);
mocks.getAllDataSourcesMock.mockReturnValue([]);
setDataSourceSrv(new MockDataSourceSrv({}));
mocks.api.fetchRules.mockResolvedValue([]);
mocks.api.fetchRulerRules.mockResolvedValue({});
renderRuleList();
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
expect(ui.newRuleButton.get()).toBeInTheDocument();
});
it('New alert button should be visible when the user has alert rule create and folder read permissions and rules already exists', async () => {
enableRBAC();
grantUserPermissions([
AccessControlAction.FoldersRead,
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleRead,
]);
mocks.getAllDataSourcesMock.mockReturnValue([]);
setDataSourceSrv(new MockDataSourceSrv({}));
mocks.api.fetchRules.mockResolvedValue(somePromRules('grafana'));
mocks.api.fetchRulerRules.mockResolvedValue(someRulerRules);
renderRuleList();
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
expect(ui.newRuleButton.get()).toBeInTheDocument();
});
});
describe('Cloud Alerts', () => {
it('New alert button should be visible when the user has the alert rule external write and datasource read permissions and no rules exists', async () => {
enableRBAC();
grantUserPermissions([
// AccessControlAction.AlertingRuleRead,
AccessControlAction.DataSourcesRead,
AccessControlAction.AlertingRuleExternalRead,
AccessControlAction.AlertingRuleExternalWrite,
]);
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
mocks.api.fetchBuildInfo.mockResolvedValue({
application: PromApplication.Cortex,
features: {
rulerApiEnabled: true,
},
});
mocks.api.fetchRules.mockResolvedValue([]);
mocks.api.fetchRulerRules.mockResolvedValue({});
renderRuleList();
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
expect(ui.newRuleButton.get()).toBeInTheDocument();
});
it('New alert button should be visible when the user has the alert rule external write and data source read permissions and rules already exists', async () => {
enableRBAC();
grantUserPermissions([
AccessControlAction.DataSourcesRead,
AccessControlAction.AlertingRuleExternalRead,
AccessControlAction.AlertingRuleExternalWrite,
]);
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
mocks.api.fetchBuildInfo.mockResolvedValue({
application: PromApplication.Cortex,
features: {
rulerApiEnabled: true,
},
});
mocks.api.fetchRules.mockResolvedValue(somePromRules('Cortex'));
mocks.api.fetchRulerRules.mockResolvedValue(someRulerRules);
renderRuleList();
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
expect(ui.newRuleButton.get()).toBeInTheDocument();
});
});
});
});

View File

@ -1,6 +1,7 @@
import React, { FC } from 'react';
import { FolderPicker, Props as FolderPickerProps } from 'app/core/components/Select/FolderPicker';
import { PermissionLevelString } from 'app/types';
export interface Folder {
title: string;
@ -12,5 +13,12 @@ export interface Props extends Omit<FolderPickerProps, 'initialTitle' | 'initial
}
export const RuleFolderPicker: FC<Props> = ({ value, ...props }) => (
<FolderPicker showRoot={false} allowEmpty={true} initialTitle={value?.title} initialFolderId={value?.id} {...props} />
<FolderPicker
showRoot={false}
allowEmpty={true}
initialTitle={value?.title}
initialFolderId={value?.id}
{...props}
permissionLevel={PermissionLevelString.View}
/>
);

View File

@ -6,7 +6,8 @@ import {
DataSourceRef,
ScopedVars,
} from '@grafana/data';
import { DataSourceSrv, GetDataSourceListFilters, config } from '@grafana/runtime';
import { config, DataSourceSrv, GetDataSourceListFilters } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import {
AlertmanagerAlert,
@ -18,8 +19,8 @@ import {
Silence,
SilenceState,
} from 'app/plugins/datasource/alertmanager/types';
import { FolderDTO } from 'app/types';
import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace, CombinedRule } from 'app/types/unified-alerting';
import { AccessControlAction, FolderDTO } from 'app/types';
import { Alert, AlertingRule, CombinedRule, RecordingRule, RuleGroup, RuleNamespace } from 'app/types/unified-alerting';
import {
GrafanaAlertStateDecision,
GrafanaRuleDefinition,
@ -465,3 +466,13 @@ export const mockFolder = (partial?: Partial<FolderDTO>): FolderDTO => {
...partial,
};
};
export const enableRBAC = () => {
jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true);
};
export const grantUserPermissions = (permissions: AccessControlAction[]) => {
jest
.spyOn(contextSrv, 'hasPermission')
.mockImplementation((action) => permissions.includes(action as AccessControlAction));
};

View File

@ -108,9 +108,11 @@ export function evaluateAccess(actions: AccessControlAction[], fallBackUserRoles
export function getRulesAccess() {
return {
canCreateGrafanaRules:
contextSrv.hasEditPermissionInFolders &&
contextSrv.hasAccess(AccessControlAction.FoldersRead, contextSrv.isEditor) &&
contextSrv.hasAccess(rulesPermissions.create.grafana, contextSrv.isEditor),
canCreateCloudRules: contextSrv.hasAccess(rulesPermissions.create.external, contextSrv.isEditor),
canCreateCloudRules:
contextSrv.hasAccess(AccessControlAction.DataSourcesRead, contextSrv.isEditor) &&
contextSrv.hasAccess(rulesPermissions.create.external, contextSrv.isEditor),
canEditRules: (rulesSourceName: string) =>
contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, contextSrv.isEditor),
};