diff --git a/pkg/api/index.go b/pkg/api/index.go index ba16d9b830c..d0eab27a06b 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -515,7 +515,7 @@ func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext) []*dtos.NavLink { hasAccess := ac.HasAccess(hs.AccessControl, c) var alertChildNavs []*dtos.NavLink - if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { + if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { alertChildNavs = append(alertChildNavs, &dtos.NavLink{ Text: "Alert rules", Id: "alert-list", Url: hs.Cfg.AppSubURL + "/alerting/list", Icon: "list-ul", }) @@ -529,7 +529,7 @@ func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext) []*dtos.NavLink { alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Notification policies", Id: "am-routes", Url: hs.Cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"}) } - if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) { + if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) { alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Silences", Id: "silences", Url: hs.Cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"}) alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Alert groups", Id: "groups", Url: hs.Cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"}) } diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 32c048e9310..a1c2c02b85a 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -116,6 +116,11 @@ var ReqGrafanaAdmin = func(c *models.ReqContext) bool { return c.IsGrafanaAdmin } +// ReqViewer returns true if the current user has models.ROLE_VIEWER. Note: this can be anonymous user as well +var ReqViewer = func(c *models.ReqContext) bool { + return c.OrgRole.Includes(models.ROLE_VIEWER) +} + var ReqOrgAdmin = func(c *models.ReqContext) bool { return c.OrgRole == models.ROLE_ADMIN } diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index d7bd1c6d8d6..c7d1f5cda2e 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -45,11 +45,26 @@ Scopes must have an order to ensure consistency and ease of search, this helps u ## Grafana Alerting - main / unreleased -- [BUGFIX] Use `NaN` and do not panic when captured alert values are empty #48370 +- [ENHANCEMENT] Scheduler: Ticker expose new metrics. In legacy, metrics are prefixed with `legacy_` #47828, #48190 + - `grafana_alerting_ticker_last_consumed_tick_timestamp_seconds` + - `grafana_alerting_ticker_next_tick_timestamp_seconds` + - `grafana_alerting_ticker_interval_seconds` +- [ENHANCEMENT] Create folder 'General Alerting' when Grafana starts from the scratch #48866 +- [ENHANCEMENT] Rule changes authorization logic to use UID folder scope instead of ID scope #48970 - [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857 - [FEATURE] Indicate whether contact point is provisioned when GETting Alertmanager configuration #48323 - [FEATURE] Indicate whether alert rule is provisioned when GETting the rule #48458 - [BUGFIX] Migration: ignore alerts that do not belong to any existing organization\dashboard #49192 +- [BUGFIX] Allow anonymous access to alerts #49203 + +## 8.5.3 + +- [BUGFIX] Migration: Remove data source disabled property when migrating alerts #48559 + +## 8.5.2 + +- [FEATURE] Migration: Adds `force_migration` as a flag to prevent truncating the unified alerting tables as we migrate. #48526 +- [BUGFIX] Use `NaN` and do not panic when captured alert values are empty #48370 ## 8.5.1 @@ -63,3 +78,4 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - [BUGFIX] (Legacy) Templates: Parse notification templates using all the matches of the alert rule when going from `Alerting` to `OK` in legacy alerting #47355 - [BUGFIX] Scheduler: Fix state manager to support OK option of `AlertRule.ExecErrState` #47670 - [ENHANCEMENT] Templates: Enable the use of classic condition values in templates #46971 + diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index 15759737c14..108d8be3b5b 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -154,7 +154,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res return response.JSON(http.StatusInternalServerError, ruleResponse) } hasAccess := func(evaluator accesscontrol.Evaluator) bool { - return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator) + return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator) } groupMap := make(map[string]*apimodels.RuleGroup) diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 5558ee35c30..b4b8b70d836 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -255,7 +255,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { req, err := http.NewRequest("GET", "/api/v1/rules", nil) require.NoError(t, err) - c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}, IsSignedIn: true} + c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID, OrgRole: models.ROLE_VIEWER}} t.Run("with no rules", func(t *testing.T) { _, _, _, api := setupAPI(t) @@ -325,7 +325,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil) require.NoError(t, err) - c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}, IsSignedIn: true} + c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID, OrgRole: models.ROLE_VIEWER}} r := api.RouteGetRuleStatuses(c) require.Equal(t, http.StatusOK, r.Status()) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index c122a19c1be..29965609273 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -174,7 +174,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response. ruleGroupConfigs := make(map[string]apimodels.GettableRuleGroupConfig) hasAccess := func(evaluator accesscontrol.Evaluator) bool { - return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator) + return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator) } provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType()) @@ -230,7 +230,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext) response.Resp ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(q.Result)) hasAccess := func(evaluator accesscontrol.Evaluator) bool { - return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator) + return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator) } provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType()) @@ -296,7 +296,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig) hasAccess := func(evaluator accesscontrol.Evaluator) bool { - return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator) + return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator) } provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType()) diff --git a/pkg/services/ngalert/api/api_ruler_test.go b/pkg/services/ngalert/api/api_ruler_test.go index 1f64133b49b..38afc283254 100644 --- a/pkg/services/ngalert/api/api_ruler_test.go +++ b/pkg/services/ngalert/api/api_ruler_test.go @@ -577,7 +577,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { ruleStore.PutRule(context.Background(), expectedRules...) ac := acMock.New().WithDisabled() - response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(createRequestContext(orgID, "", map[string]string{ + response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(createRequestContext(orgID, models2.ROLE_VIEWER, map[string]string{ ":Namespace": folder.Title, })) @@ -621,7 +621,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI) require.NoError(t, err) - response := svc.RouteGetNamespaceRulesConfig(createRequestContext(orgID, "", map[string]string{ + response := svc.RouteGetNamespaceRulesConfig(createRequestContext(orgID, models2.ROLE_VIEWER, map[string]string{ ":Namespace": folder.Title, })) diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index 21759efc298..cad6d4cecbe 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -134,7 +134,7 @@ const unifiedRoutes: RouteDescriptor[] = [ path: '/alerting/silences', roles: evaluateAccess( [AccessControlAction.AlertingInstanceRead, AccessControlAction.AlertingInstancesExternalRead], - ['Editor', 'Admin'] + ['Viewer', 'Editor', 'Admin'] ), component: SafeDynamicImport( () => import(/* webpackChunkName: "AlertSilences" */ 'app/features/alerting/unified/Silences')