diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 385646acacb..9c506052f6e 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -2,22 +2,14 @@ package guardian import ( "context" - "errors" - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" - "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" ) var ( - ErrGuardianPermissionExists = errors.New("permission already exists") - ErrGuardianOverride = errors.New("you can only override a permission to be higher") ErrGuardianGetDashboardFailure = errutil.Internal("guardian.getDashboardFailure", errutil.WithPublicMessage("Failed to get dashboard")) ErrGuardianDashboardNotFound = errutil.NotFound("guardian.dashboardNotFound") ErrGuardianFolderNotFound = errutil.NotFound("guardian.folderNotFound") @@ -33,20 +25,6 @@ type DashboardGuardian interface { CanCreate(folderID int64, isFolder bool) (bool, error) } -type dashboardGuardianImpl struct { - cfg *setting.Cfg - user *user.SignedInUser - dashId int64 - orgId int64 - acl []*dashboards.DashboardACLInfoDTO - teams []*team.TeamDTO - log log.Logger - ctx context.Context - store db.DB - dashboardService dashboards.DashboardService - teamService team.Service -} - // 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) { @@ -71,364 +49,16 @@ var NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user panic("no guardian factory implementation provided") } -// newDashboardGuardian creates a dashboard guardian by the provided dashId. -func newDashboardGuardian(ctx context.Context, cfg *setting.Cfg, dashId int64, orgId int64, user *user.SignedInUser, store db.DB, dashSvc dashboards.DashboardService, teamSvc team.Service) (*dashboardGuardianImpl, error) { - if dashId != 0 { - q := &dashboards.GetDashboardQuery{ - ID: dashId, - OrgID: orgId, - } - - if _, err := dashSvc.GetDashboard(ctx, q); err != nil { - if errors.Is(err, dashboards.ErrDashboardNotFound) { - return nil, ErrGuardianDashboardNotFound.Errorf("failed to get dashboard by UID: %w", err) - } - return nil, ErrGuardianGetDashboardFailure.Errorf("failed to get dashboard by UID: %w", err) - } - } - - return &dashboardGuardianImpl{ - cfg: cfg, - user: user, - dashId: dashId, - orgId: orgId, - log: log.New("dashboard.permissions"), - ctx: ctx, - store: store, - dashboardService: dashSvc, - teamService: teamSvc, - }, nil -} - -// newDashboardGuardianByUID creates a dashboard guardian by the provided dashUID. -func newDashboardGuardianByUID(ctx context.Context, cfg *setting.Cfg, dashUID string, orgId int64, user *user.SignedInUser, store db.DB, dashSvc dashboards.DashboardService, teamSvc team.Service) (*dashboardGuardianImpl, error) { - dashID := int64(0) - if dashUID != "" { - q := &dashboards.GetDashboardQuery{ - UID: dashUID, - OrgID: orgId, - } - - qResult, err := dashSvc.GetDashboard(ctx, q) - if err != nil { - if errors.Is(err, dashboards.ErrDashboardNotFound) { - return nil, ErrGuardianDashboardNotFound.Errorf("failed to get dashboard by UID: %w", err) - } - return nil, ErrGuardianGetDashboardFailure.Errorf("failed to get dashboard by UID: %w", err) - } - dashID = qResult.ID - } - - return &dashboardGuardianImpl{ - cfg: cfg, - user: user, - dashId: dashID, - orgId: orgId, - log: log.New("dashboard.permissions"), - ctx: ctx, - store: store, - dashboardService: dashSvc, - teamService: teamSvc, - }, nil -} - -// newDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboard. -// 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 newDashboardGuardianByDashboard(ctx context.Context, cfg *setting.Cfg, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser, store db.DB, dashSvc dashboards.DashboardService, teamSvc team.Service) (*dashboardGuardianImpl, error) { - return &dashboardGuardianImpl{ - cfg: cfg, - user: user, - dashId: dash.ID, - orgId: orgId, - log: log.New("dashboard.permissions"), - ctx: ctx, - store: store, - dashboardService: dashSvc, - teamService: teamSvc, - }, nil -} - -// newDashboardGuardianByFolder creates a dashboard guardian by the provided folder. -// This constructor should be preferred over the other two if the dashboard in available -// since it avoids querying the database for fetching the dashboard. -// The folder.ID should be the sequence ID in the dashboard table. -func newDashboardGuardianByFolder(ctx context.Context, cfg *setting.Cfg, f *folder.Folder, orgId int64, user *user.SignedInUser, store db.DB, dashSvc dashboards.DashboardService, teamSvc team.Service) (*dashboardGuardianImpl, error) { - return &dashboardGuardianImpl{ - cfg: cfg, - user: user, - dashId: f.ID, - orgId: orgId, - log: log.New("dashboard.permissions"), - ctx: ctx, - store: store, - dashboardService: dashSvc, - teamService: teamSvc, - }, nil -} - -func (g *dashboardGuardianImpl) CanSave() (bool, error) { - return g.HasPermission(dashboards.PERMISSION_EDIT) -} - -func (g *dashboardGuardianImpl) CanEdit() (bool, error) { - if g.cfg.ViewersCanEdit { - return g.HasPermission(dashboards.PERMISSION_VIEW) - } - - return g.HasPermission(dashboards.PERMISSION_EDIT) -} - -func (g *dashboardGuardianImpl) CanView() (bool, error) { - return g.HasPermission(dashboards.PERMISSION_VIEW) -} - -func (g *dashboardGuardianImpl) CanAdmin() (bool, error) { - return g.HasPermission(dashboards.PERMISSION_ADMIN) -} - -func (g *dashboardGuardianImpl) CanDelete() (bool, error) { - // when using dashboard guardian without access control a user can delete a dashboard if they can save it - return g.CanSave() -} - -func (g *dashboardGuardianImpl) CanCreate(_ int64, _ bool) (bool, error) { - // when using dashboard guardian without access control a user can create a dashboard if they can save it - return g.CanSave() -} - -func (g *dashboardGuardianImpl) HasPermission(permission dashboards.PermissionType) (bool, error) { - if g.user.OrgRole == org.RoleAdmin { - return g.logHasPermissionResult(permission, true, nil) - } - - acl, err := g.GetACL() - if err != nil { - return g.logHasPermissionResult(permission, false, err) - } - - result, err := g.checkACL(permission, acl) - return g.logHasPermissionResult(permission, result, err) -} - -func (g *dashboardGuardianImpl) logHasPermissionResult(permission dashboards.PermissionType, hasPermission bool, err error) (bool, error) { - if err != nil { - return hasPermission, err - } - - if hasPermission { - g.log.Debug("User granted access to execute action", "userId", g.user.UserID, "orgId", g.orgId, "uname", g.user.Login, "dashId", g.dashId, "action", permission) - } else { - g.log.Debug("User denied access to execute action", "userId", g.user.UserID, "orgId", g.orgId, "uname", g.user.Login, "dashId", g.dashId, "action", permission) - } - - return hasPermission, err -} - -func (g *dashboardGuardianImpl) checkACL(permission dashboards.PermissionType, acl []*dashboards.DashboardACLInfoDTO) (bool, error) { - orgRole := g.user.OrgRole - teamACLItems := []*dashboards.DashboardACLInfoDTO{} - - for _, p := range acl { - // user match - if !g.user.IsAnonymous && p.UserID > 0 { - if p.UserID == g.user.UserID && p.Permission >= permission { - return true, nil - } - } - - // role match - if p.Role != nil { - if *p.Role == orgRole && p.Permission >= permission { - return true, nil - } - } - - // remember this rule for later - if p.TeamID > 0 { - teamACLItems = append(teamACLItems, p) - } - } - - // do we have team rules? - if len(teamACLItems) == 0 { - return false, nil - } - - // load teams - teams, err := g.getTeams() - if err != nil { - return false, err - } - - // evaluate team rules - for _, p := range acl { - for _, ug := range teams { - if ug.ID == p.TeamID && p.Permission >= permission { - return true, nil - } - } - } - - return false, nil -} - -func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission dashboards.PermissionType, updatePermissions []*dashboards.DashboardACL) (bool, error) { - acl := []*dashboards.DashboardACLInfoDTO{} - adminRole := org.RoleAdmin - everyoneWithAdminRole := &dashboards.DashboardACLInfoDTO{DashboardID: g.dashId, UserID: 0, TeamID: 0, Role: &adminRole, Permission: dashboards.PERMISSION_ADMIN} - - // validate that duplicate permissions don't exists - for _, p := range updatePermissions { - aclItem := &dashboards.DashboardACLInfoDTO{DashboardID: p.DashboardID, UserID: p.UserID, TeamID: p.TeamID, Role: p.Role, Permission: p.Permission} - if aclItem.IsDuplicateOf(everyoneWithAdminRole) { - return false, ErrGuardianPermissionExists - } - - for _, a := range acl { - if a.IsDuplicateOf(aclItem) { - return false, ErrGuardianPermissionExists - } - } - - acl = append(acl, aclItem) - } - - existingPermissions, err := g.GetACL() - if err != nil { - return false, err - } - - // validate overridden permissions to be higher - for _, a := range acl { - for _, existingPerm := range existingPermissions { - if !existingPerm.Inherited { - continue - } - - if a.IsDuplicateOf(existingPerm) && a.Permission <= existingPerm.Permission { - return false, ErrGuardianOverride - } - } - } - - if g.user.OrgRole == org.RoleAdmin { - return true, nil - } - - return g.checkACL(permission, existingPermissions) -} - -// GetACL returns dashboard acl -func (g *dashboardGuardianImpl) GetACL() ([]*dashboards.DashboardACLInfoDTO, error) { - if g.acl != nil { - return g.acl, nil - } - - query := dashboards.GetDashboardACLInfoListQuery{DashboardID: g.dashId, OrgID: g.orgId} - queryResult, err := g.dashboardService.GetDashboardACLInfoList(g.ctx, &query) - if err != nil { - return nil, err - } - g.acl = queryResult - return g.acl, nil -} - -func (g *dashboardGuardianImpl) GetACLWithoutDuplicates() ([]*dashboards.DashboardACLInfoDTO, error) { - acl, err := g.GetACL() - if err != nil { - return nil, err - } - - nonInherited := []*dashboards.DashboardACLInfoDTO{} - inherited := []*dashboards.DashboardACLInfoDTO{} - for _, aclItem := range acl { - if aclItem.Inherited { - inherited = append(inherited, aclItem) - } else { - nonInherited = append(nonInherited, aclItem) - } - } - - result := []*dashboards.DashboardACLInfoDTO{} - for _, nonInheritedACLItem := range nonInherited { - duplicate := false - for _, inheritedACLItem := range inherited { - if nonInheritedACLItem.IsDuplicateOf(inheritedACLItem) && nonInheritedACLItem.Permission <= inheritedACLItem.Permission { - duplicate = true - break - } - } - - if !duplicate { - result = append(result, nonInheritedACLItem) - } - } - - result = append(inherited, result...) - - return result, nil -} - -func (g *dashboardGuardianImpl) getTeams() ([]*team.TeamDTO, error) { - if g.teams != nil { - return g.teams, nil - } - - query := team.GetTeamsByUserQuery{OrgID: g.orgId, UserID: g.user.UserID, SignedInUser: g.user} - queryResult, err := g.teamService.GetTeamsByUser(g.ctx, &query) - - g.teams = queryResult - return queryResult, err -} - -func (g *dashboardGuardianImpl) GetHiddenACL(cfg *setting.Cfg) ([]*dashboards.DashboardACL, error) { - hiddenACL := make([]*dashboards.DashboardACL, 0) - if g.user.IsGrafanaAdmin { - return hiddenACL, nil - } - - existingPermissions, err := g.GetACL() - if err != nil { - return hiddenACL, err - } - - for _, item := range existingPermissions { - if item.Inherited || item.UserLogin == g.user.Login { - continue - } - - if _, hidden := cfg.HiddenUsers[item.UserLogin]; hidden { - hiddenACL = append(hiddenACL, &dashboards.DashboardACL{ - OrgID: item.OrgID, - DashboardID: item.DashboardID, - UserID: item.UserID, - TeamID: item.TeamID, - Role: item.Role, - Permission: item.Permission, - Created: item.Created, - Updated: item.Updated, - }) - } - } - return hiddenACL, nil -} - // nolint:unused type FakeDashboardGuardian struct { - DashID int64 - DashUID string - OrgID int64 - User *user.SignedInUser - CanSaveValue bool - CanEditValue bool - CanViewValue bool - CanAdminValue bool - HasPermissionValue bool - CheckPermissionBeforeUpdateValue bool - CheckPermissionBeforeUpdateError error - GetACLValue []*dashboards.DashboardACLInfoDTO - GetHiddenACLValue []*dashboards.DashboardACL + DashID int64 + DashUID string + OrgID int64 + User *user.SignedInUser + CanSaveValue bool + CanEditValue bool + CanViewValue bool + CanAdminValue bool } func (g *FakeDashboardGuardian) CanSave() (bool, error) { @@ -455,10 +85,6 @@ func (g *FakeDashboardGuardian) CanCreate(_ int64, _ bool) (bool, error) { return g.CanSaveValue, nil } -func (g *FakeDashboardGuardian) HasPermission(permission dashboards.PermissionType) (bool, error) { - return g.HasPermissionValue, nil -} - // nolint:unused func MockDashboardGuardian(mock *FakeDashboardGuardian) { New = func(_ context.Context, dashID int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { diff --git a/pkg/services/guardian/provider.go b/pkg/services/guardian/provider.go index 74fd674acc0..0b4ba95def9 100644 --- a/pkg/services/guardian/provider.go +++ b/pkg/services/guardian/provider.go @@ -3,7 +3,6 @@ package guardian import ( "context" - "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" @@ -15,36 +14,14 @@ import ( type Provider struct{} func ProvideService( - cfg *setting.Cfg, store db.DB, ac accesscontrol.AccessControl, + cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, teamService team.Service, ) *Provider { - if !ac.IsDisabled() { - // TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935 - InitAccessControlGuardian(cfg, ac, dashboardService) - } else { - InitLegacyGuardian(cfg, store, dashboardService, teamService) - } + // TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935 + InitAccessControlGuardian(cfg, ac, dashboardService) return &Provider{} } -func InitLegacyGuardian(cfg *setting.Cfg, store db.DB, dashSvc dashboards.DashboardService, teamSvc team.Service) { - New = func(ctx context.Context, dashId int64, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { - return newDashboardGuardian(ctx, cfg, dashId, orgId, user, store, dashSvc, teamSvc) - } - - NewByUID = func(ctx context.Context, dashUID string, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { - return newDashboardGuardianByUID(ctx, cfg, dashUID, orgId, user, store, dashSvc, teamSvc) - } - - NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { - return newDashboardGuardianByDashboard(ctx, cfg, dash, orgId, user, store, dashSvc, teamSvc) - } - - NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { - return newDashboardGuardianByFolder(ctx, cfg, f, orgId, user, store, dashSvc, teamSvc) - } -} - func InitAccessControlGuardian( cfg *setting.Cfg, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, ) {