Files
grafana/pkg/services/provisioning/dashboards/validator_test.go
T
Karl Persson 4982ca3b1d Access control: Use access control for dashboard and folder (#44702)
* Add actions and scopes

* add resource service for dashboard and folder

* Add dashboard guardian with fgac permission evaluation

* Add CanDelete function to guardian interface

* Add CanDelete property to folder and dashboard dto and set values

* change to correct function name

* Add accesscontrol to folder endpoints

* add access control to dashboard endpoints

* check access for nav links

* Add fixed roles for dashboard and folders

* use correct package

* add hack to override guardian Constructor if accesscontrol is enabled

* Add services

* Add function to handle api backward compatability

* Add permissionServices to HttpServer

* Set permission when new dashboard is created

* Add default permission when creating new dashboard

* Set default permission when creating folder and dashboard

* Add access control filter for dashboard search

* Add to accept list

* Add accesscontrol to dashboardimport

* Disable access control in tests

* Add check to see if user is allow to create a dashboard

* Use SetPermissions

* Use function to set several permissions at once

* remove permissions for folder and dashboard on delete

* update required permission

* set permission for provisioning

* Add CanCreate to dashboard guardian and set correct permisisons for
provisioning

* Dont set admin on folder / dashboard creation

* Add dashboard and folder permission migrations

* Add tests for CanCreate

* Add roles and update descriptions

* Solve uid to id for dashboard and folder permissions

* Add folder and dashboard actions to permission filter

* Handle viewer_can_edit flag

* set folder and dashboard permissions services

* Add dashboard permissions when importing a new dashboard

* Set access control permissions on provisioning

* Pass feature flags and only set permissions if access control is enabled

* only add default permissions for folders and dashboards without folders

* Batch create permissions in migrations


* Remove `dashboards:edit` action

* Remove unused function from interface

* Update pkg/services/guardian/accesscontrol_guardian_test.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-03-03 15:05:47 +01:00

236 lines
9.3 KiB
Go

package dashboards
import (
"context"
"sort"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const (
dashboardContainingUID = "testdata/test-dashboards/dashboard-with-uid"
twoDashboardsWithUID = "testdata/test-dashboards/two-dashboards-with-uid"
)
func TestDuplicatesValidator(t *testing.T) {
bus.ClearBusHandlers()
fakeService := &dashboards.FakeDashboardProvisioning{}
defer fakeService.AssertExpectations(t)
bus.AddHandler("test", mockGetDashboardQuery)
cfg := &config{
Name: "Default",
Type: "file",
OrgID: 1,
Folder: "",
Options: map[string]interface{}{"path": dashboardContainingUID},
}
logger := log.New("test.logger")
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
const folderName = "duplicates-validator-folder"
r, err := NewDashboardFileReader(cfg, logger, nil, featuremgmt.WithFeatures(), nil)
require.NoError(t, err)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(6)
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(4)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
cfg1 := &config{
Name: "first", Type: "file", OrgID: 1, Folder: folderName,
Options: map[string]interface{}{"path": dashboardContainingUID},
}
cfg2 := &config{
Name: "second", Type: "file", OrgID: 1, Folder: folderName,
Options: map[string]interface{}{"path": dashboardContainingUID},
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil, featuremgmt.WithFeatures(), nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, featuremgmt.WithFeatures(), nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
err = reader2.walkDisk(context.Background())
require.NoError(t, err)
duplicates := duplicateValidator.getDuplicates()
require.Equal(t, uint8(2), duplicates[1].UIDs["Z-phNqGmz"].Sum)
uidUsageReaders := keysToSlice(duplicates[1].UIDs["Z-phNqGmz"].InvolvedReaders)
sort.Strings(uidUsageReaders)
require.Equal(t, []string{"first", "second"}, uidUsageReaders)
require.Equal(t, uint8(2), duplicates[1].Titles[identity].Sum)
titleUsageReaders := keysToSlice(duplicates[1].Titles[identity].InvolvedReaders)
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"first", "second"}, titleUsageReaders)
duplicateValidator.validate()
require.True(t, reader1.isDatabaseAccessRestricted())
require.True(t, reader2.isDatabaseAccessRestricted())
})
t.Run("Duplicates validator should not collect info about duplicate UIDs and titles within folders for different orgs", func(t *testing.T) {
const folderName = "duplicates-validator-folder"
r, err := NewDashboardFileReader(cfg, logger, nil, featuremgmt.WithFeatures(), nil)
require.NoError(t, err)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
cfg1 := &config{
Name: "first", Type: "file", OrgID: 1, Folder: folderName,
Options: map[string]interface{}{"path": dashboardContainingUID},
}
cfg2 := &config{
Name: "second", Type: "file", OrgID: 2, Folder: folderName,
Options: map[string]interface{}{"path": dashboardContainingUID},
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil, featuremgmt.WithFeatures(), nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, featuremgmt.WithFeatures(), nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
err = reader2.walkDisk(context.Background())
require.NoError(t, err)
duplicates := duplicateValidator.getDuplicates()
require.Equal(t, uint8(1), duplicates[1].UIDs["Z-phNqGmz"].Sum)
uidUsageReaders := keysToSlice(duplicates[1].UIDs["Z-phNqGmz"].InvolvedReaders)
sort.Strings(uidUsageReaders)
require.Equal(t, []string{"first"}, uidUsageReaders)
require.Equal(t, uint8(1), duplicates[2].UIDs["Z-phNqGmz"].Sum)
uidUsageReaders = keysToSlice(duplicates[2].UIDs["Z-phNqGmz"].InvolvedReaders)
sort.Strings(uidUsageReaders)
require.Equal(t, []string{"second"}, uidUsageReaders)
require.Equal(t, uint8(1), duplicates[1].Titles[identity].Sum)
titleUsageReaders := keysToSlice(duplicates[1].Titles[identity].InvolvedReaders)
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"first"}, titleUsageReaders)
require.Equal(t, uint8(1), duplicates[2].Titles[identity].Sum)
titleUsageReaders = keysToSlice(duplicates[2].Titles[identity].InvolvedReaders)
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"second"}, titleUsageReaders)
duplicateValidator.validate()
require.False(t, reader1.isDatabaseAccessRestricted())
require.False(t, reader2.isDatabaseAccessRestricted())
})
t.Run("Duplicates validator should restrict write access only for readers with duplicates", func(t *testing.T) {
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(3)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
cfg1 := &config{
Name: "first", Type: "file", OrgID: 1, Folder: "duplicates-validator-folder",
Options: map[string]interface{}{"path": twoDashboardsWithUID},
}
cfg2 := &config{
Name: "second", Type: "file", OrgID: 1, Folder: "root",
Options: map[string]interface{}{"path": defaultDashboards},
}
cfg3 := &config{
Name: "third", Type: "file", OrgID: 2, Folder: "duplicates-validator-folder",
Options: map[string]interface{}{"path": twoDashboardsWithUID},
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil, featuremgmt.WithFeatures(), nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil, featuremgmt.WithFeatures(), nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader3, err := NewDashboardFileReader(cfg3, logger, nil, featuremgmt.WithFeatures(), nil)
reader3.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2, reader3})
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
err = reader2.walkDisk(context.Background())
require.NoError(t, err)
err = reader3.walkDisk(context.Background())
require.NoError(t, err)
duplicates := duplicateValidator.getDuplicates()
r, err := NewDashboardFileReader(cfg, logger, nil, featuremgmt.WithFeatures(), nil)
require.NoError(t, err)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg1.Folder)
require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
require.Equal(t, uint8(2), duplicates[1].UIDs["Z-phNqGmz"].Sum)
uidUsageReaders := keysToSlice(duplicates[1].UIDs["Z-phNqGmz"].InvolvedReaders)
sort.Strings(uidUsageReaders)
require.Equal(t, []string{"first"}, uidUsageReaders)
require.Equal(t, uint8(2), duplicates[1].Titles[identity].Sum)
titleUsageReaders := keysToSlice(duplicates[1].Titles[identity].InvolvedReaders)
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"first"}, titleUsageReaders)
r, err = NewDashboardFileReader(cfg3, logger, nil, featuremgmt.WithFeatures(), nil)
require.NoError(t, err)
folderID, err = r.getOrCreateFolderID(context.Background(), cfg3, fakeService, cfg3.Folder)
require.NoError(t, err)
identity = dashboardIdentity{folderID: folderID, title: "Grafana"}
require.Equal(t, uint8(2), duplicates[2].UIDs["Z-phNqGmz"].Sum)
uidUsageReaders = keysToSlice(duplicates[2].UIDs["Z-phNqGmz"].InvolvedReaders)
sort.Strings(uidUsageReaders)
require.Equal(t, []string{"third"}, uidUsageReaders)
require.Equal(t, uint8(2), duplicates[2].Titles[identity].Sum)
titleUsageReaders = keysToSlice(duplicates[2].Titles[identity].InvolvedReaders)
sort.Strings(titleUsageReaders)
require.Equal(t, []string{"third"}, titleUsageReaders)
duplicateValidator.validate()
require.True(t, reader1.isDatabaseAccessRestricted())
require.False(t, reader2.isDatabaseAccessRestricted())
require.True(t, reader3.isDatabaseAccessRestricted())
})
}