mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
c4a75f9eb3
* Inject access control into dashboard service * Add function to parse id scopes * Add dashboard as return value * Update mock * Return only err to keep service interface * Add scope resolvers for dashboard id scopes * Add function to parse uid scopes * Add dashboard uid scope resolver * Register scope resolvers for dashboards Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
240 lines
8.9 KiB
Go
240 lines
8.9 KiB
Go
package dashboards
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
func TestNewFolderNameScopeResolver(t *testing.T) {
|
|
t.Run("prefix should be expected", func(t *testing.T) {
|
|
prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{})
|
|
require.Equal(t, "folders:name:", prefix)
|
|
})
|
|
|
|
t.Run("resolver should convert to uid scope", func(t *testing.T) {
|
|
dashboardStore := &FakeDashboardStore{}
|
|
|
|
_, resolver := NewFolderNameScopeResolver(dashboardStore)
|
|
|
|
orgId := rand.Int63()
|
|
title := "Very complex :title with: and /" + util.GenerateShortUID()
|
|
|
|
db := models.NewFolder(title)
|
|
db.Id = rand.Int63()
|
|
db.Uid = util.GenerateShortUID()
|
|
dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once()
|
|
|
|
scope := "folders:name:" + title
|
|
|
|
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])
|
|
|
|
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 := NewFolderNameScopeResolver(dashboardStore)
|
|
|
|
_, 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)
|
|
|
|
_, 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 := NewFolderNameScopeResolver(dashboardStore)
|
|
|
|
orgId := rand.Int63()
|
|
dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once()
|
|
|
|
scope := "folders:name:" + util.GenerateShortUID()
|
|
|
|
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
|
require.ErrorIs(t, err, models.ErrDashboardNotFound)
|
|
require.Nil(t, resolvedScopes)
|
|
})
|
|
}
|
|
|
|
func TestNewFolderIDScopeResolver(t *testing.T) {
|
|
t.Run("prefix should be expected", func(t *testing.T) {
|
|
prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{})
|
|
require.Equal(t, "folders:id:", prefix)
|
|
})
|
|
|
|
t.Run("resolver should convert to uid scope", func(t *testing.T) {
|
|
dashboardStore := &FakeDashboardStore{}
|
|
|
|
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
|
|
|
orgId := rand.Int63()
|
|
uid := util.GenerateShortUID()
|
|
|
|
db := &models.Folder{Id: rand.Int63(), Uid: uid}
|
|
dashboardStore.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, 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 := NewFolderIDScopeResolver(dashboardStore)
|
|
|
|
_, 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 (
|
|
dashboardStore = &FakeDashboardStore{}
|
|
orgId = rand.Int63()
|
|
scope = "folders:id:0"
|
|
_, resolver = NewFolderIDScopeResolver(dashboardStore)
|
|
)
|
|
|
|
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) {
|
|
dashboardStore := &FakeDashboardStore{}
|
|
_, resolver := NewFolderIDScopeResolver(dashboardStore)
|
|
|
|
_, 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 := NewFolderIDScopeResolver(dashboardStore)
|
|
|
|
orgId := rand.Int63()
|
|
dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once()
|
|
|
|
scope := "folders:id:10"
|
|
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
|
|
require.ErrorIs(t, err, models.ErrDashboardNotFound)
|
|
require.Nil(t, resolvedScopes)
|
|
})
|
|
}
|
|
|
|
func TestNewDashboardIDScopeResolver(t *testing.T) {
|
|
t.Run("prefix should be expected", func(t *testing.T) {
|
|
prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{})
|
|
require.Equal(t, "dashboards:id:", prefix)
|
|
})
|
|
|
|
t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) {
|
|
store := &FakeDashboardStore{}
|
|
|
|
_, resolver := NewDashboardIDScopeResolver(store)
|
|
|
|
orgID := rand.Int63()
|
|
folder := &models.Folder{Id: 2, Uid: "2"}
|
|
dashboard := &models.Dashboard{Id: 1, FolderId: folder.Id, Uid: "1"}
|
|
|
|
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
|
|
store.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, 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 fail if input scope is not expected", func(t *testing.T) {
|
|
_, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{})
|
|
_, 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)
|
|
|
|
dashboard := &models.Dashboard{Id: 1, FolderId: 0, Uid: "1"}
|
|
store.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(&FakeDashboardStore{})
|
|
require.Equal(t, "dashboards:uid:", prefix)
|
|
})
|
|
|
|
t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) {
|
|
store := &FakeDashboardStore{}
|
|
_, resolver := NewDashboardUIDScopeResolver(store)
|
|
|
|
orgID := rand.Int63()
|
|
folder := &models.Folder{Id: 2, Uid: "2"}
|
|
dashboard := &models.Dashboard{Id: 1, FolderId: folder.Id, Uid: "1"}
|
|
|
|
store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once()
|
|
store.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, 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 fail if input scope is not expected", func(t *testing.T) {
|
|
_, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{})
|
|
_, 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)
|
|
|
|
dashboard := &models.Dashboard{Id: 1, FolderId: 0, Uid: "1"}
|
|
store.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])
|
|
})
|
|
}
|