Alerting: Fix some templates RBAC UI action control/checks (#94898)

This commit is contained in:
Tom Ratcliffe
2024-11-13 17:14:15 +00:00
committed by GitHub
parent 9e408f842c
commit bfecce320a
10 changed files with 97 additions and 27 deletions
+5 -1
View File
@@ -435,12 +435,16 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
ac.EvalPermission(ac.ActionAlertingReceiversRead),
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
ac.EvalPermission(ac.ActionAlertingReceiversCreate),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesRead),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesWrite),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesDelete),
)
}
if hasAccess(ac.EvalAny(contactPointsPerms...)) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
Icon: "comment-alt-share",
})
}
+3
View File
@@ -4,6 +4,7 @@ import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/type
import { AccessControlAction } from 'app/types';
import { PERMISSIONS_CONTACT_POINTS } from './unified/components/contact-points/permissions';
import { PERMISSIONS_TEMPLATES } from './unified/components/templates/permissions';
import { evaluateAccess } from './unified/utils/access-control';
export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
@@ -104,6 +105,7 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
AccessControlAction.AlertingNotificationsRead,
AccessControlAction.AlertingNotificationsExternalRead,
...PERMISSIONS_CONTACT_POINTS,
...PERMISSIONS_TEMPLATES,
]),
component: importAlertingComponent(
() =>
@@ -150,6 +152,7 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
roles: evaluateAccess([
AccessControlAction.AlertingNotificationsRead,
AccessControlAction.AlertingNotificationsExternalRead,
...PERMISSIONS_TEMPLATES,
]),
component: importAlertingComponent(
() => import(/* webpackChunkName: "Templates" */ 'app/features/alerting/unified/Templates')
@@ -164,31 +164,39 @@ const NotificationTemplatesTab = () => {
);
};
const useTabQueryParam = () => {
const useTabQueryParam = (defaultTab: ActiveTab) => {
const [queryParams, setQueryParams] = useURLSearchParams();
const param = useMemo(() => {
const queryParam = queryParams.get('tab');
if (!queryParam || !Object.values(ActiveTab).map(String).includes(queryParam)) {
return ActiveTab.ContactPoints;
return defaultTab;
}
return queryParam || ActiveTab.ContactPoints;
}, [queryParams]);
return queryParam || defaultTab;
}, [defaultTab, queryParams]);
const setParam = (tab: ActiveTab) => setQueryParams({ tab });
return [param, setParam] as const;
};
export const ContactPointsPageContents = () => {
const { selectedAlertmanager } = useAlertmanager();
const [activeTab, setActiveTab] = useTabQueryParam();
const [, showContactPointsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
const [, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
// Depending on permissions, user may not have access to all tabs,
// but we can default to picking the first one that they definitely _do_ have access to
const defaultTab = [
showContactPointsTab && ActiveTab.ContactPoints,
showTemplatesTab && ActiveTab.NotificationTemplates,
].filter((tab) => !!tab)[0];
const [activeTab, setActiveTab] = useTabQueryParam(defaultTab);
const { contactPoints } = useContactPointsWithStatus({
alertmanager: selectedAlertmanager!,
});
const [_, showTemplatesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationTemplate);
const showingContactPoints = activeTab === ActiveTab.ContactPoints;
const showNotificationTemplates = activeTab === ActiveTab.NotificationTemplates;
@@ -198,12 +206,14 @@ export const ContactPointsPageContents = () => {
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager!} />
<Stack direction="column">
<TabsBar>
<Tab
label="Contact Points"
active={showingContactPoints}
counter={contactPoints.length}
onChangeTab={() => setActiveTab(ActiveTab.ContactPoints)}
/>
{showContactPointsTab && (
<Tab
label="Contact Points"
active={showingContactPoints}
counter={contactPoints.length}
onChangeTab={() => setActiveTab(ActiveTab.ContactPoints)}
/>
)}
{showTemplatesTab && (
<Tab
label="Notification Templates"
@@ -17,9 +17,5 @@ export const PERMISSIONS_CONTACT_POINTS_MODIFY = [
/**
* List of all permissions that allow contact points read/write functionality
*
* Any permission in this list will also be checked for whether the built-in Grafana Alertmanager is shown
* (as the implication is that if they have one of these permissions, then they should be able to see Grafana AM in the AM selector)
*/
export const PERMISSIONS_CONTACT_POINTS = [...PERMISSIONS_CONTACT_POINTS_READ, ...PERMISSIONS_CONTACT_POINTS_MODIFY];
@@ -166,7 +166,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
/>
</Authorize>
)}
<Authorize actions={[AlertmanagerAction.CreateContactPoint]}>
<Authorize actions={[AlertmanagerAction.CreateNotificationTemplate]}>
<ActionIcon
to={makeAMLink(
`/alerting/notifications/templates/${encodeURIComponent(uid)}/duplicate`,
@@ -1,5 +1,8 @@
import 'core-js/stable/structured-clone';
import { MemoryHistoryBuildOptions } from 'history';
import { ComponentProps, ReactNode } from 'react';
import { clickSelectOption } from 'test/helpers/selectOptionInTest';
import { screen, waitFor } from 'test/test-utils';
import { render, screen, waitFor } from 'test/test-utils';
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
import { config } from '@grafana/runtime';
@@ -8,6 +11,7 @@ import {
setOnCallFeatures,
setOnCallIntegrations,
} from 'app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins';
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
@@ -15,10 +19,21 @@ import { AccessControlAction } from 'app/types';
import { AlertmanagerConfigBuilder, setupMswServer } from '../../../mockApi';
import { grantUserPermissions } from '../../../mocks';
import { captureRequests } from '../../../mocks/server/events';
import { renderWithProvider } from '../../contact-points/ContactPoints.test';
import { GrafanaReceiverForm } from './GrafanaReceiverForm';
const renderWithProvider = (
children: ReactNode,
historyOptions?: MemoryHistoryBuildOptions,
providerProps?: Partial<ComponentProps<typeof AlertmanagerProvider>>
) =>
render(
<AlertmanagerProvider accessType="notification" {...providerProps}>
{children}
</AlertmanagerProvider>,
{ historyOptions }
);
setupMswServer();
const ui = {
@@ -0,0 +1,11 @@
import { AccessControlAction } from 'app/types';
/**
* List of all permissions that allow templates read/write functionality
*/
export const PERMISSIONS_TEMPLATES = [
AccessControlAction.AlertingTemplatesRead,
AccessControlAction.AlertingTemplatesWrite,
AccessControlAction.AlertingTemplatesDelete,
];
@@ -231,17 +231,41 @@ export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> {
// TODO: Move this into the permissions config and generalise that code to allow for an array of permissions
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversCreate : null
),
[AlertmanagerAction.ViewContactPoint]: toAbility(AlwaysSupported, notificationsPermissions.read),
[AlertmanagerAction.UpdateContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.update),
[AlertmanagerAction.DeleteContactPoint]: toAbility(hasConfigurationAPI, notificationsPermissions.delete),
[AlertmanagerAction.ViewContactPoint]: toAbility(
AlwaysSupported,
notificationsPermissions.read,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversRead : null
),
[AlertmanagerAction.UpdateContactPoint]: toAbility(
hasConfigurationAPI,
notificationsPermissions.update,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversWrite : null
),
[AlertmanagerAction.DeleteContactPoint]: toAbility(
hasConfigurationAPI,
notificationsPermissions.delete,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingReceiversWrite : null
),
// At the time of writing, only Grafana flavored alertmanager supports exporting,
// and if a user can view the contact point, then they can also export it
// So the only check we make is if the alertmanager is Grafana flavored
[AlertmanagerAction.ExportContactPoint]: [isGrafanaFlavoredAlertmanager, isGrafanaFlavoredAlertmanager],
// -- notification templates --
[AlertmanagerAction.CreateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.create),
[AlertmanagerAction.ViewNotificationTemplate]: toAbility(AlwaysSupported, notificationsPermissions.read),
[AlertmanagerAction.UpdateNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.update),
[AlertmanagerAction.CreateNotificationTemplate]: toAbility(
hasConfigurationAPI,
notificationsPermissions.create,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesWrite : null
),
[AlertmanagerAction.ViewNotificationTemplate]: toAbility(
AlwaysSupported,
notificationsPermissions.read,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesRead : null
),
[AlertmanagerAction.UpdateNotificationTemplate]: toAbility(
hasConfigurationAPI,
notificationsPermissions.update,
isGrafanaFlavoredAlertmanager ? AccessControlAction.AlertingTemplatesWrite : null
),
[AlertmanagerAction.DeleteNotificationTemplate]: toAbility(hasConfigurationAPI, notificationsPermissions.delete),
// -- notification policies --
[AlertmanagerAction.CreateNotificationPolicy]: toAbility(hasConfigurationAPI, notificationsPermissions.create),
@@ -12,6 +12,7 @@ import { PromApplication, RulesSourceApplication } from 'app/types/unified-alert
import { alertmanagerApi } from '../api/alertmanagerApi';
import { PERMISSIONS_CONTACT_POINTS } from '../components/contact-points/permissions';
import { PERMISSIONS_TEMPLATES } from '../components/templates/permissions';
import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
import { isAlertManagerWithConfigAPI } from '../state/AlertmanagerContext';
@@ -151,6 +152,7 @@ export function getAlertManagerDataSourcesByPermission(permission: 'instance' |
const builtinAlertmanagerPermissions = [
...Object.values(permissions).flatMap((permissions) => permissions.grafana),
...PERMISSIONS_CONTACT_POINTS,
...PERMISSIONS_TEMPLATES,
];
const hasPermissionsForInternalAlertmanager = builtinAlertmanagerPermissions.some((permission) =>
+5
View File
@@ -134,6 +134,11 @@ export enum AccessControlAction {
AlertingReceiversWrite = 'alert.notifications.receivers:write',
AlertingReceiversRead = 'alert.notifications.receivers:read',
// Alerting templates actions
AlertingTemplatesRead = 'alert.notifications.templates:read',
AlertingTemplatesWrite = 'alert.notifications.templates:write',
AlertingTemplatesDelete = 'alert.notifications.templates:delete',
ActionAPIKeysRead = 'apikeys:read',
ActionAPIKeysCreate = 'apikeys:create',
ActionAPIKeysDelete = 'apikeys:delete',