Alerting: Dedicated permission for Template testing API (#115032)

This commit is contained in:
Yuri Tseretyan
2025-12-10 10:56:29 -05:00
committed by GitHub
parent 5e7b900416
commit 47f7b3e095
8 changed files with 27 additions and 10 deletions
@@ -238,6 +238,7 @@ For more information on Cloud Access Policies and how to use them, see [Access p
| `alert.notifications.templates:read` | None | Read templates. |
| `alert.notifications.templates:write` | None | Create new or update existing templates. |
| `alert.notifications.templates:delete` | None | Delete existing templates. |
| `alert.notifications.templates.test:write` | None | Test templates with custom payloads (preview and payload editor functionality). |
| `alert.notifications.routes:read` | None | Read notification policies. |
| `alert.notifications.routes:write` | None | Create new, update or delete notification policies |
@@ -64,6 +64,7 @@ Grafana Alerting has the following permissions.
| `alert.notifications.templates:read` | n/a | Read templates. |
| `alert.notifications.templates:write` | n/a | Create new or update existing templates. |
| `alert.notifications.templates:delete` | n/a | Delete existing templates. |
| `alert.notifications.templates.test:write` | n/a | Test templates with custom payloads (preview and payload editor functionality). |
| `alert.notifications.routes:read` | n/a | Read notification policies. |
| `alert.notifications.routes:write` | n/a | Create new, update and update notification policies. |
@@ -60,7 +60,7 @@ Details of the fixed roles and the access they provide for Grafana Alerting are
| Contact Point Creator: `fixed:alerting.receivers:creator` | `alert.notifications.receivers:create` | Create a new contact point. The user is automatically granted full access to the created contact point. |
| Contact Point Writer: `fixed:alerting.receivers:writer` | `alert.notifications.receivers:read`, `alert.notifications.receivers:write`, `alert.notifications.receivers:delete` for scope `receivers:*` and <br> `alert.notifications.receivers:create` | Create a new contact point and manage all existing contact points. |
| Templates Reader: `fixed:alerting.templates:reader` | `alert.notifications.templates:read` | Read all notification templates. |
| Templates Writer: `fixed:alerting.templates:writer` | `alert.notifications.templates:read`, `alert.notifications.templates:write`, `alert.notifications.templates:delete` | Create new and manage existing notification templates. |
| Templates Writer: `fixed:alerting.templates:writer` | `alert.notifications.templates:read`, `alert.notifications.templates:write`, `alert.notifications.templates:delete`, `alert.notifications.templates.test:write` | Create new and manage existing notification templates. Test templates with custom payloads. |
| Time Intervals Reader: `fixed:alerting.time-intervals:reader` | `alert.notifications.time-intervals:read` | Read all time intervals. |
| Time Intervals Writer: `fixed:alerting.time-intervals:writer` | `alert.notifications.time-intervals:read`, `alert.notifications.time-intervals:write`, `alert.notifications.time-intervals:delete` | Create new and manage existing time intervals. |
| Notification Policies Reader: `fixed:alerting.routes:reader` | `alert.notifications.routes:read` | Read all time intervals. |
+1
View File
@@ -447,6 +447,7 @@ const (
ActionAlertingNotificationsTemplatesRead = "alert.notifications.templates:read"
ActionAlertingNotificationsTemplatesWrite = "alert.notifications.templates:write"
ActionAlertingNotificationsTemplatesDelete = "alert.notifications.templates:delete"
ActionAlertingNotificationsTemplatesTest = "alert.notifications.templates.test:write"
// Alerting notifications time interval actions
ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read"
+1
View File
@@ -173,6 +173,7 @@ var (
Permissions: accesscontrol.ConcatPermissions(templatesReaderRole.Role.Permissions, []accesscontrol.Permission{
{Action: accesscontrol.ActionAlertingNotificationsTemplatesWrite},
{Action: accesscontrol.ActionAlertingNotificationsTemplatesDelete},
{Action: accesscontrol.ActionAlertingNotificationsTemplatesTest},
}),
},
}
+1 -1
View File
@@ -251,7 +251,7 @@ func (api *API) authorize(method, path string) web.Handler {
case http.MethodPost + "/api/alertmanager/grafana/config/api/v1/templates/test":
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesRead),
ac.EvalPermission(ac.ActionAlertingNotificationsTemplatesTest),
)
// External Alertmanager Paths
@@ -26,8 +26,10 @@ import {
useStyles2,
} from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { contextSrv } from 'app/core/services/context_srv';
import { ActiveTab as ContactPointsActiveTabs } from 'app/features/alerting/unified/components/contact-points/ContactPoints';
import { TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types/accessControl';
import { AITemplateButtonComponent } from '../../enterprise-components/AI/AIGenTemplateButton/addAITemplateButton';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
@@ -100,6 +102,16 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
const formRef = useRef<HTMLFormElement>(null);
const isGrafanaAlertManager = alertmanager === GRAFANA_RULES_SOURCE_NAME;
// Check if user has permission to test templates
const canTestTemplates =
contextSrv.hasPermission(AccessControlAction.AlertingNotificationsTemplatesTest) ||
contextSrv.hasPermission(AccessControlAction.AlertingNotificationsWrite);
// Only show preview and payload panels if both conditions are met:
// 1. It's a Grafana Alertmanager
// 2. User has the test permission
const showPreviewAndPayload = isGrafanaAlertManager && canTestTemplates;
const error = updateTemplateError ?? createTemplateError;
const [cheatsheetOpened, toggleCheatsheetOpened] = useToggle(false);
@@ -118,16 +130,16 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
// splitter for template and payload editor
const columnSplitter = useSplitter({
direction: 'column',
// if Grafana Alertmanager, split 50/50, otherwise 100/0 because there is no payload editor
initialSize: isGrafanaAlertManager ? 0.5 : 1,
// if showing preview/payload panels, split 50/50, otherwise 100/0 because there is no payload editor
initialSize: showPreviewAndPayload ? 0.5 : 1,
dragPosition: 'middle',
});
// splitter for template editor and preview
const rowSplitter = useSplitter({
direction: 'row',
// if Grafana Alertmanager, split 60/40, otherwise 100/0 because there is no preview
initialSize: isGrafanaAlertManager ? 0.6 : 1,
// if showing preview/payload panels, split 60/40, otherwise 100/0 because there is no preview
initialSize: showPreviewAndPayload ? 0.6 : 1,
dragPosition: 'middle',
});
@@ -319,8 +331,8 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
</Box>
</div>
</div>
{/* payload editor – only available for Grafana Alertmanager */}
{isGrafanaAlertManager && (
{/* payload editor – only shown if user has test permission */}
{showPreviewAndPayload && (
<>
<div {...columnSplitter.splitterProps} />
<div {...columnSplitter.secondaryProps}>
@@ -345,8 +357,8 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
)}
</div>
</div>
{/* preview column – full height and half-width */}
{isGrafanaAlertManager && (
{/* preview column – only shown if user has test permission */}
{showPreviewAndPayload && (
<div {...rowSplitter.secondaryProps}>
<div {...rowSplitter.splitterProps} />
<TemplatePreview
+1
View File
@@ -156,6 +156,7 @@ export enum AccessControlAction {
AlertingTemplatesRead = 'alert.notifications.templates:read',
AlertingTemplatesWrite = 'alert.notifications.templates:write',
AlertingTemplatesDelete = 'alert.notifications.templates:delete',
AlertingNotificationsTemplatesTest = 'alert.notifications.templates.test:write',
// Alerting enrichments actions
AlertingEnrichmentsRead = 'alert.enrichments:read',