Alerting: Fix NotificationPreview permission checking (#114303)

Add permission check to NotificationPreview component
This commit is contained in:
Konrad Lalik
2025-11-21 14:59:25 +01:00
committed by GitHub
parent 198375599e
commit 10e0c435ad
3 changed files with 63 additions and 16 deletions
@@ -4,8 +4,10 @@ import { useEffectOnce } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Button, LoadingPlaceholder, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
import { Alert, Button, LoadingPlaceholder, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv';
import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi';
import { AccessControlAction } from 'app/types/accessControl';
import { AlertQuery, Labels } from 'app/types/unified-alerting-dto';
import { Folder, KBObjectArray } from '../../../types/rule-form';
@@ -36,6 +38,7 @@ export const NotificationPreview = ({
alertUid,
}: NotificationPreviewProps) => {
const styles = useStyles2(getStyles);
const previewRoutingDisabled = !condition || !folder;
const [trigger, { data = [], isLoading, isUninitialized: previewUninitialized }] = preview.useMutation();
@@ -67,9 +70,7 @@ export const NotificationPreview = ({
};
useEffectOnce(() => {
if (!previewRoutingDisabled) {
onPreview();
}
onPreview();
});
// Get alert managers's data source information
@@ -77,18 +78,18 @@ export const NotificationPreview = ({
const singleAlertManagerConfigured = alertManagerDataSources.length === 1;
const getTooltipContent = () => {
if (!condition) {
return (
<Trans i18nKey="alerting.notification-preview.no-condition-tooltip">
Select a query condition to preview routing
</Trans>
);
}
if (!folder) {
return (
<Trans i18nKey="alerting.notification-preview.select-folder-tooltip">Select a folder to preview routing</Trans>
);
}
if (previewRoutingDisabled) {
return (
<Trans i18nKey="alerting.notification-preview.disabled-tooltip">
You don&apos;t have sufficient permissions to preview
</Trans>
);
}
return '';
};
@@ -146,10 +147,12 @@ export const NotificationPreview = ({
</Stack>
)}
{alertManagerSource.name === 'grafana' ? (
<NotificationPreviewForGrafanaManaged
alertManagerSource={alertManagerSource}
instances={potentialInstances}
/>
<NotificationPreviewGrafanaPermissionCheck>
<NotificationPreviewForGrafanaManaged
alertManagerSource={alertManagerSource}
instances={potentialInstances}
/>
</NotificationPreviewGrafanaPermissionCheck>
) : (
<NotificationPreviewByAlertManager
alertManagerSource={alertManagerSource}
@@ -164,6 +167,32 @@ export const NotificationPreview = ({
);
};
/**
* Permission check for Grafana notification preview.
* This is a workaround because useGetAlertManagerDataSourcesByPermissionAndConfig
* doesn't properly filter by the new K8s-style RBAC permissions.
*
* We check for either:
* - alert.notifications:read (legacy permission)
* - alert.notifications.routes:read (new granular permission)
*/
function NotificationPreviewGrafanaPermissionCheck({ children }: React.PropsWithChildren) {
const hasLegacyNotificationPermission = contextSrv.hasPermission(AccessControlAction.AlertingNotificationsRead);
const hasNotificationPolicyTreePermission = contextSrv.hasPermission(AccessControlAction.AlertingRoutesRead);
if (hasLegacyNotificationPermission || hasNotificationPolicyTreePermission) {
return <>{children}</>;
}
return (
<Alert severity="warning" title={t('alerting.notification-preview.permission-warning', 'Preview not available')}>
<Trans i18nKey="alerting.notification-preview.permission-warning-message">
You don&apos;t have permission to view notification policies. Preview is not available.
</Trans>
</Alert>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
firstAlertManagerLine: css({
height: '1px',
@@ -125,6 +125,22 @@ const AlwaysSupported = true;
const NotSupported = false;
export type Action = AlertmanagerAction | AlertingAction | AlertRuleAction | FolderBulkAction;
/**
* Represents the ability to perform an action, with two distinct checks:
*
* @param actionSupported - Whether the action is technically possible in the current context.
* This depends on system capabilities (e.g., API availability), feature availability
* (e.g., Grafana vs external alertmanager), and rule state (e.g., provisioned/immutable rules).
* Examples: Can't edit provisioned rules, can't export from external alertmanagers.
*
* @param actionAllowed - Whether the user has permission to perform the action.
* This is based on RBAC permissions, folder-specific permissions, and admin status.
* Examples: User lacks AlertingRuleUpdate permission, user can't edit in this folder.
*
* Both must be true for an action to be available. This separation allows showing appropriate
* messages: "Feature not available" vs "You don't have permission".
*/
export type Ability = [actionSupported: boolean, actionAllowed: boolean];
export type Abilities<T extends Action> = Record<T, Ability>;
+3 -1
View File
@@ -1982,9 +1982,11 @@
},
"notification-preview": {
"alertmanager": "Alertmanager:",
"disabled-tooltip": "You don't have sufficient permissions to preview",
"error": "Could not load routing preview for {{alertmanager}}",
"initialized": "Based on the labels added, alert instances are routed to the following notification policies. Expand each notification policy below to view more details.",
"no-condition-tooltip": "Select a query condition to preview routing",
"permission-warning": "Preview not available",
"permission-warning-message": "You don't have permission to view notification policies. Preview is not available.",
"preview-routing": "Preview routing",
"select-folder-tooltip": "Select a folder to preview routing",
"text-loading-preview": "Loading preview...",