diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index c1c4674f169..553117fa006 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -33,6 +33,7 @@ import ( accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" + "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/dashboards/database" @@ -45,6 +46,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/libraryelements/model" + "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/org" pref "github.com/grafana/grafana/pkg/services/preference" @@ -1138,23 +1140,25 @@ func (s mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboard type mockLibraryPanelService struct { } -func (m *mockLibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error { +var _ librarypanels.Service = (*mockLibraryPanelService)(nil) + +func (m *mockLibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error { return nil } -func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func (m *mockLibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { return nil } type mockLibraryElementService struct { } -func (l *mockLibraryElementService) CreateElement(c context.Context, signedInUser *user.SignedInUser, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *mockLibraryElementService) CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { return model.LibraryElementDTO{}, nil } // GetElement gets an element from a UID. -func (l *mockLibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *mockLibraryElementService) GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { return model.LibraryElementDTO{}, nil } @@ -1164,7 +1168,7 @@ func (l *mockLibraryElementService) GetElementsForDashboard(c context.Context, d } // ConnectElementsToDashboard connects elements to a specific dashboard. -func (l *mockLibraryElementService) ConnectElementsToDashboard(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error { +func (l *mockLibraryElementService) ConnectElementsToDashboard(c context.Context, signedInUser identity.Requester, elementUIDs []string, dashboardID int64) error { return nil } @@ -1174,6 +1178,6 @@ func (l *mockLibraryElementService) DisconnectElementsFromDashboard(c context.Co } // DeleteLibraryElementsInFolder deletes all elements for a specific folder. -func (l *mockLibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser *user.SignedInUser, folderUID string) error { +func (l *mockLibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser identity.Requester, folderUID string) error { return nil } diff --git a/pkg/infra/db/sqlbuilder.go b/pkg/infra/db/sqlbuilder.go index ae4743675ff..d3b3fd15ff9 100644 --- a/pkg/infra/db/sqlbuilder.go +++ b/pkg/infra/db/sqlbuilder.go @@ -3,11 +3,11 @@ package db import ( "bytes" + "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/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -63,7 +63,7 @@ func (sb *SQLBuilder) AddParams(params ...any) { sb.params = append(sb.params, params...) } -func (sb *SQLBuilder) WriteDashboardPermissionFilter(user *user.SignedInUser, permission dashboards.PermissionType, queryType string) { +func (sb *SQLBuilder) WriteDashboardPermissionFilter(user identity.Requester, permission dashboards.PermissionType, queryType string) { var ( sql string params []any diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 24cde5f83f1..6434663080a 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -377,7 +377,7 @@ func GetOrgRoles(user identity.Requester) []string { return roles } -func BackgroundUser(name string, orgID int64, role org.RoleType, permissions []Permission) *user.SignedInUser { +func BackgroundUser(name string, orgID int64, role org.RoleType, permissions []Permission) identity.Requester { return &user.SignedInUser{ OrgID: orgID, OrgRole: role, diff --git a/pkg/services/accesscontrol/mock/mock.go b/pkg/services/accesscontrol/mock/mock.go index 8043cc5280a..90a9cda8bfe 100644 --- a/pkg/services/accesscontrol/mock/mock.go +++ b/pkg/services/accesscontrol/mock/mock.go @@ -44,16 +44,16 @@ type Mock struct { Calls Calls // Override functions - EvaluateFunc func(context.Context, *user.SignedInUser, accesscontrol.Evaluator) (bool, error) - GetUserPermissionsFunc func(context.Context, *user.SignedInUser, accesscontrol.Options) ([]accesscontrol.Permission, error) - ClearUserPermissionCacheFunc func(*user.SignedInUser) + EvaluateFunc func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error) + GetUserPermissionsFunc func(context.Context, identity.Requester, accesscontrol.Options) ([]accesscontrol.Permission, error) + ClearUserPermissionCacheFunc func(identity.Requester) DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error DeclarePluginRolesFunc func(context.Context, string, string, []plugins.RoleRegistration) error - GetUserBuiltInRolesFunc func(user *user.SignedInUser) []string + GetUserBuiltInRolesFunc func(user identity.Requester) []string RegisterFixedRolesFunc func() error RegisterScopeAttributeResolverFunc func(string, accesscontrol.ScopeAttributeResolver) DeleteUserPermissionsFunc func(context.Context, int64) error - SearchUsersPermissionsFunc func(context.Context, *user.SignedInUser, int64, accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) + SearchUsersPermissionsFunc func(context.Context, identity.Requester, int64, accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) SearchUserPermissionsFunc func(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) SaveExternalServiceRoleFunc func(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error DeleteExternalServiceRoleFunc func(ctx context.Context, externalServiceID string) error @@ -92,8 +92,7 @@ func (m *Mock) WithBuiltInRoles(builtInRoles []string) *Mock { // Evaluate evaluates access to the given resource. // This mock uses GetUserPermissions to then call the evaluator Evaluate function. -func (m *Mock) Evaluate(ctx context.Context, us identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { - usr := us.(*user.SignedInUser) +func (m *Mock) Evaluate(ctx context.Context, usr identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, usr, evaluator}) // Use override if provided if m.EvaluateFunc != nil { @@ -101,11 +100,9 @@ func (m *Mock) Evaluate(ctx context.Context, us identity.Requester, evaluator ac } var permissions map[string][]string - if usr.Permissions != nil && usr.Permissions[usr.OrgID] != nil { - permissions = usr.Permissions[usr.OrgID] - } + permissions = usr.GetPermissions() - if permissions == nil { + if len(permissions) == 0 { userPermissions, err := m.GetUserPermissions(ctx, usr, accesscontrol.Options{ReloadCache: true}) if err != nil { return false, err @@ -117,7 +114,7 @@ func (m *Mock) Evaluate(ctx context.Context, us identity.Requester, evaluator ac return true, nil } - resolvedEvaluator, err := evaluator.MutateScopes(ctx, m.scopeResolvers.GetScopeAttributeMutator(usr.OrgID)) + resolvedEvaluator, err := evaluator.MutateScopes(ctx, m.scopeResolvers.GetScopeAttributeMutator(usr.GetOrgID())) if err != nil { if errors.Is(err, accesscontrol.ErrResolverNotFound) { return false, nil @@ -130,8 +127,7 @@ func (m *Mock) Evaluate(ctx context.Context, us identity.Requester, evaluator ac // GetUserPermissions returns user permissions. // This mock return m.permissions unless an override is provided. -func (m *Mock) GetUserPermissions(ctx context.Context, usr identity.Requester, opts accesscontrol.Options) ([]accesscontrol.Permission, error) { - user := usr.(*user.SignedInUser) +func (m *Mock) GetUserPermissions(ctx context.Context, user identity.Requester, opts accesscontrol.Options) ([]accesscontrol.Permission, error) { m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user, opts}) // Use override if provided if m.GetUserPermissionsFunc != nil { @@ -141,8 +137,7 @@ func (m *Mock) GetUserPermissions(ctx context.Context, usr identity.Requester, o return m.permissions, nil } -func (m *Mock) ClearUserPermissionCache(usr identity.Requester) { - user := usr.(*user.SignedInUser) +func (m *Mock) ClearUserPermissionCache(user identity.Requester) { m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user}) // Use override if provided if m.ClearUserPermissionCacheFunc != nil { diff --git a/pkg/services/auth/identity/requester.go b/pkg/services/auth/identity/requester.go index ddab006f8e2..bd7cc9ab0c1 100644 --- a/pkg/services/auth/identity/requester.go +++ b/pkg/services/auth/identity/requester.go @@ -60,6 +60,8 @@ type Requester interface { GetCacheKey() (string, error) // HasUniqueId returns true if the entity has a unique id HasUniqueId() bool + // AuthenticatedBy returns the authentication method used to authenticate the entity. + GetAuthenticatedBy() string } // IntIdentifier converts a string identifier to an int64. diff --git a/pkg/services/authn/authnimpl/sync/permission_sync_test.go b/pkg/services/authn/authnimpl/sync/permission_sync_test.go index 556138b28d4..db4445536b4 100644 --- a/pkg/services/authn/authnimpl/sync/permission_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/permission_sync_test.go @@ -7,8 +7,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" - "github.com/grafana/grafana/pkg/services/user" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -51,7 +51,7 @@ func TestPermissionsSync_SyncPermission(t *testing.T) { func setupTestEnv() *PermissionsSync { acMock := &acmock.Mock{ - GetUserPermissionsFunc: func(ctx context.Context, siu *user.SignedInUser, o accesscontrol.Options) ([]accesscontrol.Permission, error) { + GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) { return []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, nil diff --git a/pkg/services/dashboardimport/dashboardimport.go b/pkg/services/dashboardimport/dashboardimport.go index cafa322845d..be8b414b5a0 100644 --- a/pkg/services/dashboardimport/dashboardimport.go +++ b/pkg/services/dashboardimport/dashboardimport.go @@ -4,7 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/auth/identity" ) // ImportDashboardInput definition of input parameters when importing a dashboard. @@ -25,7 +25,7 @@ type ImportDashboardRequest struct { FolderId int64 `json:"folderId"` FolderUid string `json:"folderUid"` - User *user.SignedInUser `json:"-"` + User identity.Requester `json:"-"` } // ImportDashboardResponse response object returned when importing a dashboard. diff --git a/pkg/services/dashboardimport/service/service.go b/pkg/services/dashboardimport/service/service.go index 613ce183683..33083b7f0f9 100644 --- a/pkg/services/dashboardimport/service/service.go +++ b/pkg/services/dashboardimport/service/service.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboardimport/api" "github.com/grafana/grafana/pkg/services/dashboardimport/utils" @@ -85,7 +86,7 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb // here we need to get FolderId from FolderUID if it present in the request, if both exist, FolderUID would overwrite FolderID if req.FolderUid != "" { folder, err := s.folderService.Get(ctx, &folder.GetFolderQuery{ - OrgID: req.User.OrgID, + OrgID: req.User.GetOrgID(), UID: &req.FolderUid, SignedInUser: req.User, }) @@ -96,7 +97,7 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb } else { folder, err := s.folderService.Get(ctx, &folder.GetFolderQuery{ ID: &req.FolderId, - OrgID: req.User.OrgID, + OrgID: req.User.GetOrgID(), SignedInUser: req.User, }) if err != nil { @@ -105,10 +106,18 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb req.FolderUid = folder.UID } + namespaceID, identifier := req.User.GetNamespacedID() + userID := int64(0) + + switch namespaceID { + case identity.NamespaceUser, identity.NamespaceServiceAccount: + userID, _ = identity.IntIdentifier(namespaceID, identifier) + } + saveCmd := dashboards.SaveDashboardCommand{ Dashboard: generatedDash, - OrgID: req.User.OrgID, - UserID: req.User.UserID, + OrgID: req.User.GetOrgID(), + UserID: userID, Overwrite: req.Overwrite, PluginID: req.PluginId, FolderID: req.FolderId, diff --git a/pkg/services/dashboardimport/service/service_test.go b/pkg/services/dashboardimport/service/service_test.go index 8d715fa368a..bc3da63dea2 100644 --- a/pkg/services/dashboardimport/service/service_test.go +++ b/pkg/services/dashboardimport/service/service_test.go @@ -47,11 +47,11 @@ func TestImportDashboardService(t *testing.T) { importLibraryPanelsForDashboard := false connectLibraryPanelsForDashboardCalled := false libraryPanelService := &libraryPanelServiceMock{ - importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error { + importLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { importLibraryPanelsForDashboard = true return nil }, - connectLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error { + connectLibraryPanelsForDashboardFunc: func(ctx context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error { connectLibraryPanelsForDashboardCalled = true return nil }, @@ -211,11 +211,13 @@ func (s *dashboardServiceMock) ImportDashboard(ctx context.Context, dto *dashboa type libraryPanelServiceMock struct { librarypanels.Service - connectLibraryPanelsForDashboardFunc func(c context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error - importLibraryPanelsForDashboardFunc func(c context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error + connectLibraryPanelsForDashboardFunc func(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error + importLibraryPanelsForDashboardFunc func(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error } -func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error { +var _ librarypanels.Service = (*libraryPanelServiceMock)(nil) + +func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error { if s.connectLibraryPanelsForDashboardFunc != nil { return s.connectLibraryPanelsForDashboardFunc(ctx, signedInUser, dash) } @@ -223,7 +225,7 @@ func (s *libraryPanelServiceMock) ConnectLibraryPanelsForDashboard(ctx context.C return nil } -func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func (s *libraryPanelServiceMock) ImportLibraryPanelsForDashboard(ctx context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { if s.importLibraryPanelsForDashboardFunc != nil { return s.importLibraryPanelsForDashboardFunc(ctx, signedInUser, libraryPanels, panels, folderID) } diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index acd06f4ee98..972d0292856 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -254,7 +254,7 @@ func (s *Service) getFolderByTitle(ctx context.Context, orgID int64, title strin func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) { logger := s.log.FromContext(ctx) - if cmd.SignedInUser == nil { + if cmd.SignedInUser == nil || cmd.SignedInUser.IsNil() { return nil, folder.ErrBadRequest.Errorf("missing signed in user") } diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index 630a5ae5f97..b089295fc1e 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -138,8 +138,7 @@ type GetFolderQuery struct { Title *string OrgID int64 - SignedInUser *user.SignedInUser `json:"-"` - Requester identity.Requester `json:"-"` + SignedInUser identity.Requester `json:"-"` } // GetParentsQuery captures the information required by the folder service to diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index 5a42c0c3d78..e75ac79f90c 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/kinds/librarypanel" + "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/libraryelements/model" @@ -106,7 +107,7 @@ func getLibraryElement(dialect migrator.Dialect, session *db.Session, uid string } // createLibraryElement adds a library element. -func (l *LibraryElementService) createLibraryElement(c context.Context, signedInUser *user.SignedInUser, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *LibraryElementService) createLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { if err := l.requireSupportedElementKind(cmd.Kind); err != nil { return model.LibraryElementDTO{}, err } @@ -130,8 +131,18 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn } } + userID := int64(0) + namespaceID, identifier := signedInUser.GetNamespacedID() + switch namespaceID { + case identity.NamespaceUser, identity.NamespaceServiceAccount: + userID, err = identity.IntIdentifier(namespaceID, identifier) + if err != nil { + l.log.Warn("Error while parsing userID", "namespaceID", namespaceID, "userID", identifier) + } + } + element := model.LibraryElement{ - OrgID: signedInUser.OrgID, + OrgID: signedInUser.GetOrgID(), FolderID: cmd.FolderID, UID: createUID, Name: cmd.Name, @@ -142,8 +153,8 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn Created: time.Now(), Updated: time.Now(), - CreatedBy: signedInUser.UserID, - UpdatedBy: signedInUser.UserID, + CreatedBy: userID, + UpdatedBy: userID, } if err := syncFieldsWithModel(&element); err != nil { @@ -180,13 +191,13 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn Updated: element.Updated, CreatedBy: librarypanel.LibraryElementDTOMetaUser{ Id: element.CreatedBy, - Name: signedInUser.Login, - AvatarUrl: dtos.GetGravatarUrl(signedInUser.Email), + Name: signedInUser.GetLogin(), + AvatarUrl: dtos.GetGravatarUrl(signedInUser.GetEmail()), }, UpdatedBy: librarypanel.LibraryElementDTOMetaUser{ Id: element.UpdatedBy, - Name: signedInUser.Login, - AvatarUrl: dtos.GetGravatarUrl(signedInUser.Email), + Name: signedInUser.GetLogin(), + AvatarUrl: dtos.GetGravatarUrl(signedInUser.GetEmail()), }, }, } @@ -238,7 +249,7 @@ func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedIn } // getLibraryElements gets a Library Element where param == value -func (l *LibraryElementService) getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair, features featuremgmt.FeatureToggles, cmd model.GetLibraryElementCommand) ([]model.LibraryElementDTO, error) { +func (l *LibraryElementService) getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser identity.Requester, params []Pair, features featuremgmt.FeatureToggles, cmd model.GetLibraryElementCommand) ([]model.LibraryElementDTO, error) { libraryElements := make([]model.LibraryElementWithMeta, 0) recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported() @@ -321,8 +332,8 @@ func (l *LibraryElementService) getLibraryElements(c context.Context, store db.D } // getLibraryElementByUid gets a Library Element by uid. -func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { - libraryElements, err := l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: cmd.UID}}, l.features, cmd) +func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { + libraryElements, err := l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.GetOrgID()}, {key: "uid", value: cmd.UID}}, l.features, cmd) if err != nil { return model.LibraryElementDTO{}, err } @@ -334,15 +345,15 @@ func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signed } // getLibraryElementByName gets a Library Element by name. -func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]model.LibraryElementDTO, error) { - return l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}, l.features, +func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser identity.Requester, name string) ([]model.LibraryElementDTO, error) { + return l.getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.GetOrgID()}, {"name", name}}, l.features, model.GetLibraryElementCommand{ FolderName: dashboards.RootFolderName, }) } // getAllLibraryElements gets all Library Elements. -func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedInUser *user.SignedInUser, query model.SearchLibraryElementsQuery) (model.LibraryElementSearchResult, error) { +func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedInUser identity.Requester, query model.SearchLibraryElementsQuery) (model.LibraryElementSearchResult, error) { elements := make([]model.LibraryElementWithMeta, 0) result := model.LibraryElementSearchResult{} @@ -372,7 +383,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI builder.Write(", 'General' as folder_name ") builder.Write(", '' as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) - builder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.OrgID) + builder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.GetOrgID()) writeKindSQL(query, &builder) writeSearchStringSQL(query, l.SQLStore, &builder) writeExcludeSQL(query, &builder) @@ -384,7 +395,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI builder.Write(", dashboard.uid as folder_uid ") builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id<>0") - builder.Write(` WHERE le.org_id=?`, signedInUser.OrgID) + builder.Write(` WHERE le.org_id=?`, signedInUser.GetOrgID()) writeKindSQL(query, &builder) writeSearchStringSQL(query, l.SQLStore, &builder) writeExcludeSQL(query, &builder) @@ -392,7 +403,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil { return err } - if signedInUser.OrgRole != org.RoleAdmin { + if !signedInUser.HasRole(org.RoleAdmin) { builder.WriteDashboardPermissionFilter(signedInUser, dashboards.PERMISSION_VIEW, "") } if query.SortDirection == search.SortAlphaDesc.Name { @@ -444,7 +455,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI if folderFilter.includeGeneralFolder { countBuilder.Write(selectLibraryElementDTOWithMeta) countBuilder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) - countBuilder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.OrgID) + countBuilder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.GetOrgID()) writeKindSQL(query, &countBuilder) writeSearchStringSQL(query, l.SQLStore, &countBuilder) writeExcludeSQL(query, &countBuilder) @@ -454,7 +465,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI countBuilder.Write(selectLibraryElementDTOWithMeta) countBuilder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect())) countBuilder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id and le.folder_id<>0") - countBuilder.Write(` WHERE le.org_id=?`, signedInUser.OrgID) + countBuilder.Write(` WHERE le.org_id=?`, signedInUser.GetOrgID()) writeKindSQL(query, &countBuilder) writeSearchStringSQL(query, l.SQLStore, &countBuilder) writeExcludeSQL(query, &countBuilder) @@ -708,14 +719,14 @@ func (l *LibraryElementService) getElementsForDashboardID(c context.Context, das } // connectElementsToDashboardID adds connections for all elements Library Elements in a Dashboard. -func (l *LibraryElementService) connectElementsToDashboardID(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error { +func (l *LibraryElementService) connectElementsToDashboardID(c context.Context, signedInUser identity.Requester, elementUIDs []string, dashboardID int64) error { err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { _, err := session.Exec("DELETE FROM "+model.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID) if err != nil { return err } for _, elementUID := range elementUIDs { - element, err := getLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.OrgID) + element, err := getLibraryElement(l.SQLStore.GetDialect(), session, elementUID, signedInUser.GetOrgID()) if err != nil { return err } @@ -723,12 +734,22 @@ func (l *LibraryElementService) connectElementsToDashboardID(c context.Context, return err } + namespaceID, identifier := signedInUser.GetNamespacedID() + userID := int64(0) + switch namespaceID { + case identity.NamespaceUser, identity.NamespaceServiceAccount: + userID, err = identity.IntIdentifier(namespaceID, identifier) + if err != nil { + l.log.Warn("Failed to parse user ID from namespace identifier", "namespace", namespaceID, "identifier", identifier, "error", err) + } + } + connection := model.LibraryElementConnection{ ElementID: element.ID, Kind: 1, ConnectionID: dashboardID, Created: time.Now(), - CreatedBy: signedInUser.UserID, + CreatedBy: userID, } if _, err := session.Insert(&connection); err != nil { if l.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { @@ -755,12 +776,12 @@ func (l *LibraryElementService) disconnectElementsFromDashboardID(c context.Cont } // deleteLibraryElementsInFolderUID deletes all Library Elements in a folder. -func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Context, signedInUser *user.SignedInUser, folderUID string) error { +func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Context, signedInUser identity.Requester, folderUID string) error { return l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { var folderUIDs []struct { ID int64 `xorm:"id"` } - err := session.SQL("SELECT id from dashboard WHERE uid=? AND org_id=? AND is_folder=?", folderUID, signedInUser.OrgID, l.SQLStore.GetDialect().BooleanStr(true)).Find(&folderUIDs) + err := session.SQL("SELECT id from dashboard WHERE uid=? AND org_id=? AND is_folder=?", folderUID, signedInUser.GetOrgID(), l.SQLStore.GetDialect().BooleanStr(true)).Find(&folderUIDs) if err != nil { return err } @@ -784,7 +805,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte sql := "SELECT lec.connection_id FROM library_element AS le" sql += " INNER JOIN " + model.LibraryElementConnectionTableName + " AS lec on le.id = lec.element_id" sql += " WHERE le.folder_id=? AND le.org_id=?" - err = session.SQL(sql, folderID, signedInUser.OrgID).Find(&connectionIDs) + err = session.SQL(sql, folderID, signedInUser.GetOrgID()).Find(&connectionIDs) if err != nil { return err } @@ -795,7 +816,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte var elementIDs []struct { ID int64 `xorm:"id"` } - err = session.SQL("SELECT id from library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID).Find(&elementIDs) + err = session.SQL("SELECT id from library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.GetOrgID()).Find(&elementIDs) if err != nil { return err } @@ -805,7 +826,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte return err } } - if _, err := session.Exec("DELETE FROM library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.OrgID); err != nil { + if _, err := session.Exec("DELETE FROM library_element WHERE folder_id=? AND org_id=?", folderID, signedInUser.GetOrgID()); err != nil { return err } diff --git a/pkg/services/libraryelements/guard.go b/pkg/services/libraryelements/guard.go index bad8521fe38..9e0b6e7af29 100644 --- a/pkg/services/libraryelements/guard.go +++ b/pkg/services/libraryelements/guard.go @@ -4,11 +4,11 @@ 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/guardian" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/user" ) func isGeneralFolder(folderID int64) bool { @@ -31,7 +31,7 @@ func (l *LibraryElementService) requireSupportedElementKind(kindAsInt int64) err } } -func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Context, user *user.SignedInUser, folderID int64) error { +func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Context, user identity.Requester, folderID int64) error { // TODO remove these special cases and handle General folder case in access control guardian if isGeneralFolder(folderID) && user.HasRole(org.RoleEditor) { return nil @@ -41,7 +41,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte return dashboards.ErrFolderAccessDenied } - g, err := guardian.New(ctx, folderID, user.OrgID, user) + g, err := guardian.New(ctx, folderID, user.GetOrgID(), user) if err != nil { return err } @@ -57,12 +57,12 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte return nil } -func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Context, user *user.SignedInUser, folderID int64) error { +func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Context, user identity.Requester, folderID int64) error { if isGeneralFolder(folderID) && user.HasRole(org.RoleViewer) { return nil } - g, err := guardian.New(ctx, folderID, user.OrgID, user) + g, err := guardian.New(ctx, folderID, user.GetOrgID(), user) if err != nil { return err } diff --git a/pkg/services/libraryelements/libraryelements.go b/pkg/services/libraryelements/libraryelements.go index e883bd027b5..65f60cbc7f2 100644 --- a/pkg/services/libraryelements/libraryelements.go +++ b/pkg/services/libraryelements/libraryelements.go @@ -7,10 +7,10 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/libraryelements/model" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -29,12 +29,12 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout // Service is a service for operating on library elements. type Service interface { - CreateElement(c context.Context, signedInUser *user.SignedInUser, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) - GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) + CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) + GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) GetElementsForDashboard(c context.Context, dashboardID int64) (map[string]model.LibraryElementDTO, error) - ConnectElementsToDashboard(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error + ConnectElementsToDashboard(c context.Context, signedInUser identity.Requester, elementUIDs []string, dashboardID int64) error DisconnectElementsFromDashboard(c context.Context, dashboardID int64) error - DeleteLibraryElementsInFolder(c context.Context, signedInUser *user.SignedInUser, folderUID string) error + DeleteLibraryElementsInFolder(c context.Context, signedInUser identity.Requester, folderUID string) error } // LibraryElementService is the service for the Library Element feature. @@ -47,13 +47,15 @@ type LibraryElementService struct { features featuremgmt.FeatureToggles } +var _ Service = (*LibraryElementService)(nil) + // CreateElement creates a Library Element. -func (l *LibraryElementService) CreateElement(c context.Context, signedInUser *user.SignedInUser, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *LibraryElementService) CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { return l.createLibraryElement(c, signedInUser, cmd) } // GetElement gets an element from a UID. -func (l *LibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *LibraryElementService) GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { return l.getLibraryElementByUid(c, signedInUser, cmd) } @@ -63,7 +65,7 @@ func (l *LibraryElementService) GetElementsForDashboard(c context.Context, dashb } // ConnectElementsToDashboard connects elements to a specific dashboard. -func (l *LibraryElementService) ConnectElementsToDashboard(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error { +func (l *LibraryElementService) ConnectElementsToDashboard(c context.Context, signedInUser identity.Requester, elementUIDs []string, dashboardID int64) error { return l.connectElementsToDashboardID(c, signedInUser, elementUIDs, dashboardID) } @@ -73,7 +75,7 @@ func (l *LibraryElementService) DisconnectElementsFromDashboard(c context.Contex } // DeleteLibraryElementsInFolder deletes all elements for a specific folder. -func (l *LibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser *user.SignedInUser, folderUID string) error { +func (l *LibraryElementService) DeleteLibraryElementsInFolder(c context.Context, signedInUser identity.Requester, folderUID string) error { return l.deleteLibraryElementsInFolderUID(c, signedInUser, folderUID) } diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 422371eb86c..38e40479abb 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/kinds/librarypanel" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -268,6 +269,7 @@ type scenarioContext struct { folder *folder.Folder initialResult libraryElementResult sqlStore db.DB + log log.Logger } func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64) *dashboards.Dashboard { @@ -389,6 +391,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena sc.reqContext.Req.Body = mockRequestBody(command) resp := sc.service.createHandler(sc.reqContext) sc.initialResult = validateAndUnMarshalResponse(t, resp) + sc.log = log.New("libraryelements-test") fn(t, sc) }) diff --git a/pkg/services/librarypanels/librarypanels.go b/pkg/services/librarypanels/librarypanels.go index 77b18c9c01b..fff7738a13a 100644 --- a/pkg/services/librarypanels/librarypanels.go +++ b/pkg/services/librarypanels/librarypanels.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "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/libraryelements" @@ -38,8 +39,8 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout // Service is a service for operating on library panels. type Service interface { - ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error - ImportLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error + ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error + ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error } type LibraryInfo struct { @@ -57,8 +58,10 @@ type LibraryPanelService struct { log log.Logger } +var _ Service = (*LibraryPanelService)(nil) + // ConnectLibraryPanelsForDashboard loops through all panels in dashboard JSON and connects any library panels to the dashboard. -func (lps *LibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, dash *dashboards.Dashboard) error { +func (lps *LibraryPanelService) ConnectLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, dash *dashboards.Dashboard) error { panels := dash.Data.Get("panels").MustArray() libraryPanels := make(map[string]string) err := connectLibraryPanelsRecursively(c, panels, libraryPanels) @@ -112,11 +115,11 @@ func connectLibraryPanelsRecursively(c context.Context, panels []any, libraryPan } // ImportLibraryPanelsForDashboard loops through all panels in dashboard JSON and creates any missing library panels in the database. -func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func (lps *LibraryPanelService) ImportLibraryPanelsForDashboard(c context.Context, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { return importLibraryPanelsRecursively(c, lps.LibraryElementService, signedInUser, libraryPanels, panels, folderID) } -func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser *user.SignedInUser, libraryPanels *simplejson.Json, panels []any, folderID int64) error { +func importLibraryPanelsRecursively(c context.Context, service libraryelements.Service, signedInUser identity.Requester, libraryPanels *simplejson.Json, panels []any, folderID int64) error { for _, panel := range panels { panelAsJSON := simplejson.NewFromAny(panel) libraryPanel := panelAsJSON.Get("libraryPanel") diff --git a/pkg/services/plugindashboards/service/dashboard_updater_test.go b/pkg/services/plugindashboards/service/dashboard_updater_test.go index 0b873751769..ceb5b093182 100644 --- a/pkg/services/plugindashboards/service/dashboard_updater_test.go +++ b/pkg/services/plugindashboards/service/dashboard_updater_test.go @@ -192,8 +192,8 @@ func TestDashboardUpdater(t *testing.T) { require.Len(t, ctx.importDashboardArgs, 1) require.Equal(t, "test", ctx.importDashboardArgs[0].PluginId) require.Equal(t, "updated.json", ctx.importDashboardArgs[0].Path) - require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.OrgID) - require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[0].User.OrgRole) + require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.GetOrgID()) + require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[0].User.GetOrgRole()) require.Equal(t, int64(0), ctx.importDashboardArgs[0].FolderId) require.True(t, ctx.importDashboardArgs[0].Overwrite) }) @@ -319,22 +319,22 @@ func TestDashboardUpdater(t *testing.T) { require.Len(t, ctx.importDashboardArgs, 3) require.Equal(t, "test", ctx.importDashboardArgs[0].PluginId) require.Equal(t, "dashboard1.json", ctx.importDashboardArgs[0].Path) - require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.OrgID) - require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[0].User.OrgRole) + require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.GetOrgID()) + require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[0].User.GetOrgRole()) require.Equal(t, int64(0), ctx.importDashboardArgs[0].FolderId) require.True(t, ctx.importDashboardArgs[0].Overwrite) require.Equal(t, "test", ctx.importDashboardArgs[1].PluginId) require.Equal(t, "dashboard2.json", ctx.importDashboardArgs[1].Path) - require.Equal(t, int64(2), ctx.importDashboardArgs[1].User.OrgID) - require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[1].User.OrgRole) + require.Equal(t, int64(2), ctx.importDashboardArgs[1].User.GetOrgID()) + require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[1].User.GetOrgRole()) require.Equal(t, int64(0), ctx.importDashboardArgs[1].FolderId) require.True(t, ctx.importDashboardArgs[1].Overwrite) require.Equal(t, "test", ctx.importDashboardArgs[2].PluginId) require.Equal(t, "dashboard3.json", ctx.importDashboardArgs[2].Path) - require.Equal(t, int64(2), ctx.importDashboardArgs[2].User.OrgID) - require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[2].User.OrgRole) + require.Equal(t, int64(2), ctx.importDashboardArgs[2].User.GetOrgID()) + require.Equal(t, org.RoleAdmin, ctx.importDashboardArgs[2].User.GetOrgRole()) require.Equal(t, int64(0), ctx.importDashboardArgs[2].FolderId) require.True(t, ctx.importDashboardArgs[2].Overwrite) }) diff --git a/pkg/services/sqlstore/permissions/dashboard.go b/pkg/services/sqlstore/permissions/dashboard.go index 58fed9efd96..9c1fa1d93fc 100644 --- a/pkg/services/sqlstore/permissions/dashboard.go +++ b/pkg/services/sqlstore/permissions/dashboard.go @@ -6,12 +6,12 @@ import ( "strings" "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" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" - "github.com/grafana/grafana/pkg/services/user" ) // maximum possible capacity for recursive queries array: one query for folder and one for dashboard actions @@ -23,7 +23,7 @@ type clause struct { } type accessControlDashboardPermissionFilter struct { - user *user.SignedInUser + user identity.Requester dashboardActions []string folderActions []string features featuremgmt.FeatureToggles @@ -46,7 +46,7 @@ type PermissionsFilter interface { // NewAccessControlDashboardPermissionFilter creates a new AccessControlDashboardPermissionFilter that is configured with specific actions calculated based on the dashboards.PermissionType and query type // The filter is configured to use the new permissions filter (without subqueries) if the feature flag is enabled // The filter is configured to use the old permissions filter (with subqueries) if the feature flag is disabled -func NewAccessControlDashboardPermissionFilter(user *user.SignedInUser, permissionLevel dashboards.PermissionType, queryType string, features featuremgmt.FeatureToggles, recursiveQueriesAreSupported bool) PermissionsFilter { +func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissionLevel dashboards.PermissionType, queryType string, features featuremgmt.FeatureToggles, recursiveQueriesAreSupported bool) PermissionsFilter { needEdit := permissionLevel > dashboards.PERMISSION_VIEW var folderActions []string @@ -111,14 +111,21 @@ func (f *accessControlDashboardPermissionFilter) Where() (string, []any) { } func (f *accessControlDashboardPermissionFilter) buildClauses() { - if f.user == nil || f.user.Permissions == nil || f.user.Permissions[f.user.OrgID] == nil { + if f.user == nil || f.user.IsNil() || len(f.user.GetPermissions()) == 0 { f.where = clause{string: "(1 = 0)"} return } dashWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeDashboardsPrefix) folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix) - filter, params := accesscontrol.UserRolesFilter(f.user.OrgID, f.user.UserID, f.user.Teams, accesscontrol.GetOrgRoles(f.user)) + userID := int64(0) + namespaceID, identifier := f.user.GetNamespacedID() + switch namespaceID { + case identity.NamespaceUser, identity.NamespaceServiceAccount: + userID, _ = identity.IntIdentifier(namespaceID, identifier) + } + + filter, params := accesscontrol.UserRolesFilter(f.user.GetOrgID(), userID, f.user.GetTeams(), accesscontrol.GetOrgRoles(f.user)) rolesFilter := " AND role_id IN(SELECT id FROM role " + filter + ") " var args []any builder := strings.Builder{} @@ -129,10 +136,10 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { // useSelfContainedPermissions is true if the user's permissions are stored and set from the JWT token // currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana) - useSelfContainedPermissions := f.user.AuthenticatedBy == login.ExtendedJWTModule + useSelfContainedPermissions := f.user.GetAuthenticatedBy() == login.ExtendedJWTModule if len(f.dashboardActions) > 0 { - toCheck := actionsToCheck(f.dashboardActions, f.user.Permissions[f.user.OrgID], dashWildcards, folderWildcards) + toCheck := actionsToCheck(f.dashboardActions, f.user.GetPermissions(), dashWildcards, folderWildcards) if len(toCheck) > 0 { if !useSelfContainedPermissions { @@ -236,7 +243,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { builder.WriteString(" OR ") } - toCheck := actionsToCheck(f.folderActions, f.user.Permissions[f.user.OrgID], folderWildcards) + toCheck := actionsToCheck(f.folderActions, f.user.GetPermissions(), folderWildcards) if len(toCheck) > 0 { if !useSelfContainedPermissions { permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%'") @@ -392,10 +399,10 @@ func parseStringSliceFromInterfaceSlice(slice []any) []string { return result } -func getAllowedUIDs(actions []string, user *user.SignedInUser, scopePrefix string) []any { +func getAllowedUIDs(actions []string, user identity.Requester, scopePrefix string) []any { uidToActions := make(map[string]map[string]struct{}) for _, action := range actions { - for _, uidScope := range user.Permissions[user.OrgID][action] { + for _, uidScope := range user.GetPermissions()[action] { if !strings.HasPrefix(uidScope, scopePrefix) { continue } diff --git a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go index 243baca0bf3..7b885755ae1 100644 --- a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go +++ b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go @@ -5,6 +5,7 @@ import ( "strings" "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" @@ -25,14 +26,21 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) LeftJoin() stri } func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() { - if f.user == nil || f.user.Permissions == nil || f.user.Permissions[f.user.OrgID] == nil { + if f.user == nil || f.user.IsNil() || len(f.user.GetPermissions()) == 0 { f.where = clause{string: "(1 = 0)"} return } dashWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeDashboardsPrefix) folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix) - filter, params := accesscontrol.UserRolesFilter(f.user.OrgID, f.user.UserID, f.user.Teams, accesscontrol.GetOrgRoles(f.user)) + userID := int64(0) + namespaceID, identifier := f.user.GetNamespacedID() + switch namespaceID { + case identity.NamespaceUser, identity.NamespaceServiceAccount: + userID, _ = identity.IntIdentifier(namespaceID, identifier) + } + + filter, params := accesscontrol.UserRolesFilter(f.user.GetOrgID(), userID, f.user.GetTeams(), accesscontrol.GetOrgRoles(f.user)) rolesFilter := " AND role_id IN(SELECT id FROM role " + filter + ") " var args []any builder := strings.Builder{} @@ -43,10 +51,10 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() // useSelfContainedPermissions is true if the user's permissions are stored and set from the JWT token // currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana) - useSelfContainedPermissions := f.user.AuthenticatedBy == login.ExtendedJWTModule + useSelfContainedPermissions := f.user.GetAuthenticatedBy() == login.ExtendedJWTModule if len(f.dashboardActions) > 0 { - toCheck := actionsToCheck(f.dashboardActions, f.user.Permissions[f.user.OrgID], dashWildcards, folderWildcards) + toCheck := actionsToCheck(f.dashboardActions, f.user.GetPermissions(), dashWildcards, folderWildcards) if len(toCheck) > 0 { if !useSelfContainedPermissions { @@ -152,7 +160,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() builder.WriteString(" OR ") } - toCheck := actionsToCheck(f.folderActions, f.user.Permissions[f.user.OrgID], folderWildcards) + toCheck := actionsToCheck(f.folderActions, f.user.GetPermissions(), folderWildcards) if len(toCheck) > 0 { if !useSelfContainedPermissions { permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%'") diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index b74fa5a7080..4ce24e1b58a 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -46,11 +46,14 @@ func (u *SignedInUser) NameOrFallback() string { // 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 +// In order to retrieve the user URL, we need the dto library. However, adding +// the dto library to the user service creates a circular dependency func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO { return &UserDisplayDTO{ ID: u.UserID, Login: u.Login, Name: u.Name, + // AvatarURL: dtos.GetGravatarUrl(u.GetEmail()), } } @@ -200,3 +203,8 @@ func (u *SignedInUser) GetEmail() string { func (u *SignedInUser) GetDisplayName() string { return u.NameOrFallback() } + +// DEPRECATEAD: Returns the authentication method used +func (u *SignedInUser) GetAuthenticatedBy() string { + return u.AuthenticatedBy +} diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 614fcbacd2e..0af021ee0fc 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" "time" + + "github.com/grafana/grafana/pkg/services/auth/identity" ) type HelpFlags1 uint64 @@ -104,7 +106,7 @@ type SetUsingOrgCommand struct { } type SearchUsersQuery struct { - SignedInUser *SignedInUser + SignedInUser identity.Requester OrgID int64 `xorm:"org_id"` Query string Page int diff --git a/pkg/web/webtest/webtest.go b/pkg/web/webtest/webtest.go index 0977be1b89b..8dd42eba7db 100644 --- a/pkg/web/webtest/webtest.go +++ b/pkg/web/webtest/webtest.go @@ -111,9 +111,9 @@ func RequestWithWebContext(req *http.Request, c *contextmodel.ReqContext) *http. return req } -func RequestWithSignedInUser(req *http.Request, user *user.SignedInUser) *http.Request { +func RequestWithSignedInUser(req *http.Request, usr *user.SignedInUser) *http.Request { return RequestWithWebContext(req, &contextmodel.ReqContext{ - SignedInUser: user, + SignedInUser: usr, IsSignedIn: true, }) }