grafana/pkg/services/guardian/accesscontrol_guardian.go
linoman 1b8e9b51b2
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
2023-08-30 16:51:18 +02:00

381 lines
13 KiB
Go

package guardian
import (
"context"
"errors"
"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/setting"
)
var _ DashboardGuardian = new(accessControlDashboardGuardian)
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId.
func NewAccessControlDashboardGuardian(
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.GetOrgID(),
}
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
}
if dashboard != nil && dashboard.IsFolder {
return &accessControlFolderGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("dashboard.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard,
}, nil
}
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardUID.
func NewAccessControlDashboardGuardianByUID(
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.GetOrgID(),
}
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
}
if dashboard != nil && dashboard.IsFolder {
return &accessControlFolderGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard,
}, 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 identity.Requester,
ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) {
if dashboard != nil && dashboard.IsFolder {
return &accessControlFolderGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard,
}, nil
}
// NewAccessControlFolderGuardian creates a folder guardian by the provided folder.
func NewAccessControlFolderGuardian(
ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user identity.Requester,
ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService,
) (DashboardGuardian, error) {
return &accessControlFolderGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
ac: ac,
dashboardService: dashboardService,
},
folder: f,
}, nil
}
type accessControlBaseGuardian struct {
cfg *setting.Cfg
ctx context.Context
log log.Logger
user identity.Requester
ac accesscontrol.AccessControl
dashboardService dashboards.DashboardService
}
type accessControlDashboardGuardian struct {
accessControlBaseGuardian
dashboard *dashboards.Dashboard
}
type accessControlFolderGuardian struct {
accessControlBaseGuardian
folder *folder.Folder
}
func (a *accessControlDashboardGuardian) CanSave() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check save permissions for dashboard")
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *accessControlFolderGuardian) CanSave() (bool, error) {
if a.folder == nil {
return false, ErrGuardianFolderNotFound.Errorf("failed to check save permissions for folder")
}
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)))
}
func (a *accessControlDashboardGuardian) CanEdit() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check edit permissions for dashboard")
}
if a.cfg.ViewersCanEdit {
return a.CanView()
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *accessControlFolderGuardian) CanEdit() (bool, error) {
if a.folder == nil {
return false, ErrGuardianFolderNotFound.Errorf("failed to check edit permissions for folder")
}
if a.cfg.ViewersCanEdit {
return a.CanView()
}
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)))
}
func (a *accessControlDashboardGuardian) CanView() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check view permissions for dashboard")
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *accessControlFolderGuardian) CanView() (bool, error) {
if a.folder == nil {
return false, ErrGuardianFolderNotFound.Errorf("failed to check view permissions for folder")
}
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)))
}
func (a *accessControlDashboardGuardian) CanAdmin() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check admin permissions for dashboard")
}
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 *accessControlFolderGuardian) CanAdmin() (bool, error) {
if a.folder == nil {
return false, ErrGuardianFolderNotFound.Errorf("failed to check admin permissions for folder")
}
return a.evaluate(accesscontrol.EvalAll(
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)),
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)),
))
}
func (a *accessControlDashboardGuardian) CanDelete() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check delete permissions for dashboard")
}
return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
)
}
func (a *accessControlFolderGuardian) CanDelete() (bool, error) {
if a.folder == nil {
return false, ErrGuardianFolderNotFound.Errorf("failed to check delete permissions for folder")
}
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.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 *accessControlFolderGuardian) 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)
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, "namespaceID", namespaceID, "userId", userID, "id", id)
}
if !ok && err == nil {
id := 0
if a.dashboard != nil {
id = int(a.dashboard.ID)
}
a.log.Debug("Access denied to dashboard", "namespaceID", namespaceID, "userId", userID, "id", id, "permissions", evaluator.GoString())
}
return ok, err
}
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
if a.folder != nil {
uid = a.folder.UID
orgID = int(a.folder.OrgID)
}
a.log.Debug("Failed to evaluate access control to folder", "error", err, "namespaceID", namespaceID, "userId", userID, "orgID", orgID, "uid", uid)
}
if !ok && err == nil {
uid := ""
orgID := 0
if a.folder != nil {
uid = a.folder.UID
orgID = int(a.folder.OrgID)
}
a.log.Debug("Access denied to folder", "namespaceID", namespaceID, "userId", userID, "orgID", orgID, "uid", uid, "permissions", evaluator.GoString())
}
return ok, err
}
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.GetOrgID()}
folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery)
if err != nil {
return nil, err
}
return folderQueryResult, nil
}
func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) {
if folderID == 0 {
return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil
}
folderQuery := &dashboards.GetDashboardQuery{ID: folderID, OrgID: a.user.GetOrgID()}
folderQueryResult, err := a.dashboardService.GetDashboard(a.ctx, folderQuery)
if err != nil {
return nil, err
}
return folderQueryResult, nil
}