Files
grafana/pkg/services/dashboards/accesscontrol_test.go
T
Kristin Laemmert bb798e24f3 chore(services): replace dependencies on dashboard store with dashboard service (#63937)
* chore(services): replace dependencies on dashboard store with dashboard service

This continues the backend service/store split by replacing dashboard store dependencies with service dependencies. the folder service remains the single exception for now; otherwise we'd have a dependency cycle between the folder and dashboard services. I have some ideas for that, but I'll take care of all the easy parts first.

While doing this, I identified and removed a number of unused arguments from the following functions:

NewFolderNameScopeResolver
NewFolderIDScopeResolver
NewFolderUIDScopeResolver
NewDashboardIDScopeResolver
NewDashboardUIDScopeResolver
resolveDashboardScope

I have a small enterprise PR to support this commit.

* lingering fmt
2023-03-02 08:09:57 -05:00

352 lines
12 KiB
Go

package dashboards
import (
"context"
"fmt"
"math/rand"
"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(foldertest.NewFakeService())
require.Equal(t, "folders:name:", prefix)
})
t.Run("resolver should convert to uid scope", func(t *testing.T) {
folderService := foldertest.NewFakeService()
orgId := rand.Int63()
title := "Very complex :title with: and /" + util.GenerateShortUID()
db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()}
folderService.ExpectedFolder = db
scope := "folders:name:" + title
_, resolver := NewFolderNameScopeResolver(folderService)
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), resolvedScopes[0])
})
t.Run("resolver should include inherited scopes if any", func(t *testing.T) {
orgId := rand.Int63()
title := "Very complex :title with: and /" + util.GenerateShortUID()
scope := "folders:name:" + title
uid := util.GenerateShortUID()
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolders = []*folder.Folder{
{
UID: "parent",
},
{
UID: "grandparent",
},
}
folderSvc.ExpectedFolder = &folder.Folder{Title: title, ID: rand.Int63(), UID: uid}
_, resolver := NewFolderNameScopeResolver(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", 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 := NewFolderNameScopeResolver(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) {
_, resolver := NewFolderNameScopeResolver(foldertest.NewFakeService())
_, 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) {
folderService := foldertest.NewFakeService()
_, resolver := NewFolderNameScopeResolver(folderService)
orgId := rand.Int63()
folderService.ExpectedError = ErrDashboardNotFound
scope := "folders:name:" + util.GenerateShortUID()
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.ErrorIs(t, err, ErrDashboardNotFound)
require.Nil(t, resolvedScopes)
})
}
func TestNewFolderIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewFolderIDScopeResolver(foldertest.NewFakeService())
require.Equal(t, "folders:id:", prefix)
})
t.Run("resolver should convert to uid scope", func(t *testing.T) {
folderSvc := foldertest.NewFakeService()
orgId := rand.Int63()
uid := util.GenerateShortUID()
db := &folder.Folder{ID: rand.Int63(), UID: uid}
folderSvc.ExpectedFolder = db
_, resolver := NewFolderIDScopeResolver(folderSvc)
scope := "folders:id:" + strconv.FormatInt(db.ID, 10)
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), resolvedScopes[0])
})
t.Run("resolver should should include inherited scopes if any", func(t *testing.T) {
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolders = []*folder.Folder{
{
UID: "parent",
},
{
UID: "grandparent",
},
}
_, resolver := NewFolderIDScopeResolver(folderSvc)
orgId := rand.Int63()
uid := util.GenerateShortUID()
db := &folder.Folder{ID: rand.Int63(), UID: uid}
folderSvc.ExpectedFolder = db
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)
}
})
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeService())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})
t.Run("resolver should convert id 0 to general uid scope", func(t *testing.T) {
var (
orgId = rand.Int63()
scope = "folders:id:0"
_, resolver = NewFolderIDScopeResolver(foldertest.NewFakeService())
)
resolved, err := resolver.Resolve(context.Background(), orgId, scope)
require.NoError(t, err)
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) {
_, resolver := NewFolderIDScopeResolver(foldertest.NewFakeService())
_, 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) {
svc := foldertest.NewFakeService()
svc.ExpectedError = ErrDashboardNotFound
_, resolver := NewFolderIDScopeResolver(svc)
orgId := rand.Int63()
scope := "folders:id:10"
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.ErrorIs(t, err, ErrDashboardNotFound)
require.Nil(t, resolvedScopes)
})
}
func TestNewDashboardIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
require.Equal(t, "dashboards:id:", prefix)
})
t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) {
dashSvc := NewFakeDashboardService(t)
folderService := foldertest.NewFakeService()
_, resolver := NewDashboardIDScopeResolver(dashSvc, folderService)
orgID := rand.Int63()
folder := &folder.Folder{ID: 2, UID: "2"}
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
folderService.ExpectedFolder = folder
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, 2)
require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.UID), resolvedScopes[0])
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) {
dashSvc := &FakeDashboardService{}
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolders = []*folder.Folder{
{
UID: "parent",
},
{
UID: "grandparent",
},
}
_, resolver := NewDashboardIDScopeResolver(dashSvc, folderSvc)
orgID := rand.Int63()
folder := &folder.Folder{ID: 2, UID: "2"}
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
folderSvc.ExpectedFolder = folder
dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, 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(&FakeDashboardService{}, 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) {
dashSvc := &FakeDashboardService{}
_, resolver := NewDashboardIDScopeResolver(dashSvc, foldertest.NewFakeService())
dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"}
dashSvc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil)
resolved, err := resolver.Resolve(context.Background(), 1, ac.Scope("dashboards", "id", "1"))
require.NoError(t, err)
require.Len(t, resolved, 2)
require.Equal(t, "dashboards:uid:1", resolved[0])
require.Equal(t, "folders:uid:general", resolved[1])
})
}
func TestNewDashboardUIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
require.Equal(t, "dashboards:uid:", prefix)
})
t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) {
service := &FakeDashboardService{}
folderSvc := foldertest.NewFakeService()
_, resolver := NewDashboardUIDScopeResolver(service, folderSvc)
orgID := rand.Int63()
folder := &folder.Folder{ID: 2, UID: "2"}
folderSvc.ExpectedFolder = folder
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
service.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, 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, 2)
require.Equal(t, fmt.Sprintf("dashboards:uid:%s", dashboard.UID), resolvedScopes[0])
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) {
svc := &FakeDashboardService{}
folderSvc := foldertest.NewFakeService()
folderSvc.ExpectedFolders = []*folder.Folder{
{
UID: "parent",
},
{
UID: "grandparent",
},
}
_, resolver := NewDashboardUIDScopeResolver(svc, folderSvc)
orgID := rand.Int63()
folder := &folder.Folder{ID: 2, UID: "2"}
folderSvc.ExpectedFolder = folder
dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"}
svc.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, 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(&FakeDashboardService{}, 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) {
service := &FakeDashboardService{}
_, resolver := NewDashboardUIDScopeResolver(service, foldertest.NewFakeService())
dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"}
service.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil)
resolved, err := resolver.Resolve(context.Background(), 1, ac.Scope("dashboards", "uid", "1"))
require.NoError(t, err)
require.Len(t, resolved, 2)
require.Equal(t, "dashboards:uid:1", resolved[0])
require.Equal(t, "folders:uid:general", resolved[1])
})
}