From 1b8e9b51b20adc3d37f3051f65a4bd5006bd1e5c Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:51:18 +0200 Subject: [PATCH] Replace signed in user for identity.requester (#74048) * Make identity.Requester available at Context * Clean pkg/services/guardian/guardian.go * Clean guardian provider and guardian AC * Clean pkg/api/team.go * Clean ctxhandler, datasources, plugin and live * Clean dashboards and guardian * Implement NewUserDisplayDTOFromRequester * Change status code numbers for http constants * Upgrade signature of ngalert services * log parsing errors instead of throwing error --- pkg/api/dashboard.go | 184 ++++++++++++------ pkg/api/dashboard_test.go | 62 +++--- pkg/expr/service.go | 6 +- pkg/expr/testing.go | 14 +- pkg/expr/transform.go | 4 +- pkg/services/alerting/models.go | 4 +- .../dashboardimport/service/service_test.go | 11 +- pkg/services/dashboards/models.go | 3 +- .../dashboards/service/dashboard_service.go | 50 ++++- .../dashboard_service_integration_test.go | 51 ++++- pkg/services/datasources/datasources.go | 6 +- .../datasources/fakes/fake_cache_service.go | 6 +- pkg/services/datasources/guardian/provider.go | 6 +- pkg/services/datasources/service/cache.go | 26 +-- pkg/services/folder/folderimpl/folder.go | 26 ++- pkg/services/folder/model.go | 2 + .../guardian/accesscontrol_guardian.go | 30 +-- pkg/services/guardian/guardian.go | 20 +- pkg/services/guardian/provider.go | 10 +- pkg/services/live/features/broadcast.go | 10 +- pkg/services/live/features/dashboard.go | 20 +- pkg/services/live/features/plugin.go | 10 +- pkg/services/live/features/plugin_mock.go | 4 +- pkg/services/live/live.go | 71 ++++--- pkg/services/live/livecontext/context.go | 8 +- pkg/services/live/liveplugin/plugin.go | 6 +- pkg/services/live/managedstream/runner.go | 8 +- pkg/services/live/model/model.go | 6 +- pkg/services/live/pipeline/auth.go | 6 +- pkg/services/live/pipeline/pipeline.go | 6 +- .../live/pipeline/subscribe_builtin.go | 4 +- pkg/services/live/pushws/push_pipeline.go | 2 +- pkg/services/live/pushws/push_stream.go | 2 +- pkg/services/live/runstream/manager.go | 8 +- pkg/services/live/runstream/manager_test.go | 40 ++-- pkg/services/live/runstream/mock.go | 4 +- pkg/services/ngalert/api/lotex_ruler_test.go | 6 +- .../pluginsintegration/adapters/adapters.go | 16 +- .../plugincontext/plugincontext.go | 12 +- pkg/services/query/query.go | 20 +- pkg/services/query/query_service_mock.go | 8 +- pkg/services/query/query_test.go | 5 +- pkg/services/user/identity.go | 17 ++ 43 files changed, 508 insertions(+), 312 deletions(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 5038c2e02ef..3183ae027ad 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" @@ -44,15 +45,27 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI return false, nil } - query := star.IsStarredByUserQuery{UserID: c.UserID, DashboardID: dashID} + namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() + + if namespaceID != identity.NamespaceUser { + return false, nil + } + + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + + if err != nil { + return false, err + } + + query := star.IsStarredByUserQuery{UserID: userID, DashboardID: dashID} return hs.starService.IsStarredByUser(c.Req.Context(), &query) } func dashboardGuardianResponse(err error) response.Response { if err != nil { - return response.Error(500, "Error while checking dashboard permissions", err) + return response.Error(http.StatusInternalServerError, "Error while checking dashboard permissions", err) } - return response.Error(403, "Access denied to this dashboard", nil) + return response.Error(http.StatusForbidden, "Access denied to this dashboard", nil) } // swagger:route POST /dashboards/trim dashboards trimDashboard @@ -95,7 +108,7 @@ func (hs *HTTPServer) TrimDashboard(c *contextmodel.ReqContext) response.Respons // 500: internalServerError func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response { uid := web.Params(c.Req)[":uid"] - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, 0, uid) + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, uid) if rsp != nil { return rsp } @@ -108,9 +121,9 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response // If public dashboards is enabled and we have a public dashboard, update meta // values if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { - publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.OrgID, dash.UID) + publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.SignedInUser.GetOrgID(), dash.UID) if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) { - return response.Error(500, "Error while retrieving public dashboards", err) + return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err) } if publicDashboard != nil { @@ -128,10 +141,10 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response } } if isEmptyData { - return response.Error(500, "Error while loading dashboard, dashboard data is invalid", nil) + return response.Error(http.StatusInternalServerError, "Error while loading dashboard, dashboard data is invalid", nil) } } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser) + guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -146,7 +159,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response isStarred, err := hs.isDashboardStarredByUser(c, dash.ID) if err != nil { - return response.Error(500, "Error while checking if dashboard was starred by user", err) + return response.Error(http.StatusInternalServerError, "Error while checking if dashboard was starred by user", err) } // Finding creator and last updater of the dashboard updater, creator := anonString, anonString @@ -186,13 +199,13 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response // lookup folder title if dash.FolderID > 0 { - query := dashboards.GetDashboardQuery{ID: dash.FolderID, OrgID: c.OrgID} + query := dashboards.GetDashboardQuery{ID: dash.FolderID, OrgID: c.SignedInUser.GetOrgID()} queryResult, err := hs.DashboardService.GetDashboard(c.Req.Context(), &query) if err != nil { if errors.Is(err, dashboards.ErrFolderNotFound) { - return response.Error(404, "Folder not found", err) + return response.Error(http.StatusNotFound, "Folder not found", err) } - return response.Error(500, "Dashboard folder could not be read", err) + return response.Error(http.StatusInternalServerError, "Dashboard folder could not be read", err) } meta.FolderUid = queryResult.UID meta.FolderTitle = queryResult.Title @@ -201,7 +214,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response provisioningData, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID) if err != nil { - return response.Error(500, "Error while checking if dashboard is provisioned", err) + return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned", err) } if provisioningData != nil { @@ -275,7 +288,7 @@ func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id in queryResult, err := hs.DashboardService.GetDashboard(ctx, &query) if err != nil { - return nil, response.Error(404, "Dashboard not found", err) + return nil, response.Error(http.StatusNotFound, "Dashboard not found", err) } return queryResult, nil @@ -298,11 +311,11 @@ func (hs *HTTPServer) DeleteDashboardByUID(c *contextmodel.ReqContext) response. } func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Response { - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, 0, web.Params(c.Req)[":uid"]) + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, web.Params(c.Req)[":uid"]) if rsp != nil { return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser) + guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -311,10 +324,17 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo return dashboardGuardianResponse(err) } + namespaceID, userIDStr := c.SignedInUser.GetNamespacedID() + // disconnect all library elements for this dashboard err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID) if err != nil { - hs.log.Error("Failed to disconnect library elements", "dashboard", dash.ID, "user", c.SignedInUser.UserID, "error", err) + hs.log.Error( + "Failed to disconnect library elements", + "dashboard", dash.ID, + "namespaceID", namespaceID, + "user", userIDStr, + "error", err) } // deletes all related public dashboard entities @@ -323,7 +343,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo hs.log.Error("Failed to delete public dashboard") } - err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.OrgID) + err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.SignedInUser.GetOrgID()) if err != nil { var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { @@ -331,11 +351,16 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err) } } - return response.Error(500, "Failed to delete dashboard", err) + return response.Error(http.StatusInternalServerError, "Failed to delete dashboard", err) + } + + userDTODisplay, err := user.NewUserDisplayDTOFromRequester(c.SignedInUser) + if err != nil { + return response.Error(http.StatusInternalServerError, "Error while parsing the user DTO model", err) } if hs.Live != nil { - err := hs.Live.GrafanaScope.Dashboards.DashboardDeleted(c.OrgID, c.ToUserDisplayDTO(), dash.UID) + err := hs.Live.GrafanaScope.Dashboards.DashboardDeleted(c.SignedInUser.GetOrgID(), userDTODisplay, dash.UID) if err != nil { hs.log.Error("Failed to broadcast delete info", "dashboard", dash.UID, "error", err) } @@ -396,19 +421,30 @@ func (hs *HTTPServer) PostDashboard(c *contextmodel.ReqContext) response.Respons func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.SaveDashboardCommand) response.Response { ctx := c.Req.Context() var err error - cmd.OrgID = c.OrgID - cmd.UserID = c.UserID + + namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() + if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { + hs.log.Warn("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr) + return response.Error(http.StatusBadRequest, "User does not belong to a user or service account namespace", nil) + } + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + if err != nil { + hs.log.Warn("Error while parsing user ID", "namespaceID", namespaceID, "userID", userIDstr) + } + + cmd.OrgID = c.SignedInUser.GetOrgID() + cmd.UserID = userID if cmd.FolderUID != "" { folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), UID: &cmd.FolderUID, SignedInUser: c.SignedInUser, }) if err != nil { if errors.Is(err, dashboards.ErrFolderNotFound) { - return response.Error(400, "Folder not found", err) + return response.Error(http.StatusBadRequest, "Folder not found", err) } - return response.Error(500, "Error while checking folder ID", err) + return response.Error(http.StatusInternalServerError, "Error while checking folder ID", err) } cmd.FolderID = folder.ID } @@ -418,10 +454,10 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S if newDashboard { limitReached, err := hs.QuotaService.QuotaReached(c, dashboards.QuotaTargetSrv) if err != nil { - return response.Error(500, "failed to get quota", err) + return response.Error(http.StatusInternalServerError, "failed to get quota", err) } if limitReached { - return response.Error(403, "Quota reached", nil) + return response.Error(http.StatusForbidden, "Quota reached", nil) } } @@ -429,13 +465,13 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S if dash.ID != 0 { data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID) if err != nil { - return response.Error(500, "Error while checking if dashboard is provisioned using ID", err) + return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned using ID", err) } provisioningData = data } else if dash.UID != "" { data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(c.Req.Context(), dash.OrgID, dash.UID) if err != nil && !errors.Is(err, dashboards.ErrProvisionedDashboardNotFound) && !errors.Is(err, dashboards.ErrDashboardNotFound) { - return response.Error(500, "Error while checking if dashboard is provisioned", err) + return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned", err) } provisioningData = data } @@ -448,7 +484,7 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S dashItem := &dashboards.SaveDashboardDTO{ Dashboard: dash, Message: cmd.Message, - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), User: c.SignedInUser, Overwrite: cmd.Overwrite, } @@ -461,14 +497,19 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S dashboard = dash // the original request } + userDTODisplay, err := user.NewUserDisplayDTOFromRequester(c.SignedInUser) + if err != nil { + return response.Error(http.StatusInternalServerError, "Error while parsing the user DTO model", err) + } + // This will broadcast all save requests only if a `gitops` observer exists. // gitops is useful when trying to save dashboards in an environment where the user can not save channel := hs.Live.GrafanaScope.Dashboards - liveerr := channel.DashboardSaved(c.SignedInUser.OrgID, c.SignedInUser.ToUserDisplayDTO(), cmd.Message, dashboard, err) + liveerr := channel.DashboardSaved(c.SignedInUser.GetOrgID(), userDTODisplay, cmd.Message, dashboard, err) // When an error exists, but the value broadcast to a gitops listener return 202 - if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.OrgID) { - return response.JSON(202, util.DynMap{ + if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.GetOrgID()) { + return response.JSON(http.StatusAccepted, util.DynMap{ "status": "pending", "message": "changes were broadcast to the gitops listener", }) @@ -492,7 +533,7 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S // connect library panels for this dashboard after the dashboard is stored and has an ID err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard) if err != nil { - return response.Error(500, "Error while connecting library panels", err) + return response.Error(http.StatusInternalServerError, "Error while connecting library panels", err) } c.TimeRequest(metrics.MApiDashboardSave) @@ -515,12 +556,22 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S // 401: unauthorisedError // 500: internalServerError func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Response { - prefsQuery := pref.GetPreferenceWithDefaultsQuery{OrgID: c.OrgID, UserID: c.SignedInUser.UserID, Teams: c.Teams} + namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() + + if namespaceID != identity.NamespaceUser { + return response.Error(http.StatusBadRequest, "User does not belong to a user namespace", nil) + } + + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + if err != nil { + hs.log.Warn("Error while parsing user ID", "namespaceID", namespaceID, "userID", userIDstr, "err", err) + } + prefsQuery := pref.GetPreferenceWithDefaultsQuery{OrgID: c.SignedInUser.GetOrgID(), UserID: userID, Teams: c.SignedInUser.GetTeams()} homePage := hs.Cfg.HomePage preference, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) if err != nil { - return response.Error(500, "Failed to get preferences", err) + return response.Error(http.StatusInternalServerError, "Failed to get preferences", err) } if preference.HomeDashboardID == 0 && len(homePage) > 0 { @@ -549,7 +600,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp // nolint:gosec file, err := os.Open(filePath) if err != nil { - return response.Error(500, "Failed to load home dashboard", err) + return response.Error(http.StatusInternalServerError, "Failed to load home dashboard", err) } defer func() { if err := file.Close(); err != nil { @@ -564,7 +615,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp jsonParser := json.NewDecoder(file) if err := jsonParser.Decode(dash.Dashboard); err != nil { - return response.Error(500, "Failed to load home dashboard", err) + return response.Error(http.StatusInternalServerError, "Failed to load home dashboard", err) } hs.addGettingStartedPanelToHomeDashboard(c, dash.Dashboard) @@ -636,12 +687,12 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response. } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID) + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID) if rsp != nil { return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser) + guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -650,7 +701,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response. } query := dashver.ListDashboardVersionsQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), DashboardID: dash.ID, DashboardUID: dash.UID, Limit: c.QueryInt("limit"), @@ -659,7 +710,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response. versions, err := hs.dashboardVersionService.List(c.Req.Context(), &query) if err != nil { - return response.Error(404, fmt.Sprintf("No versions found for dashboardId %d", dash.ID), err) + return response.Error(http.StatusNotFound, fmt.Sprintf("No versions found for dashboardId %d", dash.ID), err) } loginMem := make(map[int64]string, len(versions)) @@ -747,12 +798,12 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID) + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID) if rsp != nil { return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser) + guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -763,7 +814,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R version, _ := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 32) query := dashver.GetDashboardVersionQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), DashboardID: dash.ID, DashboardUID: dash.UID, Version: int(version), @@ -771,7 +822,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R res, err := hs.dashboardVersionService.Get(c.Req.Context(), &query) if err != nil { - return response.Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dash.ID), err) + return response.Error(http.StatusInternalServerError, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dash.ID), err) } creator := anonString @@ -879,7 +930,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons if err := web.Bind(c.Req, &apiOptions); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - guardianBase, err := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.OrgID, c.SignedInUser) + guardianBase, err := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -889,7 +940,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons } if apiOptions.Base.DashboardId != apiOptions.New.DashboardId { - guardianNew, err := guardian.New(c.Req.Context(), apiOptions.New.DashboardId, c.OrgID, c.SignedInUser) + guardianNew, err := guardian.New(c.Req.Context(), apiOptions.New.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -900,7 +951,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons } options := dashdiffs.Options{ - OrgId: c.OrgID, + OrgId: c.SignedInUser.GetOrgID(), DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType), Base: dashdiffs.DiffTarget{ DashboardId: apiOptions.Base.DashboardId, @@ -923,9 +974,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons baseVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &baseVersionQuery) if err != nil { if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(404, "Dashboard version not found", err) + return response.Error(http.StatusNotFound, "Dashboard version not found", err) } - return response.Error(500, "Unable to compute diff", err) + return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) } newVersionQuery := dashver.GetDashboardVersionQuery{ @@ -937,9 +988,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons newVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &newVersionQuery) if err != nil { if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(404, "Dashboard version not found", err) + return response.Error(http.StatusNotFound, "Dashboard version not found", err) } - return response.Error(500, "Unable to compute diff", err) + return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) } baseData := baseVersionRes.Data @@ -949,9 +1000,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons if err != nil { if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(404, "Dashboard version not found", err) + return response.Error(http.StatusNotFound, "Dashboard version not found", err) } - return response.Error(500, "Unable to compute diff", err) + return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) } if options.DiffType == dashdiffs.DiffDelta { @@ -1003,12 +1054,12 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID) + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID) if rsp != nil { return rsp } - guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser) + guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return response.Err(err) } @@ -1017,16 +1068,25 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon return dashboardGuardianResponse(err) } - versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.OrgID} + versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.SignedInUser.GetOrgID()} version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery) if err != nil { - return response.Error(404, "Dashboard version not found", nil) + return response.Error(http.StatusNotFound, "Dashboard version not found", nil) + } + + namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() + if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { + return response.Error(http.StatusBadRequest, "User does not belong to a user or service namespace", nil) + } + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + if err != nil { + return response.Error(http.StatusInternalServerError, "failed to get user id", err) } saveCmd := dashboards.SaveDashboardCommand{} saveCmd.RestoredFrom = version.Version - saveCmd.OrgID = c.OrgID - saveCmd.UserID = c.UserID + saveCmd.OrgID = c.SignedInUser.GetOrgID() + saveCmd.UserID = userID saveCmd.Dashboard = version.Data saveCmd.Dashboard.Set("version", dash.Version) saveCmd.Dashboard.Set("uid", dash.UID) @@ -1045,7 +1105,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon // 401: unauthorisedError // 500: internalServerError func (hs *HTTPServer) GetDashboardTags(c *contextmodel.ReqContext) { - query := dashboards.GetDashboardTagsQuery{OrgID: c.OrgID} + query := dashboards.GetDashboardTagsQuery{OrgID: c.SignedInUser.GetOrgID()} queryResult, err := hs.DashboardService.GetDashboardTags(c.Req.Context(), &query) if err != nil { c.JsonApiErr(500, "Failed to get tags from database", err) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 45f305a545f..1c15735dff7 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -79,6 +79,7 @@ func TestGetHomeDashboard(t *testing.T) { preferenceService: prefService, dashboardVersionService: dashboardVersionService, Kinds: corekind.NewBase(nil), + log: log.New("test-logger"), } tests := []struct { @@ -478,7 +479,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) { callPostDashboard(sc) - assert.Equal(t, 500, sc.resp.Code) + assert.Equal(t, http.StatusInternalServerError, sc.resp.Code) }) }) @@ -488,23 +489,23 @@ func TestDashboardAPIEndpoint(t *testing.T) { SaveError error ExpectedStatusCode int }{ - {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: 404}, - {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, - {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, - {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, - {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422}, - {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, - {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, - {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: http.StatusNotFound}, + {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: http.StatusPreconditionFailed}, + {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: http.StatusPreconditionFailed}, + {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: http.StatusUnprocessableEntity}, + {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: http.StatusForbidden}, + {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: http.StatusBadRequest}, + {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: http.StatusPreconditionFailed}, } cmd := dashboards.SaveDashboardCommand{ @@ -521,7 +522,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { postDashboardScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()), "/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) { callPostDashboard(sc) - assert.Equal(t, tc.ExpectedStatusCode, sc.resp.Code) + assert.Equal(t, tc.ExpectedStatusCode, sc.resp.Code, sc.resp.Body.String()) }) } }) @@ -540,7 +541,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callPostDashboard(sc) result := sc.ToJSON() - assert.Equal(t, 422, sc.resp.Code) + assert.Equal(t, http.StatusUnprocessableEntity, sc.resp.Code) assert.False(t, result.Get("isValid").MustBool()) assert.NotEmpty(t, result.Get("message").MustString()) }, sqlmock) @@ -556,7 +557,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callPostDashboard(sc) result := sc.ToJSON() - assert.Equal(t, 412, sc.resp.Code) + assert.Equal(t, http.StatusPreconditionFailed, sc.resp.Code) assert.False(t, result.Get("isValid").MustBool()) assert.Equal(t, "invalid schema version", result.Get("message").MustString()) }, sqlmock) @@ -575,7 +576,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callPostDashboard(sc) result := sc.ToJSON() - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) assert.True(t, result.Get("isValid").MustBool()) }, sqlmock) }) @@ -618,7 +619,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: false}) callPostDashboard(sc) - assert.Equal(t, 403, sc.resp.Code) + assert.Equal(t, http.StatusForbidden, sc.resp.Code) }, sqlmock, fakeDashboardVersionService) }) @@ -629,7 +630,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { // This test shouldn't hit GetDashboardACLInfoList, so no setup needed sc.dashboardVersionService = fakeDashboardVersionService callPostDashboard(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) }, sqlmock, fakeDashboardVersionService) }) }) @@ -666,7 +667,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { sc.dashboardVersionService = fakeDashboardVersionService callRestoreDashboardVersion(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) }, mockSQLStore) }) @@ -699,7 +700,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore", "/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) { callRestoreDashboardVersion(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) }, mockSQLStore) }) @@ -752,7 +753,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { } hs.callGetDashboard(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) dash := dtos.DashboardFullWithMeta{} err := json.NewDecoder(sc.resp.Body).Decode(&dash) @@ -814,7 +815,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { ExpectedUser: &user.User{ID: 1, Login: "test-user"}, }).callGetDashboardVersions(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) var versions []dashver.DashboardVersionMeta err := json.NewDecoder(sc.resp.Body).Decode(&versions) require.NoError(t, err) @@ -840,7 +841,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { ExpectedError: user.ErrUserNotFound, }).callGetDashboardVersions(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) var versions []dashver.DashboardVersionMeta err := json.NewDecoder(sc.resp.Body).Decode(&versions) require.NoError(t, err) @@ -866,7 +867,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) { ExpectedError: fmt.Errorf("some error"), }).callGetDashboardVersions(sc) - assert.Equal(t, 200, sc.resp.Code) + assert.Equal(t, http.StatusOK, sc.resp.Code) var versions []dashver.DashboardVersionMeta err := json.NewDecoder(sc.resp.Body).Decode(&versions) require.NoError(t, err) @@ -981,6 +982,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s Features: featuremgmt.WithFeatures(), Kinds: corekind.NewBase(nil), accesscontrolService: actest.FakeService{}, + log: log.New("test-logger"), } sc := setupScenarioContext(t, url) diff --git a/pkg/expr/service.go b/pkg/expr/service.go index 678be80c093..63fca9e83a4 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -12,10 +12,10 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -68,8 +68,8 @@ type Service struct { } type pluginContextProvider interface { - Get(ctx context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) - GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, error) + Get(ctx context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error) + GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) } func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider, diff --git a/pkg/expr/testing.go b/pkg/expr/testing.go index 38fbda4bbb1..4ffcf124610 100644 --- a/pkg/expr/testing.go +++ b/pkg/expr/testing.go @@ -5,8 +5,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" ) type fakePluginContextProvider struct { @@ -20,7 +20,7 @@ type fakePluginContextProvider struct { var _ pluginContextProvider = &fakePluginContextProvider{} -func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) { +func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error) { f.recordings = append(f.recordings, struct { method string params []interface{} @@ -31,9 +31,9 @@ func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user var u *backend.User if user != nil { u = &backend.User{ - Login: user.Login, - Name: user.Name, - Email: user.Email, + Login: user.GetLogin(), + Name: user.GetDisplayName(), + Email: user.GetEmail(), } } return backend.PluginContext{ @@ -45,7 +45,7 @@ func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user }, nil } -func (f *fakePluginContextProvider) GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, error) { +func (f *fakePluginContextProvider) GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) { f.recordings = append(f.recordings, struct { method string params []interface{} @@ -57,7 +57,7 @@ func (f *fakePluginContextProvider) GetWithDataSource(ctx context.Context, plugi orgId := int64(1) if user != nil { - orgId = user.OrgID + orgId = user.GetOrgID() } r, err := f.Get(ctx, pluginID, user, orgId) if ds != nil { diff --git a/pkg/expr/transform.go b/pkg/expr/transform.go index ead26b6dde6..ac97c01930c 100644 --- a/pkg/expr/transform.go +++ b/pkg/expr/transform.go @@ -8,8 +8,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" ) // Request is similar to plugins.DataQuery but with the Time Ranges is per Query. @@ -18,7 +18,7 @@ type Request struct { Debug bool OrgId int64 Queries []Query - User *user.SignedInUser + User identity.Requester } // Query is like plugins.DataSubQuery, but with a a time range, and only the UID diff --git a/pkg/services/alerting/models.go b/pkg/services/alerting/models.go index 3193b54c655..d2706752819 100644 --- a/pkg/services/alerting/models.go +++ b/pkg/services/alerting/models.go @@ -4,8 +4,8 @@ import ( "sync" "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/user" ) // Job holds state about when the alert rule should be evaluated. @@ -46,7 +46,7 @@ type EvalMatch struct { } type DashAlertInfo struct { - User *user.SignedInUser + User identity.Requester Dash *dashboards.Dashboard OrgID int64 } diff --git a/pkg/services/dashboardimport/service/service_test.go b/pkg/services/dashboardimport/service/service_test.go index c0c14d601e2..2ed02333ff1 100644 --- a/pkg/services/dashboardimport/service/service_test.go +++ b/pkg/services/dashboardimport/service/service_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" @@ -83,9 +84,12 @@ func TestImportDashboardService(t *testing.T) { require.NotNil(t, resp) require.Equal(t, "UDdpyzz7z", resp.UID) + userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID()) + require.NoError(t, err) + require.NotNil(t, importDashboardArg) require.Equal(t, int64(3), importDashboardArg.OrgID) - require.Equal(t, int64(2), importDashboardArg.User.UserID) + require.Equal(t, int64(2), userID) require.Equal(t, "prometheus", importDashboardArg.Dashboard.PluginID) require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID) @@ -147,9 +151,12 @@ func TestImportDashboardService(t *testing.T) { require.NotNil(t, resp) require.Equal(t, "UDdpyzz7z", resp.UID) + userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID()) + require.NoError(t, err) + require.NotNil(t, importDashboardArg) require.Equal(t, int64(3), importDashboardArg.OrgID) - require.Equal(t, int64(2), importDashboardArg.User.UserID) + require.Equal(t, int64(2), userID) require.Equal(t, "", importDashboardArg.Dashboard.PluginID) require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID) diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 7ce7d14a284..a5253cbaaac 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/kinds" "github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" @@ -337,7 +338,7 @@ type GetDashboardRefByIDQuery struct { type SaveDashboardDTO struct { OrgID int64 UpdatedAt time.Time - User *user.SignedInUser + User identity.Requester Message string Overwrite bool Dashboard *Dashboard diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 24d2fb37082..e54aac448cb 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -21,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/errutil" ) var ( @@ -181,12 +183,17 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d } } + userID, err := resolveUserID(dto.User, dr.log) + if err != nil { + return nil, err + } + cmd := &dashboards.SaveDashboardCommand{ Dashboard: dash.Data, Message: dto.Message, OrgID: dto.OrgID, Overwrite: dto.Overwrite, - UserID: dto.User.UserID, + UserID: userID, FolderID: dash.FolderID, IsFolder: dash.IsFolder, PluginID: dash.PluginID, @@ -199,6 +206,20 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d return cmd, nil } +func resolveUserID(user identity.Requester, log log.Logger) (int64, error) { + namespaceID, identifier := user.GetNamespacedID() + if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { + return 0, errutil.BadRequest("account doesn't belong to the user or service namespace") + } + + userID, err := identity.IntIdentifier(namespaceID, identifier) + + if err != nil { + log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", identifier, "error", err) + } + return userID, nil +} + func (dr *DashboardServiceImpl) UpdateDashboardACL(ctx context.Context, uid int64, items []*dashboards.DashboardACL) error { return dr.dashboardStore.UpdateDashboardACL(ctx, uid, items) } @@ -209,7 +230,7 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context. // getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard // It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck() -func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user *user.SignedInUser) (guardian.DashboardGuardian, error) { +func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) { newDashboard := d.ID == 0 if newDashboard { @@ -297,7 +318,8 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt if dto.Dashboard.ID == 0 { if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil { - dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err) + namespaceID, userID := dto.User.GetNamespacedID() + dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err) } } @@ -337,7 +359,8 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C if dto.Dashboard.ID == 0 { if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil { - dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err) + namespaceID, userID := dto.User.GetNamespacedID() + dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err) } } @@ -385,7 +408,8 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar // new dashboard created if dto.Dashboard.ID == 0 { if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil { - dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err) + namespaceID, userID := dto.User.GetNamespacedID() + dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err) } } @@ -442,7 +466,8 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo } if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil { - dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err) + namespaceID, userID := dto.User.GetNamespacedID() + dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err) } return dash, nil @@ -462,9 +487,16 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto * inFolder := dash.FolderID > 0 var permissions []accesscontrol.SetResourcePermissionCommand - if !provisioned && dto.User.IsRealUser() && !dto.User.IsAnonymous { + namespaceID, userIDstr := dto.User.GetNamespacedID() + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + + if err != nil { + return err + } + + if !provisioned && namespaceID == identity.NamespaceUser { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ - UserID: dto.User.UserID, Permission: dashboards.PERMISSION_ADMIN.String(), + UserID: userID, Permission: dashboards.PERMISSION_ADMIN.String(), }) } @@ -480,7 +512,7 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto * svc = dr.folderPermissions } - _, err := svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...) + _, err = svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...) if err != nil { return err } diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 5e14b530152..fb888707d37 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -15,6 +15,7 @@ import ( accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting/models" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -112,9 +113,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, "", sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When creating a new dashboard in other folder, it should create dashboard guardian for other folder with correct arguments and rsult in access denied error", @@ -132,9 +136,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When creating a new dashboard by existing title in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -152,9 +159,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When creating a new dashboard by existing UID in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -173,9 +183,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When updating a dashboard by existing id in the General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -194,9 +207,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When updating a dashboard by existing id in other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -215,9 +231,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When moving a dashboard by existing ID to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -236,9 +255,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When moving a dashboard by existing id to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -257,9 +279,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When moving a dashboard by existing uid to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -278,9 +303,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) permissionScenario(t, "When moving a dashboard by existing UID to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error", @@ -299,9 +327,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { err := callSaveWithError(t, cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) + userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) + require.NoError(t, err) + assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) - assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) + assert.Equal(t, cmd.UserID, userID) }) }) diff --git a/pkg/services/datasources/datasources.go b/pkg/services/datasources/datasources.go index 598803ca07f..accc2075887 100644 --- a/pkg/services/datasources/datasources.go +++ b/pkg/services/datasources/datasources.go @@ -7,7 +7,7 @@ import ( sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) // DataSourceService interface for interacting with datasources. @@ -64,8 +64,8 @@ type DataSourceService interface { // CacheService interface for retrieving a cached datasource. type CacheService interface { // GetDatasource gets a datasource identified by datasource numeric identifier. - GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*DataSource, error) + GetDatasource(ctx context.Context, datasourceID int64, user identity.Requester, skipCache bool) (*DataSource, error) // GetDatasourceByUID gets a datasource identified by datasource unique identifier (UID). - GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*DataSource, error) + GetDatasourceByUID(ctx context.Context, datasourceUID string, user identity.Requester, skipCache bool) (*DataSource, error) } diff --git a/pkg/services/datasources/fakes/fake_cache_service.go b/pkg/services/datasources/fakes/fake_cache_service.go index 58222a4f702..ec0521aced7 100644 --- a/pkg/services/datasources/fakes/fake_cache_service.go +++ b/pkg/services/datasources/fakes/fake_cache_service.go @@ -3,8 +3,8 @@ package datasources import ( "context" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" ) type FakeCacheService struct { @@ -13,7 +13,7 @@ type FakeCacheService struct { var _ datasources.CacheService = &FakeCacheService{} -func (c *FakeCacheService) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { +func (c *FakeCacheService) GetDatasource(ctx context.Context, datasourceID int64, user identity.Requester, skipCache bool) (*datasources.DataSource, error) { for _, datasource := range c.DataSources { if datasource.ID == datasourceID { return datasource, nil @@ -22,7 +22,7 @@ func (c *FakeCacheService) GetDatasource(ctx context.Context, datasourceID int64 return nil, datasources.ErrDataSourceNotFound } -func (c *FakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { +func (c *FakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, user identity.Requester, skipCache bool) (*datasources.DataSource, error) { for _, datasource := range c.DataSources { if datasource.UID == datasourceUID { return datasource, nil diff --git a/pkg/services/datasources/guardian/provider.go b/pkg/services/datasources/guardian/provider.go index 5be645ae4a1..d2cd4d529ac 100644 --- a/pkg/services/datasources/guardian/provider.go +++ b/pkg/services/datasources/guardian/provider.go @@ -1,12 +1,12 @@ package guardian import ( + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" ) type DatasourceGuardianProvider interface { - New(orgID int64, user *user.SignedInUser, dataSources ...datasources.DataSource) DatasourceGuardian + New(orgID int64, user identity.Requester, dataSources ...datasources.DataSource) DatasourceGuardian } type DatasourceGuardian interface { @@ -20,6 +20,6 @@ func ProvideGuardian() *OSSProvider { type OSSProvider struct{} -func (p *OSSProvider) New(orgID int64, user *user.SignedInUser, dataSources ...datasources.DataSource) DatasourceGuardian { +func (p *OSSProvider) New(orgID int64, user identity.Requester, dataSources ...datasources.DataSource) DatasourceGuardian { return &AllowGuardian{} } diff --git a/pkg/services/datasources/service/cache.go b/pkg/services/datasources/service/cache.go index a97b9cbcadc..bb56d0fd8e3 100644 --- a/pkg/services/datasources/service/cache.go +++ b/pkg/services/datasources/service/cache.go @@ -8,9 +8,9 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources/guardian" - "github.com/grafana/grafana/pkg/services/user" ) const ( @@ -38,7 +38,7 @@ type CacheServiceImpl struct { func (dc *CacheServiceImpl) GetDatasource( ctx context.Context, datasourceID int64, - user *user.SignedInUser, + user identity.Requester, skipCache bool, ) (*datasources.DataSource, error) { cacheKey := idKey(datasourceID) @@ -46,7 +46,7 @@ func (dc *CacheServiceImpl) GetDatasource( if !skipCache { if cached, found := dc.CacheService.Get(cacheKey); found { ds := cached.(*datasources.DataSource) - if ds.OrgID == user.OrgID { + if ds.OrgID == user.GetOrgID() { if err := dc.canQuery(user, ds); err != nil { return nil, err } @@ -55,9 +55,9 @@ func (dc *CacheServiceImpl) GetDatasource( } } - dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgID) + dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.GetOrgID()) - query := &datasources.GetDataSourceQuery{ID: datasourceID, OrgID: user.OrgID} + query := &datasources.GetDataSourceQuery{ID: datasourceID, OrgID: user.GetOrgID()} ss := SqlStore{db: dc.SQLStore, logger: dc.logger} ds, err := ss.GetDataSource(ctx, query) if err != nil { @@ -79,21 +79,21 @@ func (dc *CacheServiceImpl) GetDatasource( func (dc *CacheServiceImpl) GetDatasourceByUID( ctx context.Context, datasourceUID string, - user *user.SignedInUser, + user identity.Requester, skipCache bool, ) (*datasources.DataSource, error) { if datasourceUID == "" { return nil, fmt.Errorf("can not get data source by uid, uid is empty") } - if user.OrgID == 0 { + if user.GetOrgID() == 0 { return nil, fmt.Errorf("can not get data source by uid, orgId is missing") } - uidCacheKey := uidKey(user.OrgID, datasourceUID) + uidCacheKey := uidKey(user.GetOrgID(), datasourceUID) if !skipCache { if cached, found := dc.CacheService.Get(uidCacheKey); found { ds := cached.(*datasources.DataSource) - if ds.OrgID == user.OrgID { + if ds.OrgID == user.GetOrgID() { if err := dc.canQuery(user, ds); err != nil { return nil, err } @@ -102,8 +102,8 @@ func (dc *CacheServiceImpl) GetDatasourceByUID( } } - dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.OrgID) - query := &datasources.GetDataSourceQuery{UID: datasourceUID, OrgID: user.OrgID} + dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.GetOrgID()) + query := &datasources.GetDataSourceQuery{UID: datasourceUID, OrgID: user.GetOrgID()} ss := SqlStore{db: dc.SQLStore, logger: dc.logger} ds, err := ss.GetDataSource(ctx, query) if err != nil { @@ -128,8 +128,8 @@ func uidKey(orgID int64, uid string) string { return fmt.Sprintf("ds-orgid-uid-%d-%s", orgID, uid) } -func (dc *CacheServiceImpl) canQuery(user *user.SignedInUser, ds *datasources.DataSource) error { - guardian := dc.dsGuardian.New(user.OrgID, user, *ds) +func (dc *CacheServiceImpl) canQuery(user identity.Requester, ds *datasources.DataSource) error { + guardian := dc.dsGuardian.New(user.GetOrgID(), user, *ds) if canQuery, err := guardian.CanQuery(ds.ID); err != nil || !canQuery { if err != nil { return err diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 98193ab0d9f..acd06f4ee98 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -280,7 +281,16 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( dashFolder.SetUID(trimmedUID) user := cmd.SignedInUser - userID := user.UserID + namespaceID, userIDstr := user.GetNamespacedID() + if namespaceID == identity.NamespaceAPIKey { + s.log.Warn("namespace API key detected, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr) + userIDstr = "0" + } + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + if err != nil { + s.log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", userIDstr, "error", err) + } + if userID == 0 { userID = -1 } @@ -763,12 +773,22 @@ func (s *Service) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards } } + namespaceID, userIDstr := dto.User.GetNamespacedID() + if namespaceID == identity.NamespaceAPIKey { + s.log.Warn("namespace API key detected, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr) + userIDstr = "0" + } + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + if err != nil { + s.log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", userIDstr, "error", err) + } + cmd := &dashboards.SaveDashboardCommand{ Dashboard: dash.Data, Message: dto.Message, OrgID: dto.OrgID, Overwrite: dto.Overwrite, - UserID: dto.User.UserID, + UserID: userID, FolderID: dash.FolderID, IsFolder: dash.IsFolder, PluginID: dash.PluginID, @@ -783,7 +803,7 @@ func (s *Service) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards // getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard // It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck() -func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user *user.SignedInUser) (guardian.DashboardGuardian, error) { +func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) { newDashboard := d.ID == 0 if newDashboard { diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index cc157c3d05a..630a5ae5f97 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -5,6 +5,7 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/slugify" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" @@ -138,6 +139,7 @@ type GetFolderQuery struct { OrgID int64 SignedInUser *user.SignedInUser `json:"-"` + Requester identity.Requester `json:"-"` } // GetParentsQuery captures the information required by the folder service to diff --git a/pkg/services/guardian/accesscontrol_guardian.go b/pkg/services/guardian/accesscontrol_guardian.go index b63371abddf..0378d0d12b6 100644 --- a/pkg/services/guardian/accesscontrol_guardian.go +++ b/pkg/services/guardian/accesscontrol_guardian.go @@ -6,9 +6,9 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -16,14 +16,14 @@ var _ DashboardGuardian = new(accessControlDashboardGuardian) // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId. func NewAccessControlDashboardGuardian( - ctx context.Context, cfg *setting.Cfg, dashboardId int64, user *user.SignedInUser, + ctx context.Context, cfg *setting.Cfg, dashboardId int64, user identity.Requester, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) (DashboardGuardian, error) { var dashboard *dashboards.Dashboard if dashboardId != 0 { q := &dashboards.GetDashboardQuery{ ID: dashboardId, - OrgID: user.OrgID, + OrgID: user.GetOrgID(), } qResult, err := dashboardService.GetDashboard(ctx, q) @@ -65,14 +65,14 @@ func NewAccessControlDashboardGuardian( // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID. func NewAccessControlDashboardGuardianByUID( - ctx context.Context, cfg *setting.Cfg, dashboardUID string, user *user.SignedInUser, + ctx context.Context, cfg *setting.Cfg, dashboardUID string, user identity.Requester, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) (DashboardGuardian, error) { var dashboard *dashboards.Dashboard if dashboardUID != "" { q := &dashboards.GetDashboardQuery{ UID: dashboardUID, - OrgID: user.OrgID, + OrgID: user.GetOrgID(), } qResult, err := dashboardService.GetDashboard(ctx, q) @@ -116,7 +116,7 @@ func NewAccessControlDashboardGuardianByUID( // This constructor should be preferred over the other two if the dashboard in available // since it avoids querying the database for fetching the dashboard. func NewAccessControlDashboardGuardianByDashboard( - ctx context.Context, cfg *setting.Cfg, dashboard *dashboards.Dashboard, user *user.SignedInUser, + ctx context.Context, cfg *setting.Cfg, dashboard *dashboards.Dashboard, user identity.Requester, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) (DashboardGuardian, error) { if dashboard != nil && dashboard.IsFolder { @@ -148,7 +148,7 @@ func NewAccessControlDashboardGuardianByDashboard( // NewAccessControlFolderGuardian creates a folder guardian by the provided folder. func NewAccessControlFolderGuardian( - ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user *user.SignedInUser, + ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user identity.Requester, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) (DashboardGuardian, error) { return &accessControlFolderGuardian{ @@ -168,7 +168,7 @@ type accessControlBaseGuardian struct { cfg *setting.Cfg ctx context.Context log log.Logger - user *user.SignedInUser + user identity.Requester ac accesscontrol.AccessControl dashboardService dashboards.DashboardService } @@ -309,12 +309,13 @@ func (a *accessControlFolderGuardian) CanCreate(folderID int64, isFolder bool) ( func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) + namespaceID, userID := a.user.GetNamespacedID() if err != nil { id := 0 if a.dashboard != nil { id = int(a.dashboard.ID) } - a.log.Debug("Failed to evaluate access control to dashboard", "error", err, "userId", a.user.UserID, "id", id) + a.log.Debug("Failed to evaluate access control to dashboard", "error", err, "namespaceID", namespaceID, "userId", userID, "id", id) } if !ok && err == nil { @@ -322,7 +323,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua if a.dashboard != nil { id = int(a.dashboard.ID) } - a.log.Debug("Access denied to dashboard", "userId", a.user.UserID, "id", id, "permissions", evaluator.GoString()) + a.log.Debug("Access denied to dashboard", "namespaceID", namespaceID, "userId", userID, "id", id, "permissions", evaluator.GoString()) } return ok, err @@ -330,6 +331,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) + namespaceID, userID := a.user.GetNamespacedID() if err != nil { uid := "" orgID := 0 @@ -337,7 +339,7 @@ func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator uid = a.folder.UID orgID = int(a.folder.OrgID) } - a.log.Debug("Failed to evaluate access control to folder", "error", err, "userId", a.user.UserID, "orgID", orgID, "uid", uid) + a.log.Debug("Failed to evaluate access control to folder", "error", err, "namespaceID", namespaceID, "userId", userID, "orgID", orgID, "uid", uid) } if !ok && err == nil { @@ -347,7 +349,7 @@ func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator uid = a.folder.UID orgID = int(a.folder.OrgID) } - a.log.Debug("Access denied to folder", "userId", a.user.UserID, "orgID", orgID, "uid", uid, "permissions", evaluator.GoString()) + a.log.Debug("Access denied to folder", "namespaceID", namespaceID, "userId", userID, "orgID", orgID, "uid", uid, "permissions", evaluator.GoString()) } return ok, err @@ -357,7 +359,7 @@ func (a *accessControlDashboardGuardian) loadParentFolder(folderID int64) (*dash if folderID == 0 { return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil } - folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.OrgID} + folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.GetOrgID()} folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery) if err != nil { return nil, err @@ -369,7 +371,7 @@ func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*dashboa if folderID == 0 { return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil } - folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.OrgID} + folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.GetOrgID()} folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery) if err != nil { return nil, err diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 9c506052f6e..79578025388 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -3,9 +3,9 @@ package guardian import ( "context" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util/errutil" ) @@ -27,25 +27,25 @@ type DashboardGuardian interface { // New factory for creating a new dashboard guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var New = func(ctx context.Context, dashId int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { +var New = func(ctx context.Context, dashId int64, orgId int64, user identity.Requester) (DashboardGuardian, error) { panic("no guardian factory implementation provided") } // NewByUID factory for creating a new dashboard guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var NewByUID = func(ctx context.Context, dashUID string, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { +var NewByUID = func(ctx context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { panic("no guardian factory implementation provided") } // NewByDashboard factory for creating a new dashboard guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { +var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { panic("no guardian factory implementation provided") } // NewByFolder factory for creating a new folder guardian instance // When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned -var NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { +var NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { panic("no guardian factory implementation provided") } @@ -54,7 +54,7 @@ type FakeDashboardGuardian struct { DashID int64 DashUID string OrgID int64 - User *user.SignedInUser + User identity.Requester CanSaveValue bool CanEditValue bool CanViewValue bool @@ -87,21 +87,21 @@ func (g *FakeDashboardGuardian) CanCreate(_ int64, _ bool) (bool, error) { // nolint:unused func MockDashboardGuardian(mock *FakeDashboardGuardian) { - New = func(_ context.Context, dashID int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + New = func(_ context.Context, dashID int64, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashID = dashID mock.User = user return mock, nil } - NewByUID = func(_ context.Context, dashUID string, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByUID = func(_ context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashUID = dashUID mock.User = user return mock, nil } - NewByDashboard = func(_ context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByDashboard = func(_ context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashUID = dash.UID mock.DashID = dash.ID @@ -109,7 +109,7 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) { return mock, nil } - NewByFolder = func(_ context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByFolder = func(_ context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { mock.OrgID = orgId mock.DashUID = f.UID mock.DashID = f.ID diff --git a/pkg/services/guardian/provider.go b/pkg/services/guardian/provider.go index 0b4ba95def9..060f4b360a7 100644 --- a/pkg/services/guardian/provider.go +++ b/pkg/services/guardian/provider.go @@ -4,10 +4,10 @@ import ( "context" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/team" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -25,19 +25,19 @@ func ProvideService( func InitAccessControlGuardian( cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) { - New = func(ctx context.Context, dashId int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + New = func(ctx context.Context, dashId int64, orgId int64, user identity.Requester) (DashboardGuardian, error) { return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService) } - NewByUID = func(ctx context.Context, dashUID string, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByUID = func(ctx context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) { return NewAccessControlDashboardGuardianByUID(ctx, cfg, dashUID, user, ac, dashboardService) } - NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) { return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService) } - NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { + NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) { return NewAccessControlFolderGuardian(ctx, cfg, f, user, ac, dashboardService) } } diff --git a/pkg/services/live/features/broadcast.go b/pkg/services/live/features/broadcast.go index e85741056e2..51cdd46e2a4 100644 --- a/pkg/services/live/features/broadcast.go +++ b/pkg/services/live/features/broadcast.go @@ -6,8 +6,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/live/model" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -37,13 +37,13 @@ func (b *BroadcastRunner) GetHandlerForPath(_ string) (model.ChannelHandler, err } // OnSubscribe will let anyone connect to the path -func (b *BroadcastRunner) OnSubscribe(_ context.Context, u *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { +func (b *BroadcastRunner) OnSubscribe(_ context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { reply := model.SubscribeReply{ Presence: true, JoinLeave: true, } query := &model.GetLiveMessageQuery{ - OrgID: u.OrgID, + OrgID: u.GetOrgID(), Channel: e.Channel, } msg, ok, err := b.liveMessageStore.GetLiveMessage(query) @@ -57,9 +57,9 @@ func (b *BroadcastRunner) OnSubscribe(_ context.Context, u *user.SignedInUser, e } // OnPublish is called when a client wants to broadcast on the websocket -func (b *BroadcastRunner) OnPublish(_ context.Context, u *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { +func (b *BroadcastRunner) OnPublish(_ context.Context, u identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { query := &model.SaveLiveMessageQuery{ - OrgID: u.OrgID, + OrgID: u.GetOrgID(), Channel: e.Channel, Data: e.Data, } diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index d98b4ddf73d..0c9ff3915bf 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/live/model" @@ -52,7 +53,7 @@ func (h *DashboardHandler) GetHandlerForPath(_ string) (model.ChannelHandler, er } // OnSubscribe for now allows anyone to subscribe to any dashboard -func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { +func (h *DashboardHandler) OnSubscribe(ctx context.Context, user identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { parts := strings.Split(e.Path, "/") if parts[0] == "gitops" { // gitops gets all changes for everything, so lets make sure it is an admin user @@ -66,7 +67,7 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU // make sure can view this dashboard if len(parts) == 2 && parts[0] == "uid" { - query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.OrgID} + query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.GetOrgID()} queryResult, err := h.DashboardService.GetDashboard(ctx, &query) if err != nil { logger.Error("Error getting dashboard", "query", query, "error", err) @@ -74,7 +75,7 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU } dash := queryResult - guard, err := guardian.NewByDashboard(ctx, dash, user.OrgID, user) + guard, err := guardian.NewByDashboard(ctx, dash, user.GetOrgID(), user) if err != nil { return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err } @@ -94,11 +95,11 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU } // OnPublish is called when someone begins to edit a dashboard -func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { +func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { parts := strings.Split(e.Path, "/") if parts[0] == "gitops" { // gitops gets all changes for everything, so lets make sure it is an admin user - if !user.HasRole(org.RoleAdmin) { + if !requester.HasRole(org.RoleAdmin) { return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil } @@ -117,14 +118,14 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUse // just ignore the event return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???") } - query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.OrgID} + query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: requester.GetOrgID()} queryResult, err := h.DashboardService.GetDashboard(ctx, &query) if err != nil { logger.Error("Unknown dashboard", "query", query) return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil } - guard, err := guardian.NewByDashboard(ctx, queryResult, user.OrgID, user) + guard, err := guardian.NewByDashboard(ctx, queryResult, requester.GetOrgID(), requester) if err != nil { logger.Error("Failed to create guardian", "err", err) return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error") @@ -141,7 +142,10 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUse } // Tell everyone who is editing - event.User = user.ToUserDisplayDTO() + event.User, err = user.NewUserDisplayDTOFromRequester(requester) + if err != nil { + return model.PublishReply{}, backend.PublishStreamStatusNotFound, err + } msg, err := json.Marshal(event) if err != nil { diff --git a/pkg/services/live/features/plugin.go b/pkg/services/live/features/plugin.go index aa8ad33249d..373f6eaf51d 100644 --- a/pkg/services/live/features/plugin.go +++ b/pkg/services/live/features/plugin.go @@ -7,17 +7,17 @@ import ( "github.com/centrifugal/centrifuge" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/live/model" "github.com/grafana/grafana/pkg/services/live/orgchannel" "github.com/grafana/grafana/pkg/services/live/runstream" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/user" ) //go:generate mockgen -destination=plugin_mock.go -package=features github.com/grafana/grafana/pkg/services/live/features PluginContextGetter type PluginContextGetter interface { - GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) + GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) } // PluginRunner can handle streaming operations for channels belonging to plugins. @@ -63,7 +63,7 @@ type PluginPathRunner struct { } // OnSubscribe passes control to a plugin. -func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { +func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false) if err != nil { if errors.Is(err, plugincontext.ErrPluginNotFound) { @@ -86,7 +86,7 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInU return model.SubscribeReply{}, resp.Status, nil } - submitResult, err := r.runStreamManager.SubmitStream(ctx, user, orgchannel.PrependOrgID(user.OrgID, e.Channel), r.path, e.Data, pCtx, r.handler, false) + submitResult, err := r.runStreamManager.SubmitStream(ctx, user, orgchannel.PrependOrgID(user.GetOrgID(), e.Channel), r.path, e.Data, pCtx, r.handler, false) if err != nil { logger.Error("Error submitting stream to manager", "error", err, "path", r.path) return model.SubscribeReply{}, 0, centrifuge.ErrorInternal @@ -107,7 +107,7 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInU } // OnPublish passes control to a plugin. -func (r *PluginPathRunner) OnPublish(ctx context.Context, user *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { +func (r *PluginPathRunner) OnPublish(ctx context.Context, user identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false) if err != nil { if errors.Is(err, plugincontext.ErrPluginNotFound) { diff --git a/pkg/services/live/features/plugin_mock.go b/pkg/services/live/features/plugin_mock.go index 969dbb7a469..53ec2baa529 100644 --- a/pkg/services/live/features/plugin_mock.go +++ b/pkg/services/live/features/plugin_mock.go @@ -10,7 +10,7 @@ import ( gomock "github.com/golang/mock/gomock" backend "github.com/grafana/grafana-plugin-sdk-go/backend" - user "github.com/grafana/grafana/pkg/services/user" + identity "github.com/grafana/grafana/pkg/services/auth/identity" ) // MockPluginContextGetter is a mock of PluginContextGetter interface. @@ -37,7 +37,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder } // GetPluginContext mocks base method. -func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) { +func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 identity.Requester, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(backend.PluginContext) diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index f035acfca8a..591e17c9ca1 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -32,6 +32,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/annotations" + "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" @@ -299,10 +300,15 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r g.websocketHandler = func(ctx *contextmodel.ReqContext) { user := ctx.SignedInUser + namespaceID, userID := user.GetNamespacedID() + + if namespaceID != identity.NamespaceUser { + return // Only users can connect to Live + } // Centrifuge expects Credentials in context with a current user ID. cred := ¢rifuge.Credentials{ - UserID: fmt.Sprintf("%d", user.UserID), + UserID: userID, } newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred) newCtx = livecontext.SetContextSignedUser(newCtx, user) @@ -593,7 +599,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge. return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal } - if user.OrgID != orgID { + if user.GetOrgID() != orgID { logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) return centrifuge.SubscribeReply{}, centrifuge.ErrorPermissionDenied } @@ -603,7 +609,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge. var ruleFound bool if g.Pipeline != nil { - rule, ok, err := g.Pipeline.Get(user.OrgID, channel) + rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel) if err != nil { logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal @@ -694,13 +700,13 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl return centrifuge.PublishReply{}, centrifuge.ErrorInternal } - if user.OrgID != orgID { + if user.GetOrgID() != orgID { logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) return centrifuge.PublishReply{}, centrifuge.ErrorPermissionDenied } if g.Pipeline != nil { - rule, ok, err := g.Pipeline.Get(user.OrgID, channel) + rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel) if err != nil { logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) return centrifuge.PublishReply{}, centrifuge.ErrorInternal @@ -724,7 +730,7 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl return centrifuge.PublishReply{}, ¢rifuge.Error{Code: uint32(code), Message: text} } } - _, err := g.Pipeline.ProcessInput(client.Context(), user.OrgID, channel, e.Data) + _, err := g.Pipeline.ProcessInput(client.Context(), user.GetOrgID(), channel, e.Data) if err != nil { logger.Error("Error processing input", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) return centrifuge.PublishReply{}, centrifuge.ErrorInternal @@ -805,7 +811,7 @@ func publishStatusToHTTPError(status backend.PublishStreamStatus) (int, string) } // GetChannelHandler gives thread-safe access to the channel. -func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user *user.SignedInUser, channel string) (model.ChannelHandler, live.Channel, error) { +func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user identity.Requester, channel string) (model.ChannelHandler, live.Channel, error) { // Parse the identifier ${scope}/${namespace}/${path} addr, err := live.ParseChannel(channel) if err != nil { @@ -846,7 +852,7 @@ func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user *user.SignedIn // GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace. // It gives thread-safe access to the channel. -func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user *user.SignedInUser, scope string, namespace string) (model.ChannelHandlerFactory, error) { +func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user identity.Requester, scope string, namespace string) (model.ChannelHandlerFactory, error) { switch scope { case live.ScopeGrafana: return g.handleGrafanaScope(user, namespace) @@ -861,14 +867,14 @@ func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user *user.S } } -func (g *GrafanaLive) handleGrafanaScope(_ *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) { +func (g *GrafanaLive) handleGrafanaScope(_ identity.Requester, namespace string) (model.ChannelHandlerFactory, error) { if p, ok := g.GrafanaScope.Features[namespace]; ok { return p, nil } return nil, fmt.Errorf("unknown feature: %q", namespace) } -func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) { +func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ identity.Requester, namespace string) (model.ChannelHandlerFactory, error) { streamHandler, err := g.getStreamPlugin(ctx, namespace) if err != nil { return nil, fmt.Errorf("can't find stream plugin: %s", namespace) @@ -882,11 +888,11 @@ func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ *user.SignedInUse ), nil } -func (g *GrafanaLive) handleStreamScope(u *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) { - return g.ManagedStreamRunner.GetOrCreateStream(u.OrgID, live.ScopeStream, namespace) +func (g *GrafanaLive) handleStreamScope(u identity.Requester, namespace string) (model.ChannelHandlerFactory, error) { + return g.ManagedStreamRunner.GetOrCreateStream(u.GetOrgID(), live.ScopeStream, namespace) } -func (g *GrafanaLive) handleDatasourceScope(ctx context.Context, user *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) { +func (g *GrafanaLive) handleDatasourceScope(ctx context.Context, user identity.Requester, namespace string) (model.ChannelHandlerFactory, error) { ds, err := g.DataSourceCache.GetDatasourceByUID(ctx, namespace, user, false) if err != nil { return nil, fmt.Errorf("error getting datasource: %w", err) @@ -929,12 +935,13 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R return response.Error(http.StatusBadRequest, "invalid channel ID", nil) } - logger.Debug("Publish API cmd", "user", ctx.SignedInUser.UserID, "channel", cmd.Channel) + namespaceID, userID := ctx.SignedInUser.GetNamespacedID() + logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel) user := ctx.SignedInUser channel := cmd.Channel if g.Pipeline != nil { - rule, ok, err := g.Pipeline.Get(user.OrgID, channel) + rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel) if err != nil { logger.Error("Error getting channel rule", "user", user, "channel", channel, "error", err) return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) @@ -954,7 +961,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R return response.Error(http.StatusForbidden, http.StatusText(http.StatusForbidden), nil) } } - _, err := g.Pipeline.ProcessInput(ctx.Req.Context(), user.OrgID, channel, cmd.Data) + _, err := g.Pipeline.ProcessInput(ctx.Req.Context(), user.GetOrgID(), channel, cmd.Data) if err != nil { logger.Error("Error processing input", "user", user, "channel", channel, "error", err) return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) @@ -980,13 +987,13 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R return response.Error(code, text, nil) } if reply.Data != nil { - err = g.Publish(ctx.OrgID, cmd.Channel, cmd.Data) + err = g.Publish(ctx.SignedInUser.GetOrgID(), cmd.Channel, cmd.Data) if err != nil { logger.Error("Error publish to channel", "error", err, "channel", cmd.Channel) return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) } } - logger.Debug("Publication successful", "user", ctx.SignedInUser.UserID, "channel", cmd.Channel) + logger.Debug("Publication successful", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel) return response.JSON(http.StatusOK, dtos.LivePublishResponse{}) } @@ -999,9 +1006,9 @@ func (g *GrafanaLive) HandleListHTTP(c *contextmodel.ReqContext) response.Respon var channels []*managedstream.ManagedChannel var err error if g.IsHA() { - channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.OrgID) + channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.GetOrgID()) } else { - channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.OrgID) + channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.GetOrgID()) } if err != nil { return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err) @@ -1017,7 +1024,7 @@ func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Resp path := web.Params(ctx.Req)["*"] if path == "grafana/dashboards/gitops" { return response.JSON(http.StatusOK, util.DynMap{ - "active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.SignedInUser.OrgID), + "active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.SignedInUser.GetOrgID()), }) } return response.JSONStreaming(404, util.DynMap{ @@ -1027,7 +1034,7 @@ func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Resp // HandleChannelRulesListHTTP ... func (g *GrafanaLive) HandleChannelRulesListHTTP(c *contextmodel.ReqContext) response.Response { - result, err := g.pipelineStorage.ListChannelRules(c.Req.Context(), c.OrgID) + result, err := g.pipelineStorage.ListChannelRules(c.Req.Context(), c.SignedInUser.GetOrgID()) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to get channel rules", err) } @@ -1112,7 +1119,7 @@ func (g *GrafanaLive) HandlePipelineConvertTestHTTP(c *contextmodel.ReqContext) if err != nil { return response.Error(http.StatusInternalServerError, "Error creating pipeline", err) } - rule, ok, err := channelRuleGetter.Get(c.OrgID, req.Channel) + rule, ok, err := channelRuleGetter.Get(c.SignedInUser.GetOrgID(), req.Channel) if err != nil { return response.Error(http.StatusInternalServerError, "Error getting channel rule", err) } @@ -1122,7 +1129,7 @@ func (g *GrafanaLive) HandlePipelineConvertTestHTTP(c *contextmodel.ReqContext) if rule.Converter == nil { return response.Error(http.StatusNotFound, "No converter found", nil) } - channelFrames, err := pipe.DataToChannelFrames(c.Req.Context(), *rule, c.OrgID, req.Channel, []byte(req.Data)) + channelFrames, err := pipe.DataToChannelFrames(c.Req.Context(), *rule, c.SignedInUser.GetOrgID(), req.Channel, []byte(req.Data)) if err != nil { return response.Error(http.StatusInternalServerError, "Error converting data", err) } @@ -1142,7 +1149,7 @@ func (g *GrafanaLive) HandleChannelRulesPostHTTP(c *contextmodel.ReqContext) res if err != nil { return response.Error(http.StatusBadRequest, "Error decoding channel rule", err) } - rule, err := g.pipelineStorage.CreateChannelRule(c.Req.Context(), c.OrgID, cmd) + rule, err := g.pipelineStorage.CreateChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to create channel rule", err) } @@ -1165,7 +1172,7 @@ func (g *GrafanaLive) HandleChannelRulesPutHTTP(c *contextmodel.ReqContext) resp if cmd.Pattern == "" { return response.Error(http.StatusBadRequest, "Rule pattern required", nil) } - rule, err := g.pipelineStorage.UpdateChannelRule(c.Req.Context(), c.OrgID, cmd) + rule, err := g.pipelineStorage.UpdateChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to update channel rule", err) } @@ -1188,7 +1195,7 @@ func (g *GrafanaLive) HandleChannelRulesDeleteHTTP(c *contextmodel.ReqContext) r if cmd.Pattern == "" { return response.Error(http.StatusBadRequest, "Rule pattern required", nil) } - err = g.pipelineStorage.DeleteChannelRule(c.Req.Context(), c.OrgID, cmd) + err = g.pipelineStorage.DeleteChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to delete channel rule", err) } @@ -1208,7 +1215,7 @@ func (g *GrafanaLive) HandlePipelineEntitiesListHTTP(_ *contextmodel.ReqContext) // HandleWriteConfigsListHTTP ... func (g *GrafanaLive) HandleWriteConfigsListHTTP(c *contextmodel.ReqContext) response.Response { - backends, err := g.pipelineStorage.ListWriteConfigs(c.Req.Context(), c.OrgID) + backends, err := g.pipelineStorage.ListWriteConfigs(c.Req.Context(), c.SignedInUser.GetOrgID()) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to get write configs", err) } @@ -1232,7 +1239,7 @@ func (g *GrafanaLive) HandleWriteConfigsPostHTTP(c *contextmodel.ReqContext) res if err != nil { return response.Error(http.StatusBadRequest, "Error decoding write config create command", err) } - result, err := g.pipelineStorage.CreateWriteConfig(c.Req.Context(), c.OrgID, cmd) + result, err := g.pipelineStorage.CreateWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to create write config", err) } @@ -1255,7 +1262,7 @@ func (g *GrafanaLive) HandleWriteConfigsPutHTTP(c *contextmodel.ReqContext) resp if cmd.UID == "" { return response.Error(http.StatusBadRequest, "UID required", nil) } - existingBackend, ok, err := g.pipelineStorage.GetWriteConfig(c.Req.Context(), c.OrgID, pipeline.WriteConfigGetCmd{ + existingBackend, ok, err := g.pipelineStorage.GetWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), pipeline.WriteConfigGetCmd{ UID: cmd.UID, }) if err != nil { @@ -1276,7 +1283,7 @@ func (g *GrafanaLive) HandleWriteConfigsPutHTTP(c *contextmodel.ReqContext) resp } } } - result, err := g.pipelineStorage.UpdateWriteConfig(c.Req.Context(), c.OrgID, cmd) + result, err := g.pipelineStorage.UpdateWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to update write config", err) } @@ -1299,7 +1306,7 @@ func (g *GrafanaLive) HandleWriteConfigsDeleteHTTP(c *contextmodel.ReqContext) r if cmd.UID == "" { return response.Error(http.StatusBadRequest, "UID required", nil) } - err = g.pipelineStorage.DeleteWriteConfig(c.Req.Context(), c.OrgID, cmd) + err = g.pipelineStorage.DeleteWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to delete write config", err) } diff --git a/pkg/services/live/livecontext/context.go b/pkg/services/live/livecontext/context.go index c2ee0c61bc1..0b4df1ef451 100644 --- a/pkg/services/live/livecontext/context.go +++ b/pkg/services/live/livecontext/context.go @@ -3,21 +3,21 @@ package livecontext import ( "context" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) type signedUserContextKeyType int var signedUserContextKey signedUserContextKeyType -func SetContextSignedUser(ctx context.Context, user *user.SignedInUser) context.Context { +func SetContextSignedUser(ctx context.Context, user identity.Requester) context.Context { ctx = context.WithValue(ctx, signedUserContextKey, user) return ctx } -func GetContextSignedUser(ctx context.Context) (*user.SignedInUser, bool) { +func GetContextSignedUser(ctx context.Context) (identity.Requester, bool) { if val := ctx.Value(signedUserContextKey); val != nil { - user, ok := val.(*user.SignedInUser) + user, ok := val.(identity.Requester) return user, ok } return nil, false diff --git a/pkg/services/live/liveplugin/plugin.go b/pkg/services/live/liveplugin/plugin.go index 8a9a9a35c99..17dc25ecaa7 100644 --- a/pkg/services/live/liveplugin/plugin.go +++ b/pkg/services/live/liveplugin/plugin.go @@ -7,11 +7,11 @@ import ( "github.com/centrifugal/centrifuge" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/live/orgchannel" "github.com/grafana/grafana/pkg/services/live/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/user" ) type ChannelLocalPublisher struct { @@ -72,9 +72,9 @@ func NewContextGetter(pluginContextProvider *plugincontext.Provider, dataSourceC } } -func (g *ContextGetter) GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) { +func (g *ContextGetter) GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) { if datasourceUID == "" { - return g.pluginContextProvider.Get(ctx, pluginID, user, user.OrgID) + return g.pluginContextProvider.Get(ctx, pluginID, user, user.GetOrgID()) } ds, err := g.dataSourceCache.GetDatasourceByUID(ctx, datasourceUID, user, skipCache) diff --git a/pkg/services/live/managedstream/runner.go b/pkg/services/live/managedstream/runner.go index b9fd21ad5e6..393a261dd5e 100644 --- a/pkg/services/live/managedstream/runner.go +++ b/pkg/services/live/managedstream/runner.go @@ -13,9 +13,9 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/live" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/live/model" "github.com/grafana/grafana/pkg/services/live/orgchannel" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -239,9 +239,9 @@ func (s *NamespaceStream) GetHandlerForPath(_ string) (model.ChannelHandler, err return s, nil } -func (s *NamespaceStream) OnSubscribe(ctx context.Context, u *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { +func (s *NamespaceStream) OnSubscribe(ctx context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { reply := model.SubscribeReply{} - frameJSON, ok, err := s.frameCache.GetFrame(ctx, u.OrgID, e.Channel) + frameJSON, ok, err := s.frameCache.GetFrame(ctx, u.GetOrgID(), e.Channel) if err != nil { return reply, 0, err } @@ -251,6 +251,6 @@ func (s *NamespaceStream) OnSubscribe(ctx context.Context, u *user.SignedInUser, return reply, backend.SubscribeStreamStatusOK, nil } -func (s *NamespaceStream) OnPublish(_ context.Context, _ *user.SignedInUser, _ model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { +func (s *NamespaceStream) OnPublish(_ context.Context, _ identity.Requester, _ model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil } diff --git a/pkg/services/live/model/model.go b/pkg/services/live/model/model.go index 612650fae11..b6b58733204 100644 --- a/pkg/services/live/model/model.go +++ b/pkg/services/live/model/model.go @@ -7,7 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) // ChannelPublisher writes data into a channel. Note that permissions are not checked. @@ -54,10 +54,10 @@ type PublishReply struct { // ChannelHandler defines the core channel behavior type ChannelHandler interface { // OnSubscribe is called when a client wants to subscribe to a channel - OnSubscribe(ctx context.Context, user *user.SignedInUser, e SubscribeEvent) (SubscribeReply, backend.SubscribeStreamStatus, error) + OnSubscribe(ctx context.Context, user identity.Requester, e SubscribeEvent) (SubscribeReply, backend.SubscribeStreamStatus, error) // OnPublish is called when a client writes a message to the channel websocket. - OnPublish(ctx context.Context, user *user.SignedInUser, e PublishEvent) (PublishReply, backend.PublishStreamStatus, error) + OnPublish(ctx context.Context, user identity.Requester, e PublishEvent) (PublishReply, backend.PublishStreamStatus, error) } // ChannelHandlerFactory should be implemented by all core features. diff --git a/pkg/services/live/pipeline/auth.go b/pkg/services/live/pipeline/auth.go index f24bdcff6aa..8d6e3efec6a 100644 --- a/pkg/services/live/pipeline/auth.go +++ b/pkg/services/live/pipeline/auth.go @@ -3,8 +3,8 @@ package pipeline import ( "context" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/user" ) type RoleCheckAuthorizer struct { @@ -15,10 +15,10 @@ func NewRoleCheckAuthorizer(role org.RoleType) *RoleCheckAuthorizer { return &RoleCheckAuthorizer{role: role} } -func (s *RoleCheckAuthorizer) CanSubscribe(_ context.Context, u *user.SignedInUser) (bool, error) { +func (s *RoleCheckAuthorizer) CanSubscribe(_ context.Context, u identity.Requester) (bool, error) { return u.HasRole(s.role), nil } -func (s *RoleCheckAuthorizer) CanPublish(_ context.Context, u *user.SignedInUser) (bool, error) { +func (s *RoleCheckAuthorizer) CanPublish(_ context.Context, u identity.Requester) (bool, error) { return u.HasRole(s.role), nil } diff --git a/pkg/services/live/pipeline/pipeline.go b/pkg/services/live/pipeline/pipeline.go index 2d4fb8ffcf2..281f9fab345 100644 --- a/pkg/services/live/pipeline/pipeline.go +++ b/pkg/services/live/pipeline/pipeline.go @@ -17,8 +17,8 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/live/model" - "github.com/grafana/grafana/pkg/services/user" ) const ( @@ -113,12 +113,12 @@ type Subscriber interface { // PublishAuthChecker checks whether current user can publish to a channel. type PublishAuthChecker interface { - CanPublish(ctx context.Context, u *user.SignedInUser) (bool, error) + CanPublish(ctx context.Context, u identity.Requester) (bool, error) } // SubscribeAuthChecker checks whether current user can subscribe to a channel. type SubscribeAuthChecker interface { - CanSubscribe(ctx context.Context, u *user.SignedInUser) (bool, error) + CanSubscribe(ctx context.Context, u identity.Requester) (bool, error) } // LiveChannelRule is an in-memory representation of each specific rule to be executed by Pipeline. diff --git a/pkg/services/live/pipeline/subscribe_builtin.go b/pkg/services/live/pipeline/subscribe_builtin.go index 14e35488ad4..f594f8d393d 100644 --- a/pkg/services/live/pipeline/subscribe_builtin.go +++ b/pkg/services/live/pipeline/subscribe_builtin.go @@ -6,9 +6,9 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/live" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/services/live/model" - "github.com/grafana/grafana/pkg/services/user" ) type BuiltinSubscriber struct { @@ -16,7 +16,7 @@ type BuiltinSubscriber struct { } type ChannelHandlerGetter interface { - GetChannelHandler(ctx context.Context, user *user.SignedInUser, channel string) (model.ChannelHandler, live.Channel, error) + GetChannelHandler(ctx context.Context, user identity.Requester, channel string) (model.ChannelHandler, live.Channel, error) } const SubscriberTypeBuiltin = "builtin" diff --git a/pkg/services/live/pushws/push_pipeline.go b/pkg/services/live/pushws/push_pipeline.go index c96251193c9..0005318bebd 100644 --- a/pkg/services/live/pushws/push_pipeline.go +++ b/pkg/services/live/pushws/push_pipeline.go @@ -71,7 +71,7 @@ func (s *PipelinePushHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) "bodyLength", len(body), ) - ruleFound, err := s.pipeline.ProcessInput(r.Context(), user.OrgID, channelID, body) + ruleFound, err := s.pipeline.ProcessInput(r.Context(), user.GetOrgID(), channelID, body) if err != nil { logger.Error("Pipeline input processing error", "error", err, "body", string(body)) return diff --git a/pkg/services/live/pushws/push_stream.go b/pkg/services/live/pushws/push_stream.go index 2a9a42a781e..12f6adf309d 100644 --- a/pkg/services/live/pushws/push_stream.go +++ b/pkg/services/live/pushws/push_stream.go @@ -67,7 +67,7 @@ func (s *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { break } - stream, err := s.managedStreamRunner.GetOrCreateStream(user.OrgID, liveDto.ScopeStream, streamID) + stream, err := s.managedStreamRunner.GetOrCreateStream(user.GetOrgID(), liveDto.ScopeStream, streamID) if err != nil { logger.Error("Error getting stream", "error", err) continue diff --git a/pkg/services/live/runstream/manager.go b/pkg/services/live/runstream/manager.go index fc52e9ee1cb..229e0601850 100644 --- a/pkg/services/live/runstream/manager.go +++ b/pkg/services/live/runstream/manager.go @@ -11,8 +11,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -26,7 +26,7 @@ type ChannelLocalPublisher interface { } type PluginContextGetter interface { - GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) + GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) } type NumLocalSubscribersGetter interface { @@ -374,7 +374,7 @@ func (s *Manager) Run(ctx context.Context) error { type streamRequest struct { Channel string Path string - user *user.SignedInUser + user identity.Requester PluginContext backend.PluginContext StreamRunner StreamRunner Data []byte @@ -401,7 +401,7 @@ var errDatasourceNotFound = errors.New("datasource not found") // SubmitStream submits stream handler in Manager to manage. // The stream will be opened and kept till channel has active subscribers. -func (s *Manager) SubmitStream(ctx context.Context, user *user.SignedInUser, channel string, path string, data []byte, pCtx backend.PluginContext, streamRunner StreamRunner, isResubmit bool) (*submitResult, error) { +func (s *Manager) SubmitStream(ctx context.Context, user identity.Requester, channel string, path string, data []byte, pCtx backend.PluginContext, streamRunner StreamRunner, isResubmit bool) (*submitResult, error) { if isResubmit { // Resolve new plugin context as it could be modified since last call. var datasourceUID string diff --git a/pkg/services/live/runstream/manager_test.go b/pkg/services/live/runstream/manager_test.go index 2048dda7840..e2459724ac1 100644 --- a/pkg/services/live/runstream/manager_test.go +++ b/pkg/services/live/runstream/manager_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/user" ) @@ -71,9 +72,11 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) { }, } - mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { - require.Equal(t, int64(2), user.UserID) - require.Equal(t, int64(1), user.OrgID) + mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + userID, err := identity.IntIdentifier(user.GetNamespacedID()) + require.NoError(t, err) + require.Equal(t, int64(2), userID) + require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, testPluginContext.PluginID, pluginID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) return testPluginContext, true, nil @@ -133,7 +136,7 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) { mockPacketSender.EXPECT().PublishLocal("1/test", gomock.Any()).Times(1) mockPacketSender.EXPECT().PublishLocal("2/test", gomock.Any()).Times(1) - mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { return backend.PluginContext{}, true, nil }).Times(0) @@ -205,7 +208,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) { startedCh := make(chan struct{}) doneCh := make(chan struct{}) - mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { return backend.PluginContext{}, true, nil }).Times(0) @@ -254,9 +257,11 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) { }, } - mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { - require.Equal(t, int64(2), user.UserID) - require.Equal(t, int64(1), user.OrgID) + mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + userID, err := identity.IntIdentifier(user.GetNamespacedID()) + require.NoError(t, err) + require.Equal(t, int64(2), userID) + require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, testPluginContext.PluginID, pluginID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) return testPluginContext, true, nil @@ -296,7 +301,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) { _ = manager.Run(ctx) }() - mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { return backend.PluginContext{}, true, nil }).Times(0) @@ -337,9 +342,12 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) { }, } - mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { - require.Equal(t, int64(2), user.UserID) - require.Equal(t, int64(1), user.OrgID) + mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + userID, err := identity.IntIdentifier(user.GetNamespacedID()) + require.NoError(t, err) + + require.Equal(t, int64(2), userID) + require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, testPluginContext.PluginID, pluginID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) return testPluginContext, true, nil @@ -403,9 +411,11 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) { }, } - mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { - require.Equal(t, int64(2), user.UserID) - require.Equal(t, int64(1), user.OrgID) + mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { + userID, err := identity.IntIdentifier(user.GetNamespacedID()) + require.NoError(t, err) + require.Equal(t, int64(2), userID) + require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, testPluginContext.PluginID, pluginID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) return testPluginContext, true, nil diff --git a/pkg/services/live/runstream/mock.go b/pkg/services/live/runstream/mock.go index 96978c317ec..a802d1c24d7 100644 --- a/pkg/services/live/runstream/mock.go +++ b/pkg/services/live/runstream/mock.go @@ -10,7 +10,7 @@ import ( gomock "github.com/golang/mock/gomock" backend "github.com/grafana/grafana-plugin-sdk-go/backend" - user "github.com/grafana/grafana/pkg/services/user" + identity "github.com/grafana/grafana/pkg/services/auth/identity" ) // MockChannelLocalPublisher is a mock of ChannelLocalPublisher interface. @@ -149,7 +149,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder } // GetPluginContext mocks base method. -func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) { +func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 identity.Requester, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(backend.PluginContext) diff --git a/pkg/services/ngalert/api/lotex_ruler_test.go b/pkg/services/ngalert/api/lotex_ruler_test.go index 1d3fc8e964c..661377d68f3 100644 --- a/pkg/services/ngalert/api/lotex_ruler_test.go +++ b/pkg/services/ngalert/api/lotex_ruler_test.go @@ -9,10 +9,10 @@ import ( "github.com/stretchr/testify/require" "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/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/web" ) @@ -114,7 +114,7 @@ type fakeCacheService struct { err error } -func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _ *user.SignedInUser, _ bool) (*datasources.DataSource, error) { +func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _ identity.Requester, _ bool) (*datasources.DataSource, error) { if f.err != nil { return nil, f.err } @@ -122,7 +122,7 @@ func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _ return f.datasource, nil } -func (f fakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { +func (f fakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, _ identity.Requester, skipCache bool) (*datasources.DataSource, error) { if f.err != nil { return nil, f.err } diff --git a/pkg/services/pluginsintegration/adapters/adapters.go b/pkg/services/pluginsintegration/adapters/adapters.go index 9940d94fe7a..fbe1b4acabf 100644 --- a/pkg/services/pluginsintegration/adapters/adapters.go +++ b/pkg/services/pluginsintegration/adapters/adapters.go @@ -7,8 +7,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/user" ) // ModelToInstanceSettings converts a datasources.DataSource to a backend.DataSourceInstanceSettings. @@ -43,16 +43,16 @@ func ModelToInstanceSettings(ds *datasources.DataSource, decryptFn func(ds *data }, err } -// BackendUserFromSignedInUser converts Grafana's SignedInUser model +// BackendUserFromSignedInUser converts Grafana's context request identity // to the backend plugin's model. -func BackendUserFromSignedInUser(su *user.SignedInUser) *backend.User { - if su == nil { +func BackendUserFromSignedInUser(requester identity.Requester) *backend.User { + if requester == nil { return nil } return &backend.User{ - Login: su.Login, - Name: su.Name, - Email: su.Email, - Role: string(su.OrgRole), + Login: requester.GetLogin(), + Name: requester.GetDisplayName(), + Email: requester.GetEmail(), + Role: string(requester.GetOrgRole()), } } diff --git a/pkg/services/pluginsintegration/plugincontext/plugincontext.go b/pkg/services/pluginsintegration/plugincontext/plugincontext.go index 34b9be7056c..6039d03319a 100644 --- a/pkg/services/pluginsintegration/plugincontext/plugincontext.go +++ b/pkg/services/pluginsintegration/plugincontext/plugincontext.go @@ -11,10 +11,10 @@ import ( "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/adapters" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" - "github.com/grafana/grafana/pkg/services/user" ) var ErrPluginNotFound = errors.New("plugin not found") @@ -39,8 +39,8 @@ type Provider struct { // Get allows getting plugin context by its ID. If datasourceUID is not empty string // then PluginContext.DataSourceInstanceSettings will be resolved and appended to // returned context. -// Note: *user.SignedInUser can be nil. -func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) { +// Note: identity.Requester can be nil. +func (p *Provider) Get(ctx context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error) { plugin, exists := p.pluginStore.Plugin(ctx, pluginID) if !exists { return backend.PluginContext{}, ErrPluginNotFound @@ -50,7 +50,7 @@ func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedIn PluginID: pluginID, } if user != nil { - pCtx.OrgID = user.OrgID + pCtx.OrgID = user.GetOrgID() pCtx.User = adapters.BackendUserFromSignedInUser(user) } @@ -68,7 +68,7 @@ func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedIn // GetWithDataSource allows getting plugin context by its ID and PluginContext.DataSourceInstanceSettings will be // resolved and appended to the returned context. // Note: *user.SignedInUser can be nil. -func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, error) { +func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) { _, exists := p.pluginStore.Plugin(ctx, pluginID) if !exists { return backend.PluginContext{}, ErrPluginNotFound @@ -78,7 +78,7 @@ func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user PluginID: pluginID, } if user != nil { - pCtx.OrgID = user.OrgID + pCtx.OrgID = user.GetOrgID() pCtx.User = adapters.BackendUserFromSignedInUser(user) } diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index ba005572541..05dbc029688 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -16,10 +16,10 @@ import ( "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "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/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/grafanads" @@ -61,7 +61,7 @@ func ProvideService( //go:generate mockery --name Service --structname FakeQueryService --inpackage --filename query_service_mock.go type Service interface { Run(ctx context.Context) error - QueryData(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) + QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) } // Gives us compile time error if the service does not adhere to the contract of the interface @@ -85,7 +85,7 @@ func (s *ServiceImpl) Run(ctx context.Context) error { } // QueryData processes queries and returns query responses. It handles queries to single or mixed datasources, as well as expressions. -func (s *ServiceImpl) QueryData(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) { +func (s *ServiceImpl) QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) { // Parse the request into parsed queries grouped by datasource uid parsedReq, err := s.parseMetricRequest(ctx, user, skipDSCache, reqDTO) if err != nil { @@ -111,7 +111,7 @@ type splitResponse struct { } // executeConcurrentQueries executes queries to multiple datasources concurrently and returns the aggregate result. -func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest, queriesbyDs map[string][]parsedQuery) (*backend.QueryDataResponse, error) { +func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest, queriesbyDs map[string][]parsedQuery) (*backend.QueryDataResponse, error) { g, ctx := errgroup.WithContext(ctx) g.SetLimit(s.concurrentQueryLimit) // prevent too many concurrent requests rchan := make(chan splitResponse, len(queriesbyDs)) @@ -198,14 +198,14 @@ func buildErrorResponses(err error, queries []*simplejson.Json) splitResponse { } // handleExpressions handles POST /api/ds/query when there is an expression. -func (s *ServiceImpl) handleExpressions(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { +func (s *ServiceImpl) handleExpressions(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { exprReq := expr.Request{ Queries: []expr.Query{}, } if user != nil { // for passthrough authentication, SSE does not authenticate exprReq.User = user - exprReq.OrgId = user.OrgID + exprReq.OrgId = user.GetOrgID() } for _, pq := range parsedReq.getFlattenedQueries() { @@ -239,7 +239,7 @@ func (s *ServiceImpl) handleExpressions(ctx context.Context, user *user.SignedIn } // handleQuerySingleDatasource handles one or more queries to a single datasource -func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { +func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { queries := parsedReq.getFlattenedQueries() ds := queries[0].datasource if err := s.pluginRequestValidator.Validate(ds.URL, nil); err != nil { @@ -271,7 +271,7 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user *use } // parseRequest parses a request into parsed queries grouped by datasource uid -func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) { +func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) { if len(reqDTO.Queries) == 0 { return nil, ErrNoQueriesFound } @@ -332,7 +332,7 @@ func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user *user.SignedI return req, req.validateRequest(ctx) } -func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user *user.SignedInUser, skipDSCache bool, query *simplejson.Json, history map[string]*datasources.DataSource) (*datasources.DataSource, error) { +func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user identity.Requester, skipDSCache bool, query *simplejson.Json, history map[string]*datasources.DataSource) (*datasources.DataSource, error) { var err error uid := query.Get("datasource").Get("uid").MustString() @@ -352,7 +352,7 @@ func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user *user.Sig } if uid == grafanads.DatasourceUID { - return grafanads.DataSourceModel(user.OrgID), nil + return grafanads.DataSourceModel(user.GetOrgID()), nil } if uid != "" { diff --git a/pkg/services/query/query_service_mock.go b/pkg/services/query/query_service_mock.go index 4d12dd838a4..f3fc8684661 100644 --- a/pkg/services/query/query_service_mock.go +++ b/pkg/services/query/query_service_mock.go @@ -11,7 +11,7 @@ import ( mock "github.com/stretchr/testify/mock" - user "github.com/grafana/grafana/pkg/services/user" + identity "github.com/grafana/grafana/pkg/services/auth/identity" ) // FakeQueryService is an autogenerated mock type for the Service type @@ -20,11 +20,11 @@ type FakeQueryService struct { } // QueryData provides a mock function with given fields: ctx, _a1, skipDSCache, reqDTO -func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) { +func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) { ret := _m.Called(ctx, _a1, skipDSCache, reqDTO) var r0 *backend.QueryDataResponse - if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, bool, dtos.MetricRequest) *backend.QueryDataResponse); ok { + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, bool, dtos.MetricRequest) *backend.QueryDataResponse); ok { r0 = rf(ctx, _a1, skipDSCache, reqDTO) } else { if ret.Get(0) != nil { @@ -33,7 +33,7 @@ func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 *user.SignedInUse } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, bool, dtos.MetricRequest) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, identity.Requester, bool, dtos.MetricRequest) error); ok { r1 = rf(ctx, _a1, skipDSCache, reqDTO) } else { r1 = ret.Error(1) diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index 5f0d3da55fa..847b30671ee 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/plugins" pluginFakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -524,12 +525,12 @@ type fakeDataSourceCache struct { cache []*datasources.DataSource } -func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { +func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user identity.Requester, skipCache bool) (*datasources.DataSource, error) { // deprecated: fake an error to ensure we are using GetDatasourceByUID return nil, fmt.Errorf("not found") } -func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { +func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user identity.Requester, skipCache bool) (*datasources.DataSource, error) { for _, ds := range c.cache { if ds.UID == datasourceUID { return ds, nil diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index dd33d0420fc..b74fa5a7080 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -43,6 +43,9 @@ func (u *SignedInUser) NameOrFallback() string { return u.Email } +// TODO: There's a need to remove this struct since it creates a circular dependency + +// DEPRECATED: This function uses `UserDisplayDTO` model which we want to remove func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO { return &UserDisplayDTO{ ID: u.UserID, @@ -51,6 +54,20 @@ func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO { } } +// Static function to parse a requester into a UserDisplayDTO +func NewUserDisplayDTOFromRequester(requester identity.Requester) (*UserDisplayDTO, error) { + namespaceID, identifier := requester.GetNamespacedID() + if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { + identifier = "0" + } + userID, _ := identity.IntIdentifier(namespaceID, identifier) + return &UserDisplayDTO{ + ID: userID, + Login: requester.GetLogin(), + Name: requester.GetDisplayName(), + }, nil +} + func (u *SignedInUser) HasRole(role roletype.RoleType) bool { if u.IsGrafanaAdmin { return true