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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user