mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access Control: Refactor scope resolvers with support to resolve into several scopes (#48202)
* Refactor Scope resolver to support resolving into several scopes * Change permission evaluator to match at least one of passed scopes
This commit is contained in:
@@ -28,49 +28,47 @@ var (
|
||||
ScopeFoldersProvider = ac.NewScopeProvider(ScopeFoldersRoot)
|
||||
)
|
||||
|
||||
// NewNameScopeResolver provides an AttributeScopeResolver that is able to convert a scope prefixed with "folders:name:" into an uid based scope.
|
||||
func NewNameScopeResolver(db Store) (string, ac.AttributeScopeResolveFunc) {
|
||||
// NewFolderNameScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:name:" into an uid based scope.
|
||||
func NewFolderNameScopeResolver(db Store) (string, ac.ScopeAttributeResolver) {
|
||||
prefix := ScopeFoldersProvider.GetResourceScopeName("")
|
||||
resolver := func(ctx context.Context, orgID int64, scope string) (string, error) {
|
||||
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
|
||||
if !strings.HasPrefix(scope, prefix) {
|
||||
return "", ac.ErrInvalidScope
|
||||
return nil, ac.ErrInvalidScope
|
||||
}
|
||||
nsName := scope[len(prefix):]
|
||||
if len(nsName) == 0 {
|
||||
return "", ac.ErrInvalidScope
|
||||
return nil, ac.ErrInvalidScope
|
||||
}
|
||||
folder, err := db.GetFolderByTitle(ctx, orgID, nsName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return ScopeFoldersProvider.GetResourceScopeUID(folder.Uid), nil
|
||||
}
|
||||
return prefix, resolver
|
||||
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.Uid)}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// NewIDScopeResolver provides an AttributeScopeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope.
|
||||
func NewIDScopeResolver(db Store) (string, ac.AttributeScopeResolveFunc) {
|
||||
// NewFolderIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope.
|
||||
func NewFolderIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) {
|
||||
prefix := ScopeFoldersProvider.GetResourceScope("")
|
||||
resolver := func(ctx context.Context, orgID int64, scope string) (string, error) {
|
||||
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
|
||||
if !strings.HasPrefix(scope, prefix) {
|
||||
return "", ac.ErrInvalidScope
|
||||
return nil, ac.ErrInvalidScope
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(scope[len(prefix):], 10, 64)
|
||||
if err != nil {
|
||||
return "", ac.ErrInvalidScope
|
||||
return nil, ac.ErrInvalidScope
|
||||
}
|
||||
|
||||
if id == 0 {
|
||||
return ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID), nil
|
||||
return []string{ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, nil
|
||||
}
|
||||
|
||||
folder, err := db.GetFolderByID(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ScopeFoldersProvider.GetResourceScopeUID(folder.Uid), nil
|
||||
}
|
||||
return prefix, resolver
|
||||
return []string{ScopeFoldersProvider.GetResourceScopeUID(folder.Uid)}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,16 +15,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestNewNameScopeResolver(t *testing.T) {
|
||||
func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewNameScopeResolver(&FakeDashboardStore{})
|
||||
prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{})
|
||||
require.Equal(t, "folders:name:", prefix)
|
||||
})
|
||||
|
||||
t.Run("resolver should convert to uid scope", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
|
||||
_, resolver := NewNameScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore)
|
||||
|
||||
orgId := rand.Int63()
|
||||
title := "Very complex :title with: and /" + util.GenerateShortUID()
|
||||
@@ -36,53 +36,54 @@ func TestNewNameScopeResolver(t *testing.T) {
|
||||
|
||||
scope := "folders:name:" + title
|
||||
|
||||
resolvedScope, err := resolver(context.Background(), orgId, scope)
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resolvedScopes, 1)
|
||||
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScope)
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScopes[0])
|
||||
|
||||
dashboardStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title)
|
||||
})
|
||||
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
_, resolver := NewNameScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore)
|
||||
|
||||
_, err := resolver(context.Background(), rand.Int63(), "folders:id:123")
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
_, resolver := NewNameScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore)
|
||||
|
||||
_, err := resolver(context.Background(), rand.Int63(), "folders:name:")
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:name:")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
t.Run("returns 'not found' if folder does not exist", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
|
||||
_, resolver := NewNameScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore)
|
||||
|
||||
orgId := rand.Int63()
|
||||
dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once()
|
||||
|
||||
scope := "folders:name:" + util.GenerateShortUID()
|
||||
|
||||
resolvedScope, err := resolver(context.Background(), orgId, scope)
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.ErrorIs(t, err, models.ErrDashboardNotFound)
|
||||
require.Empty(t, resolvedScope)
|
||||
require.Nil(t, resolvedScopes)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewIDScopeResolver(t *testing.T) {
|
||||
func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewIDScopeResolver(&FakeDashboardStore{})
|
||||
prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{})
|
||||
require.Equal(t, "folders:id:", prefix)
|
||||
})
|
||||
|
||||
t.Run("resolver should convert to uid scope", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
|
||||
_, resolver := NewIDScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
||||
|
||||
orgId := rand.Int63()
|
||||
uid := util.GenerateShortUID()
|
||||
@@ -92,18 +93,18 @@ func TestNewIDScopeResolver(t *testing.T) {
|
||||
|
||||
scope := "folders:id:" + strconv.FormatInt(db.Id, 10)
|
||||
|
||||
resolvedScope, err := resolver(context.Background(), orgId, scope)
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScope)
|
||||
require.Len(t, resolvedScopes, 1)
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%v", db.Uid), resolvedScopes[0])
|
||||
|
||||
dashboardStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.Id)
|
||||
})
|
||||
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
_, resolver := NewIDScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
||||
|
||||
_, err := resolver(context.Background(), rand.Int63(), "folders:uid:123")
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
|
||||
@@ -112,33 +113,34 @@ func TestNewIDScopeResolver(t *testing.T) {
|
||||
dashboardStore = &FakeDashboardStore{}
|
||||
orgId = rand.Int63()
|
||||
scope = "folders:id:0"
|
||||
_, resolver = NewIDScopeResolver(dashboardStore)
|
||||
_, resolver = NewFolderIDScopeResolver(dashboardStore)
|
||||
)
|
||||
|
||||
resolvedScope, err := resolver(context.Background(), orgId, scope)
|
||||
resolved, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "folders:uid:general", resolvedScope)
|
||||
require.Len(t, resolved, 1)
|
||||
require.Equal(t, "folders:uid:general", resolved[0])
|
||||
})
|
||||
|
||||
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
_, resolver := NewIDScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
||||
|
||||
_, err := resolver(context.Background(), rand.Int63(), "folders:id:")
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
t.Run("returns 'not found' if folder does not exist", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
|
||||
_, resolver := NewIDScopeResolver(dashboardStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
||||
|
||||
orgId := rand.Int63()
|
||||
dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once()
|
||||
|
||||
scope := "folders:id:10"
|
||||
resolvedScope, err := resolver(context.Background(), orgId, scope)
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.ErrorIs(t, err, models.ErrDashboardNotFound)
|
||||
require.Empty(t, resolvedScope)
|
||||
require.Nil(t, resolvedScopes)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ func ProvideFolderService(
|
||||
searchService *search.SearchService, features featuremgmt.FeatureToggles, permissionsServices accesscontrol.PermissionsServices,
|
||||
ac accesscontrol.AccessControl, sqlStore sqlstore.Store,
|
||||
) *FolderServiceImpl {
|
||||
ac.RegisterAttributeScopeResolver(dashboards.NewNameScopeResolver(dashboardStore))
|
||||
ac.RegisterAttributeScopeResolver(dashboards.NewIDScopeResolver(dashboardStore))
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore))
|
||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore))
|
||||
|
||||
return &FolderServiceImpl{
|
||||
cfg: cfg,
|
||||
|
||||
Reference in New Issue
Block a user