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/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
@ -44,15 +45,27 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI
return false, nil
}
query := star.IsStarredByUserQuery{UserID: c.UserID, DashboardID: dashID}
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser {
return false, nil
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
return false, err
}
query := star.IsStarredByUserQuery{UserID: userID, DashboardID: dashID}
return hs.starService.IsStarredByUser(c.Req.Context(), &query)
}
func dashboardGuardianResponse(err error) response.Response {
if err != nil {
return response.Error(500, "Error while checking dashboard permissions", err)
return response.Error(http.StatusInternalServerError, "Error while checking dashboard permissions", err)
}
return response.Error(403, "Access denied to this dashboard", nil)
return response.Error(http.StatusForbidden, "Access denied to this dashboard", nil)
}
// swagger:route POST /dashboards/trim dashboards trimDashboard
@ -95,7 +108,7 @@ func (hs *HTTPServer) TrimDashboard(c *contextmodel.ReqContext) response.Respons
// 500: internalServerError
func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response {
uid := web.Params(c.Req)[":uid"]
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, 0, uid)
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, uid)
if rsp != nil {
return rsp
}
@ -108,9 +121,9 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
// If public dashboards is enabled and we have a public dashboard, update meta
// values
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) {
publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.OrgID, dash.UID)
publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.SignedInUser.GetOrgID(), dash.UID)
if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) {
return response.Error(500, "Error while retrieving public dashboards", err)
return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err)
}
if publicDashboard != nil {
@ -128,10 +141,10 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
}
}
if isEmptyData {
return response.Error(500, "Error while loading dashboard, dashboard data is invalid", nil)
return response.Error(http.StatusInternalServerError, "Error while loading dashboard, dashboard data is invalid", nil)
}
}
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser)
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -146,7 +159,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
isStarred, err := hs.isDashboardStarredByUser(c, dash.ID)
if err != nil {
return response.Error(500, "Error while checking if dashboard was starred by user", err)
return response.Error(http.StatusInternalServerError, "Error while checking if dashboard was starred by user", err)
}
// Finding creator and last updater of the dashboard
updater, creator := anonString, anonString
@ -186,13 +199,13 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
// lookup folder title
if dash.FolderID > 0 {
query := dashboards.GetDashboardQuery{ID: dash.FolderID, OrgID: c.OrgID}
query := dashboards.GetDashboardQuery{ID: dash.FolderID, OrgID: c.SignedInUser.GetOrgID()}
queryResult, err := hs.DashboardService.GetDashboard(c.Req.Context(), &query)
if err != nil {
if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(404, "Folder not found", err)
return response.Error(http.StatusNotFound, "Folder not found", err)
}
return response.Error(500, "Dashboard folder could not be read", err)
return response.Error(http.StatusInternalServerError, "Dashboard folder could not be read", err)
}
meta.FolderUid = queryResult.UID
meta.FolderTitle = queryResult.Title
@ -201,7 +214,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
provisioningData, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID)
if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned", err)
return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned", err)
}
if provisioningData != nil {
@ -275,7 +288,7 @@ func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id in
queryResult, err := hs.DashboardService.GetDashboard(ctx, &query)
if err != nil {
return nil, response.Error(404, "Dashboard not found", err)
return nil, response.Error(http.StatusNotFound, "Dashboard not found", err)
}
return queryResult, nil
@ -298,11 +311,11 @@ func (hs *HTTPServer) DeleteDashboardByUID(c *contextmodel.ReqContext) response.
}
func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Response {
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, 0, web.Params(c.Req)[":uid"])
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), 0, web.Params(c.Req)[":uid"])
if rsp != nil {
return rsp
}
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser)
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -311,10 +324,17 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
return dashboardGuardianResponse(err)
}
namespaceID, userIDStr := c.SignedInUser.GetNamespacedID()
// disconnect all library elements for this dashboard
err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID)
if err != nil {
hs.log.Error("Failed to disconnect library elements", "dashboard", dash.ID, "user", c.SignedInUser.UserID, "error", err)
hs.log.Error(
"Failed to disconnect library elements",
"dashboard", dash.ID,
"namespaceID", namespaceID,
"user", userIDStr,
"error", err)
}
// deletes all related public dashboard entities
@ -323,7 +343,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
hs.log.Error("Failed to delete public dashboard")
}
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.OrgID)
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.SignedInUser.GetOrgID())
if err != nil {
var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok {
@ -331,11 +351,16 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
}
}
return response.Error(500, "Failed to delete dashboard", err)
return response.Error(http.StatusInternalServerError, "Failed to delete dashboard", err)
}
userDTODisplay, err := user.NewUserDisplayDTOFromRequester(c.SignedInUser)
if err != nil {
return response.Error(http.StatusInternalServerError, "Error while parsing the user DTO model", err)
}
if hs.Live != nil {
err := hs.Live.GrafanaScope.Dashboards.DashboardDeleted(c.OrgID, c.ToUserDisplayDTO(), dash.UID)
err := hs.Live.GrafanaScope.Dashboards.DashboardDeleted(c.SignedInUser.GetOrgID(), userDTODisplay, dash.UID)
if err != nil {
hs.log.Error("Failed to broadcast delete info", "dashboard", dash.UID, "error", err)
}
@ -396,19 +421,30 @@ func (hs *HTTPServer) PostDashboard(c *contextmodel.ReqContext) response.Respons
func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.SaveDashboardCommand) response.Response {
ctx := c.Req.Context()
var err error
cmd.OrgID = c.OrgID
cmd.UserID = c.UserID
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
hs.log.Warn("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr)
return response.Error(http.StatusBadRequest, "User does not belong to a user or service account namespace", nil)
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
hs.log.Warn("Error while parsing user ID", "namespaceID", namespaceID, "userID", userIDstr)
}
cmd.OrgID = c.SignedInUser.GetOrgID()
cmd.UserID = userID
if cmd.FolderUID != "" {
folder, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.OrgID,
OrgID: c.SignedInUser.GetOrgID(),
UID: &cmd.FolderUID,
SignedInUser: c.SignedInUser,
})
if err != nil {
if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(400, "Folder not found", err)
return response.Error(http.StatusBadRequest, "Folder not found", err)
}
return response.Error(500, "Error while checking folder ID", err)
return response.Error(http.StatusInternalServerError, "Error while checking folder ID", err)
}
cmd.FolderID = folder.ID
}
@ -418,10 +454,10 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
if newDashboard {
limitReached, err := hs.QuotaService.QuotaReached(c, dashboards.QuotaTargetSrv)
if err != nil {
return response.Error(500, "failed to get quota", err)
return response.Error(http.StatusInternalServerError, "failed to get quota", err)
}
if limitReached {
return response.Error(403, "Quota reached", nil)
return response.Error(http.StatusForbidden, "Quota reached", nil)
}
}
@ -429,13 +465,13 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
if dash.ID != 0 {
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(c.Req.Context(), dash.ID)
if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned using ID", err)
return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned using ID", err)
}
provisioningData = data
} else if dash.UID != "" {
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(c.Req.Context(), dash.OrgID, dash.UID)
if err != nil && !errors.Is(err, dashboards.ErrProvisionedDashboardNotFound) && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return response.Error(500, "Error while checking if dashboard is provisioned", err)
return response.Error(http.StatusInternalServerError, "Error while checking if dashboard is provisioned", err)
}
provisioningData = data
}
@ -448,7 +484,7 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgID: c.OrgID,
OrgID: c.SignedInUser.GetOrgID(),
User: c.SignedInUser,
Overwrite: cmd.Overwrite,
}
@ -461,14 +497,19 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
dashboard = dash // the original request
}
userDTODisplay, err := user.NewUserDisplayDTOFromRequester(c.SignedInUser)
if err != nil {
return response.Error(http.StatusInternalServerError, "Error while parsing the user DTO model", err)
}
// This will broadcast all save requests only if a `gitops` observer exists.
// gitops is useful when trying to save dashboards in an environment where the user can not save
channel := hs.Live.GrafanaScope.Dashboards
liveerr := channel.DashboardSaved(c.SignedInUser.OrgID, c.SignedInUser.ToUserDisplayDTO(), cmd.Message, dashboard, err)
liveerr := channel.DashboardSaved(c.SignedInUser.GetOrgID(), userDTODisplay, cmd.Message, dashboard, err)
// When an error exists, but the value broadcast to a gitops listener return 202
if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.OrgID) {
return response.JSON(202, util.DynMap{
if liveerr == nil && err != nil && channel.HasGitOpsObserver(c.SignedInUser.GetOrgID()) {
return response.JSON(http.StatusAccepted, util.DynMap{
"status": "pending",
"message": "changes were broadcast to the gitops listener",
})
@ -492,7 +533,7 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
// connect library panels for this dashboard after the dashboard is stored and has an ID
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard)
if err != nil {
return response.Error(500, "Error while connecting library panels", err)
return response.Error(http.StatusInternalServerError, "Error while connecting library panels", err)
}
c.TimeRequest(metrics.MApiDashboardSave)
@ -515,12 +556,22 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
// 401: unauthorisedError
// 500: internalServerError
func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Response {
prefsQuery := pref.GetPreferenceWithDefaultsQuery{OrgID: c.OrgID, UserID: c.SignedInUser.UserID, Teams: c.Teams}
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser {
return response.Error(http.StatusBadRequest, "User does not belong to a user namespace", nil)
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
hs.log.Warn("Error while parsing user ID", "namespaceID", namespaceID, "userID", userIDstr, "err", err)
}
prefsQuery := pref.GetPreferenceWithDefaultsQuery{OrgID: c.SignedInUser.GetOrgID(), UserID: userID, Teams: c.SignedInUser.GetTeams()}
homePage := hs.Cfg.HomePage
preference, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery)
if err != nil {
return response.Error(500, "Failed to get preferences", err)
return response.Error(http.StatusInternalServerError, "Failed to get preferences", err)
}
if preference.HomeDashboardID == 0 && len(homePage) > 0 {
@ -549,7 +600,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp
// nolint:gosec
file, err := os.Open(filePath)
if err != nil {
return response.Error(500, "Failed to load home dashboard", err)
return response.Error(http.StatusInternalServerError, "Failed to load home dashboard", err)
}
defer func() {
if err := file.Close(); err != nil {
@ -564,7 +615,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Resp
jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(dash.Dashboard); err != nil {
return response.Error(500, "Failed to load home dashboard", err)
return response.Error(http.StatusInternalServerError, "Failed to load home dashboard", err)
}
hs.addGettingStartedPanelToHomeDashboard(c, dash.Dashboard)
@ -636,12 +687,12 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response.
}
}
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID)
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID)
if rsp != nil {
return rsp
}
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser)
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -650,7 +701,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response.
}
query := dashver.ListDashboardVersionsQuery{
OrgID: c.OrgID,
OrgID: c.SignedInUser.GetOrgID(),
DashboardID: dash.ID,
DashboardUID: dash.UID,
Limit: c.QueryInt("limit"),
@ -659,7 +710,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *contextmodel.ReqContext) response.
versions, err := hs.dashboardVersionService.List(c.Req.Context(), &query)
if err != nil {
return response.Error(404, fmt.Sprintf("No versions found for dashboardId %d", dash.ID), err)
return response.Error(http.StatusNotFound, fmt.Sprintf("No versions found for dashboardId %d", dash.ID), err)
}
loginMem := make(map[int64]string, len(versions))
@ -747,12 +798,12 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
}
}
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID)
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID)
if rsp != nil {
return rsp
}
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser)
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -763,7 +814,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
version, _ := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 32)
query := dashver.GetDashboardVersionQuery{
OrgID: c.OrgID,
OrgID: c.SignedInUser.GetOrgID(),
DashboardID: dash.ID,
DashboardUID: dash.UID,
Version: int(version),
@ -771,7 +822,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
res, err := hs.dashboardVersionService.Get(c.Req.Context(), &query)
if err != nil {
return response.Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dash.ID), err)
return response.Error(http.StatusInternalServerError, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dash.ID), err)
}
creator := anonString
@ -879,7 +930,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
if err := web.Bind(c.Req, &apiOptions); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
guardianBase, err := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.OrgID, c.SignedInUser)
guardianBase, err := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -889,7 +940,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
}
if apiOptions.Base.DashboardId != apiOptions.New.DashboardId {
guardianNew, err := guardian.New(c.Req.Context(), apiOptions.New.DashboardId, c.OrgID, c.SignedInUser)
guardianNew, err := guardian.New(c.Req.Context(), apiOptions.New.DashboardId, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -900,7 +951,7 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
}
options := dashdiffs.Options{
OrgId: c.OrgID,
OrgId: c.SignedInUser.GetOrgID(),
DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType),
Base: dashdiffs.DiffTarget{
DashboardId: apiOptions.Base.DashboardId,
@ -923,9 +974,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
baseVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &baseVersionQuery)
if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) {
return response.Error(404, "Dashboard version not found", err)
return response.Error(http.StatusNotFound, "Dashboard version not found", err)
}
return response.Error(500, "Unable to compute diff", err)
return response.Error(http.StatusInternalServerError, "Unable to compute diff", err)
}
newVersionQuery := dashver.GetDashboardVersionQuery{
@ -937,9 +988,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
newVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &newVersionQuery)
if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) {
return response.Error(404, "Dashboard version not found", err)
return response.Error(http.StatusNotFound, "Dashboard version not found", err)
}
return response.Error(500, "Unable to compute diff", err)
return response.Error(http.StatusInternalServerError, "Unable to compute diff", err)
}
baseData := baseVersionRes.Data
@ -949,9 +1000,9 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) respons
if err != nil {
if errors.Is(err, dashver.ErrDashboardVersionNotFound) {
return response.Error(404, "Dashboard version not found", err)
return response.Error(http.StatusNotFound, "Dashboard version not found", err)
}
return response.Error(500, "Unable to compute diff", err)
return response.Error(http.StatusInternalServerError, "Unable to compute diff", err)
}
if options.DiffType == dashdiffs.DiffDelta {
@ -1003,12 +1054,12 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
}
}
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgID, dashID, dashUID)
dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID)
if rsp != nil {
return rsp
}
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.OrgID, c.SignedInUser)
guardian, err := guardian.NewByDashboard(c.Req.Context(), dash, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return response.Err(err)
}
@ -1017,16 +1068,25 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
return dashboardGuardianResponse(err)
}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.OrgID}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.SignedInUser.GetOrgID()}
version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery)
if err != nil {
return response.Error(404, "Dashboard version not found", nil)
return response.Error(http.StatusNotFound, "Dashboard version not found", nil)
}
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
return response.Error(http.StatusBadRequest, "User does not belong to a user or service namespace", nil)
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get user id", err)
}
saveCmd := dashboards.SaveDashboardCommand{}
saveCmd.RestoredFrom = version.Version
saveCmd.OrgID = c.OrgID
saveCmd.UserID = c.UserID
saveCmd.OrgID = c.SignedInUser.GetOrgID()
saveCmd.UserID = userID
saveCmd.Dashboard = version.Data
saveCmd.Dashboard.Set("version", dash.Version)
saveCmd.Dashboard.Set("uid", dash.UID)
@ -1045,7 +1105,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
// 401: unauthorisedError
// 500: internalServerError
func (hs *HTTPServer) GetDashboardTags(c *contextmodel.ReqContext) {
query := dashboards.GetDashboardTagsQuery{OrgID: c.OrgID}
query := dashboards.GetDashboardTagsQuery{OrgID: c.SignedInUser.GetOrgID()}
queryResult, err := hs.DashboardService.GetDashboardTags(c.Req.Context(), &query)
if err != nil {
c.JsonApiErr(500, "Failed to get tags from database", err)

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

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

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

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

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

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

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

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@ -21,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
)
var (
@ -181,12 +183,17 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
}
}
userID, err := resolveUserID(dto.User, dr.log)
if err != nil {
return nil, err
}
cmd := &dashboards.SaveDashboardCommand{
Dashboard: dash.Data,
Message: dto.Message,
OrgID: dto.OrgID,
Overwrite: dto.Overwrite,
UserID: dto.User.UserID,
UserID: userID,
FolderID: dash.FolderID,
IsFolder: dash.IsFolder,
PluginID: dash.PluginID,
@ -199,6 +206,20 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
return cmd, nil
}
func resolveUserID(user identity.Requester, log log.Logger) (int64, error) {
namespaceID, identifier := user.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
return 0, errutil.BadRequest("account doesn't belong to the user or service namespace")
}
userID, err := identity.IntIdentifier(namespaceID, identifier)
if err != nil {
log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", identifier, "error", err)
}
return userID, nil
}
func (dr *DashboardServiceImpl) UpdateDashboardACL(ctx context.Context, uid int64, items []*dashboards.DashboardACL) error {
return dr.dashboardStore.UpdateDashboardACL(ctx, uid, items)
}
@ -209,7 +230,7 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.
// getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck()
func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user *user.SignedInUser) (guardian.DashboardGuardian, error) {
func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) {
newDashboard := d.ID == 0
if newDashboard {
@ -297,7 +318,8 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
if dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err)
namespaceID, userID := dto.User.GetNamespacedID()
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
}
}
@ -337,7 +359,8 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
if dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err)
namespaceID, userID := dto.User.GetNamespacedID()
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
}
}
@ -385,7 +408,8 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
// new dashboard created
if dto.Dashboard.ID == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err)
namespaceID, userID := dto.User.GetNamespacedID()
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
}
}
@ -442,7 +466,8 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
}
if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserID, "error", err)
namespaceID, userID := dto.User.GetNamespacedID()
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
}
return dash, nil
@ -462,9 +487,16 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
inFolder := dash.FolderID > 0
var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned && dto.User.IsRealUser() && !dto.User.IsAnonymous {
namespaceID, userIDstr := dto.User.GetNamespacedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
return err
}
if !provisioned && namespaceID == identity.NamespaceUser {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: dto.User.UserID, Permission: dashboards.PERMISSION_ADMIN.String(),
UserID: userID, Permission: dashboards.PERMISSION_ADMIN.String(),
})
}
@ -480,7 +512,7 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
svc = dr.folderPermissions
}
_, err := svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...)
_, err = svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...)
if err != nil {
return err
}

@ -15,6 +15,7 @@ import (
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/alerting/models"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@ -112,9 +113,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, "", sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When creating a new dashboard in other folder, it should create dashboard guardian for other folder with correct arguments and rsult in access denied error",
@ -132,9 +136,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When creating a new dashboard by existing title in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -152,9 +159,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When creating a new dashboard by existing UID in folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -173,9 +183,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When updating a dashboard by existing id in the General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -194,9 +207,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When updating a dashboard by existing id in other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -215,9 +231,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When moving a dashboard by existing ID to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -236,9 +255,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When moving a dashboard by existing id to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -257,9 +279,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When moving a dashboard by existing uid to other folder from General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -278,9 +303,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
permissionScenario(t, "When moving a dashboard by existing UID to the General folder from other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
@ -299,9 +327,12 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID())
require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
assert.Equal(t, cmd.OrgID, sc.dashboardGuardianMock.OrgID)
assert.Equal(t, cmd.UserID, sc.dashboardGuardianMock.User.UserID)
assert.Equal(t, cmd.UserID, userID)
})
})

@ -7,7 +7,7 @@ import (
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
// DataSourceService interface for interacting with datasources.
@ -64,8 +64,8 @@ type DataSourceService interface {
// CacheService interface for retrieving a cached datasource.
type CacheService interface {
// GetDatasource gets a datasource identified by datasource numeric identifier.
GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*DataSource, error)
GetDatasource(ctx context.Context, datasourceID int64, user identity.Requester, skipCache bool) (*DataSource, error)
// GetDatasourceByUID gets a datasource identified by datasource unique identifier (UID).
GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*DataSource, error)
GetDatasourceByUID(ctx context.Context, datasourceUID string, user identity.Requester, skipCache bool) (*DataSource, error)
}

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

@ -1,12 +1,12 @@
package guardian
import (
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
)
type DatasourceGuardianProvider interface {
New(orgID int64, user *user.SignedInUser, dataSources ...datasources.DataSource) DatasourceGuardian
New(orgID int64, user identity.Requester, dataSources ...datasources.DataSource) DatasourceGuardian
}
type DatasourceGuardian interface {
@ -20,6 +20,6 @@ func ProvideGuardian() *OSSProvider {
type OSSProvider struct{}
func (p *OSSProvider) New(orgID int64, user *user.SignedInUser, dataSources ...datasources.DataSource) DatasourceGuardian {
func (p *OSSProvider) New(orgID int64, user identity.Requester, dataSources ...datasources.DataSource) DatasourceGuardian {
return &AllowGuardian{}
}

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

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@ -280,7 +281,16 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
dashFolder.SetUID(trimmedUID)
user := cmd.SignedInUser
userID := user.UserID
namespaceID, userIDstr := user.GetNamespacedID()
if namespaceID == identity.NamespaceAPIKey {
s.log.Warn("namespace API key detected, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
userIDstr = "0"
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
s.log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", userIDstr, "error", err)
}
if userID == 0 {
userID = -1
}
@ -763,12 +773,22 @@ func (s *Service) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards
}
}
namespaceID, userIDstr := dto.User.GetNamespacedID()
if namespaceID == identity.NamespaceAPIKey {
s.log.Warn("namespace API key detected, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
userIDstr = "0"
}
userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil {
s.log.Warn("failed to parse user ID", "namespaceID", namespaceID, "userID", userIDstr, "error", err)
}
cmd := &dashboards.SaveDashboardCommand{
Dashboard: dash.Data,
Message: dto.Message,
OrgID: dto.OrgID,
Overwrite: dto.Overwrite,
UserID: dto.User.UserID,
UserID: userID,
FolderID: dash.FolderID,
IsFolder: dash.IsFolder,
PluginID: dash.PluginID,
@ -783,7 +803,7 @@ func (s *Service) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards
// getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck()
func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user *user.SignedInUser) (guardian.DashboardGuardian, error) {
func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) {
newDashboard := d.ID == 0
if newDashboard {

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

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

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

@ -4,10 +4,10 @@ import (
"context"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
@ -25,19 +25,19 @@ func ProvideService(
func InitAccessControlGuardian(
cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) {
New = func(ctx context.Context, dashId int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
New = func(ctx context.Context, dashId int64, orgId int64, user identity.Requester) (DashboardGuardian, error) {
return NewAccessControlDashboardGuardian(ctx, cfg, dashId, user, ac, dashboardService)
}
NewByUID = func(ctx context.Context, dashUID string, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
NewByUID = func(ctx context.Context, dashUID string, orgId int64, user identity.Requester) (DashboardGuardian, error) {
return NewAccessControlDashboardGuardianByUID(ctx, cfg, dashUID, user, ac, dashboardService)
}
NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user identity.Requester) (DashboardGuardian, error) {
return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, ac, dashboardService)
}
NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user identity.Requester) (DashboardGuardian, error) {
return NewAccessControlFolderGuardian(ctx, cfg, f, user, ac, dashboardService)
}
}

@ -6,8 +6,8 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/user"
)
var (
@ -37,13 +37,13 @@ func (b *BroadcastRunner) GetHandlerForPath(_ string) (model.ChannelHandler, err
}
// OnSubscribe will let anyone connect to the path
func (b *BroadcastRunner) OnSubscribe(_ context.Context, u *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
func (b *BroadcastRunner) OnSubscribe(_ context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
reply := model.SubscribeReply{
Presence: true,
JoinLeave: true,
}
query := &model.GetLiveMessageQuery{
OrgID: u.OrgID,
OrgID: u.GetOrgID(),
Channel: e.Channel,
}
msg, ok, err := b.liveMessageStore.GetLiveMessage(query)
@ -57,9 +57,9 @@ func (b *BroadcastRunner) OnSubscribe(_ context.Context, u *user.SignedInUser, e
}
// OnPublish is called when a client wants to broadcast on the websocket
func (b *BroadcastRunner) OnPublish(_ context.Context, u *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
func (b *BroadcastRunner) OnPublish(_ context.Context, u identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
query := &model.SaveLiveMessageQuery{
OrgID: u.OrgID,
OrgID: u.GetOrgID(),
Channel: e.Channel,
Data: e.Data,
}

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/live/model"
@ -52,7 +53,7 @@ func (h *DashboardHandler) GetHandlerForPath(_ string) (model.ChannelHandler, er
}
// OnSubscribe for now allows anyone to subscribe to any dashboard
func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
func (h *DashboardHandler) OnSubscribe(ctx context.Context, user identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
parts := strings.Split(e.Path, "/")
if parts[0] == "gitops" {
// gitops gets all changes for everything, so lets make sure it is an admin user
@ -66,7 +67,7 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU
// make sure can view this dashboard
if len(parts) == 2 && parts[0] == "uid" {
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.OrgID}
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.GetOrgID()}
queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
if err != nil {
logger.Error("Error getting dashboard", "query", query, "error", err)
@ -74,7 +75,7 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU
}
dash := queryResult
guard, err := guardian.NewByDashboard(ctx, dash, user.OrgID, user)
guard, err := guardian.NewByDashboard(ctx, dash, user.GetOrgID(), user)
if err != nil {
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err
}
@ -94,11 +95,11 @@ func (h *DashboardHandler) OnSubscribe(ctx context.Context, user *user.SignedInU
}
// OnPublish is called when someone begins to edit a dashboard
func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
parts := strings.Split(e.Path, "/")
if parts[0] == "gitops" {
// gitops gets all changes for everything, so lets make sure it is an admin user
if !user.HasRole(org.RoleAdmin) {
if !requester.HasRole(org.RoleAdmin) {
return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil
}
@ -117,14 +118,14 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUse
// just ignore the event
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???")
}
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: user.OrgID}
query := dashboards.GetDashboardQuery{UID: parts[1], OrgID: requester.GetOrgID()}
queryResult, err := h.DashboardService.GetDashboard(ctx, &query)
if err != nil {
logger.Error("Unknown dashboard", "query", query)
return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil
}
guard, err := guardian.NewByDashboard(ctx, queryResult, user.OrgID, user)
guard, err := guardian.NewByDashboard(ctx, queryResult, requester.GetOrgID(), requester)
if err != nil {
logger.Error("Failed to create guardian", "err", err)
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error")
@ -141,7 +142,10 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, user *user.SignedInUse
}
// Tell everyone who is editing
event.User = user.ToUserDisplayDTO()
event.User, err = user.NewUserDisplayDTOFromRequester(requester)
if err != nil {
return model.PublishReply{}, backend.PublishStreamStatusNotFound, err
}
msg, err := json.Marshal(event)
if err != nil {

@ -7,17 +7,17 @@ import (
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/live/runstream"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
)
//go:generate mockgen -destination=plugin_mock.go -package=features github.com/grafana/grafana/pkg/services/live/features PluginContextGetter
type PluginContextGetter interface {
GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error)
GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error)
}
// PluginRunner can handle streaming operations for channels belonging to plugins.
@ -63,7 +63,7 @@ type PluginPathRunner struct {
}
// OnSubscribe passes control to a plugin.
func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
@ -86,7 +86,7 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInU
return model.SubscribeReply{}, resp.Status, nil
}
submitResult, err := r.runStreamManager.SubmitStream(ctx, user, orgchannel.PrependOrgID(user.OrgID, e.Channel), r.path, e.Data, pCtx, r.handler, false)
submitResult, err := r.runStreamManager.SubmitStream(ctx, user, orgchannel.PrependOrgID(user.GetOrgID(), e.Channel), r.path, e.Data, pCtx, r.handler, false)
if err != nil {
logger.Error("Error submitting stream to manager", "error", err, "path", r.path)
return model.SubscribeReply{}, 0, centrifuge.ErrorInternal
@ -107,7 +107,7 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInU
}
// OnPublish passes control to a plugin.
func (r *PluginPathRunner) OnPublish(ctx context.Context, user *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
func (r *PluginPathRunner) OnPublish(ctx context.Context, user identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {

@ -10,7 +10,7 @@ import (
gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
user "github.com/grafana/grafana/pkg/services/user"
identity "github.com/grafana/grafana/pkg/services/auth/identity"
)
// MockPluginContextGetter is a mock of PluginContextGetter interface.
@ -37,7 +37,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
}
// GetPluginContext mocks base method.
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 identity.Requester, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext)

@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
@ -299,10 +300,15 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
g.websocketHandler = func(ctx *contextmodel.ReqContext) {
user := ctx.SignedInUser
namespaceID, userID := user.GetNamespacedID()
if namespaceID != identity.NamespaceUser {
return // Only users can connect to Live
}
// Centrifuge expects Credentials in context with a current user ID.
cred := &centrifuge.Credentials{
UserID: fmt.Sprintf("%d", user.UserID),
UserID: userID,
}
newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred)
newCtx = livecontext.SetContextSignedUser(newCtx, user)
@ -593,7 +599,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge.
return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal
}
if user.OrgID != orgID {
if user.GetOrgID() != orgID {
logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel)
return centrifuge.SubscribeReply{}, centrifuge.ErrorPermissionDenied
}
@ -603,7 +609,7 @@ func (g *GrafanaLive) handleOnSubscribe(ctx context.Context, client *centrifuge.
var ruleFound bool
if g.Pipeline != nil {
rule, ok, err := g.Pipeline.Get(user.OrgID, channel)
rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel)
if err != nil {
logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal
@ -694,13 +700,13 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl
return centrifuge.PublishReply{}, centrifuge.ErrorInternal
}
if user.OrgID != orgID {
if user.GetOrgID() != orgID {
logger.Info("Error subscribing: wrong orgId", "user", client.UserID(), "client", client.ID(), "channel", e.Channel)
return centrifuge.PublishReply{}, centrifuge.ErrorPermissionDenied
}
if g.Pipeline != nil {
rule, ok, err := g.Pipeline.Get(user.OrgID, channel)
rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel)
if err != nil {
logger.Error("Error getting channel rule", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.PublishReply{}, centrifuge.ErrorInternal
@ -724,7 +730,7 @@ func (g *GrafanaLive) handleOnPublish(ctx context.Context, client *centrifuge.Cl
return centrifuge.PublishReply{}, &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 {
logger.Error("Error processing input", "user", client.UserID(), "client", client.ID(), "channel", e.Channel, "error", err)
return centrifuge.PublishReply{}, centrifuge.ErrorInternal
@ -805,7 +811,7 @@ func publishStatusToHTTPError(status backend.PublishStreamStatus) (int, string)
}
// GetChannelHandler gives thread-safe access to the channel.
func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user *user.SignedInUser, channel string) (model.ChannelHandler, live.Channel, error) {
func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user identity.Requester, channel string) (model.ChannelHandler, live.Channel, error) {
// Parse the identifier ${scope}/${namespace}/${path}
addr, err := live.ParseChannel(channel)
if err != nil {
@ -846,7 +852,7 @@ func (g *GrafanaLive) GetChannelHandler(ctx context.Context, user *user.SignedIn
// GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace.
// It gives thread-safe access to the channel.
func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user *user.SignedInUser, scope string, namespace string) (model.ChannelHandlerFactory, error) {
func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user identity.Requester, scope string, namespace string) (model.ChannelHandlerFactory, error) {
switch scope {
case live.ScopeGrafana:
return g.handleGrafanaScope(user, namespace)
@ -861,14 +867,14 @@ func (g *GrafanaLive) GetChannelHandlerFactory(ctx context.Context, user *user.S
}
}
func (g *GrafanaLive) handleGrafanaScope(_ *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) {
func (g *GrafanaLive) handleGrafanaScope(_ identity.Requester, namespace string) (model.ChannelHandlerFactory, error) {
if p, ok := g.GrafanaScope.Features[namespace]; ok {
return p, nil
}
return nil, fmt.Errorf("unknown feature: %q", namespace)
}
func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) {
func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ identity.Requester, namespace string) (model.ChannelHandlerFactory, error) {
streamHandler, err := g.getStreamPlugin(ctx, namespace)
if err != nil {
return nil, fmt.Errorf("can't find stream plugin: %s", namespace)
@ -882,11 +888,11 @@ func (g *GrafanaLive) handlePluginScope(ctx context.Context, _ *user.SignedInUse
), nil
}
func (g *GrafanaLive) handleStreamScope(u *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) {
return g.ManagedStreamRunner.GetOrCreateStream(u.OrgID, live.ScopeStream, namespace)
func (g *GrafanaLive) handleStreamScope(u identity.Requester, namespace string) (model.ChannelHandlerFactory, error) {
return g.ManagedStreamRunner.GetOrCreateStream(u.GetOrgID(), live.ScopeStream, namespace)
}
func (g *GrafanaLive) handleDatasourceScope(ctx context.Context, user *user.SignedInUser, namespace string) (model.ChannelHandlerFactory, error) {
func (g *GrafanaLive) handleDatasourceScope(ctx context.Context, user identity.Requester, namespace string) (model.ChannelHandlerFactory, error) {
ds, err := g.DataSourceCache.GetDatasourceByUID(ctx, namespace, user, false)
if err != nil {
return nil, fmt.Errorf("error getting datasource: %w", err)
@ -929,12 +935,13 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R
return response.Error(http.StatusBadRequest, "invalid channel ID", nil)
}
logger.Debug("Publish API cmd", "user", ctx.SignedInUser.UserID, "channel", cmd.Channel)
namespaceID, userID := ctx.SignedInUser.GetNamespacedID()
logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel)
user := ctx.SignedInUser
channel := cmd.Channel
if g.Pipeline != nil {
rule, ok, err := g.Pipeline.Get(user.OrgID, channel)
rule, ok, err := g.Pipeline.Get(user.GetOrgID(), channel)
if err != nil {
logger.Error("Error getting channel rule", "user", user, "channel", channel, "error", err)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil)
@ -954,7 +961,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R
return response.Error(http.StatusForbidden, http.StatusText(http.StatusForbidden), nil)
}
}
_, err := g.Pipeline.ProcessInput(ctx.Req.Context(), user.OrgID, channel, cmd.Data)
_, err := g.Pipeline.ProcessInput(ctx.Req.Context(), user.GetOrgID(), channel, cmd.Data)
if err != nil {
logger.Error("Error processing input", "user", user, "channel", channel, "error", err)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil)
@ -980,13 +987,13 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R
return response.Error(code, text, nil)
}
if reply.Data != nil {
err = g.Publish(ctx.OrgID, cmd.Channel, cmd.Data)
err = g.Publish(ctx.SignedInUser.GetOrgID(), cmd.Channel, cmd.Data)
if err != nil {
logger.Error("Error publish to channel", "error", err, "channel", cmd.Channel)
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil)
}
}
logger.Debug("Publication successful", "user", ctx.SignedInUser.UserID, "channel", cmd.Channel)
logger.Debug("Publication successful", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel)
return response.JSON(http.StatusOK, dtos.LivePublishResponse{})
}
@ -999,9 +1006,9 @@ func (g *GrafanaLive) HandleListHTTP(c *contextmodel.ReqContext) response.Respon
var channels []*managedstream.ManagedChannel
var err error
if g.IsHA() {
channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.OrgID)
channels, err = g.surveyCaller.CallManagedStreams(c.SignedInUser.GetOrgID())
} else {
channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.OrgID)
channels, err = g.ManagedStreamRunner.GetManagedChannels(c.SignedInUser.GetOrgID())
}
if err != nil {
return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err)
@ -1017,7 +1024,7 @@ func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Resp
path := web.Params(ctx.Req)["*"]
if path == "grafana/dashboards/gitops" {
return response.JSON(http.StatusOK, util.DynMap{
"active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.SignedInUser.OrgID),
"active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.SignedInUser.GetOrgID()),
})
}
return response.JSONStreaming(404, util.DynMap{
@ -1027,7 +1034,7 @@ func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Resp
// HandleChannelRulesListHTTP ...
func (g *GrafanaLive) HandleChannelRulesListHTTP(c *contextmodel.ReqContext) response.Response {
result, err := g.pipelineStorage.ListChannelRules(c.Req.Context(), c.OrgID)
result, err := g.pipelineStorage.ListChannelRules(c.Req.Context(), c.SignedInUser.GetOrgID())
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get channel rules", err)
}
@ -1112,7 +1119,7 @@ func (g *GrafanaLive) HandlePipelineConvertTestHTTP(c *contextmodel.ReqContext)
if err != nil {
return response.Error(http.StatusInternalServerError, "Error creating pipeline", err)
}
rule, ok, err := channelRuleGetter.Get(c.OrgID, req.Channel)
rule, ok, err := channelRuleGetter.Get(c.SignedInUser.GetOrgID(), req.Channel)
if err != nil {
return response.Error(http.StatusInternalServerError, "Error getting channel rule", err)
}
@ -1122,7 +1129,7 @@ func (g *GrafanaLive) HandlePipelineConvertTestHTTP(c *contextmodel.ReqContext)
if rule.Converter == nil {
return response.Error(http.StatusNotFound, "No converter found", nil)
}
channelFrames, err := pipe.DataToChannelFrames(c.Req.Context(), *rule, c.OrgID, req.Channel, []byte(req.Data))
channelFrames, err := pipe.DataToChannelFrames(c.Req.Context(), *rule, c.SignedInUser.GetOrgID(), req.Channel, []byte(req.Data))
if err != nil {
return response.Error(http.StatusInternalServerError, "Error converting data", err)
}
@ -1142,7 +1149,7 @@ func (g *GrafanaLive) HandleChannelRulesPostHTTP(c *contextmodel.ReqContext) res
if err != nil {
return response.Error(http.StatusBadRequest, "Error decoding channel rule", err)
}
rule, err := g.pipelineStorage.CreateChannelRule(c.Req.Context(), c.OrgID, cmd)
rule, err := g.pipelineStorage.CreateChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to create channel rule", err)
}
@ -1165,7 +1172,7 @@ func (g *GrafanaLive) HandleChannelRulesPutHTTP(c *contextmodel.ReqContext) resp
if cmd.Pattern == "" {
return response.Error(http.StatusBadRequest, "Rule pattern required", nil)
}
rule, err := g.pipelineStorage.UpdateChannelRule(c.Req.Context(), c.OrgID, cmd)
rule, err := g.pipelineStorage.UpdateChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to update channel rule", err)
}
@ -1188,7 +1195,7 @@ func (g *GrafanaLive) HandleChannelRulesDeleteHTTP(c *contextmodel.ReqContext) r
if cmd.Pattern == "" {
return response.Error(http.StatusBadRequest, "Rule pattern required", nil)
}
err = g.pipelineStorage.DeleteChannelRule(c.Req.Context(), c.OrgID, cmd)
err = g.pipelineStorage.DeleteChannelRule(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to delete channel rule", err)
}
@ -1208,7 +1215,7 @@ func (g *GrafanaLive) HandlePipelineEntitiesListHTTP(_ *contextmodel.ReqContext)
// HandleWriteConfigsListHTTP ...
func (g *GrafanaLive) HandleWriteConfigsListHTTP(c *contextmodel.ReqContext) response.Response {
backends, err := g.pipelineStorage.ListWriteConfigs(c.Req.Context(), c.OrgID)
backends, err := g.pipelineStorage.ListWriteConfigs(c.Req.Context(), c.SignedInUser.GetOrgID())
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get write configs", err)
}
@ -1232,7 +1239,7 @@ func (g *GrafanaLive) HandleWriteConfigsPostHTTP(c *contextmodel.ReqContext) res
if err != nil {
return response.Error(http.StatusBadRequest, "Error decoding write config create command", err)
}
result, err := g.pipelineStorage.CreateWriteConfig(c.Req.Context(), c.OrgID, cmd)
result, err := g.pipelineStorage.CreateWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to create write config", err)
}
@ -1255,7 +1262,7 @@ func (g *GrafanaLive) HandleWriteConfigsPutHTTP(c *contextmodel.ReqContext) resp
if cmd.UID == "" {
return response.Error(http.StatusBadRequest, "UID required", nil)
}
existingBackend, ok, err := g.pipelineStorage.GetWriteConfig(c.Req.Context(), c.OrgID, pipeline.WriteConfigGetCmd{
existingBackend, ok, err := g.pipelineStorage.GetWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), pipeline.WriteConfigGetCmd{
UID: cmd.UID,
})
if err != nil {
@ -1276,7 +1283,7 @@ func (g *GrafanaLive) HandleWriteConfigsPutHTTP(c *contextmodel.ReqContext) resp
}
}
}
result, err := g.pipelineStorage.UpdateWriteConfig(c.Req.Context(), c.OrgID, cmd)
result, err := g.pipelineStorage.UpdateWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to update write config", err)
}
@ -1299,7 +1306,7 @@ func (g *GrafanaLive) HandleWriteConfigsDeleteHTTP(c *contextmodel.ReqContext) r
if cmd.UID == "" {
return response.Error(http.StatusBadRequest, "UID required", nil)
}
err = g.pipelineStorage.DeleteWriteConfig(c.Req.Context(), c.OrgID, cmd)
err = g.pipelineStorage.DeleteWriteConfig(c.Req.Context(), c.SignedInUser.GetOrgID(), cmd)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to delete write config", err)
}

@ -3,21 +3,21 @@ package livecontext
import (
"context"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
type signedUserContextKeyType int
var signedUserContextKey signedUserContextKeyType
func SetContextSignedUser(ctx context.Context, user *user.SignedInUser) context.Context {
func SetContextSignedUser(ctx context.Context, user identity.Requester) context.Context {
ctx = context.WithValue(ctx, signedUserContextKey, user)
return ctx
}
func GetContextSignedUser(ctx context.Context) (*user.SignedInUser, bool) {
func GetContextSignedUser(ctx context.Context) (identity.Requester, bool) {
if val := ctx.Value(signedUserContextKey); val != nil {
user, ok := val.(*user.SignedInUser)
user, ok := val.(identity.Requester)
return user, ok
}
return nil, false

@ -7,11 +7,11 @@ import (
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/live/pipeline"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
)
type ChannelLocalPublisher struct {
@ -72,9 +72,9 @@ func NewContextGetter(pluginContextProvider *plugincontext.Provider, dataSourceC
}
}
func (g *ContextGetter) GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) {
func (g *ContextGetter) GetPluginContext(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) {
if datasourceUID == "" {
return g.pluginContextProvider.Get(ctx, pluginID, user, user.OrgID)
return g.pluginContextProvider.Get(ctx, pluginID, user, user.GetOrgID())
}
ds, err := g.dataSourceCache.GetDatasourceByUID(ctx, datasourceUID, user, skipCache)

@ -13,9 +13,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/live"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/user"
)
var (
@ -239,9 +239,9 @@ func (s *NamespaceStream) GetHandlerForPath(_ string) (model.ChannelHandler, err
return s, nil
}
func (s *NamespaceStream) OnSubscribe(ctx context.Context, u *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
func (s *NamespaceStream) OnSubscribe(ctx context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
reply := model.SubscribeReply{}
frameJSON, ok, err := s.frameCache.GetFrame(ctx, u.OrgID, e.Channel)
frameJSON, ok, err := s.frameCache.GetFrame(ctx, u.GetOrgID(), e.Channel)
if err != nil {
return reply, 0, err
}
@ -251,6 +251,6 @@ func (s *NamespaceStream) OnSubscribe(ctx context.Context, u *user.SignedInUser,
return reply, backend.SubscribeStreamStatusOK, nil
}
func (s *NamespaceStream) OnPublish(_ context.Context, _ *user.SignedInUser, _ model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
func (s *NamespaceStream) OnPublish(_ context.Context, _ identity.Requester, _ model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
return model.PublishReply{}, backend.PublishStreamStatusPermissionDenied, nil
}

@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
// ChannelPublisher writes data into a channel. Note that permissions are not checked.
@ -54,10 +54,10 @@ type PublishReply struct {
// ChannelHandler defines the core channel behavior
type ChannelHandler interface {
// OnSubscribe is called when a client wants to subscribe to a channel
OnSubscribe(ctx context.Context, user *user.SignedInUser, e SubscribeEvent) (SubscribeReply, backend.SubscribeStreamStatus, error)
OnSubscribe(ctx context.Context, user identity.Requester, e SubscribeEvent) (SubscribeReply, backend.SubscribeStreamStatus, error)
// OnPublish is called when a client writes a message to the channel websocket.
OnPublish(ctx context.Context, user *user.SignedInUser, e PublishEvent) (PublishReply, backend.PublishStreamStatus, error)
OnPublish(ctx context.Context, user identity.Requester, e PublishEvent) (PublishReply, backend.PublishStreamStatus, error)
}
// ChannelHandlerFactory should be implemented by all core features.

@ -3,8 +3,8 @@ package pipeline
import (
"context"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
)
type RoleCheckAuthorizer struct {
@ -15,10 +15,10 @@ func NewRoleCheckAuthorizer(role org.RoleType) *RoleCheckAuthorizer {
return &RoleCheckAuthorizer{role: role}
}
func (s *RoleCheckAuthorizer) CanSubscribe(_ context.Context, u *user.SignedInUser) (bool, error) {
func (s *RoleCheckAuthorizer) CanSubscribe(_ context.Context, u identity.Requester) (bool, error) {
return u.HasRole(s.role), nil
}
func (s *RoleCheckAuthorizer) CanPublish(_ context.Context, u *user.SignedInUser) (bool, error) {
func (s *RoleCheckAuthorizer) CanPublish(_ context.Context, u identity.Requester) (bool, error) {
return u.HasRole(s.role), nil
}

@ -17,8 +17,8 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/user"
)
const (
@ -113,12 +113,12 @@ type Subscriber interface {
// PublishAuthChecker checks whether current user can publish to a channel.
type PublishAuthChecker interface {
CanPublish(ctx context.Context, u *user.SignedInUser) (bool, error)
CanPublish(ctx context.Context, u identity.Requester) (bool, error)
}
// SubscribeAuthChecker checks whether current user can subscribe to a channel.
type SubscribeAuthChecker interface {
CanSubscribe(ctx context.Context, u *user.SignedInUser) (bool, error)
CanSubscribe(ctx context.Context, u identity.Requester) (bool, error)
}
// LiveChannelRule is an in-memory representation of each specific rule to be executed by Pipeline.

@ -6,9 +6,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/live"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/live/livecontext"
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/user"
)
type BuiltinSubscriber struct {
@ -16,7 +16,7 @@ type BuiltinSubscriber struct {
}
type ChannelHandlerGetter interface {
GetChannelHandler(ctx context.Context, user *user.SignedInUser, channel string) (model.ChannelHandler, live.Channel, error)
GetChannelHandler(ctx context.Context, user identity.Requester, channel string) (model.ChannelHandler, live.Channel, error)
}
const SubscriberTypeBuiltin = "builtin"

@ -71,7 +71,7 @@ func (s *PipelinePushHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
"bodyLength", len(body),
)
ruleFound, err := s.pipeline.ProcessInput(r.Context(), user.OrgID, channelID, body)
ruleFound, err := s.pipeline.ProcessInput(r.Context(), user.GetOrgID(), channelID, body)
if err != nil {
logger.Error("Pipeline input processing error", "error", err, "body", string(body))
return

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

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

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user"
)
@ -71,9 +72,11 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
},
}
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
require.Equal(t, int64(2), user.UserID)
require.Equal(t, int64(1), user.OrgID)
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
require.Equal(t, testPluginContext.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil
@ -133,7 +136,7 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
mockPacketSender.EXPECT().PublishLocal("1/test", gomock.Any()).Times(1)
mockPacketSender.EXPECT().PublishLocal("2/test", gomock.Any()).Times(1)
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
return backend.PluginContext{}, true, nil
}).Times(0)
@ -205,7 +208,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) {
startedCh := make(chan struct{})
doneCh := make(chan struct{})
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
return backend.PluginContext{}, true, nil
}).Times(0)
@ -254,9 +257,11 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
},
}
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
require.Equal(t, int64(2), user.UserID)
require.Equal(t, int64(1), user.OrgID)
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
require.Equal(t, testPluginContext.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil
@ -296,7 +301,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) {
_ = manager.Run(ctx)
}()
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
return backend.PluginContext{}, true, nil
}).Times(0)
@ -337,9 +342,12 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
},
}
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
require.Equal(t, int64(2), user.UserID)
require.Equal(t, int64(1), user.OrgID)
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
require.Equal(t, testPluginContext.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil
@ -403,9 +411,11 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
},
}
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
require.Equal(t, int64(2), user.UserID)
require.Equal(t, int64(1), user.OrgID)
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID())
require.Equal(t, testPluginContext.PluginID, pluginID)
require.Equal(t, testPluginContext.DataSourceInstanceSettings.UID, datasourceUID)
return testPluginContext, true, nil

@ -10,7 +10,7 @@ import (
gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
user "github.com/grafana/grafana/pkg/services/user"
identity "github.com/grafana/grafana/pkg/services/auth/identity"
)
// MockChannelLocalPublisher is a mock of ChannelLocalPublisher interface.
@ -149,7 +149,7 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
}
// GetPluginContext mocks base method.
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 identity.Requester, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext)

@ -9,10 +9,10 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/web"
)
@ -114,7 +114,7 @@ type fakeCacheService struct {
err error
}
func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _ *user.SignedInUser, _ bool) (*datasources.DataSource, error) {
func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _ identity.Requester, _ bool) (*datasources.DataSource, error) {
if f.err != nil {
return nil, f.err
}
@ -122,7 +122,7 @@ func (f fakeCacheService) GetDatasource(_ context.Context, datasourceID int64, _
return f.datasource, nil
}
func (f fakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
func (f fakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, _ identity.Requester, skipCache bool) (*datasources.DataSource, error) {
if f.err != nil {
return nil, f.err
}

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

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

@ -16,10 +16,10 @@ import (
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafanads"
@ -61,7 +61,7 @@ func ProvideService(
//go:generate mockery --name Service --structname FakeQueryService --inpackage --filename query_service_mock.go
type Service interface {
Run(ctx context.Context) error
QueryData(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error)
QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error)
}
// Gives us compile time error if the service does not adhere to the contract of the interface
@ -85,7 +85,7 @@ func (s *ServiceImpl) Run(ctx context.Context) error {
}
// QueryData processes queries and returns query responses. It handles queries to single or mixed datasources, as well as expressions.
func (s *ServiceImpl) QueryData(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) {
func (s *ServiceImpl) QueryData(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) {
// Parse the request into parsed queries grouped by datasource uid
parsedReq, err := s.parseMetricRequest(ctx, user, skipDSCache, reqDTO)
if err != nil {
@ -111,7 +111,7 @@ type splitResponse struct {
}
// executeConcurrentQueries executes queries to multiple datasources concurrently and returns the aggregate result.
func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest, queriesbyDs map[string][]parsedQuery) (*backend.QueryDataResponse, error) {
func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest, queriesbyDs map[string][]parsedQuery) (*backend.QueryDataResponse, error) {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(s.concurrentQueryLimit) // prevent too many concurrent requests
rchan := make(chan splitResponse, len(queriesbyDs))
@ -198,14 +198,14 @@ func buildErrorResponses(err error, queries []*simplejson.Json) splitResponse {
}
// handleExpressions handles POST /api/ds/query when there is an expression.
func (s *ServiceImpl) handleExpressions(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
func (s *ServiceImpl) handleExpressions(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
exprReq := expr.Request{
Queries: []expr.Query{},
}
if user != nil { // for passthrough authentication, SSE does not authenticate
exprReq.User = user
exprReq.OrgId = user.OrgID
exprReq.OrgId = user.GetOrgID()
}
for _, pq := range parsedReq.getFlattenedQueries() {
@ -239,7 +239,7 @@ func (s *ServiceImpl) handleExpressions(ctx context.Context, user *user.SignedIn
}
// handleQuerySingleDatasource handles one or more queries to a single datasource
func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
queries := parsedReq.getFlattenedQueries()
ds := queries[0].datasource
if err := s.pluginRequestValidator.Validate(ds.URL, nil); err != nil {
@ -271,7 +271,7 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user *use
}
// parseRequest parses a request into parsed queries grouped by datasource uid
func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) {
func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) {
if len(reqDTO.Queries) == 0 {
return nil, ErrNoQueriesFound
}
@ -332,7 +332,7 @@ func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user *user.SignedI
return req, req.validateRequest(ctx)
}
func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user *user.SignedInUser, skipDSCache bool, query *simplejson.Json, history map[string]*datasources.DataSource) (*datasources.DataSource, error) {
func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user identity.Requester, skipDSCache bool, query *simplejson.Json, history map[string]*datasources.DataSource) (*datasources.DataSource, error) {
var err error
uid := query.Get("datasource").Get("uid").MustString()
@ -352,7 +352,7 @@ func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user *user.Sig
}
if uid == grafanads.DatasourceUID {
return grafanads.DataSourceModel(user.OrgID), nil
return grafanads.DataSourceModel(user.GetOrgID()), nil
}
if uid != "" {

@ -11,7 +11,7 @@ import (
mock "github.com/stretchr/testify/mock"
user "github.com/grafana/grafana/pkg/services/user"
identity "github.com/grafana/grafana/pkg/services/auth/identity"
)
// FakeQueryService is an autogenerated mock type for the Service type
@ -20,11 +20,11 @@ type FakeQueryService struct {
}
// QueryData provides a mock function with given fields: ctx, _a1, skipDSCache, reqDTO
func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 *user.SignedInUser, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) {
func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) {
ret := _m.Called(ctx, _a1, skipDSCache, reqDTO)
var r0 *backend.QueryDataResponse
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, bool, dtos.MetricRequest) *backend.QueryDataResponse); ok {
if rf, ok := ret.Get(0).(func(context.Context, identity.Requester, bool, dtos.MetricRequest) *backend.QueryDataResponse); ok {
r0 = rf(ctx, _a1, skipDSCache, reqDTO)
} else {
if ret.Get(0) != nil {
@ -33,7 +33,7 @@ func (_m *FakeQueryService) QueryData(ctx context.Context, _a1 *user.SignedInUse
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, bool, dtos.MetricRequest) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, identity.Requester, bool, dtos.MetricRequest) error); ok {
r1 = rf(ctx, _a1, skipDSCache, reqDTO)
} else {
r1 = ret.Error(1)

@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/plugins"
pluginFakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
@ -524,12 +525,12 @@ type fakeDataSourceCache struct {
cache []*datasources.DataSource
}
func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user identity.Requester, skipCache bool) (*datasources.DataSource, error) {
// deprecated: fake an error to ensure we are using GetDatasourceByUID
return nil, fmt.Errorf("not found")
}
func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user identity.Requester, skipCache bool) (*datasources.DataSource, error) {
for _, ds := range c.cache {
if ds.UID == datasourceUID {
return ds, nil

@ -43,6 +43,9 @@ func (u *SignedInUser) NameOrFallback() string {
return u.Email
}
// TODO: There's a need to remove this struct since it creates a circular dependency
// DEPRECATED: This function uses `UserDisplayDTO` model which we want to remove
func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO {
return &UserDisplayDTO{
ID: u.UserID,
@ -51,6 +54,20 @@ func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO {
}
}
// Static function to parse a requester into a UserDisplayDTO
func NewUserDisplayDTOFromRequester(requester identity.Requester) (*UserDisplayDTO, error) {
namespaceID, identifier := requester.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
identifier = "0"
}
userID, _ := identity.IntIdentifier(namespaceID, identifier)
return &UserDisplayDTO{
ID: userID,
Login: requester.GetLogin(),
Name: requester.GetDisplayName(),
}, nil
}
func (u *SignedInUser) HasRole(role roletype.RoleType) bool {
if u.IsGrafanaAdmin {
return true

Loading…
Cancel
Save