mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Fix folder permissions (#48189)
* Use the folder view permission in alerting * Add tests
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -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));
|
||||||
|
};
|
||||||
|
@@ -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),
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user