From 580477bf8ee0eebd5fc74d9dc274d795aa510461 Mon Sep 17 00:00:00 2001 From: Jo Date: Tue, 14 Nov 2023 15:47:34 +0100 Subject: [PATCH] NGAlerting: Use identity.Requester interface instead of SignedInUser (#76360) * unfurl SignedInUserAttrs services * replace signedInUser with Requester replace signedInUser with requester * fix tests * linting --------- Co-authored-by: Ieva --- pkg/services/dashboards/database/database.go | 4 +- pkg/services/dashboards/models.go | 6 +-- pkg/services/navtree/navtreeimpl/admin.go | 2 +- pkg/services/navtree/navtreeimpl/navtree.go | 17 +++++--- pkg/services/ngalert/api/api_configuration.go | 6 +-- pkg/services/ngalert/api/api_prometheus.go | 2 +- pkg/services/ngalert/api/api_provisioning.go | 11 +++-- pkg/services/ngalert/api/api_ruler.go | 43 +++++++++++-------- pkg/services/ngalert/api/api_ruler_export.go | 8 ++-- pkg/services/ngalert/api/persist.go | 8 ++-- pkg/services/ngalert/backtesting/engine.go | 7 +-- .../ngalert/backtesting/engine_test.go | 7 +-- pkg/services/ngalert/eval/context.go | 6 +-- pkg/services/ngalert/eval/eval.go | 2 +- pkg/services/ngalert/models/history.go | 4 +- .../ngalert/provisioning/contactpoints.go | 6 +-- pkg/services/ngalert/store/alert_rule.go | 7 ++- pkg/services/ngalert/tests/fakes/rules.go | 7 ++- .../user_header_middleware.go | 6 ++- 19 files changed, 87 insertions(+), 72 deletions(-) diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index d2d8416c9a0..fea61c5cd09 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -981,8 +981,8 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F if query.OrgId != 0 { orgID = query.OrgId filters = append(filters, searchstore.OrgFilter{OrgId: orgID}) - } else if query.SignedInUser.OrgID != 0 { - orgID = query.SignedInUser.OrgID + } else if query.SignedInUser.GetOrgID() != 0 { + orgID = query.SignedInUser.GetOrgID() filters = append(filters, searchstore.OrgFilter{OrgId: orgID}) } diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index f1449e13240..98c2a59eacd 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/kinds" @@ -14,9 +16,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/search/model" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const RootFolderName = "General" @@ -482,7 +482,7 @@ type GetDashboardACLInfoListQuery struct { type FindPersistedDashboardsQuery struct { Title string OrgId int64 - SignedInUser *user.SignedInUser + SignedInUser identity.Requester DashboardIds []int64 DashboardUIDs []string Type string diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 695f56082b9..d02c9f8c142 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -135,7 +135,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink configNode := &navtree.NavLink{ Id: navtree.NavIDCfg, Text: "Administration", - SubTitle: "Organization: " + c.OrgName, + SubTitle: "Organization: " + c.SignedInUser.GetOrgName(), Icon: "cog", SortWeight: navtree.WeightConfig, Children: configNodes, diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 3da2b05213b..496e429565a 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -7,8 +7,10 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models/roletype" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" @@ -240,10 +242,10 @@ func (s *ServiceImpl) addHelpLinks(treeRoot *navtree.NavTreeRoot, c *contextmode func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLink { // Only set login if it's different from the name var login string - if c.SignedInUser.Login != c.SignedInUser.NameOrFallback() { - login = c.SignedInUser.Login + if c.SignedInUser.GetLogin() != c.SignedInUser.GetDisplayName() { + login = c.SignedInUser.GetLogin() } - gravatarURL := dtos.GetGravatarUrl(c.Email) + gravatarURL := dtos.GetGravatarUrl(c.SignedInUser.GetEmail()) children := []*navtree.NavLink{ { @@ -275,7 +277,7 @@ func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLin } return &navtree.NavLink{ - Text: c.SignedInUser.NameOrFallback(), + Text: c.SignedInUser.GetDisplayName(), SubTitle: login, Id: "profile", Img: gravatarURL, @@ -289,8 +291,9 @@ func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLin func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*navtree.NavLink, error) { starredItemsChildNavs := []*navtree.NavLink{} + userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) query := star.GetUserStarsQuery{ - UserID: c.SignedInUser.UserID, + UserID: userID, } starredDashboardResult, err := s.starService.GetByUser(c.Req.Context(), &query) @@ -394,7 +397,7 @@ func (s *ServiceImpl) buildLegacyAlertNavLinks(c *contextmodel.ReqContext) *navt Text: "Alert rules", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul", }) - if c.HasRole(org.RoleEditor) { + if c.SignedInUser.HasRole(roletype.RoleEditor) { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "Notification channels", Id: "channels", Url: s.cfg.AppSubURL + "/alerting/notifications", Icon: "comment-alt-share", @@ -437,7 +440,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"}) } - if c.OrgRole == org.RoleAdmin { + if c.SignedInUser.GetOrgRole() == org.RoleAdmin { alertChildNavs = append(alertChildNavs, &navtree.NavLink{ Text: "Admin", Id: "alerting-admin", Url: s.cfg.AppSubURL + "/alerting/admin", Icon: "cog", diff --git a/pkg/services/ngalert/api/api_configuration.go b/pkg/services/ngalert/api/api_configuration.go index bcd65080db6..dd309988257 100644 --- a/pkg/services/ngalert/api/api_configuration.go +++ b/pkg/services/ngalert/api/api_configuration.go @@ -44,7 +44,7 @@ func (srv ConfigSrv) RouteGetAlertmanagers(c *contextmodel.ReqContext) response. } func (srv ConfigSrv) RouteGetNGalertConfig(c *contextmodel.ReqContext) response.Response { - if c.OrgRole != org.RoleAdmin { + if c.SignedInUser.GetOrgRole() != org.RoleAdmin { return accessForbiddenResp() } @@ -66,7 +66,7 @@ func (srv ConfigSrv) RouteGetNGalertConfig(c *contextmodel.ReqContext) response. } func (srv ConfigSrv) RoutePostNGalertConfig(c *contextmodel.ReqContext, body apimodels.PostableNGalertConfig) response.Response { - if c.OrgRole != org.RoleAdmin { + if c.SignedInUser.GetOrgRole() != org.RoleAdmin { return accessForbiddenResp() } @@ -100,7 +100,7 @@ func (srv ConfigSrv) RoutePostNGalertConfig(c *contextmodel.ReqContext, body api } func (srv ConfigSrv) RouteDeleteNGalertConfig(c *contextmodel.ReqContext) response.Response { - if c.OrgRole != org.RoleAdmin { + if c.SignedInUser.GetOrgRole() != org.RoleAdmin { return accessForbiddenResp() } diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index 56010382593..c407650c5c6 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -201,7 +201,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon } alertRuleQuery := ngmodels.ListAlertRulesQuery{ - OrgID: c.SignedInUser.OrgID, + OrgID: c.SignedInUser.GetOrgID(), NamespaceUIDs: namespaceUIDs, DashboardUID: dashboardUID, PanelID: panelID, diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index a7bec82dd88..af83536815d 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -9,13 +9,13 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/ngalert/api/hcl" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -31,7 +31,7 @@ type ProvisioningSrv struct { } type ContactPointService interface { - GetContactPoints(ctx context.Context, q provisioning.ContactPointQuery, user *user.SignedInUser) ([]definitions.EmbeddedContactPoint, error) + GetContactPoints(ctx context.Context, q provisioning.ContactPointQuery, user identity.Requester) ([]definitions.EmbeddedContactPoint, error) CreateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) (definitions.EmbeddedContactPoint, error) UpdateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) error DeleteContactPoint(ctx context.Context, orgID int64, uid string) error @@ -325,7 +325,8 @@ func (srv *ProvisioningSrv) RoutePostAlertRule(c *contextmodel.ReqContext, ar de return ErrResp(http.StatusBadRequest, err, "") } provenance := determineProvenance(c) - createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), upstreamModel, alerting_models.Provenance(provenance), c.UserID) + userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) + createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), upstreamModel, alerting_models.Provenance(provenance), userID) if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) { return ErrResp(http.StatusBadRequest, err, "") } @@ -478,7 +479,9 @@ func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, a ErrResp(http.StatusBadRequest, err, "") } provenance := determineProvenance(c) - err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), groupModel, c.UserID, alerting_models.Provenance(provenance)) + + userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) + err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), groupModel, userID, alerting_models.Provenance(provenance)) if errors.Is(err, alerting_models.ErrAlertRuleUniqueConstraintViolation) { return ErrResp(http.StatusBadRequest, err, "") } diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 106e1c69345..04466c3e7e5 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -22,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/quota" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -52,14 +52,17 @@ var ( // Returns http.StatusUnauthorized if user does not have access to any of the rules that match the filter. // Returns http.StatusBadRequest if all rules that match the filter and the user is authorized to delete are provisioned. func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { - namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } + userNamespace, id := c.SignedInUser.GetNamespacedID() var loggerCtx = []any{ "userId", - c.SignedInUser.UserID, + id, + "userNamespace", + userNamespace, "namespaceUid", namespace.UID, } @@ -68,7 +71,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT } logger := srv.log.New(loggerCtx...) - provenances, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType()) + provenances, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.GetOrgID(), (&ngmodels.AlertRule{}).ResourceType()) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to fetch provenances of alert rules") } @@ -111,7 +114,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT rulesToDelete = append(rulesToDelete, uid...) } if len(rulesToDelete) > 0 { - err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.OrgID, rulesToDelete...) + err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.GetOrgID(), rulesToDelete...) if err != nil { return err } @@ -141,7 +144,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT // RouteGetNamespaceRulesConfig returns all rules in a specific folder that user has access to func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, namespaceTitle string) response.Response { - namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } @@ -150,7 +153,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam if err != nil { return errorToResponse(err) } - provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType()) + provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.GetOrgID(), (&ngmodels.AlertRule{}).ResourceType()) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get provenance for rule group") } @@ -167,7 +170,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam // RouteGetRulesGroupConfig returns rules that belong to a specific group in a specific namespace (folder). // If user does not have access to at least one of the rule in the group, returns status 401 Unauthorized func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespaceTitle string, ruleGroup string) response.Response { - namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } @@ -181,7 +184,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa return errorToResponse(err) } - provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType()) + provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.GetOrgID(), (&ngmodels.AlertRule{}).ResourceType()) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules") } @@ -223,7 +226,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res if err != nil { return errorToResponse(err) } - provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType()) + provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.GetOrgID(), (&ngmodels.AlertRule{}).ResourceType()) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules") } @@ -231,7 +234,8 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res for groupKey, rules := range configs { folder, ok := namespaceMap[groupKey.NamespaceUID] if !ok { - srv.log.Error("Namespace not visible to the user", "user", c.SignedInUser.UserID, "namespace", groupKey.NamespaceUID) + userNamespace, id := c.SignedInUser.GetNamespacedID() + srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID) continue } namespace := folder.Title @@ -241,18 +245,18 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res } func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig, namespaceTitle string) response.Response { - namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } - rules, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.OrgID, namespace, srv.cfg) + rules, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace, srv.cfg) if err != nil { return ErrResp(http.StatusBadRequest, err, "") } groupKey := ngmodels.AlertRuleGroupKey{ - OrgID: c.SignedInUser.OrgID, + OrgID: c.SignedInUser.GetOrgID(), NamespaceUID: namespace.UID, RuleGroup: ruleGroupConfig.Name, } @@ -266,7 +270,9 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey var finalChanges *store.GroupDelta hasAccess := accesscontrol.HasAccess(srv.ac, c) err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error { - logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group", groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", c.UserID) + userNamespace, id := c.SignedInUser.GetNamespacedID() + logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group", + groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace) groupChanges, err := store.CalculateChanges(tranCtx, srv.store, groupKey, rules) if err != nil { return err @@ -303,7 +309,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey UIDs = append(UIDs, rule.UID) } - if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.OrgID, UIDs...); err != nil { + if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.GetOrgID(), UIDs...); err != nil { return fmt.Errorf("failed to delete rules: %w", err) } } @@ -343,9 +349,10 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey } if len(finalChanges.New) > 0 { + userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, "a.ScopeParameters{ OrgID: c.SignedInUser.GetOrgID(), - UserID: c.UserID, + UserID: userID, }) // alert rule is table name if err != nil { return fmt.Errorf("failed to get alert rules quota: %w", err) @@ -481,7 +488,7 @@ func verifyProvisionedRulesNotAffected(ctx context.Context, provenanceStore prov return fmt.Errorf("%w: alert rule group [%s]", errProvisionedResource, errorMsg.String()) } -func validateQueries(ctx context.Context, groupChanges *store.GroupDelta, validator ConditionValidator, user *user.SignedInUser) error { +func validateQueries(ctx context.Context, groupChanges *store.GroupDelta, validator ConditionValidator, user identity.Requester) error { if len(groupChanges.New) > 0 { for _, rule := range groupChanges.New { err := validator.Validate(eval.NewContext(ctx, user), rule.GetEvalCondition()) diff --git a/pkg/services/ngalert/api/api_ruler_export.go b/pkg/services/ngalert/api/api_ruler_export.go index 0a34cf0b050..abe3a29ea04 100644 --- a/pkg/services/ngalert/api/api_ruler_export.go +++ b/pkg/services/ngalert/api/api_ruler_export.go @@ -14,12 +14,12 @@ import ( // ExportFromPayload converts the rule groups from the argument `ruleGroupConfig` to export format. All rules are expected to be fully specified. The access to data sources mentioned in the rules is not enforced. // Can return 403 StatusForbidden if user is not authorized to read folder `namespaceTitle` func (srv RulerSrv) ExportFromPayload(c *contextmodel.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig, namespaceTitle string) response.Response { - namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } - rulesWithOptionals, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.OrgID, namespace, srv.cfg) + rulesWithOptionals, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace, srv.cfg) if err != nil { return ErrResp(http.StatusBadRequest, err, "") } @@ -106,7 +106,7 @@ func (srv RulerSrv) getRuleWithFolderTitleByRuleUid(c *contextmodel.ReqContext, if err != nil { return ngmodels.AlertRuleGroupWithFolderTitle{}, err } - namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), rule.NamespaceUID, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), rule.NamespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return ngmodels.AlertRuleGroupWithFolderTitle{}, errors.Join(errFolderAccess, err) } @@ -115,7 +115,7 @@ func (srv RulerSrv) getRuleWithFolderTitleByRuleUid(c *contextmodel.ReqContext, // getRuleGroupWithFolderTitle calls getAuthorizedRuleGroup and combines its result with folder (aka namespace) title. func (srv RulerSrv) getRuleGroupWithFolderTitle(c *contextmodel.ReqContext, ruleGroupKey ngmodels.AlertRuleGroupKey) (ngmodels.AlertRuleGroupWithFolderTitle, error) { - namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), ruleGroupKey.NamespaceUID, c.SignedInUser.OrgID, c.SignedInUser) + namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), ruleGroupKey.NamespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return ngmodels.AlertRuleGroupWithFolderTitle{}, errors.Join(errFolderAccess, err) } diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index b9ecfe20a89..264d9faff54 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -3,16 +3,16 @@ package api import ( "context" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/folder" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/user" ) // RuleStore is the interface for persisting alert rules and instances type RuleStore interface { - GetUserVisibleNamespaces(context.Context, int64, *user.SignedInUser) (map[string]*folder.Folder, error) - GetNamespaceByTitle(context.Context, string, int64, *user.SignedInUser) (*folder.Folder, error) - GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*folder.Folder, error) + GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error) + GetNamespaceByTitle(context.Context, string, int64, identity.Requester) (*folder.Folder, error) + GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error) diff --git a/pkg/services/ngalert/backtesting/engine.go b/pkg/services/ngalert/backtesting/engine.go index 7752f8fac9c..db058607245 100644 --- a/pkg/services/ngalert/backtesting/engine.go +++ b/pkg/services/ngalert/backtesting/engine.go @@ -9,14 +9,15 @@ import ( "time" "github.com/benbjohnson/clock" + "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/state" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -61,7 +62,7 @@ func NewEngine(appUrl *url.URL, evalFactory eval.EvaluatorFactory, tracer tracin } } -func (e *Engine) Test(ctx context.Context, user *user.SignedInUser, rule *models.AlertRule, from, to time.Time) (*data.Frame, error) { +func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models.AlertRule, from, to time.Time) (*data.Frame, error) { ruleCtx := models.WithRuleKey(ctx, rule.GetKey()) logger := logger.FromContext(ctx) @@ -125,7 +126,7 @@ func (e *Engine) Test(ctx context.Context, user *user.SignedInUser, rule *models return result, nil } -func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFactory, user *user.SignedInUser, condition models.Condition) (backtestingEvaluator, error) { +func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition) (backtestingEvaluator, error) { for _, q := range condition.Data { if q.DatasourceUID == "__data__" || q.QueryType == "__data__" { if len(condition.Data) != 1 { diff --git a/pkg/services/ngalert/backtesting/engine_test.go b/pkg/services/ngalert/backtesting/engine_test.go index e11ab3a033f..f275593cd52 100644 --- a/pkg/services/ngalert/backtesting/engine_test.go +++ b/pkg/services/ngalert/backtesting/engine_test.go @@ -9,14 +9,15 @@ import ( "testing" "time" - "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/stretchr/testify/require" + "github.com/grafana/grafana-plugin-sdk-go/data" + + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/state" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -174,7 +175,7 @@ func TestEvaluatorTest(t *testing.T) { } manager := &fakeStateManager{} - backtestingEvaluatorFactory = func(ctx context.Context, evalFactory eval.EvaluatorFactory, user *user.SignedInUser, condition models.Condition) (backtestingEvaluator, error) { + backtestingEvaluatorFactory = func(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition) (backtestingEvaluator, error) { return evaluator, nil } diff --git a/pkg/services/ngalert/eval/context.go b/pkg/services/ngalert/eval/context.go index 3cdaefceb22..7315d727fa4 100644 --- a/pkg/services/ngalert/eval/context.go +++ b/pkg/services/ngalert/eval/context.go @@ -3,16 +3,16 @@ package eval import ( "context" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) // EvaluationContext represents the context in which a condition is evaluated. type EvaluationContext struct { Ctx context.Context - User *user.SignedInUser + User identity.Requester } -func NewContext(ctx context.Context, user *user.SignedInUser) EvaluationContext { +func NewContext(ctx context.Context, user identity.Requester) EvaluationContext { return EvaluationContext{ Ctx: ctx, User: user, diff --git a/pkg/services/ngalert/eval/eval.go b/pkg/services/ngalert/eval/eval.go index 5e554c70434..d079022b6ac 100644 --- a/pkg/services/ngalert/eval/eval.go +++ b/pkg/services/ngalert/eval/eval.go @@ -271,7 +271,7 @@ func buildDatasourceHeaders(ctx context.Context) map[string]string { // getExprRequest validates the condition, gets the datasource information and creates an expr.Request from it. func getExprRequest(ctx EvaluationContext, data []models.AlertQuery, dsCacheService datasources.CacheService) (*expr.Request, error) { req := &expr.Request{ - OrgId: ctx.User.OrgID, + OrgId: ctx.User.GetOrgID(), Headers: buildDatasourceHeaders(ctx.Ctx), User: ctx.User, } diff --git a/pkg/services/ngalert/models/history.go b/pkg/services/ngalert/models/history.go index ebc74e94d2e..509b1a74398 100644 --- a/pkg/services/ngalert/models/history.go +++ b/pkg/services/ngalert/models/history.go @@ -3,7 +3,7 @@ package models import ( "time" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) // HistoryQuery represents a query for alert state history. @@ -16,5 +16,5 @@ type HistoryQuery struct { From time.Time To time.Time Limit int - SignedInUser *user.SignedInUser + SignedInUser identity.Requester } diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 7695c6df58d..a72bf7d115b 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -14,11 +14,11 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config" "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -51,7 +51,7 @@ type ContactPointQuery struct { Decrypt bool } -func (ecp *ContactPointService) canDecryptSecrets(ctx context.Context, u *user.SignedInUser) bool { +func (ecp *ContactPointService) canDecryptSecrets(ctx context.Context, u identity.Requester) bool { if u == nil { return false } @@ -64,7 +64,7 @@ func (ecp *ContactPointService) canDecryptSecrets(ctx context.Context, u *user.S } // GetContactPoints returns contact points. If q.Decrypt is true and the user is an OrgAdmin, decrypted secure settings are included instead of redacted ones. -func (ecp *ContactPointService) GetContactPoints(ctx context.Context, q ContactPointQuery, u *user.SignedInUser) ([]apimodels.EmbeddedContactPoint, error) { +func (ecp *ContactPointService) GetContactPoints(ctx context.Context, q ContactPointQuery, u identity.Requester) ([]apimodels.EmbeddedContactPoint, error) { if q.Decrypt && !ecp.canDecryptSecrets(ctx, u) { return nil, fmt.Errorf("%w: user requires Admin role or alert.provisioning.secrets:read permission to view decrypted secure settings", ErrPermissionDenied) } diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index a88d5258619..0ac146f328d 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/store/entity" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -426,7 +425,7 @@ func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespa } // GetUserVisibleNamespaces returns the folders that are visible to the user and have at least one alert in it -func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *user.SignedInUser) (map[string]*folder.Folder, error) { +func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) { namespaceMap := make(map[string]*folder.Folder) searchQuery := dashboards.FindPersistedDashboardsQuery{ @@ -470,7 +469,7 @@ func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, use } // GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces. -func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *user.SignedInUser) (*folder.Folder, error) { +func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user identity.Requester) (*folder.Folder, error) { folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, Title: &namespace, SignedInUser: user}) if err != nil { return nil, err @@ -480,7 +479,7 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, org } // GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces. -func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user *user.SignedInUser) (*folder.Folder, error) { +func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) { folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, UID: &uid, SignedInUser: user}) if err != nil { return nil, err diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index df5d691c85c..af9d35b3f03 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -229,7 +228,7 @@ func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQu return ruleList, nil } -func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *user.SignedInUser) (map[string]*folder.Folder, error) { +func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ identity.Requester) (map[string]*folder.Folder, error) { f.mtx.Lock() defer f.mtx.Unlock() @@ -246,7 +245,7 @@ func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ * return namespacesMap, nil } -func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *user.SignedInUser) (*folder.Folder, error) { +func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ identity.Requester) (*folder.Folder, error) { folders := f.Folders[orgID] for _, folder := range folders { if folder.Title == title { @@ -256,7 +255,7 @@ func (f *RuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID i return nil, fmt.Errorf("not found") } -func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ *user.SignedInUser) (*folder.Folder, error) { +func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ identity.Requester) (*folder.Folder, error) { f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ Name: "GetNamespaceByUID", Params: []any{orgID, uid}, diff --git a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go index ddce870d0b2..0dd19267642 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/util/proxyutil" ) @@ -31,8 +32,9 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo } h.DeleteHTTPHeader(proxyutil.UserHeaderName) - if !reqCtx.IsAnonymous { - h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.Login) + namespace, _ := reqCtx.SignedInUser.GetNamespacedID() + if namespace != identity.NamespaceAnonymous { + h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin()) } }