Guardian: Split dashboard and folder guardian implementation (#69722)

* Split dashboard and folder guardian implementations

* Replace guardian constructors

* Simplify tests

* Add tests

* Apply suggestion from code review

Differentiate errors for dashboard and folders

* Remove tests for general folder

* Add tests for general scope
This commit is contained in:
Sofia Papagiannaki
2023-07-25 15:31:12 +03:00
committed by GitHub
parent f7c6491f73
commit 1f742fcf93
7 changed files with 985 additions and 229 deletions

View File

@@ -86,7 +86,7 @@ func (hs *HTTPServer) GetFolderByUID(c *contextmodel.ReqContext) response.Respon
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g, err := guardian.NewByUID(c.Req.Context(), folder.UID, c.OrgID, c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), folder, c.OrgID, c.SignedInUser)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }
@@ -119,7 +119,7 @@ func (hs *HTTPServer) GetFolderByID(c *contextmodel.ReqContext) response.Respons
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g, err := guardian.NewByUID(c.Req.Context(), folder.UID, c.OrgID, c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), folder, c.OrgID, c.SignedInUser)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }
@@ -160,7 +160,7 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response
// Required for cases when caller wants to immediately interact with the newly created object // Required for cases when caller wants to immediately interact with the newly created object
hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser)
g, err := guardian.NewByUID(c.Req.Context(), folder.UID, c.OrgID, c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), folder, c.OrgID, c.SignedInUser)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }
@@ -215,7 +215,7 @@ func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusInternalServerError, "move folder failed", err) return response.Error(http.StatusInternalServerError, "move folder failed", err)
} }
g, err := guardian.NewByUID(c.Req.Context(), cmd.UID, c.OrgID, c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), theFolder, c.OrgID, c.SignedInUser)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }
@@ -251,7 +251,7 @@ func (hs *HTTPServer) UpdateFolder(c *contextmodel.ReqContext) response.Response
if err != nil { if err != nil {
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)
} }
g, err := guardian.NewByUID(c.Req.Context(), result.UID, c.OrgID, c.SignedInUser) g, err := guardian.NewByFolder(c.Req.Context(), result, c.OrgID, c.SignedInUser)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }

View File

@@ -418,9 +418,10 @@ type DashboardACL struct {
func (p DashboardACL) TableName() string { return "dashboard_acl" } func (p DashboardACL) TableName() string { return "dashboard_acl" }
type DashboardACLInfoDTO struct { type DashboardACLInfoDTO struct {
OrgID int64 `json:"-" xorm:"org_id"` OrgID int64 `json:"-" xorm:"org_id"`
DashboardID int64 `json:"dashboardId,omitempty" xorm:"dashboard_id"` DashboardID int64 `json:"dashboardId,omitempty" xorm:"dashboard_id"`
FolderID int64 `json:"folderId,omitempty" xorm:"folder_id"` FolderID int64 `json:"folderId,omitempty" xorm:"folder_id"`
FolderUID string `json:"folderUid,omitempty" xorm:"folder_uid"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`

View File

@@ -127,7 +127,7 @@ func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.
// do not get guardian by the folder ID because it differs from the nested folder ID // do not get guardian by the folder ID because it differs from the nested folder ID
// and the legacy folder ID has been associated with the permissions: // and the legacy folder ID has been associated with the permissions:
// use the folde UID instead that is the same for both // use the folde UID instead that is the same for both
g, err := guardian.NewByUID(ctx, dashFolder.UID, dashFolder.OrgID, cmd.SignedInUser) g, err := guardian.NewByFolder(ctx, dashFolder, dashFolder.OrgID, cmd.SignedInUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -204,7 +204,7 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery)
continue continue
} }
g, err := guardian.NewByUID(ctx, f.UID, f.OrgID, cmd.SignedInUser) g, err := guardian.NewByFolder(ctx, dashFolder, dashFolder.OrgID, cmd.SignedInUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards" "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/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -19,7 +20,7 @@ var permissionMap = map[string]dashboards.PermissionType{
"Admin": dashboards.PERMISSION_ADMIN, "Admin": dashboards.PERMISSION_ADMIN,
} }
var _ DashboardGuardian = new(AccessControlDashboardGuardian) var _ DashboardGuardian = new(accessControlDashboardGuardian)
// NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId. // NewAccessControlDashboardGuardianByDashboard creates a dashboard guardian by the provided dashboardId.
func NewAccessControlDashboardGuardian( func NewAccessControlDashboardGuardian(
@@ -28,7 +29,7 @@ func NewAccessControlDashboardGuardian(
folderPermissionsService accesscontrol.FolderPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService, dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) { ) (DashboardGuardian, error) {
var dashboard *dashboards.Dashboard var dashboard *dashboards.Dashboard
if dashboardId != 0 { if dashboardId != 0 {
q := &dashboards.GetDashboardQuery{ q := &dashboards.GetDashboardQuery{
@@ -46,17 +47,34 @@ func NewAccessControlDashboardGuardian(
dashboard = qResult dashboard = qResult
} }
return &AccessControlDashboardGuardian{ if dashboard != nil && dashboard.IsFolder {
ctx: ctx, return &accessControlFolderGuardian{
cfg: cfg, accessControlBaseGuardian: accessControlBaseGuardian{
log: log.New("dashboard.permissions"), ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
folderPermissionsService: folderPermissionsService,
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
ctx: ctx,
cfg: cfg,
log: log.New("dashboard.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard, dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService, dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil }, nil
} }
@@ -67,7 +85,7 @@ func NewAccessControlDashboardGuardianByUID(
folderPermissionsService accesscontrol.FolderPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService, dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) { ) (DashboardGuardian, error) {
var dashboard *dashboards.Dashboard var dashboard *dashboards.Dashboard
if dashboardUID != "" { if dashboardUID != "" {
q := &dashboards.GetDashboardQuery{ q := &dashboards.GetDashboardQuery{
@@ -85,17 +103,34 @@ func NewAccessControlDashboardGuardianByUID(
dashboard = qResult dashboard = qResult
} }
return &AccessControlDashboardGuardian{ if dashboard != nil && dashboard.IsFolder {
cfg: cfg, return &accessControlFolderGuardian{
ctx: ctx, accessControlBaseGuardian: accessControlBaseGuardian{
log: log.New("dashboard.permissions"), ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
folderPermissionsService: folderPermissionsService,
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard, dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService, dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil }, nil
} }
@@ -108,41 +143,86 @@ func NewAccessControlDashboardGuardianByDashboard(
folderPermissionsService accesscontrol.FolderPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService,
dashboardService dashboards.DashboardService, dashboardService dashboards.DashboardService,
) (*AccessControlDashboardGuardian, error) { ) (DashboardGuardian, error) {
return &AccessControlDashboardGuardian{ if dashboard != nil && dashboard.IsFolder {
cfg: cfg, return &accessControlFolderGuardian{
ctx: ctx, accessControlBaseGuardian: accessControlBaseGuardian{
log: log.New("dashboard.permissions"), ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
folder: dashboards.FromDashboard(dashboard),
folderPermissionsService: folderPermissionsService,
}, nil
}
return &accessControlDashboardGuardian{
accessControlBaseGuardian: accessControlBaseGuardian{
cfg: cfg,
ctx: ctx,
log: log.New("dashboard.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
dashboard: dashboard, dashboard: dashboard,
user: user,
store: store,
ac: ac,
folderPermissionsService: folderPermissionsService,
dashboardPermissionsService: dashboardPermissionsService, dashboardPermissionsService: dashboardPermissionsService,
dashboardService: dashboardService,
}, nil }, nil
} }
type AccessControlDashboardGuardian struct { // NewAccessControlFolderGuardian creates a folder guardian by the provided folder.
cfg *setting.Cfg func NewAccessControlFolderGuardian(
ctx context.Context ctx context.Context, cfg *setting.Cfg, f *folder.Folder, user *user.SignedInUser,
log log.Logger store db.DB, ac accesscontrol.AccessControl,
dashboard *dashboards.Dashboard folderPermissionsService accesscontrol.FolderPermissionsService,
user *user.SignedInUser dashboardPermissionsService accesscontrol.DashboardPermissionsService,
store db.DB dashboardService dashboards.DashboardService,
ac accesscontrol.AccessControl ) (DashboardGuardian, error) {
folderPermissionsService accesscontrol.FolderPermissionsService return &accessControlFolderGuardian{
dashboardPermissionsService accesscontrol.DashboardPermissionsService accessControlBaseGuardian: accessControlBaseGuardian{
dashboardService dashboards.DashboardService ctx: ctx,
cfg: cfg,
log: log.New("folder.permissions"),
user: user,
store: store,
ac: ac,
dashboardService: dashboardService,
},
folder: f,
folderPermissionsService: folderPermissionsService,
}, nil
} }
func (a *AccessControlDashboardGuardian) CanSave() (bool, error) { type accessControlBaseGuardian struct {
if a.dashboard == nil { cfg *setting.Cfg
return false, ErrGuardianDashboardNotFound ctx context.Context
} log log.Logger
user *user.SignedInUser
ac accesscontrol.AccessControl
store db.DB
dashboardService dashboards.DashboardService
}
if a.dashboard.IsFolder { type accessControlDashboardGuardian struct {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID))) accessControlBaseGuardian
dashboard *dashboards.Dashboard
dashboardPermissionsService accesscontrol.DashboardPermissionsService
}
type accessControlFolderGuardian struct {
accessControlBaseGuardian
folder *folder.Folder
folderPermissionsService accesscontrol.FolderPermissionsService
}
func (a *accessControlDashboardGuardian) CanSave() (bool, error) {
if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check save permissions for dashboard")
} }
return a.evaluate( return a.evaluate(
@@ -150,31 +230,43 @@ func (a *AccessControlDashboardGuardian) CanSave() (bool, error) {
) )
} }
func (a *AccessControlDashboardGuardian) CanEdit() (bool, error) { 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 { if a.dashboard == nil {
return false, ErrGuardianDashboardNotFound return false, ErrGuardianDashboardNotFound.Errorf("failed to check edit permissions for dashboard")
} }
if a.cfg.ViewersCanEdit { if a.cfg.ViewersCanEdit {
return a.CanView() return a.CanView()
} }
if a.dashboard.IsFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID)))
}
return a.evaluate( return a.evaluate(
accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)), accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(a.dashboard.UID)),
) )
} }
func (a *AccessControlDashboardGuardian) CanView() (bool, error) { func (a *accessControlFolderGuardian) CanEdit() (bool, error) {
if a.dashboard == nil { if a.folder == nil {
return false, ErrGuardianDashboardNotFound return false, ErrGuardianFolderNotFound.Errorf("failed to check edit permissions for folder")
} }
if a.dashboard.IsFolder { if a.cfg.ViewersCanEdit {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID))) 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( return a.evaluate(
@@ -182,16 +274,17 @@ func (a *AccessControlDashboardGuardian) CanView() (bool, error) {
) )
} }
func (a *AccessControlDashboardGuardian) CanAdmin() (bool, error) { func (a *accessControlFolderGuardian) CanView() (bool, error) {
if a.dashboard == nil { if a.folder == nil {
return false, ErrGuardianDashboardNotFound return false, ErrGuardianFolderNotFound.Errorf("failed to check view permissions for folder")
} }
if a.dashboard.IsFolder { return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.folder.UID)))
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)), 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( return a.evaluate(accesscontrol.EvalAll(
@@ -200,13 +293,20 @@ func (a *AccessControlDashboardGuardian) CanAdmin() (bool, error) {
)) ))
} }
func (a *AccessControlDashboardGuardian) CanDelete() (bool, error) { func (a *accessControlFolderGuardian) CanAdmin() (bool, error) {
if a.dashboard == nil { if a.folder == nil {
return false, ErrGuardianDashboardNotFound return false, ErrGuardianFolderNotFound.Errorf("failed to check admin permissions for folder")
} }
if a.dashboard.IsFolder { return a.evaluate(accesscontrol.EvalAll(
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(a.dashboard.UID))) 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( return a.evaluate(
@@ -214,7 +314,15 @@ func (a *AccessControlDashboardGuardian) CanDelete() (bool, error) {
) )
} }
func (a *AccessControlDashboardGuardian) CanCreate(folderID int64, isFolder bool) (bool, error) { 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 { if isFolder {
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)) return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionFoldersCreate))
} }
@@ -225,14 +333,25 @@ func (a *AccessControlDashboardGuardian) CanCreate(folderID int64, isFolder bool
return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))) return a.evaluate(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID)))
} }
func (a *AccessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { 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) ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
if err != nil { if err != nil {
id := 0 id := 0
if a.dashboard != nil { if a.dashboard != nil {
id = int(a.dashboard.ID) 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) a.log.Debug("Failed to evaluate access control to dashboard", "error", err, "userId", a.user.UserID, "id", id)
} }
if !ok && err == nil { if !ok && err == nil {
@@ -240,29 +359,49 @@ func (a *AccessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
if a.dashboard != nil { if a.dashboard != nil {
id = int(a.dashboard.ID) id = int(a.dashboard.ID)
} }
a.log.Debug("Access denied to folder or dashboard", "userId", a.user.UserID, "id", id, "permissions", evaluator.GoString()) a.log.Debug("Access denied to dashboard", "userId", a.user.UserID, "id", id, "permissions", evaluator.GoString())
} }
return ok, err return ok, err
} }
func (a *AccessControlDashboardGuardian) CheckPermissionBeforeUpdate(permission dashboards.PermissionType, updatePermissions []*dashboards.DashboardACL) (bool, error) { func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
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, "userId", a.user.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", "userId", a.user.UserID, "orgID", orgID, "uid", uid, "permissions", evaluator.GoString())
}
return ok, err
}
func (a *accessControlBaseGuardian) CheckPermissionBeforeUpdate(permission dashboards.PermissionType, updatePermissions []*dashboards.DashboardACL) (bool, error) {
// always true for access control // always true for access control
return true, nil return true, nil
} }
// GetACL translate access control permissions to dashboard acl info // GetACL translate access control permissions to dashboard acl info
func (a *AccessControlDashboardGuardian) GetACL() ([]*dashboards.DashboardACLInfoDTO, error) { func (a *accessControlDashboardGuardian) GetACL() ([]*dashboards.DashboardACLInfoDTO, error) {
if a.dashboard == nil { if a.dashboard == nil {
return nil, ErrGuardianGetDashboardFailure return nil, ErrGuardianGetDashboardFailure.Errorf("failed to translate access control permissions to dashboard acl info")
} }
var svc accesscontrol.PermissionsService svc := a.dashboardPermissionsService
if a.dashboard.IsFolder {
svc = a.folderPermissionsService
} else {
svc = a.dashboardPermissionsService
}
permissions, err := svc.GetPermissions(a.ctx, a.user, a.dashboard.UID) permissions, err := svc.GetPermissions(a.ctx, a.user, a.dashboard.UID)
if err != nil { if err != nil {
@@ -308,11 +447,67 @@ func (a *AccessControlDashboardGuardian) GetACL() ([]*dashboards.DashboardACLInf
return acl, nil return acl, nil
} }
func (a *AccessControlDashboardGuardian) GetACLWithoutDuplicates() ([]*dashboards.DashboardACLInfoDTO, error) { // GetACL translate access control permissions to dashboard acl info
func (a *accessControlFolderGuardian) GetACL() ([]*dashboards.DashboardACLInfoDTO, error) {
if a.folder == nil {
return nil, ErrGuardianGetFolderFailure.Errorf("failed to translate access control permissions to dashboard acl info")
}
svc := a.folderPermissionsService
permissions, err := svc.GetPermissions(a.ctx, a.user, a.folder.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.folder.OrgID,
DashboardID: a.folder.ID,
FolderUID: a.folder.ParentUID,
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.folder.UID,
Title: a.folder.Title,
//Slug: a.folder.Slug,
IsFolder: true,
URL: a.folder.WithURL().URL,
Inherited: false,
})
}
return acl, nil
}
func (a *accessControlDashboardGuardian) GetACLWithoutDuplicates() ([]*dashboards.DashboardACLInfoDTO, error) {
return a.GetACL() return a.GetACL()
} }
func (a *AccessControlDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*dashboards.DashboardACL, error) { func (a *accessControlFolderGuardian) GetACLWithoutDuplicates() ([]*dashboards.DashboardACLInfoDTO, error) {
return a.GetACL()
}
func (a *accessControlDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*dashboards.DashboardACL, error) {
var hiddenACL []*dashboards.DashboardACL var hiddenACL []*dashboards.DashboardACL
if a.user.IsGrafanaAdmin { if a.user.IsGrafanaAdmin {
return hiddenACL, nil return hiddenACL, nil
@@ -345,7 +540,52 @@ func (a *AccessControlDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*dash
return hiddenACL, nil return hiddenACL, nil
} }
func (a *AccessControlDashboardGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) { func (a *accessControlFolderGuardian) 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
}
func (a *accessControlFolderGuardian) loadParentFolder(folderID int64) (*dashboards.Dashboard, error) {
if folderID == 0 { if folderID == 0 {
return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil return &dashboards.Dashboard{UID: accesscontrol.GeneralFolderUID}, nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards" "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/org"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@@ -18,7 +19,9 @@ var (
ErrGuardianPermissionExists = errors.New("permission already exists") ErrGuardianPermissionExists = errors.New("permission already exists")
ErrGuardianOverride = errors.New("you can only override a permission to be higher") ErrGuardianOverride = errors.New("you can only override a permission to be higher")
ErrGuardianGetDashboardFailure = errutil.NewBase(errutil.StatusInternal, "guardian.getDashboardFailure", errutil.WithPublicMessage("Failed to get dashboard")) ErrGuardianGetDashboardFailure = errutil.NewBase(errutil.StatusInternal, "guardian.getDashboardFailure", errutil.WithPublicMessage("Failed to get dashboard"))
ErrGuardianGetFolderFailure = errutil.NewBase(errutil.StatusInternal, "guardian.getFolderFailure", errutil.WithPublicMessage("Failed to get folder"))
ErrGuardianDashboardNotFound = errutil.NewBase(errutil.StatusNotFound, "guardian.dashboardNotFound") ErrGuardianDashboardNotFound = errutil.NewBase(errutil.StatusNotFound, "guardian.dashboardNotFound")
ErrGuardianFolderNotFound = errutil.NewBase(errutil.StatusNotFound, "guardian.folderNotFound")
) )
// DashboardGuardian to be used for guard against operations without access on dashboard and acl // DashboardGuardian to be used for guard against operations without access on dashboard and acl
@@ -73,6 +76,12 @@ var NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId
panic("no guardian factory implementation provided") panic("no guardian factory implementation provided")
} }
// NewByFolder factory for creating a new folder guardian instance
// When using access control this function is replaced on startup and the AccessControlDashboardGuardian is returned
var NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
panic("no guardian factory implementation provided")
}
// newDashboardGuardian creates a dashboard guardian by the provided dashId. // 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) { 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 { if dashId != 0 {
@@ -151,6 +160,24 @@ func newDashboardGuardianByDashboard(ctx context.Context, cfg *setting.Cfg, dash
}, nil }, 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) { func (g *dashboardGuardianImpl) CanSave() (bool, error) {
return g.HasPermission(dashboards.PERMISSION_EDIT) return g.HasPermission(dashboards.PERMISSION_EDIT)
} }
@@ -482,4 +509,12 @@ func MockDashboardGuardian(mock *FakeDashboardGuardian) {
mock.User = user mock.User = user
return mock, nil return mock, nil
} }
NewByFolder = func(_ context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
mock.OrgID = orgId
mock.DashUID = f.UID
mock.DashID = f.ID
mock.User = user
return mock, nil
}
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -39,6 +40,10 @@ func InitLegacyGuardian(cfg *setting.Cfg, store db.DB, dashSvc dashboards.Dashbo
NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
return newDashboardGuardianByDashboard(ctx, cfg, dash, orgId, user, store, dashSvc, teamSvc) 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( func InitAccessControlGuardian(
@@ -56,4 +61,8 @@ func InitAccessControlGuardian(
NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) { NewByDashboard = func(ctx context.Context, dash *dashboards.Dashboard, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService) return NewAccessControlDashboardGuardianByDashboard(ctx, cfg, dash, user, store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService)
} }
NewByFolder = func(ctx context.Context, f *folder.Folder, orgId int64, user *user.SignedInUser) (DashboardGuardian, error) {
return NewAccessControlFolderGuardian(ctx, cfg, f, user, store, ac, folderPermissionsService, dashboardPermissionsService, dashboardService)
}
} }