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
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 { locationService, setDataSourceSrv } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore'; import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto'; import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
import RuleList from './RuleList'; import RuleList from './RuleList';
import { fetchBuildInfo } from './api/buildInfo'; import { fetchBuildInfo } from './api/buildInfo';
import { fetchRules } from './api/prometheus'; import { fetchRules } from './api/prometheus';
import { fetchRulerRules, deleteRulerRulesGroup, deleteNamespace, setRulerRuleGroup } from './api/ruler'; import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
import { import {
enableRBAC,
grantUserPermissions,
mockDataSource, mockDataSource,
MockDataSourceSrv,
mockPromAlert, mockPromAlert,
mockPromAlertingRule, mockPromAlertingRule,
mockPromRecordingRule, mockPromRecordingRule,
mockPromRuleGroup, mockPromRuleGroup,
mockPromRuleNamespace, mockPromRuleNamespace,
MockDataSourceSrv,
somePromRules, somePromRules,
someRulerRules, someRulerRules,
} from './mocks'; } from './mocks';
@@ -101,6 +104,8 @@ const ui = {
moreErrorsButton: byRole('button', { name: /more errors/ }), moreErrorsButton: byRole('button', { name: /more errors/ }),
editCloudGroupIcon: byTestId('edit-group'), editCloudGroupIcon: byTestId('edit-group'),
newRuleButton: byRole('link', { name: 'New alert rule' }),
editGroupModal: { editGroupModal: {
namespaceInput: byLabelText('Namespace'), namespaceInput: byLabelText('Namespace'),
ruleGroupInput: byLabelText('Rule group'), 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 React, { FC } from 'react';
import { FolderPicker, Props as FolderPickerProps } from 'app/core/components/Select/FolderPicker'; import { FolderPicker, Props as FolderPickerProps } from 'app/core/components/Select/FolderPicker';
import { PermissionLevelString } from 'app/types';
export interface Folder { export interface Folder {
title: string; title: string;
@@ -12,5 +13,12 @@ export interface Props extends Omit<FolderPickerProps, 'initialTitle' | 'initial
} }
export const RuleFolderPicker: FC<Props> = ({ value, ...props }) => ( 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, DataSourceRef,
ScopedVars, ScopedVars,
} from '@grafana/data'; } 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 { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import { import {
AlertmanagerAlert, AlertmanagerAlert,
@@ -18,8 +19,8 @@ import {
Silence, Silence,
SilenceState, SilenceState,
} from 'app/plugins/datasource/alertmanager/types'; } from 'app/plugins/datasource/alertmanager/types';
import { FolderDTO } from 'app/types'; import { AccessControlAction, FolderDTO } from 'app/types';
import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace, CombinedRule } from 'app/types/unified-alerting'; import { Alert, AlertingRule, CombinedRule, RecordingRule, RuleGroup, RuleNamespace } from 'app/types/unified-alerting';
import { import {
GrafanaAlertStateDecision, GrafanaAlertStateDecision,
GrafanaRuleDefinition, GrafanaRuleDefinition,
@@ -465,3 +466,13 @@ export const mockFolder = (partial?: Partial<FolderDTO>): FolderDTO => {
...partial, ...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() { export function getRulesAccess() {
return { return {
canCreateGrafanaRules: canCreateGrafanaRules:
contextSrv.hasEditPermissionInFolders && contextSrv.hasAccess(AccessControlAction.FoldersRead, contextSrv.isEditor) &&
contextSrv.hasAccess(rulesPermissions.create.grafana, 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) => canEditRules: (rulesSourceName: string) =>
contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, contextSrv.isEditor), contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, contextSrv.isEditor),
}; };