Access control: Move call to create default permissions into folder and dashboard service (#46186)

* Move call to create permissions into folder service

* Inject cfg, feature toggles and permissions services into dashboard
service

* Move logic to set default permissions on create dashboard from api to
service

* Move call to set default permissions on import dashboard to dashboard
service

* Set permissions for provisioned dashboard and folders in service
This commit is contained in:
Karl Persson
2022-03-10 12:58:18 +01:00
committed by GitHub
parent a327c4d7d6
commit 52decfaebc
22 changed files with 304 additions and 254 deletions
@@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"strconv"
"strings"
"time"
@@ -14,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/alerting"
m "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -30,16 +32,27 @@ var (
)
type DashboardServiceImpl struct {
dashboardStore m.Store
dashAlertExtractor alerting.DashAlertExtractor
log log.Logger
cfg *setting.Cfg
log log.Logger
dashboardStore m.Store
dashAlertExtractor alerting.DashAlertExtractor
features featuremgmt.FeatureToggles
folderPermissions accesscontrol.PermissionsService
dashboardPermissions accesscontrol.PermissionsService
}
func ProvideDashboardService(store m.Store, dashAlertExtractor alerting.DashAlertExtractor) *DashboardServiceImpl {
func ProvideDashboardService(
cfg *setting.Cfg, store m.Store, dashAlertExtractor alerting.DashAlertExtractor,
features featuremgmt.FeatureToggles, permissionsServices accesscontrol.PermissionsServices,
) *DashboardServiceImpl {
return &DashboardServiceImpl{
dashboardStore: store,
dashAlertExtractor: dashAlertExtractor,
log: log.New("dashboard-service"),
cfg: cfg,
log: log.New("dashboard-service"),
dashboardStore: store,
dashAlertExtractor: dashAlertExtractor,
features: features,
folderPermissions: permissionsServices.GetFolderService(),
dashboardPermissions: permissionsServices.GetDashboardService(),
}
}
@@ -234,6 +247,12 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
return nil, err
}
if dto.Dashboard.Id == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserId, "error", err)
}
}
return dash, nil
}
@@ -269,6 +288,12 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
return nil, err
}
if dto.Dashboard.Id == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, true); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserId, "error", err)
}
}
return dash, nil
}
@@ -307,6 +332,13 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *m.SaveDa
return nil, err
}
// new dashboard created
if dto.Dashboard.Id == 0 {
if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserId, "error", err)
}
}
return dash, nil
}
@@ -398,6 +430,10 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *m.Save
return nil, err
}
if err := dr.setDefaultPermissions(ctx, dto, dash, false); err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "user", dto.User.UserId, "error", err)
}
return dash, nil
}
@@ -406,3 +442,39 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *m.Save
func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId)
}
func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *m.SaveDashboardDTO, dash *models.Dashboard, provisioned bool) error {
inFolder := dash.FolderId > 0
if dr.features.IsEnabled(featuremgmt.FlagAccesscontrol) {
resourceID := strconv.FormatInt(dash.Id, 10)
var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: dto.User.UserId, Permission: models.PERMISSION_ADMIN.String(),
})
}
if !inFolder {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(models.ROLE_EDITOR), Permission: models.PERMISSION_EDIT.String()},
{BuiltinRole: string(models.ROLE_VIEWER), Permission: models.PERMISSION_VIEW.String()},
}...)
}
svc := dr.dashboardPermissions
if dash.IsFolder {
svc = dr.folderPermissions
}
_, err := svc.SetPermissions(ctx, dto.OrgId, resourceID, permissions...)
if err != nil {
return err
}
} else if dr.cfg.EditorsCanAdmin && !provisioned {
if err := dr.MakeUserAdmin(ctx, dto.OrgId, dto.User.UserId, dash.Id, !inFolder); err != nil {
return err
}
}
return nil
}
@@ -5,15 +5,18 @@ package service
import (
"context"
"github.com/grafana/grafana/pkg/services/alerting"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting"
dashbboardservice "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -854,7 +857,11 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
dto := toSaveDashboardDto(cmd)
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
service := ProvideDashboardService(
setting.NewCfg(), dashboardStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(), accesscontrolmock.NewPermissionsServicesMock(),
)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
@@ -863,7 +870,11 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) error {
dto := toSaveDashboardDto(cmd)
dashboardStore := database.ProvideDashboardStore(sqlStore)
_, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
service := ProvideDashboardService(
setting.NewCfg(), dashboardStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(), accesscontrolmock.NewPermissionsServicesMock(),
)
_, err := service.SaveDashboard(context.Background(), &dto, false)
return err
}
@@ -890,7 +901,11 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
}
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
service := ProvideDashboardService(
setting.NewCfg(), dashboardStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(), accesscontrolmock.NewPermissionsServicesMock(),
)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
@@ -918,7 +933,11 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
}
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
service := ProvideDashboardService(
setting.NewCfg(), dashboardStore, &dummyDashAlertExtractor{},
featuremgmt.WithFeatures(), accesscontrolmock.NewPermissionsServicesMock(),
)
res, err := service.SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
@@ -3,29 +3,43 @@ package service
import (
"context"
"errors"
"strconv"
"strings"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"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/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
)
type FolderServiceImpl struct {
log log.Logger
cfg *setting.Cfg
dashboardService dashboards.DashboardService
dashboardStore dashboards.Store
searchService *search.SearchService
log log.Logger
features featuremgmt.FeatureToggles
permissions accesscontrol.PermissionsService
}
func ProvideFolderService(dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, searchService *search.SearchService) *FolderServiceImpl {
func ProvideFolderService(
cfg *setting.Cfg, dashboardService dashboards.DashboardService, dashboardStore dashboards.Store,
searchService *search.SearchService, features featuremgmt.FeatureToggles, permissionsServices accesscontrol.PermissionsServices,
) *FolderServiceImpl {
return &FolderServiceImpl{
cfg: cfg,
log: log.New("folder-service"),
dashboardService: dashboardService,
dashboardStore: dashboardStore,
searchService: searchService,
log: log.New("folder-service"),
features: features,
permissions: permissionsServices.GetFolderService(),
}
}
@@ -149,6 +163,22 @@ func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.Signe
return nil, toFolderError(err)
}
var permissionErr error
if f.features.IsEnabled(featuremgmt.FlagAccesscontrol) {
resourceID := strconv.FormatInt(dashFolder.Id, 10)
_, permissionErr = f.permissions.SetPermissions(ctx, orgID, resourceID, []accesscontrol.SetResourcePermissionCommand{
{UserID: userID, Permission: models.PERMISSION_ADMIN.String()},
{BuiltinRole: string(models.ROLE_EDITOR), Permission: models.PERMISSION_EDIT.String()},
{BuiltinRole: string(models.ROLE_VIEWER), Permission: models.PERMISSION_VIEW.String()},
}...)
} else if f.cfg.EditorsCanAdmin {
permissionErr = f.MakeUserAdmin(ctx, orgID, userID, dashFolder.Id, true)
}
if permissionErr != nil {
f.log.Error("Could not make user admin", "folder", dashFolder.Title, "user", userID, "error", permissionErr)
}
return dashToFolder(dashFolder), nil
}
@@ -9,9 +9,12 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@@ -24,10 +27,13 @@ func TestFolderService(t *testing.T) {
t.Run("Folder service tests", func(t *testing.T) {
store := &database.FakeDashboardStore{}
defer store.AssertExpectations(t)
cfg := setting.NewCfg()
features := featuremgmt.WithFeatures()
permissionsServices := acmock.NewPermissionsServicesMock()
dashboardService := ProvideDashboardService(cfg, store, nil, features, permissionsServices)
service := ProvideFolderService(
&dashboards.FakeDashboardService{DashboardService: ProvideDashboardService(store, nil)},
store,
nil,
cfg, &dashboards.FakeDashboardService{DashboardService: dashboardService},
store, nil, features, permissionsServices,
)
t.Run("Given user has no permissions", func(t *testing.T) {