mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Use folders' RBAC permission to control rules actions (#51434)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
@@ -7,19 +7,21 @@ import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { BackendSrv, locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { DashboardSearchHit } from 'app/features/search/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { GrafanaAlertStateDecision, PromApplication } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions';
|
||||
import { backendSrv } from '../../../core/services/backend_srv';
|
||||
import { AccessControlAction } from '../../../types';
|
||||
|
||||
import RuleEditor from './RuleEditor';
|
||||
import { discoverFeatures } from './api/buildInfo';
|
||||
import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
|
||||
import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
|
||||
import { disableRBAC, mockDataSource, MockDataSourceSrv } from './mocks';
|
||||
import { disableRBAC, mockDataSource, MockDataSourceSrv, mockFolder } from './mocks';
|
||||
import { getAllDataSources } from './utils/config';
|
||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
import { getDefaultQueries } from './utils/rule-form';
|
||||
@@ -404,10 +406,7 @@ describe('RuleEditor', () => {
|
||||
uid: 'abcd',
|
||||
id: 1,
|
||||
};
|
||||
const getFolderByUid = jest.fn().mockResolvedValue({
|
||||
...folder,
|
||||
canSave: true,
|
||||
});
|
||||
|
||||
const dataSources = {
|
||||
default: mockDataSource({
|
||||
type: 'prometheus',
|
||||
@@ -416,10 +415,13 @@ describe('RuleEditor', () => {
|
||||
}),
|
||||
};
|
||||
|
||||
const backendSrv = {
|
||||
getFolderByUid,
|
||||
} as any as BackendSrv;
|
||||
setBackendSrv(backendSrv);
|
||||
jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({
|
||||
...mockFolder(),
|
||||
accessControl: {
|
||||
[AccessControlAction.AlertingRuleUpdate]: true,
|
||||
},
|
||||
});
|
||||
|
||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
||||
|
||||
mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
|
||||
|
||||
@@ -23,14 +23,14 @@ describe('useIsRuleEditable', () => {
|
||||
describe('RBAC enabled', () => {
|
||||
beforeEach(enableRBAC);
|
||||
describe('Grafana rules', () => {
|
||||
// When RBAC is enabled we require only folder:read permission and apriopriate alerting permissions
|
||||
// When RBAC is enabled we require appropriate alerting permissions in the folder scope
|
||||
it('Should allow editing when the user has the alert rule update permission in the folder', () => {
|
||||
mockUseFolder({
|
||||
accessControl: {
|
||||
[AccessControlAction.AlertingRuleUpdate]: true,
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseFolder({ canSave: false });
|
||||
});
|
||||
|
||||
it('Should allow editing when the user has the alert rule update permission', () => {
|
||||
mockPermissions([AccessControlAction.AlertingRuleUpdate]);
|
||||
const wrapper = getProviderWrapper();
|
||||
|
||||
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
|
||||
@@ -40,7 +40,12 @@ describe('useIsRuleEditable', () => {
|
||||
});
|
||||
|
||||
it('Should allow deleting when the user has the alert rule delete permission', () => {
|
||||
mockPermissions([AccessControlAction.AlertingRuleDelete]);
|
||||
mockUseFolder({
|
||||
accessControl: {
|
||||
[AccessControlAction.AlertingRuleDelete]: true,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = getProviderWrapper();
|
||||
|
||||
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
|
||||
@@ -50,7 +55,8 @@ describe('useIsRuleEditable', () => {
|
||||
});
|
||||
|
||||
it('Should forbid editing when the user has no alert rule update permission', () => {
|
||||
mockPermissions([]);
|
||||
mockUseFolder({ accessControl: {} });
|
||||
|
||||
const wrapper = getProviderWrapper();
|
||||
|
||||
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
|
||||
@@ -60,7 +66,8 @@ describe('useIsRuleEditable', () => {
|
||||
});
|
||||
|
||||
it('Should forbid deleting when the user has no alert rule delete permission', () => {
|
||||
mockPermissions([]);
|
||||
mockUseFolder({ accessControl: {} });
|
||||
|
||||
const wrapper = getProviderWrapper();
|
||||
|
||||
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
|
||||
@@ -69,9 +76,15 @@ describe('useIsRuleEditable', () => {
|
||||
expect(result.current.isRemovable).toBe(false);
|
||||
});
|
||||
|
||||
it('Should allow editing and deleting when the user has aler rule permissions but does not have folder canSave permission', () => {
|
||||
mockPermissions([AccessControlAction.AlertingRuleUpdate, AccessControlAction.AlertingRuleDelete]);
|
||||
mockUseFolder({ canSave: false });
|
||||
it('Should allow editing and deleting when the user has alert rule permissions but does not have folder canSave permission', () => {
|
||||
mockUseFolder({
|
||||
canSave: false,
|
||||
accessControl: {
|
||||
[AccessControlAction.AlertingRuleUpdate]: true,
|
||||
[AccessControlAction.AlertingRuleDelete]: true,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = getProviderWrapper();
|
||||
|
||||
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
|
||||
|
||||
@@ -35,8 +35,18 @@ export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO):
|
||||
);
|
||||
}
|
||||
|
||||
const canEditGrafanaRules = contextSrv.hasAccess(rulePermission.update, folder?.canSave ?? false);
|
||||
const canRemoveGrafanaRules = contextSrv.hasAccess(rulePermission.delete, folder?.canSave ?? false);
|
||||
if (!folder) {
|
||||
// Loading or invalid folder UID
|
||||
return {
|
||||
isEditable: false,
|
||||
isRemovable: false,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
const rbacDisabledFallback = folder.canSave;
|
||||
|
||||
const canEditGrafanaRules = contextSrv.hasAccessInMetadata(rulePermission.update, folder, rbacDisabledFallback);
|
||||
const canRemoveGrafanaRules = contextSrv.hasAccessInMetadata(rulePermission.delete, folder, rbacDisabledFallback);
|
||||
|
||||
return {
|
||||
isEditable: canEditGrafanaRules,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import {
|
||||
AlertmanagerAlert,
|
||||
AlertManagerCortexConfig,
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from 'app/types/unified-alerting';
|
||||
import { PromApplication, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { backendSrv } from '../../../../core/services/backend_srv';
|
||||
import {
|
||||
addAlertManagers,
|
||||
createOrUpdateSilence,
|
||||
@@ -568,7 +569,7 @@ export const deleteTemplateAction = (templateName: string, alertManagerSourceNam
|
||||
|
||||
export const fetchFolderAction = createAsyncThunk(
|
||||
'unifiedalerting/fetchFolder',
|
||||
(uid: string): Promise<FolderDTO> => withSerializedError((getBackendSrv() as any).getFolderByUid(uid))
|
||||
(uid: string): Promise<FolderDTO> => withSerializedError(backendSrv.getFolderByUid(uid, { withAccessControl: true }))
|
||||
);
|
||||
|
||||
export const fetchFolderIfNotFetchedAction = (uid: string): ThunkResult<void> => {
|
||||
|
||||
Reference in New Issue
Block a user