grafana/pkg/services/guardian/accesscontrol_guardian.go
Jo 6b6cf5f4b7
Cfg: Move ViewersCanEdit into cfg (#64876)
move ViewersCanEdit into cfg
2023-03-16 10:54:01 +01:00

359 lines
12 KiB
Go

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/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
var permissionMap = map[string]dashboards.PermissionType{
"View": dashboards.PERMISSION_VIEW,
"Edit": dashboards.PERMISSION_EDIT,
"Admin": dashboards.PERMISSION_ADMIN,
}
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,
store db.DB, ac accesscontrol.AccessControl,
folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) {
var dashboard *dashboards.Dashboard
if dashboardId != 0 {
q := &dashboards.GetDashboardQuery{
ID: dashboardId,
OrgID: user.OrgID,
}
qResult, err := dashboardService.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)
}
dashboard = qResult
}
return &AccessControlDashboardGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("dashboard.permissions"),
dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil
}
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID.
func NewAccessControlDashboardGuardianByUID(
ctx context.Context, cfg *setting.Cfg, dashboardUID string, user *user.SignedInUser,
store db.DB, ac accesscontrol.AccessControl,
folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) {
var dashboard *dashboards.Dashboard
if dashboardUID != "" {
q := &dashboards.GetDashboardQuery{
UID: dashboardUID,
OrgID: user.OrgID,
}
qResult, err := dashboardService.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)
}
dashboard = qResult
}
return &AccessControlDashboardGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil
}
// NewAccessControlDashboardGuardianByDashboard 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 NewAccessControlDashboardGuardianByDashboard(
ctx context.Context, cfg *setting.Cfg, dashboard *dashboards.Dashboard, user *user.SignedInUser,
store db.DB, ac accesscontrol.AccessControl,
folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) {
return &AccessControlDashboardGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil
}
type AccessControlDashboardGuardian struct {
cfg *setting.Cfg
ctx context.Context
log log.Logger
dashboard *dashboards.Dashboard
user *user.SignedInUser
store db.DB
ac accesscontrol.AccessControl
folderPermissionsService accesscontrol.FolderPermissionsService
dashboardPermissionsService accesscontrol.DashboardPermissionsService
dashboardService dashboards.DashboardService
}
func (a *AccessControlDashboardGuardian) CanSave() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound
}
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)))
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *AccessControlDashboardGuardian) CanEdit() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound
}
if a.cfg.ViewersCanEdit {
return a.CanView()
}
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)))
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *AccessControlDashboardGuardian) CanView() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound
}
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)))
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *AccessControlDashboardGuardian) CanAdmin() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound
}
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalAll(
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)),
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)),
))
}
return a.evaluate(accesscontrol.EvalAll(
accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
accesscontrol.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
))
}
func (a *AccessControlDashboardGuardian) CanDelete() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound
}
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)))
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *AccessControlDashboardGuardian) CanCreate(folderID int64, isFolder bool) (bool, error) {
if isFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersCreate))
}
folder, err := a.loadParentFolder(folderID)
if err != nil {
return false, err
}
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID)))
}
func (a *AccessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
if err != nil {
id := 0
if a.dashboard != nil {
id = int(a.dashboard.ID)
}
a.log.Debug("Failed to evaluate access control to folder or dashboard", "error", err, "userId", a.user.UserID, "id", id)
}
if !ok && err == nil {
id := 0
if a.dashboard != nil {
id = int(a.dashboard.ID)
}
a.log.Debug("Access denied to folder or dashboard", "userId", a.user.UserID, "id", id, "permissions", evaluator.GoString())
}
return ok, err
}
func (a *AccessControlDashboardGuardian) CheckPermissionBeforeUpdate(permission dashboards.PermissionType, updatePermissions []*dashboards.DashboardACL) (bool, error) {
// always true for access control
return true, nil
}
// GetACL translate access control permissions to dashboard acl info
func (a *AccessControlDashboardGuardian) GetACL() ([]*dashboards.DashboardACLInfoDTO, error) {
if a.dashboard == nil {
return nil, ErrGuardianGetDashboardFailure
}
var svc accesscontrol.PermissionsService
if a.dashboard.IsFolder {
svc = a.folderPermissionsService
} else {
svc = a.dashboardPermissionsService
}
permissions, err := svc.GetPermissions(a.ctx, a.user, a.dashboard.UID)
if err != nil {
return nil, err
}
acl := make([]*dashboards.DashboardACLInfoDTO, 0, len(permissions))
for _, p := range permissions {
if !p.IsManaged {
continue
}
var role *org.RoleType
if p.BuiltInRole != "" {
tmp := org.RoleType(p.BuiltInRole)
role = &tmp
}
acl = append(acl, &dashboards.DashboardACLInfoDTO{
OrgID: a.dashboard.OrgID,
DashboardID: a.dashboard.ID,
FolderID: a.dashboard.FolderID,
Created: p.Created,
Updated: p.Updated,
UserID: p.UserId,
UserLogin: p.UserLogin,
UserEmail: p.UserEmail,
TeamID: p.TeamId,
TeamEmail: p.TeamEmail,
Team: p.Team,
Role: role,
Permission: permissionMap[svc.MapActions(p)],
PermissionName: permissionMap[svc.MapActions(p)].String(),
UID: a.dashboard.UID,
Title: a.dashboard.Title,
Slug: a.dashboard.Slug,
IsFolder: a.dashboard.IsFolder,
URL: a.dashboard.GetURL(),
Inherited: false,
})
}
return acl, nil
}
func (a *AccessControlDashboardGuardian) GetACLWithoutDuplicates() ([]*dashboards.DashboardACLInfoDTO, error) {
return a.GetACL()
}
func (a *AccessControlDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*dashboards.DashboardACL, error) {
var hiddenACL []*dashboards.DashboardACL
if a.user.IsGrafanaAdmin {
return hiddenACL, nil
}
existingPermissions, err := a.GetACL()
if err != nil {
return hiddenACL, err
}
for _, item := range existingPermissions {
if item.Inherited || item.UserLogin == a.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
}
func (a *AccessControlDashboardGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) {
if folderID == 0 {
return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil
}
folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.OrgID}
folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery)
if err != nil {
return nil, err
}
return folderQueryResult, nil
}