RBAC: inherit folder permissions when resolving managed permissions (#62244)
* add nested folder scope inheritance to managed permission services * add a more specific erorr * remove circular dependencies * use errutil for returning erorr * fix tests * fix tests * define a new error in ac package
This commit is contained in:
@@ -8,8 +8,11 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
var ErrInternal = errutil.NewBase(errutil.StatusInternal, "accesscontrol.internal")
|
||||
|
||||
// RoleRegistration stores a role and its assignments to built-in roles
|
||||
// (Viewer, Editor, Admin, Grafana Admin)
|
||||
type RoleRegistration struct {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
||||
@@ -114,7 +115,7 @@ var DashboardAdminActions = append(DashboardEditActions, []string{dashboards.Act
|
||||
|
||||
func ProvideDashboardPermissions(
|
||||
cfg *setting.Cfg, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, service accesscontrol.Service,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service,
|
||||
) (*DashboardPermissionsService, error) {
|
||||
getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*dashboards.Dashboard, error) {
|
||||
@@ -152,7 +153,13 @@ func ProvideDashboardPermissions(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{dashboards.ScopeFoldersProvider.GetResourceScopeUID(queryResult.UID)}, nil
|
||||
parentScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(queryResult.UID)
|
||||
|
||||
nestedScopes, err := dashboards.GetInheritedScopes(ctx, orgID, queryResult.UID, folderService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{parentScope}, nestedScopes...), nil
|
||||
}
|
||||
return []string{}, nil
|
||||
},
|
||||
@@ -195,7 +202,7 @@ var FolderAdminActions = append(FolderEditActions, []string{dashboards.ActionFol
|
||||
|
||||
func ProvideFolderPermissions(
|
||||
cfg *setting.Cfg, router routing.RouteRegister, sql db.DB, accesscontrol accesscontrol.AccessControl,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, service accesscontrol.Service,
|
||||
license licensing.Licensing, dashboardStore dashboards.Store, folderService folder.Service, service accesscontrol.Service,
|
||||
teamService team.Service, userService user.Service,
|
||||
) (*FolderPermissionsService, error) {
|
||||
options := resourcepermissions.Options{
|
||||
@@ -214,6 +221,9 @@ func ProvideFolderPermissions(
|
||||
|
||||
return nil
|
||||
},
|
||||
InheritedScopesSolver: func(ctx context.Context, orgID int64, resourceID string) ([]string, error) {
|
||||
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
|
||||
@@ -54,7 +54,7 @@ func NewFolderNameScopeResolver(db Store, folderDB FolderStore, folderSvc folder
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := getInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
|
||||
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func NewFolderIDScopeResolver(db Store, folderDB FolderStore, folderSvc folder.S
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := getInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
|
||||
result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -158,7 +158,7 @@ func resolveDashboardScope(ctx context.Context, db Store, folderDB FolderStore,
|
||||
folderUID = folder.UID
|
||||
}
|
||||
|
||||
result, err := getInheritedScopes(ctx, orgID, folderUID, folderSvc)
|
||||
result, err := GetInheritedScopes(ctx, orgID, folderUID, folderSvc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -173,15 +173,14 @@ func resolveDashboardScope(ctx context.Context, db Store, folderDB FolderStore,
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getInheritedScopes(ctx context.Context, orgID int64, folderUID string, folderSvc folder.Service) ([]string, error) {
|
||||
func GetInheritedScopes(ctx context.Context, orgID int64, folderUID string, folderSvc folder.Service) ([]string, error) {
|
||||
ancestors, err := folderSvc.GetParents(ctx, folder.GetParentsQuery{
|
||||
UID: folderUID,
|
||||
OrgID: orgID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// TODO return a specific error
|
||||
return nil, err
|
||||
return nil, ac.ErrInternal.Errorf("could not retrieve folder parents: %w", err)
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(ancestors))
|
||||
|
||||
@@ -32,7 +32,6 @@ type Service struct {
|
||||
dashboardStore dashboards.Store
|
||||
dashboardFolderStore dashboards.FolderStore
|
||||
features featuremgmt.FeatureToggles
|
||||
permissions accesscontrol.FolderPermissionsService
|
||||
accessControl accesscontrol.AccessControl
|
||||
|
||||
// bus is currently used to publish events that cause scheduler to update rules.
|
||||
@@ -47,7 +46,6 @@ func ProvideService(
|
||||
folderStore dashboards.FolderStore,
|
||||
db db.DB, // DB for the (new) nested folder store
|
||||
features featuremgmt.FeatureToggles,
|
||||
folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||
) folder.Service {
|
||||
store := ProvideStore(db, cfg, features)
|
||||
svr := &Service{
|
||||
@@ -57,7 +55,6 @@ func ProvideService(
|
||||
dashboardFolderStore: folderStore,
|
||||
store: store,
|
||||
features: features,
|
||||
permissions: folderPermissionsService,
|
||||
accessControl: ac,
|
||||
bus: bus,
|
||||
}
|
||||
@@ -311,29 +308,6 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var permissionErr error
|
||||
if !accesscontrol.IsDisabled(s.cfg) {
|
||||
var permissions []accesscontrol.SetResourcePermissionCommand
|
||||
if user.IsRealUser() && !user.IsAnonymous {
|
||||
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
|
||||
UserID: userID, Permission: dashboards.PERMISSION_ADMIN.String(),
|
||||
})
|
||||
}
|
||||
|
||||
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
|
||||
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
|
||||
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()},
|
||||
}...)
|
||||
|
||||
_, permissionErr = s.permissions.SetPermissions(ctx, cmd.OrgID, createdFolder.UID, permissions...)
|
||||
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
|
||||
permissionErr = s.MakeUserAdmin(ctx, cmd.OrgID, userID, createdFolder.ID, true)
|
||||
}
|
||||
|
||||
if permissionErr != nil {
|
||||
logger.Error("Could not make user admin", "folder", createdFolder.Title, "user", userID, "error", permissionErr)
|
||||
}
|
||||
|
||||
var nestedFolder *folder.Folder
|
||||
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||
cmd := &folder.CreateFolderCommand{
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
|
||||
t.Run("should register scope resolvers", func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
ac := acmock.New()
|
||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{}, nil)
|
||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{})
|
||||
|
||||
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
|
||||
})
|
||||
@@ -57,7 +57,6 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
features := featuremgmt.WithFeatures()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
|
||||
service := &Service{
|
||||
cfg: cfg,
|
||||
@@ -66,7 +65,6 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
dashboardFolderStore: folderStore,
|
||||
store: nestedFolderStore,
|
||||
features: features,
|
||||
permissions: folderPermissions,
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
}
|
||||
|
||||
|
||||
@@ -610,10 +610,10 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce
|
||||
require.NoError(t, err)
|
||||
|
||||
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, ac, teamSvc, userSvc)
|
||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, foldertest.NewFakeService(), ac, teamSvc, userSvc)
|
||||
require.NoError(t, err)
|
||||
dashboardPermissions, err := ossaccesscontrol.ProvideDashboardPermissions(
|
||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, ac, teamSvc, userSvc)
|
||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, foldertest.NewFakeService(), ac, teamSvc, userSvc)
|
||||
require.NoError(t, err)
|
||||
if dashboardSvc == nil {
|
||||
fakeDashboardService := dashboards.NewFakeDashboardService(t)
|
||||
|
||||
@@ -313,12 +313,11 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S
|
||||
features := featuremgmt.WithFeatures()
|
||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions)
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features)
|
||||
t.Logf("Creating folder with title and UID %q", title)
|
||||
ctx := appcontext.WithUser(context.Background(), &user)
|
||||
folder, err := s.Create(ctx, &folder.CreateFolderCommand{
|
||||
@@ -447,7 +446,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
service := LibraryElementService{
|
||||
Cfg: sqlStore.Cfg,
|
||||
SQLStore: sqlStore,
|
||||
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, dashboardStore, nil, features, folderPermissions),
|
||||
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, dashboardStore, nil, features),
|
||||
}
|
||||
|
||||
// deliberate difference between signed in user and user in db to make it crystal clear
|
||||
|
||||
@@ -725,11 +725,10 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
features := featuremgmt.WithFeatures()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions)
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features)
|
||||
|
||||
t.Logf("Creating folder with title and UID %q", title)
|
||||
ctx := appcontext.WithUser(context.Background(), user)
|
||||
@@ -837,9 +836,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
|
||||
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions)
|
||||
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features)
|
||||
|
||||
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
|
||||
service := LibraryPanelService{
|
||||
|
||||
@@ -87,7 +87,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
bus := bus.ProvideBus(tracer)
|
||||
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, dashboardStore, nil, features, folderPermissions)
|
||||
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, dashboardStore, nil, features)
|
||||
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
||||
|
||||
Reference in New Issue
Block a user