Access control: Modify dashboard/folder resolvers so that return also the inherited scopes (#62025)
* Access Control: Add folder service dependency to the dashboard/folder resolvers * Expose the function fetching parents to folder interface * Add generic prepend utility * Modify dashboard resolvers to return inherited scopes
This commit is contained in:
committed by
GitHub
parent
8e3d22ca7a
commit
cd27562c76
@@ -7,17 +7,19 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
require.Equal(t, "folders:name:", prefix)
|
||||
})
|
||||
|
||||
@@ -33,7 +35,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
|
||||
scope := "folders:name:" + title
|
||||
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService())
|
||||
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
@@ -43,16 +45,53 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
|
||||
folderStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title)
|
||||
})
|
||||
t.Run("resolver should include inherited scopes if any", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
orgId := rand.Int63()
|
||||
title := "Very complex :title with: and /" + util.GenerateShortUID()
|
||||
|
||||
db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()}
|
||||
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once()
|
||||
|
||||
scope := "folders:name:" + title
|
||||
|
||||
folderSvc := foldertest.NewFakeService()
|
||||
folderSvc.ExpectedFolders = []*folder.Folder{
|
||||
{
|
||||
UID: "parent",
|
||||
},
|
||||
{
|
||||
UID: "grandparent",
|
||||
},
|
||||
}
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore, folderSvc)
|
||||
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resolvedScopes, 3)
|
||||
|
||||
if diff := cmp.Diff([]string{
|
||||
fmt.Sprintf("folders:uid:%v", db.UID),
|
||||
"folders:uid:parent",
|
||||
"folders:uid:grandparent",
|
||||
}, resolvedScopes); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
folderStore.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 := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t))
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
_, 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 := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t))
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:name:")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
@@ -61,7 +100,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore)
|
||||
_, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService())
|
||||
|
||||
orgId := rand.Int63()
|
||||
folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
|
||||
@@ -76,7 +115,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
|
||||
|
||||
func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
require.Equal(t, "folders:id:", prefix)
|
||||
})
|
||||
|
||||
@@ -84,7 +123,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService())
|
||||
|
||||
orgId := rand.Int63()
|
||||
uid := util.GenerateShortUID()
|
||||
@@ -101,9 +140,46 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
|
||||
folderStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.ID)
|
||||
})
|
||||
t.Run("resolver should should include inherited scopes if any", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
folderSvc := foldertest.NewFakeService()
|
||||
folderSvc.ExpectedFolders = []*folder.Folder{
|
||||
{
|
||||
UID: "parent",
|
||||
},
|
||||
{
|
||||
UID: "grandparent",
|
||||
},
|
||||
}
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore, folderSvc)
|
||||
|
||||
orgId := rand.Int63()
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
db := &folder.Folder{ID: rand.Int63(), UID: uid}
|
||||
folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once()
|
||||
|
||||
scope := "folders:id:" + strconv.FormatInt(db.ID, 10)
|
||||
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resolvedScopes, 3)
|
||||
|
||||
if diff := cmp.Diff([]string{
|
||||
fmt.Sprintf("folders:uid:%v", db.UID),
|
||||
"folders:uid:parent",
|
||||
"folders:uid:grandparent",
|
||||
}, resolvedScopes); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
folderStore.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 := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t))
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
@@ -114,7 +190,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
dashboardStore = &FakeDashboardStore{}
|
||||
orgId = rand.Int63()
|
||||
scope = "folders:id:0"
|
||||
_, resolver = NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t))
|
||||
_, resolver = NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
)
|
||||
|
||||
resolved, err := resolver.Resolve(context.Background(), orgId, scope)
|
||||
@@ -126,7 +202,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
|
||||
t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t))
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
@@ -135,7 +211,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
dashboardStore := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore)
|
||||
_, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService())
|
||||
|
||||
orgId := rand.Int63()
|
||||
folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
|
||||
@@ -149,7 +225,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
|
||||
|
||||
func TestNewDashboardIDScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
require.Equal(t, "dashboards:id:", prefix)
|
||||
})
|
||||
|
||||
@@ -157,7 +233,7 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
_, resolver := NewDashboardIDScopeResolver(store, folderStore)
|
||||
_, resolver := NewDashboardIDScopeResolver(store, folderStore, foldertest.NewFakeService())
|
||||
|
||||
orgID := rand.Int63()
|
||||
folder := &folder.Folder{ID: 2, UID: "2"}
|
||||
@@ -174,15 +250,52 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1])
|
||||
})
|
||||
|
||||
t.Run("resolver should inlude inherited scopes if any", func(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
folderSvc := foldertest.NewFakeService()
|
||||
folderSvc.ExpectedFolders = []*folder.Folder{
|
||||
{
|
||||
UID: "parent",
|
||||
},
|
||||
{
|
||||
UID: "grandparent",
|
||||
},
|
||||
}
|
||||
_, resolver := NewDashboardIDScopeResolver(store, folderStore, folderSvc)
|
||||
|
||||
orgID := rand.Int63()
|
||||
folder := &folder.Folder{ID: 2, UID: "2"}
|
||||
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
|
||||
|
||||
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
|
||||
folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once()
|
||||
|
||||
scope := ac.Scope("dashboards", "id", strconv.FormatInt(dashboard.ID, 10))
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resolvedScopes, 4)
|
||||
|
||||
if diff := cmp.Diff([]string{
|
||||
fmt.Sprintf("dashboards:uid:%s", dashboard.UID),
|
||||
fmt.Sprintf("folders:uid:%s", folder.UID),
|
||||
"folders:uid:parent",
|
||||
"folders:uid:grandparent",
|
||||
}, resolvedScopes); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
|
||||
_, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
_, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
|
||||
t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
_, resolver := NewDashboardIDScopeResolver(store, NewFakeFolderStore(t))
|
||||
_, resolver := NewDashboardIDScopeResolver(store, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"}
|
||||
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil)
|
||||
@@ -197,14 +310,14 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
|
||||
|
||||
func TestNewDashboardUIDScopeResolver(t *testing.T) {
|
||||
t.Run("prefix should be expected", func(t *testing.T) {
|
||||
prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
require.Equal(t, "dashboards:uid:", prefix)
|
||||
})
|
||||
|
||||
t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
_, resolver := NewDashboardUIDScopeResolver(store, folderStore)
|
||||
_, resolver := NewDashboardUIDScopeResolver(store, folderStore, foldertest.NewFakeService())
|
||||
|
||||
orgID := rand.Int63()
|
||||
folder := &folder.Folder{ID: 2, UID: "2"}
|
||||
@@ -221,15 +334,53 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) {
|
||||
require.Equal(t, fmt.Sprintf("folders:uid:%s", folder.UID), resolvedScopes[1])
|
||||
})
|
||||
|
||||
t.Run("resolver should include inherited scopes if any", func(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
folderStore := NewFakeFolderStore(t)
|
||||
|
||||
folderSvc := foldertest.NewFakeService()
|
||||
folderSvc.ExpectedFolders = []*folder.Folder{
|
||||
{
|
||||
UID: "parent",
|
||||
},
|
||||
{
|
||||
UID: "grandparent",
|
||||
},
|
||||
}
|
||||
|
||||
_, resolver := NewDashboardUIDScopeResolver(store, folderStore, folderSvc)
|
||||
|
||||
orgID := rand.Int63()
|
||||
folder := &folder.Folder{ID: 2, UID: "2"}
|
||||
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
|
||||
|
||||
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
|
||||
folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once()
|
||||
|
||||
scope := ac.Scope("dashboards", "uid", dashboard.UID)
|
||||
resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resolvedScopes, 4)
|
||||
|
||||
if diff := cmp.Diff([]string{
|
||||
fmt.Sprintf("dashboards:uid:%s", dashboard.UID),
|
||||
fmt.Sprintf("folders:uid:%s", folder.UID),
|
||||
"folders:uid:parent",
|
||||
"folders:uid:grandparent",
|
||||
}, resolvedScopes); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
|
||||
_, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t))
|
||||
_, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123")
|
||||
require.ErrorIs(t, err, ac.ErrInvalidScope)
|
||||
})
|
||||
|
||||
t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) {
|
||||
store := &FakeDashboardStore{}
|
||||
_, resolver := NewDashboardUIDScopeResolver(store, NewFakeFolderStore(t))
|
||||
_, resolver := NewDashboardUIDScopeResolver(store, NewFakeFolderStore(t), foldertest.NewFakeService())
|
||||
|
||||
dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"}
|
||||
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil)
|
||||
|
||||
Reference in New Issue
Block a user