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
pull/71684/head^2
linoman 2 years ago committed by GitHub
parent e079e00bfb
commit 1b8e9b51b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 184
      pkg/api/dashboard.go
  2. 62
      pkg/api/dashboard_test.go
  3. 6
      pkg/expr/service.go
  4. 14
      pkg/expr/testing.go
  5. 4
      pkg/expr/transform.go
  6. 4
      pkg/services/alerting/models.go
  7. 11
      pkg/services/dashboardimport/service/service_test.go
  8. 3
      pkg/services/dashboards/models.go
  9. 50
      pkg/services/dashboards/service/dashboard_service.go
  10. 51
      pkg/services/dashboards/service/dashboard_service_integration_test.go
  11. 6
      pkg/services/datasources/datasources.go
  12. 6
      pkg/services/datasources/fakes/fake_cache_service.go
  13. 6
      pkg/services/datasources/guardian/provider.go
  14. 26
      pkg/services/datasources/service/cache.go
  15. 26
      pkg/services/folder/folderimpl/folder.go
  16. 2
      pkg/services/folder/model.go
  17. 30
      pkg/services/guardian/accesscontrol_guardian.go
  18. 20
      pkg/services/guardian/guardian.go
  19. 10
      pkg/services/guardian/provider.go
  20. 10
      pkg/services/live/features/broadcast.go
  21. 20
      pkg/services/live/features/dashboard.go
  22. 10
      pkg/services/live/features/plugin.go
  23. 4
      pkg/services/live/features/plugin_mock.go
  24. 71
      pkg/services/live/live.go
  25. 8
      pkg/services/live/livecontext/context.go
  26. 6
      pkg/services/live/liveplugin/plugin.go
  27. 8
      pkg/services/live/managedstream/runner.go
  28. 6
      pkg/services/live/model/model.go
  29. 6
      pkg/services/live/pipeline/auth.go
  30. 6
      pkg/services/live/pipeline/pipeline.go
  31. 4
      pkg/services/live/pipeline/subscribe_builtin.go
  32. 2
      pkg/services/live/pushws/push_pipeline.go
  33. 2
      pkg/services/live/pushws/push_stream.go
  34. 8
      pkg/services/live/runstream/manager.go
  35. 40
      pkg/services/live/runstream/manager_test.go
  36. 4
      pkg/services/live/runstream/mock.go
  37. 6
      pkg/services/ngalert/api/lotex_ruler_test.go
  38. 16
      pkg/services/pluginsintegration/adapters/adapters.go
  39. 12
      pkg/services/pluginsintegration/plugincontext/plugincontext.go
  40. 20
      pkg/services/query/query.go
  41. 8
      pkg/services/query/query_service_mock.go
  42. 5
      pkg/services/query/query_test.go
  43. 17
      pkg/services/user/identity.go

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/kinds/dashboard"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
@ -44,15 +45,27 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI
return false, nil 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) return hs.starService.IsStarredByUser(c.Req.Context(), &query)
} }
func dashboardGuardianResponse(err error) response.Response { func dashboardGuardianResponse(err error) response.Response {
if err != nil { 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 // swagger:route POST /dashboards/trim dashboards trimDashboard
@ -95,7 +108,7 @@ func (hs *HTTPServer) TrimDashboard(c *contextmodel.ReqContext) response.Respons
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response {
uid := web.Params(c.Req)[":uid"] 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 { if rsp != nil {
return rsp 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 // If public dashboards is enabled and we have a public dashboard, update meta
// values // values
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { 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) { 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 { if publicDashboard != nil {
@ -128,10 +141,10 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
} }
} }
if isEmptyData { 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -146,7 +159,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
isStarred, err := hs.isDashboardStarredByUser(c, dash.ID) isStarred, err := hs.isDashboardStarredByUser(c, dash.ID)
if err != nil { 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 // Finding creator and last updater of the dashboard
updater, creator := anonString, anonString updater, creator := anonString, anonString
@ -186,13 +199,13 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
// lookup folder title // lookup folder title
if dash.FolderID > 0 { 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) queryResult, err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
if err != nil { if err != nil {
if errors.Is(err, dashboards.ErrFolderNotFound) { 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.FolderUid = queryResult.UID
meta.FolderTitle = queryResult.Title 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) provisioningData, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID)
if err != nil { 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 { 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) queryResult, err := hs.DashboardService.GetDashboard(ctx, &query)
if err != nil { 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 return queryResult, nil
@ -298,11 +311,11 @@ func (hs *HTTPServer) DeleteDashboardByUID(c *contextmodel.ReqContext) response.
} }
func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.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 { if rsp != nil {
return rsp 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -311,10 +324,17 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
namespaceID, userIDStr := c.SignedInUser.GetNamespacedID()
// disconnect all library elements for this dashboard // disconnect all library elements for this dashboard
err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID) err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID)
if err != nil { 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 // 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") 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 { if err != nil {
var dashboardErr dashboards.DashboardErr var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { 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(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 { 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 { if err != nil {
hs.log.Error("Failed to broadcast delete info", "dashboard", dash.UID, "error", err) 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 { func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.SaveDashboardCommand) response.Response {
ctx := c.Req.Context() ctx := c.Req.Context()
var err error 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 != "" { if cmd.FolderUID != "" {
folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{ folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.OrgID, OrgID: c.SignedInUser.GetOrgID(),
UID: &cmd.FolderUID, UID: &cmd.FolderUID,
SignedInUser: c.SignedInUser, SignedInUser: c.SignedInUser,
}) })
if err != nil { if err != nil {
if errors.Is(err, dashboards.ErrFolderNotFound) { 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 cmd.FolderID = folder.ID
} }
@ -418,10 +454,10 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
if newDashboard { if newDashboard {
limitReached, err := hs.QuotaService.QuotaReached(c, dashboards.QuotaTargetSrv) limitReached, err := hs.QuotaService.QuotaReached(c, dashboards.QuotaTargetSrv)
if err != nil { if err != nil {
return response.Error(500, "failed to get quota", err) return response.Error(http.StatusInternalServerError, "failed to get quota", err)
} }
if limitReached { 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 { if dash.ID != 0 {
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID) data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID)
if err != nil { 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 provisioningData = data
} else if dash.UID != "" { } else if dash.UID != "" {
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(c.Req.Context(), dash.OrgID, 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) { 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 provisioningData = data
} }
@ -448,7 +484,7 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
dashItem := &dashboards.SaveDashboardDTO{ dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash, Dashboard: dash,
Message: cmd.Message, Message: cmd.Message,
OrgID: c.OrgID, OrgID: c.SignedInUser.GetOrgID(),
User: c.SignedInUser, User: c.SignedInUser,
Overwrite: cmd.Overwrite, Overwrite: cmd.Overwrite,
} }
@ -461,14 +497,19 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
dashboard = dash // the original request 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. // 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 // gitops is useful when trying to save dashboards in an environment where the user can not save
channel := hs.Live.GrafanaScope.Dashboards 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 // When an error exists, but the value broadcast to a gitops listener return 202
if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.OrgID) { if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.GetOrgID()) {
return response.JSON(202, util.DynMap{ return response.JSON(http.StatusAccepted, util.DynMap{
"status": "pending", "status": "pending",
"message": "changes were broadcast to the gitops listener", "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 // connect library panels for this dashboard after the dashboard is stored and has an ID
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard) err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard)
if err != nil { 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) c.TimeRequest(metrics.MApiDashboardSave)
@ -515,12 +556,22 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
// 401: unauthorisedError // 401: unauthorisedError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Response { 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 homePage := hs.Cfg.HomePage
preference, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) preference, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery)
if err != nil { 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 { if preference.HomeDashboardID == 0 && len(homePage) > 0 {
@ -549,7 +600,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp
// nolint:gosec // nolint:gosec
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { 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() { defer func() {
if err := file.Close(); err != nil { if err := file.Close(); err != nil {
@ -564,7 +615,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp
jsonParser := json.NewDecoder(file) jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(dash.Dashboard); err != nil { 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) 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 { if rsp != nil {
return rsp 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -650,7 +701,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response.
} }
query := dashver.ListDashboardVersionsQuery{ query := dashver.ListDashboardVersionsQuery{
OrgID: c.OrgID, OrgID: c.SignedInUser.GetOrgID(),
DashboardID: dash.ID, DashboardID: dash.ID,
DashboardUID: dash.UID, DashboardUID: dash.UID,
Limit: c.QueryInt("limit"), 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) versions, err := hs.dashboardVersionService.List(c.Req.Context(), &query)
if err != nil { 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)) 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 { if rsp != nil {
return rsp 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 { if err != nil {
return response.Err(err) 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) version, _ := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 32)
query := dashver.GetDashboardVersionQuery{ query := dashver.GetDashboardVersionQuery{
OrgID: c.OrgID, OrgID: c.SignedInUser.GetOrgID(),
DashboardID: dash.ID, DashboardID: dash.ID,
DashboardUID: dash.UID, DashboardUID: dash.UID,
Version: int(version), 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) res, err := hs.dashboardVersionService.Get(c.Req.Context(), &query)
if err != nil { 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 creator := anonString
@ -879,7 +930,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
if err := web.Bind(c.Req, &apiOptions); err != nil { if err := web.Bind(c.Req, &apiOptions); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err) 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -889,7 +940,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
} }
if apiOptions.Base.DashboardId != apiOptions.New.DashboardId { 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -900,7 +951,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
} }
options := dashdiffs.Options{ options := dashdiffs.Options{
OrgId: c.OrgID, OrgId: c.SignedInUser.GetOrgID(),
DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType), DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType),
Base: dashdiffs.DiffTarget{ Base: dashdiffs.DiffTarget{
DashboardId: apiOptions.Base.DashboardId, 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) baseVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &baseVersionQuery)
if err != nil { if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) { 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{ newVersionQuery := dashver.GetDashboardVersionQuery{
@ -937,9 +988,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
newVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &newVersionQuery) newVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &newVersionQuery)
if err != nil { if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) { 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 baseData := baseVersionRes.Data
@ -949,9 +1000,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
if err != nil { if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) { 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 { 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 { if rsp != nil {
return rsp 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 { if err != nil {
return response.Err(err) return response.Err(err)
} }
@ -1017,16 +1068,25 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
return dashboardGuardianResponse(err) 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) version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery)
if err != nil { 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 := dashboards.SaveDashboardCommand{}
saveCmd.RestoredFrom = version.Version saveCmd.RestoredFrom = version.Version
saveCmd.OrgID = c.OrgID saveCmd.OrgID = c.SignedInUser.GetOrgID()
saveCmd.UserID = c.UserID saveCmd.UserID = userID
saveCmd.Dashboard = version.Data saveCmd.Dashboard = version.Data
saveCmd.Dashboard.Set("version", dash.Version) saveCmd.Dashboard.Set("version", dash.Version)
saveCmd.Dashboard.Set("uid", dash.UID) saveCmd.Dashboard.Set("uid", dash.UID)
@ -1045,7 +1105,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
// 401: unauthorisedError // 401: unauthorisedError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetDashboardTags(c *contextmodel.ReqContext) { 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) queryResult, err := hs.DashboardService.GetDashboardTags(c.Req.Context(), &query)
if err != nil { if err != nil {
c.JsonApiErr(500, "Failed to get tags from database", err) c.JsonApiErr(500, "Failed to get tags from database", err)

@ -79,6 +79,7 @@ func TestGetHomeDashboard(t *testing.T) {
preferenceService: prefService, preferenceService: prefService,
dashboardVersionService: dashboardVersionService, dashboardVersionService: dashboardVersionService,
Kinds: corekind.NewBase(nil), Kinds: corekind.NewBase(nil),
log: log.New("test-logger"),
} }
tests := []struct { 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) { postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, dashboardService, mockFolder, func(sc *scenarioContext) {
callPostDashboard(sc) 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 SaveError error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: 404}, {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: http.StatusNotFound},
{SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: http.StatusPreconditionFailed},
{SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: http.StatusPreconditionFailed},
{SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422}, {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: http.StatusUnprocessableEntity},
{SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: http.StatusForbidden},
{SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: http.StatusBadRequest},
{SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: http.StatusPreconditionFailed},
} }
cmd := dashboards.SaveDashboardCommand{ 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()), postDashboardScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()),
"/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) { "/api/dashboards", "/api/dashboards", cmd, dashboardService, nil, func(sc *scenarioContext) {
callPostDashboard(sc) 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) callPostDashboard(sc)
result := sc.ToJSON() 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.False(t, result.Get("isValid").MustBool())
assert.NotEmpty(t, result.Get("message").MustString()) assert.NotEmpty(t, result.Get("message").MustString())
}, sqlmock) }, sqlmock)
@ -556,7 +557,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
callPostDashboard(sc) callPostDashboard(sc)
result := sc.ToJSON() 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.False(t, result.Get("isValid").MustBool())
assert.Equal(t, "invalid schema version", result.Get("message").MustString()) assert.Equal(t, "invalid schema version", result.Get("message").MustString())
}, sqlmock) }, sqlmock)
@ -575,7 +576,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
callPostDashboard(sc) callPostDashboard(sc)
result := sc.ToJSON() 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()) assert.True(t, result.Get("isValid").MustBool())
}, sqlmock) }, sqlmock)
}) })
@ -618,7 +619,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: false}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: false})
callPostDashboard(sc) callPostDashboard(sc)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, http.StatusForbidden, sc.resp.Code)
}, sqlmock, fakeDashboardVersionService) }, sqlmock, fakeDashboardVersionService)
}) })
@ -629,7 +630,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
// This test shouldn't hit GetDashboardACLInfoList, so no setup needed // This test shouldn't hit GetDashboardACLInfoList, so no setup needed
sc.dashboardVersionService = fakeDashboardVersionService sc.dashboardVersionService = fakeDashboardVersionService
callPostDashboard(sc) callPostDashboard(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
}, sqlmock, fakeDashboardVersionService) }, sqlmock, fakeDashboardVersionService)
}) })
}) })
@ -666,7 +667,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
sc.dashboardVersionService = fakeDashboardVersionService sc.dashboardVersionService = fakeDashboardVersionService
callRestoreDashboardVersion(sc) callRestoreDashboardVersion(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
}) })
@ -699,7 +700,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore", restoreDashboardVersionScenario(t, "When calling POST on", "/api/dashboards/id/1/restore",
"/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) { "/api/dashboards/id/:dashboardId/restore", dashboardService, fakeDashboardVersionService, cmd, func(sc *scenarioContext) {
callRestoreDashboardVersion(sc) callRestoreDashboardVersion(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
}) })
@ -752,7 +753,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
} }
hs.callGetDashboard(sc) hs.callGetDashboard(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
dash := dtos.DashboardFullWithMeta{} dash := dtos.DashboardFullWithMeta{}
err := json.NewDecoder(sc.resp.Body).Decode(&dash) 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"}, ExpectedUser: &user.User{ID: 1, Login: "test-user"},
}).callGetDashboardVersions(sc) }).callGetDashboardVersions(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
var versions []dashver.DashboardVersionMeta var versions []dashver.DashboardVersionMeta
err := json.NewDecoder(sc.resp.Body).Decode(&versions) err := json.NewDecoder(sc.resp.Body).Decode(&versions)
require.NoError(t, err) require.NoError(t, err)
@ -840,7 +841,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) {
ExpectedError: user.ErrUserNotFound, ExpectedError: user.ErrUserNotFound,
}).callGetDashboardVersions(sc) }).callGetDashboardVersions(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
var versions []dashver.DashboardVersionMeta var versions []dashver.DashboardVersionMeta
err := json.NewDecoder(sc.resp.Body).Decode(&versions) err := json.NewDecoder(sc.resp.Body).Decode(&versions)
require.NoError(t, err) require.NoError(t, err)
@ -866,7 +867,7 @@ func TestDashboardVersionsAPIEndpoint(t *testing.T) {
ExpectedError: fmt.Errorf("some error"), ExpectedError: fmt.Errorf("some error"),
}).callGetDashboardVersions(sc) }).callGetDashboardVersions(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
var versions []dashver.DashboardVersionMeta var versions []dashver.DashboardVersionMeta
err := json.NewDecoder(sc.resp.Body).Decode(&versions) err := json.NewDecoder(sc.resp.Body).Decode(&versions)
require.NoError(t, err) require.NoError(t, err)
@ -981,6 +982,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),
Kinds: corekind.NewBase(nil), Kinds: corekind.NewBase(nil),
accesscontrolService: actest.FakeService{}, accesscontrolService: actest.FakeService{},
log: log.New("test-logger"),
} }
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)

@ -12,10 +12,10 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins" "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/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -68,8 +68,8 @@ type Service struct {
} }
type pluginContextProvider interface { type pluginContextProvider interface {
Get(ctx context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) Get(ctx context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error)
GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (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, func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider,

@ -5,8 +5,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/datasources"
"github.com/grafana/grafana/pkg/services/user"
) )
type fakePluginContextProvider struct { type fakePluginContextProvider struct {
@ -20,7 +20,7 @@ type fakePluginContextProvider struct {
var _ pluginContextProvider = &fakePluginContextProvider{} 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 { f.recordings = append(f.recordings, struct {
method string method string
params []interface{} params []interface{}
@ -31,9 +31,9 @@ func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user
var u *backend.User var u *backend.User
if user != nil { if user != nil {
u = &backend.User{ u = &backend.User{
Login: user.Login, Login: user.GetLogin(),
Name: user.Name, Name: user.GetDisplayName(),
Email: user.Email, Email: user.GetEmail(),
} }
} }
return backend.PluginContext{ return backend.PluginContext{
@ -45,7 +45,7 @@ func (f *fakePluginContextProvider) Get(_ context.Context, pluginID string, user
}, nil }, 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 { f.recordings = append(f.recordings, struct {
method string method string
params []interface{} params []interface{}
@ -57,7 +57,7 @@ func (f *fakePluginContextProvider) GetWithDataSource(ctx context.Context, plugi
orgId := int64(1) orgId := int64(1)
if user != nil { if user != nil {
orgId = user.OrgID orgId = user.GetOrgID()
} }
r, err := f.Get(ctx, pluginID, user, orgId) r, err := f.Get(ctx, pluginID, user, orgId)
if ds != nil { if ds != nil {

@ -8,8 +8,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/datasources"
"github.com/grafana/grafana/pkg/services/user"
) )
// Request is similar to plugins.DataQuery but with the Time Ranges is per Query. // Request is similar to plugins.DataQuery but with the Time Ranges is per Query.
@ -18,7 +18,7 @@ type Request struct {
Debug bool Debug bool
OrgId int64 OrgId int64
Queries []Query Queries []Query
User *user.SignedInUser User identity.Requester
} }
// Query is like plugins.DataSubQuery, but with a a time range, and only the UID // Query is like plugins.DataSubQuery, but with a a time range, and only the UID

@ -4,8 +4,8 @@ import (
"sync" "sync"
"github.com/grafana/grafana/pkg/components/null" "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/dashboards"
"github.com/grafana/grafana/pkg/services/user"
) )
// Job holds state about when the alert rule should be evaluated. // Job holds state about when the alert rule should be evaluated.
@ -46,7 +46,7 @@ type EvalMatch struct {
} }
type DashAlertInfo struct { type DashAlertInfo struct {
User *user.SignedInUser User identity.Requester
Dash *dashboards.Dashboard Dash *dashboards.Dashboard
OrgID int64 OrgID int64
} }

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "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/dashboardimport"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
@ -83,9 +84,12 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID) require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID())
require.NoError(t, err)
require.NotNil(t, importDashboardArg) require.NotNil(t, importDashboardArg)
require.Equal(t, int64(3), importDashboardArg.OrgID) 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, "prometheus", importDashboardArg.Dashboard.PluginID)
require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID) require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID)
@ -147,9 +151,12 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID) require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID())
require.NoError(t, err)
require.NotNil(t, importDashboardArg) require.NotNil(t, importDashboardArg)
require.Equal(t, int64(3), importDashboardArg.OrgID) 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, "", importDashboardArg.Dashboard.PluginID)
require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID) require.Equal(t, int64(5), importDashboardArg.Dashboard.FolderID)

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/kinds" "github.com/grafana/grafana/pkg/kinds"
"github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/kinds/dashboard"
"github.com/grafana/grafana/pkg/models" "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/folder"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
@ -337,7 +338,7 @@ type GetDashboardRefByIDQuery struct {
type SaveDashboardDTO struct { type SaveDashboardDTO struct {
OrgID int64 OrgID int64
UpdatedAt time.Time UpdatedAt time.Time
User *user.SignedInUser User identity.Requester
Message string Message string
Overwrite bool Overwrite bool
Dashboard *Dashboard Dashboard *Dashboard

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting" "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/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -21,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
) )
var ( 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{ cmd := &dashboards.SaveDashboardCommand{
Dashboard: dash.Data, Dashboard: dash.Data,
Message: dto.Message, Message: dto.Message,
OrgID: dto.OrgID, OrgID: dto.OrgID,
Overwrite: dto.Overwrite, Overwrite: dto.Overwrite,
UserID: dto.User.UserID, UserID: userID,
FolderID: dash.FolderID, FolderID: dash.FolderID,
IsFolder: dash.IsFolder, IsFolder: dash.IsFolder,
PluginID: dash.PluginID, PluginID: dash.PluginID,
@ -199,6 +206,20 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
return cmd, nil 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 { func (dr *DashboardServiceImpl) UpdateDashboardACL(ctx context.Context, uid int64, items []*dashboards.DashboardACL) error {
return dr.dashboardStore.UpdateDashboardACL(ctx, uid, items) 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 // getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck() // 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 newDashboard := d.ID == 0
if newDashboard { if newDashboard {
@ -297,7 +318,8 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
if dto.Dashboard.ID == 0 { if dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil { 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 dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil { 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 // new dashboard created
if dto.Dashboard.ID == 0 { if dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil { 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 { 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 return dash, nil
@ -462,9 +487,16 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
inFolder := dash.FolderID > 0 inFolder := dash.FolderID > 0
var permissions []accesscontrol.SetResourcePermissionCommand 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{ 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 svc = dr.folderPermissions
} }
_, err := svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...) _, err = svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...)
if err != nil { if err != nil {
return err return err
} }

@ -15,6 +15,7 @@ import (
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/alerting/models" "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"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -112,9 +113,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sqlStore) err := callSaveWithError(t, cmd, sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, "", sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) 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", 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) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) 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, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID) assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID) assert.Equal(t, cmd.UserID, userID)
}) })
}) })

@ -7,7 +7,7 @@ import (
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/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. // DataSourceService interface for interacting with datasources.
@ -64,8 +64,8 @@ type DataSourceService interface {
// CacheService interface for retrieving a cached datasource. // CacheService interface for retrieving a cached datasource.
type CacheService interface { type CacheService interface {
// GetDatasource gets a datasource identified by datasource numeric identifier. // 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 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)
} }

@ -3,8 +3,8 @@ package datasources
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
) )
type FakeCacheService struct { type FakeCacheService struct {
@ -13,7 +13,7 @@ type FakeCacheService struct {
var _ datasources.CacheService = &FakeCacheService{} 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 { for _, datasource := range c.DataSources {
if datasource.ID == datasourceID { if datasource.ID == datasourceID {
return datasource, nil return datasource, nil
@ -22,7 +22,7 @@ func (c *FakeCacheService) GetDatasource(ctx context.Context, datasourceID int64
return nil, datasources.ErrDataSourceNotFound 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 { for _, datasource := range c.DataSources {
if datasource.UID == datasourceUID { if datasource.UID == datasourceUID {
return datasource, nil return datasource, nil

@ -1,12 +1,12 @@
package guardian package guardian
import ( import (
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
) )
type DatasourceGuardianProvider interface { 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 { type DatasourceGuardian interface {
@ -20,6 +20,6 @@ func ProvideGuardian() *OSSProvider {
type OSSProvider struct{} 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{} return &AllowGuardian{}
} }

@ -8,9 +8,9 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log" "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"
"github.com/grafana/grafana/pkg/services/datasources/guardian" "github.com/grafana/grafana/pkg/services/datasources/guardian"
"github.com/grafana/grafana/pkg/services/user"
) )
const ( const (
@ -38,7 +38,7 @@ type CacheServiceImpl struct {
func (dc *CacheServiceImpl) GetDatasource( func (dc *CacheServiceImpl) GetDatasource(
ctx context.Context, ctx context.Context,
datasourceID int64, datasourceID int64,
user *user.SignedInUser, user identity.Requester,
skipCache bool, skipCache bool,
) (*datasources.DataSource, error) { ) (*datasources.DataSource, error) {
cacheKey := idKey(datasourceID) cacheKey := idKey(datasourceID)
@ -46,7 +46,7 @@ func (dc *CacheServiceImpl) GetDatasource(
if !skipCache { if !skipCache {
if cached, found := dc.CacheService.Get(cacheKey); found { if cached, found := dc.CacheService.Get(cacheKey); found {
ds := cached.(*datasources.DataSource) ds := cached.(*datasources.DataSource)
if ds.OrgID == user.OrgID { if ds.OrgID == user.GetOrgID() {
if err := dc.canQuery(user, ds); err != nil { if err := dc.canQuery(user, ds); err != nil {
return nil, err 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} ss := SqlStore{db: dc.SQLStore, logger: dc.logger}
ds, err := ss.GetDataSource(ctx, query) ds, err := ss.GetDataSource(ctx, query)
if err != nil { if err != nil {
@ -79,21 +79,21 @@ func (dc *CacheServiceImpl) GetDatasource(
func (dc *CacheServiceImpl) GetDatasourceByUID( func (dc *CacheServiceImpl) GetDatasourceByUID(
ctx context.Context, ctx context.Context,
datasourceUID string, datasourceUID string,
user *user.SignedInUser, user identity.Requester,
skipCache bool, skipCache bool,
) (*datasources.DataSource, error) { ) (*datasources.DataSource, error) {
if datasourceUID == "" { if datasourceUID == "" {
return nil, fmt.Errorf("can not get data source by uid, uid is empty") 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") 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 !skipCache {
if cached, found := dc.CacheService.Get(uidCacheKey); found { if cached, found := dc.CacheService.Get(uidCacheKey); found {
ds := cached.(*datasources.DataSource) ds := cached.(*datasources.DataSource)
if ds.OrgID == user.OrgID { if ds.OrgID == user.GetOrgID() {
if err := dc.canQuery(user, ds); err != nil { if err := dc.canQuery(user, ds); err != nil {
return nil, err 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) dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.GetOrgID())
query := &datasources.GetDataSourceQuery{UID: datasourceUID, OrgID: user.OrgID} query := &datasources.GetDataSourceQuery{UID: datasourceUID, OrgID: user.GetOrgID()}
ss := SqlStore{db: dc.SQLStore, logger: dc.logger} ss := SqlStore{db: dc.SQLStore, logger: dc.logger}
ds, err := ss.GetDataSource(ctx, query) ds, err := ss.GetDataSource(ctx, query)
if err != nil { if err != nil {
@ -128,8 +128,8 @@ func uidKey(orgID int64, uid string) string {
return fmt.Sprintf("ds-orgid-uid-%d-%s", orgID, uid) return fmt.Sprintf("ds-orgid-uid-%d-%s", orgID, uid)
} }
func (dc *CacheServiceImpl) canQuery(user *user.SignedInUser, ds *datasources.DataSource) error { func (dc *CacheServiceImpl) canQuery(user identity.Requester, ds *datasources.DataSource) error {
guardian := dc.dsGuardian.New(user.OrgID, user, *ds) guardian := dc.dsGuardian.New(user.GetOrgID(), user, *ds)
if canQuery, err := guardian.CanQuery(ds.ID); err != nil || !canQuery { if canQuery, err := guardian.CanQuery(ds.ID); err != nil || !canQuery {
if err != nil { if err != nil {
return err return err

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "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/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
@ -280,7 +281,16 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
dashFolder.SetUID(trimmedUID) dashFolder.SetUID(trimmedUID)
user := cmd.SignedInUser 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 { if userID == 0 {
userID = -1 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{ cmd := &dashboards.SaveDashboardCommand{
Dashboard: dash.Data, Dashboard: dash.Data,
Message: dto.Message, Message: dto.Message,
OrgID: dto.OrgID, OrgID: dto.OrgID,
Overwrite: dto.Overwrite, Overwrite: dto.Overwrite,
UserID: dto.User.UserID, UserID: userID,
FolderID: dash.FolderID, FolderID: dash.FolderID,
IsFolder: dash.IsFolder, IsFolder: dash.IsFolder,
PluginID: dash.PluginID, 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 // getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck() // 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 newDashboard := d.ID == 0
if newDashboard { if newDashboard {

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/infra/slugify" "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/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
@ -138,6 +139,7 @@ type GetFolderQuery struct {
OrgID int64 OrgID int64
SignedInUser *user.SignedInUser `json:"-"` SignedInUser *user.SignedInUser `json:"-"`
Requester identity.Requester `json:"-"`
} }
// GetParentsQuery captures the information required by the folder service to // GetParentsQuery captures the information required by the folder service to

@ -6,9 +6,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "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/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -16,14 +16,14 @@ var _ DashboardGuardian = new(accessControlDashboardGuardian)
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId. // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId.
func NewAccessControlDashboardGuardian( 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, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) { ) (DashboardGuardian, error) {
var dashboard *dashboards.Dashboard var dashboard *dashboards.Dashboard
if dashboardId != 0 { if dashboardId != 0 {
q := &dashboards.GetDashboardQuery{ q := &dashboards.GetDashboardQuery{
ID: dashboardId, ID: dashboardId,
OrgID: user.OrgID, OrgID: user.GetOrgID(),
} }
qResult, err := dashboardService.GetDashboard(ctx, q) qResult, err := dashboardService.GetDashboard(ctx, q)
@ -65,14 +65,14 @@ func NewAccessControlDashboardGuardian(
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID. // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID.
func NewAccessControlDashboardGuardianByUID( 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, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) { ) (DashboardGuardian, error) {
var dashboard *dashboards.Dashboard var dashboard *dashboards.Dashboard
if dashboardUID != "" { if dashboardUID != "" {
q := &dashboards.GetDashboardQuery{ q := &dashboards.GetDashboardQuery{
UID: dashboardUID, UID: dashboardUID,
OrgID: user.OrgID, OrgID: user.GetOrgID(),
} }
qResult, err := dashboardService.GetDashboard(ctx, q) 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 // This constructor should be preferred over the other two if the dashboard in available
// since it avoids querying the database for fetching the dashboard. // since it avoids querying the database for fetching the dashboard.
func NewAccessControlDashboardGuardianByDashboard( 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, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) { ) (DashboardGuardian, error) {
if dashboard != nil && dashboard.IsFolder { if dashboard != nil && dashboard.IsFolder {
@ -148,7 +148,7 @@ func NewAccessControlDashboardGuardianByDashboard(
// NewAccessControlFolderGuardian creates a folder guardian by the provided folder. // NewAccessControlFolderGuardian creates a folder guardian by the provided folder.
func NewAccessControlFolderGuardian( 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, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) { ) (DashboardGuardian, error) {
return &accessControlFolderGuardian{ return &accessControlFolderGuardian{
@ -168,7 +168,7 @@ type accessControlBaseGuardian struct {
cfg *setting.Cfg cfg *setting.Cfg
ctx context.Context ctx context.Context
log log.Logger log log.Logger
user *user.SignedInUser user identity.Requester
ac accesscontrol.AccessControl ac accesscontrol.AccessControl
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
} }
@ -309,12 +309,13 @@ func (a *accessControlFolderGuardian) CanCreate(folderID int64, isFolder bool) (
func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
if err != nil { if err != nil {
id := 0 id := 0
if a.dashboard != nil { if a.dashboard != nil {
id = int(a.dashboard.ID) 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 { if !ok && err == nil {
@ -322,7 +323,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
if a.dashboard != nil { if a.dashboard != nil {
id = int(a.dashboard.ID) 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 return ok, err
@ -330,6 +331,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
if err != nil { if err != nil {
uid := "" uid := ""
orgID := 0 orgID := 0
@ -337,7 +339,7 @@ func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator
uid = a.folder.UID uid = a.folder.UID
orgID = int(a.folder.OrgID) 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 { if !ok && err == nil {
@ -347,7 +349,7 @@ func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator
uid = a.folder.UID uid = a.folder.UID
orgID = int(a.folder.OrgID) 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 return ok, err
@ -357,7 +359,7 @@ func (a *accessControlDashboardGuardian) loadParentFolder(folderID int64) (*dash
if folderID == 0 { if folderID == 0 {
return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil 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) folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery)
if err != nil { if err != nil {
return nil, err return nil, err
@ -369,7 +371,7 @@ func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*dashboa
if folderID == 0 { if folderID == 0 {
return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil 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) folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery)
if err != nil { if err != nil {
return nil, err return nil, err

@ -3,9 +3,9 @@ package guardian
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
) )
@ -27,25 +27,25 @@ type DashboardGuardian interface {
// New factory for creating a new dashboard guardian instance // New factory for creating a new dashboard guardian instance
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned // 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") panic("no guardian factory implementation provided")
} }
// NewByUID factory for creating a new dashboard guardian instance // NewByUID factory for creating a new dashboard guardian instance
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned // 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") panic("no guardian factory implementation provided")
} }
// NewByDashboard factory for creating a new dashboard guardian instance // NewByDashboard factory for creating a new dashboard guardian instance
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned // 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") panic("no guardian factory implementation provided")
} }
// NewByFolder factory for creating a new folder guardian instance // NewByFolder factory for creating a new folder guardian instance
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned // 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") panic("no guardian factory implementation provided")
} }
@ -54,7 +54,7 @@ type FakeDashboardGuardian struct {
DashID int64 DashID int64
DashUID string DashUID string
OrgID int64 OrgID int64
User *user.SignedInUser User identity.Requester
CanSaveValue bool CanSaveValue bool
CanEditValue bool CanEditValue bool
CanViewValue bool CanViewValue bool
@ -87,21 +87,21 @@ func (g *FakeDashboardGuardian) CanCreate(_ int64, _ bool) (bool, error) {
// nolint:unused // nolint:unused
func MockDashboardGuardian(mock *FakeDashboardGuardian) { 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.OrgID = orgId
mock.DashID = dashID mock.DashID = dashID
mock.User = user mock.User = user
return mock, nil 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.OrgID = orgId
mock.DashUID = dashUID mock.DashUID = dashUID
mock.User = user mock.User = user
return mock, nil 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.OrgID = orgId
mock.DashUID = dash.UID mock.DashUID = dash.UID
mock.DashID = dash.ID mock.DashID = dash.ID
@ -109,7 +109,7 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) {
return mock, nil 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.OrgID = orgId
mock.DashUID = f.UID mock.DashUID = f.UID
mock.DashID = f.ID mock.DashID = f.ID

@ -4,10 +4,10 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/services/accesscontrol" "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/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -25,19 +25,19 @@ func ProvideService(
func InitAccessControlGuardian( func InitAccessControlGuardian(
cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, 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) 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) 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) 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) return NewAccessControlFolderGuardian(ctx, cfg, f, user, ac, dashboardService)
} }
} }

@ -6,8 +6,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "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/model"
"github.com/grafana/grafana/pkg/services/user"
) )
var ( var (
@ -37,13 +37,13 @@ func (b *BroadcastRunner) GetHandlerForPath(_ string) (model.ChannelHandler, err
} }
// OnSubscribe will let anyone connect to the path // 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{ reply := model.SubscribeReply{
Presence: true, Presence: true,
JoinLeave: true, JoinLeave: true,
} }
query := &model.GetLiveMessageQuery{ query := &model.GetLiveMessageQuery{
OrgID: u.OrgID, OrgID: u.GetOrgID(),
Channel: e.Channel, Channel: e.Channel,
} }
msg, ok, err := b.liveMessageStore.GetLiveMessage(query) 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 // 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{ query := &model.SaveLiveMessageQuery{
OrgID: u.OrgID, OrgID: u.GetOrgID(),
Channel: e.Channel, Channel: e.Channel,
Data: e.Data, Data: e.Data,
} }

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/db" "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/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/live/model" "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 // 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, "/") parts := strings.Split(e.Path, "/")
if parts[0] == "gitops" { if parts[0] == "gitops" {
// gitops gets all changes for everything, so lets make sure it is an admin user // 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 // make sure can view this dashboard
if len(parts) == 2 && parts[0] == "uid" { 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) queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
if err != nil { if err != nil {
logger.Error("Error getting dashboard", "query", query, "error", err) 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 dash := queryResult
guard, err := guardian.NewByDashboard(ctx, dash, user.OrgID, user) guard, err := guardian.NewByDashboard(ctx, dash, user.GetOrgID(), user)
if err != nil { if err != nil {
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err 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 // 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, "/") parts := strings.Split(e.Path, "/")
if parts[0] == "gitops" { if parts[0] == "gitops" {
// gitops gets all changes for everything, so lets make sure it is an admin user // 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 return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil
} }
@ -117,14 +118,14 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUse
// just ignore the event // just ignore the event
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???") 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) queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
if err != nil { if err != nil {
logger.Error("Unknown dashboard", "query", query) logger.Error("Unknown dashboard", "query", query)
return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil 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 { if err != nil {
logger.Error("Failed to create guardian", "err", err) logger.Error("Failed to create guardian", "err", err)
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error") 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 // 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) msg, err := json.Marshal(event)
if err != nil { if err != nil {

@ -7,17 +7,17 @@ import (
"github.com/centrifugal/centrifuge" "github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/model"
"github.com/grafana/grafana/pkg/services/live/orgchannel" "github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/live/runstream" "github.com/grafana/grafana/pkg/services/live/runstream"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "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 //go:generate mockgen -destination=plugin_mock.go -package=features github.com/grafana/grafana/pkg/services/live/features PluginContextGetter
type PluginContextGetter 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)
} }
// PluginRunner can handle streaming operations for channels belonging to plugins. // PluginRunner can handle streaming operations for channels belonging to plugins.
@ -63,7 +63,7 @@ type PluginPathRunner struct {
} }
// OnSubscribe passes control to a plugin. // 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) pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil { if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) { 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 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 { if err != nil {
logger.Error("Error submitting stream to manager", "error", err, "path", r.path) logger.Error("Error submitting stream to manager", "error", err, "path", r.path)
return model.SubscribeReply{}, 0, centrifuge.ErrorInternal 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. // 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) pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil { if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) { if errors.Is(err, plugincontext.ErrPluginNotFound) {

@ -10,7 +10,7 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend" 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. // MockPluginContextGetter is a mock of PluginContextGetter interface.
@ -37,7 +37,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
} }
// GetPluginContext mocks base method. // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4) ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext) ret0, _ := ret[0].(backend.PluginContext)

@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "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) { g.websocketHandler = func(ctx *contextmodel.ReqContext) {
user := ctx.SignedInUser 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. // Centrifuge expects Credentials in context with a current user ID.
cred := &centrifuge.Credentials{ cred := &centrifuge.Credentials{
UserID: fmt.Sprintf("%d", user.UserID), UserID: userID,
} }
newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred) newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred)
newCtx = livecontext.SetContextSignedUser(newCtx, user) newCtx = livecontext.SetContextSignedUser(newCtx, user)
@ -593,7 +599,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge.
return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal 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) logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel)
return centrifuge.SubscribeReply{}, centrifuge.ErrorPermissionDenied return centrifuge.SubscribeReply{}, centrifuge.ErrorPermissionDenied
} }
@ -603,7 +609,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge.
var ruleFound bool var ruleFound bool
if g.Pipeline != nil { 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 { if err != nil {
logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal
@ -694,13 +700,13 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl
return centrifuge.PublishReply{}, centrifuge.ErrorInternal 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) logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel)
return centrifuge.PublishReply{}, centrifuge.ErrorPermissionDenied return centrifuge.PublishReply{}, centrifuge.ErrorPermissionDenied
} }
if g.Pipeline != nil { 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 { if err != nil {
logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.PublishReply{}, centrifuge.ErrorInternal return centrifuge.PublishReply{}, centrifuge.ErrorInternal
@ -724,7 +730,7 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl
return centrifuge.PublishReply{}, &centrifuge.Error{Code: uint32(code), Message: text} return centrifuge.PublishReply{}, &centrifuge.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 { if err != nil {
logger.Error("Error processing input", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err) logger.Error("Error processing input", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.PublishReply{}, centrifuge.ErrorInternal return centrifuge.PublishReply{}, centrifuge.ErrorInternal
@ -805,7 +811,7 @@ func publishStatusToHTTPError(status backend.PublishStreamStatus) (int, string)
} }
// GetChannelHandler gives thread-safe access to the channel. // 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} // Parse the identifier ${scope}/${namespace}/${path}
addr, err := live.ParseChannel(channel) addr, err := live.ParseChannel(channel)
if err != nil { if err != nil {
@ -846,7 +852,7 @@ func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user *user.SignedIn
// GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace. // GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace.
// It gives thread-safe access to the channel. // 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 { switch scope {
case live.ScopeGrafana: case live.ScopeGrafana:
return g.handleGrafanaScope(user, namespace) 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 { if p, ok := g.GrafanaScope.Features[namespace]; ok {
return p, nil return p, nil
} }
return nil, fmt.Errorf("unknown feature: %q", namespace) 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) streamHandler, err := g.getStreamPlugin(ctx, namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't find stream plugin: %s", namespace) 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 ), nil
} }
func (g *GrafanaLive) handleStreamScope(u *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) { func (g *GrafanaLive) handleStreamScope(u identity.Requester, namespace string) (model.ChannelHandlerFactory, error) {
return g.ManagedStreamRunner.GetOrCreateStream(u.OrgID, live.ScopeStream, namespace) 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) ds, err := g.DataSourceCache.GetDatasourceByUID(ctx, namespace, user, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting datasource: %w", err) 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) 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 user := ctx.SignedInUser
channel := cmd.Channel channel := cmd.Channel
if g.Pipeline != nil { 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 { if err != nil {
logger.Error("Error getting channel rule", "user", user, "channel", channel, "error", err) logger.Error("Error getting channel rule", "user", user, "channel", channel, "error", err)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) 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) 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 { if err != nil {
logger.Error("Error processing input", "user", user, "channel", channel, "error", err) logger.Error("Error processing input", "user", user, "channel", channel, "error", err)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) 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) return response.Error(code, text, nil)
} }
if reply.Data != 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 { if err != nil {
logger.Error("Error publish to channel", "error", err, "channel", cmd.Channel) logger.Error("Error publish to channel", "error", err, "channel", cmd.Channel)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) 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{}) 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 channels []*managedstream.ManagedChannel
var err error var err error
if g.IsHA() { if g.IsHA() {
channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.OrgID) channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.GetOrgID())
} else { } else {
channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.OrgID) channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.GetOrgID())
} }
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err) 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)["*"] path := web.Params(ctx.Req)["*"]
if path == "grafana/dashboards/gitops" { if path == "grafana/dashboards/gitops" {
return response.JSON(http.StatusOK, util.DynMap{ 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{ return response.JSONStreaming(404, util.DynMap{
@ -1027,7 +1034,7 @@ func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Resp
// HandleChannelRulesListHTTP ... // HandleChannelRulesListHTTP ...
func (g *GrafanaLive) HandleChannelRulesListHTTP(c *contextmodel.ReqContext) response.Response { 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get channel rules", err) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Error creating pipeline", err) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Error getting channel rule", err) 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 { if rule.Converter == nil {
return response.Error(http.StatusNotFound, "No converter found", 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Error converting data", err) return response.Error(http.StatusInternalServerError, "Error converting data", err)
} }
@ -1142,7 +1149,7 @@ func (g *GrafanaLive) HandleChannelRulesPostHTTP(c *contextmodel.ReqContext) res
if err != nil { if err != nil {
return response.Error(http.StatusBadRequest, "Error decoding channel rule", err) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to create channel rule", err) 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 == "" { if cmd.Pattern == "" {
return response.Error(http.StatusBadRequest, "Rule pattern required", nil) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to update channel rule", err) 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 == "" { if cmd.Pattern == "" {
return response.Error(http.StatusBadRequest, "Rule pattern required", nil) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to delete channel rule", err) return response.Error(http.StatusInternalServerError, "Failed to delete channel rule", err)
} }
@ -1208,7 +1215,7 @@ func (g *GrafanaLive) HandlePipelineEntitiesListHTTP(_ *contextmodel.ReqContext)
// HandleWriteConfigsListHTTP ... // HandleWriteConfigsListHTTP ...
func (g *GrafanaLive) HandleWriteConfigsListHTTP(c *contextmodel.ReqContext) response.Response { 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get write configs", err) 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 { if err != nil {
return response.Error(http.StatusBadRequest, "Error decoding write config create command", err) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to create write config", err) 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 == "" { if cmd.UID == "" {
return response.Error(http.StatusBadRequest, "UID required", nil) 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, UID: cmd.UID,
}) })
if err != nil { 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to update write config", err) 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 == "" { if cmd.UID == "" {
return response.Error(http.StatusBadRequest, "UID required", nil) 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 { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to delete write config", err) return response.Error(http.StatusInternalServerError, "Failed to delete write config", err)
} }

@ -3,21 +3,21 @@ package livecontext
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/auth/identity"
) )
type signedUserContextKeyType int type signedUserContextKeyType int
var signedUserContextKey signedUserContextKeyType 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) ctx = context.WithValue(ctx, signedUserContextKey, user)
return ctx 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 { if val := ctx.Value(signedUserContextKey); val != nil {
user, ok := val.(*user.SignedInUser) user, ok := val.(identity.Requester)
return user, ok return user, ok
} }
return nil, false return nil, false

@ -7,11 +7,11 @@ import (
"github.com/centrifugal/centrifuge" "github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/datasources"
"github.com/grafana/grafana/pkg/services/live/orgchannel" "github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/live/pipeline" "github.com/grafana/grafana/pkg/services/live/pipeline"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
) )
type ChannelLocalPublisher struct { 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 == "" { 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) ds, err := g.dataSourceCache.GetDatasourceByUID(ctx, datasourceUID, user, skipCache)

@ -13,9 +13,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/live" "github.com/grafana/grafana-plugin-sdk-go/live"
"github.com/grafana/grafana/pkg/infra/log" "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/model"
"github.com/grafana/grafana/pkg/services/live/orgchannel" "github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/user"
) )
var ( var (
@ -239,9 +239,9 @@ func (s *NamespaceStream) GetHandlerForPath(_ string) (model.ChannelHandler, err
return s, nil 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{} 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 { if err != nil {
return reply, 0, err return reply, 0, err
} }
@ -251,6 +251,6 @@ func (s *NamespaceStream) OnSubscribe(ctx context.Context, u *user.SignedInUser,
return reply, backend.SubscribeStreamStatusOK, nil 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 return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil
} }

@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "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. // 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 // ChannelHandler defines the core channel behavior
type ChannelHandler interface { type ChannelHandler interface {
// OnSubscribe is called when a client wants to subscribe to a channel // 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 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. // ChannelHandlerFactory should be implemented by all core features.

@ -3,8 +3,8 @@ package pipeline
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
) )
type RoleCheckAuthorizer struct { type RoleCheckAuthorizer struct {
@ -15,10 +15,10 @@ func NewRoleCheckAuthorizer(role org.RoleType) *RoleCheckAuthorizer {
return &RoleCheckAuthorizer{role: role} 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 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 return u.HasRole(s.role), nil
} }

@ -17,8 +17,8 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.4.0" semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace" "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/live/model"
"github.com/grafana/grafana/pkg/services/user"
) )
const ( const (
@ -113,12 +113,12 @@ type Subscriber interface {
// PublishAuthChecker checks whether current user can publish to a channel. // PublishAuthChecker checks whether current user can publish to a channel.
type PublishAuthChecker interface { 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. // SubscribeAuthChecker checks whether current user can subscribe to a channel.
type SubscribeAuthChecker interface { 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. // LiveChannelRule is an in-memory representation of each specific rule to be executed by Pipeline.

@ -6,9 +6,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/live" "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/livecontext"
"github.com/grafana/grafana/pkg/services/live/model" "github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/user"
) )
type BuiltinSubscriber struct { type BuiltinSubscriber struct {
@ -16,7 +16,7 @@ type BuiltinSubscriber struct {
} }
type ChannelHandlerGetter interface { 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" const SubscriberTypeBuiltin = "builtin"

@ -71,7 +71,7 @@ func (s *PipelinePushHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
"bodyLength", len(body), "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 { if err != nil {
logger.Error("Pipeline input processing error", "error", err, "body", string(body)) logger.Error("Pipeline input processing error", "error", err, "body", string(body))
return return

@ -67,7 +67,7 @@ func (s *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
break break
} }
stream, err := s.managedStreamRunner.GetOrCreateStream(user.OrgID, liveDto.ScopeStream, streamID) stream, err := s.managedStreamRunner.GetOrCreateStream(user.GetOrgID(), liveDto.ScopeStream, streamID)
if err != nil { if err != nil {
logger.Error("Error getting stream", "error", err) logger.Error("Error getting stream", "error", err)
continue continue

@ -11,8 +11,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "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/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
) )
var ( var (
@ -26,7 +26,7 @@ type ChannelLocalPublisher interface {
} }
type PluginContextGetter 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 { type NumLocalSubscribersGetter interface {
@ -374,7 +374,7 @@ func (s *Manager) Run(ctx context.Context) error {
type streamRequest struct { type streamRequest struct {
Channel string Channel string
Path string Path string
user *user.SignedInUser user identity.Requester
PluginContext backend.PluginContext PluginContext backend.PluginContext
StreamRunner StreamRunner StreamRunner StreamRunner
Data []byte Data []byte
@ -401,7 +401,7 @@ var errDatasourceNotFound = errors.New("datasource not found")
// SubmitStream submits stream handler in Manager to manage. // SubmitStream submits stream handler in Manager to manage.
// The stream will be opened and kept till channel has active subscribers. // 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 { if isResubmit {
// Resolve new plugin context as it could be modified since last call. // Resolve new plugin context as it could be modified since last call.
var datasourceUID string var datasourceUID string

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user" "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) { 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) {
require.Equal(t, int64(2), user.UserID) userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.Equal(t, int64(1), user.OrgID) 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.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil 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("1/test", gomock.Any()).Times(1)
mockPacketSender.EXPECT().PublishLocal("2/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 return backend.PluginContext{}, true, nil
}).Times(0) }).Times(0)
@ -205,7 +208,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) {
startedCh := make(chan struct{}) startedCh := make(chan struct{})
doneCh := 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 return backend.PluginContext{}, true, nil
}).Times(0) }).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) { 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) {
require.Equal(t, int64(2), user.UserID) userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.Equal(t, int64(1), user.OrgID) 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.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil return testPluginContext, true, nil
@ -296,7 +301,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) {
_ = manager.Run(ctx) _ = 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 return backend.PluginContext{}, true, nil
}).Times(0) }).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) { 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) {
require.Equal(t, int64(2), user.UserID) userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.Equal(t, int64(1), user.OrgID) 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.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil 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) { 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) {
require.Equal(t, int64(2), user.UserID) userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.Equal(t, int64(1), user.OrgID) 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.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID) require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil return testPluginContext, true, nil

@ -10,7 +10,7 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend" 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. // MockChannelLocalPublisher is a mock of ChannelLocalPublisher interface.
@ -149,7 +149,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
} }
// GetPluginContext mocks base method. // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4) ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext) ret0, _ := ret[0].(backend.PluginContext)

@ -9,10 +9,10 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -114,7 +114,7 @@ type fakeCacheService struct {
err error 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 { if f.err != nil {
return nil, f.err return nil, f.err
} }
@ -122,7 +122,7 @@ func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _
return f.datasource, nil 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 { if f.err != nil {
return nil, f.err return nil, f.err
} }

@ -7,8 +7,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/datasources"
"github.com/grafana/grafana/pkg/services/user"
) )
// ModelToInstanceSettings converts a datasources.DataSource to a backend.DataSourceInstanceSettings. // ModelToInstanceSettings converts a datasources.DataSource to a backend.DataSourceInstanceSettings.
@ -43,16 +43,16 @@ func ModelToInstanceSettings(ds *datasources.DataSource, decryptFn func(ds *data
}, err }, err
} }
// BackendUserFromSignedInUser converts Grafana's SignedInUser model // BackendUserFromSignedInUser converts Grafana's context request identity
// to the backend plugin's model. // to the backend plugin's model.
func BackendUserFromSignedInUser(su *user.SignedInUser) *backend.User { func BackendUserFromSignedInUser(requester identity.Requester) *backend.User {
if su == nil { if requester == nil {
return nil return nil
} }
return &backend.User{ return &backend.User{
Login: su.Login, Login: requester.GetLogin(),
Name: su.Name, Name: requester.GetDisplayName(),
Email: su.Email, Email: requester.GetEmail(),
Role: string(su.OrgRole), Role: string(requester.GetOrgRole()),
} }
} }

@ -11,10 +11,10 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/plugins" "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/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/adapters" "github.com/grafana/grafana/pkg/services/pluginsintegration/adapters"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
"github.com/grafana/grafana/pkg/services/user"
) )
var ErrPluginNotFound = errors.New("plugin not found") 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 // Get allows getting plugin context by its ID. If datasourceUID is not empty string
// then PluginContext.DataSourceInstanceSettings will be resolved and appended to // then PluginContext.DataSourceInstanceSettings will be resolved and appended to
// returned context. // returned context.
// Note: *user.SignedInUser can be nil. // Note: identity.Requester can be nil.
func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) { func (p *Provider) Get(ctx context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID) plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists { if !exists {
return backend.PluginContext{}, ErrPluginNotFound return backend.PluginContext{}, ErrPluginNotFound
@ -50,7 +50,7 @@ func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedIn
PluginID: pluginID, PluginID: pluginID,
} }
if user != nil { if user != nil {
pCtx.OrgID = user.OrgID pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user) 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 // GetWithDataSource allows getting plugin context by its ID and PluginContext.DataSourceInstanceSettings will be
// resolved and appended to the returned context. // resolved and appended to the returned context.
// Note: *user.SignedInUser can be nil. // 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) _, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists { if !exists {
return backend.PluginContext{}, ErrPluginNotFound return backend.PluginContext{}, ErrPluginNotFound
@ -78,7 +78,7 @@ func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user
PluginID: pluginID, PluginID: pluginID,
} }
if user != nil { if user != nil {
pCtx.OrgID = user.OrgID pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user) pCtx.User = adapters.BackendUserFromSignedInUser(user)
} }

@ -16,10 +16,10 @@ import (
"github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins" "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/contexthandler"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "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/services/validations"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafanads" "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 //go:generate mockery --name Service --structname FakeQueryService --inpackage --filename query_service_mock.go
type Service interface { type Service interface {
Run(ctx context.Context) error 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 // 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. // 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 // Parse the request into parsed queries grouped by datasource uid
parsedReq, err := s.parseMetricRequest(ctx, user, skipDSCache, reqDTO) parsedReq, err := s.parseMetricRequest(ctx, user, skipDSCache, reqDTO)
if err != nil { if err != nil {
@ -111,7 +111,7 @@ type splitResponse struct {
} }
// executeConcurrentQueries executes queries to multiple datasources concurrently and returns the aggregate result. // 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, ctx := errgroup.WithContext(ctx)
g.SetLimit(s.concurrentQueryLimit) // prevent too many concurrent requests g.SetLimit(s.concurrentQueryLimit) // prevent too many concurrent requests
rchan := make(chan splitResponse, len(queriesbyDs)) 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. // 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{ exprReq := expr.Request{
Queries: []expr.Query{}, Queries: []expr.Query{},
} }
if user != nil { // for passthrough authentication, SSE does not authenticate if user != nil { // for passthrough authentication, SSE does not authenticate
exprReq.User = user exprReq.User = user
exprReq.OrgId = user.OrgID exprReq.OrgId = user.GetOrgID()
} }
for _, pq := range parsedReq.getFlattenedQueries() { 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 // 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() queries := parsedReq.getFlattenedQueries()
ds := queries[0].datasource ds := queries[0].datasource
if err := s.pluginRequestValidator.Validate(ds.URL, nil); err != nil { 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 // 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 { if len(reqDTO.Queries) == 0 {
return nil, ErrNoQueriesFound return nil, ErrNoQueriesFound
} }
@ -332,7 +332,7 @@ func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user *user.SignedI
return req, req.validateRequest(ctx) 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 var err error
uid := query.Get("datasource").Get("uid").MustString() 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 { if uid == grafanads.DatasourceUID {
return grafanads.DataSourceModel(user.OrgID), nil return grafanads.DataSourceModel(user.GetOrgID()), nil
} }
if uid != "" { if uid != "" {

@ -11,7 +11,7 @@ import (
mock "github.com/stretchr/testify/mock" 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 // 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 // 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) ret := _m.Called(ctx, _a1, skipDSCache, reqDTO)
var r0 *backend.QueryDataResponse 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) r0 = rf(ctx, _a1, skipDSCache, reqDTO)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
@ -33,7 +33,7 @@ func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 *user.SignedInUse
} }
var r1 error 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) r1 = rf(ctx, _a1, skipDSCache, reqDTO)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)

@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
pluginFakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" 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"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
@ -524,12 +525,12 @@ type fakeDataSourceCache struct {
cache []*datasources.DataSource 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 // deprecated: fake an error to ensure we are using GetDatasourceByUID
return nil, fmt.Errorf("not found") 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 { for _, ds := range c.cache {
if ds.UID == datasourceUID { if ds.UID == datasourceUID {
return ds, nil return ds, nil

@ -43,6 +43,9 @@ func (u *SignedInUser) NameOrFallback() string {
return u.Email 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 { func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO {
return &UserDisplayDTO{ return &UserDisplayDTO{
ID: u.UserID, 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 { func (u *SignedInUser) HasRole(role roletype.RoleType) bool {
if u.IsGrafanaAdmin { if u.IsGrafanaAdmin {
return true return true

Loading…
Cancel
Save