Dashboards+Folders: Ensure the service identity is used for resolvers (#100128)

* Dashboards+Folders: Ensure the service identity is used for dashboard and folder resolvers

* Add convinient function to call closure with service context
pull/100182/head
Karl Persson 5 months ago committed by GitHub
parent 0916994d0a
commit e05413dcc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      pkg/api/annotations_test.go
  2. 13
      pkg/apimachinery/identity/context.go
  3. 39
      pkg/services/dashboards/accesscontrol.go
  4. 8
      pkg/services/dashboards/accesscontrol_test.go
  5. 4
      pkg/services/dashboards/service/dashboard_service.go
  6. 2
      pkg/services/guardian/accesscontrol_guardian_test.go

@ -403,7 +403,7 @@ func TestAPI_Annotations(t *testing.T) {
hs.folderService = folderService hs.folderService = folderService
hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService)) hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService))
hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, folderService)) hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(dashService, folderService))
}) })
var body io.Reader var body io.Reader
if tt.body != "" { if tt.body != "" {

@ -32,7 +32,7 @@ func checkNilRequester(r Requester) bool {
const serviceName = "service" const serviceName = "service"
// WithServiceIdentity sets creates an identity representing the service itself in provided org and store it in context. // WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with // This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
// static permissions so it can be used in legacy code paths. // static permissions so it can be used in legacy code paths.
func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) { func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) {
@ -53,6 +53,17 @@ func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Req
return WithRequester(ctx, r), r return WithRequester(ctx, r), r
} }
// WithServiceIdentityContext sets an identity representing the service itself in context.
func WithServiceIdentityContext(ctx context.Context, orgID int64) context.Context {
ctx, _ = WithServiceIdentity(ctx, orgID)
return ctx
}
// WithServiceIdentityFN calls provided closure with an context contaning the identity of the service.
func WithServiceIdentityFn[T any](ctx context.Context, orgID int64, fn func(ctx context.Context) (T, error)) (T, error) {
return fn(WithServiceIdentityContext(ctx, orgID))
}
func getWildcardPermissions(actions ...string) map[string][]string { func getWildcardPermissions(actions ...string) map[string][]string {
permissions := make(map[string][]string, len(actions)) permissions := make(map[string][]string, len(actions))
for _, a := range actions { for _, a := range actions {

@ -5,7 +5,7 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
@ -65,6 +65,7 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv
return []string{ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, nil return []string{ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, nil
} }
return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) {
folder, err := folderDB.GetFolderByID(ctx, orgID, id) folder, err := folderDB.GetFolderByID(ctx, orgID, id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -75,8 +76,8 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv
return nil, err return nil, err
} }
result = append([]string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, result...) return append([]string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, result...), nil
return result, nil })
}) })
} }
@ -97,17 +98,19 @@ func NewFolderUIDScopeResolver(folderSvc folder.Service) (string, ac.ScopeAttrib
return nil, err return nil, err
} }
return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) {
inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderSvc) inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderSvc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return append(inheritedScopes, ScopeFoldersProvider.GetResourceScopeUID(uid)), nil return append(inheritedScopes, ScopeFoldersProvider.GetResourceScopeUID(uid)), nil
}) })
})
} }
// NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:" // NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:"
// into uid based scopes for both dashboard and folder // into uid based scopes for both dashboard and folder
func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { func NewDashboardIDScopeResolver(ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
prefix := ScopeDashboardsProvider.GetResourceScope("") prefix := ScopeDashboardsProvider.GetResourceScope("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewDashboardIDScopeResolver") ctx, span := tracer.Start(ctx, "dashboards.NewDashboardIDScopeResolver")
@ -122,18 +125,20 @@ func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardServic
return nil, err return nil, err
} }
return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) {
dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{ID: id, OrgID: orgID}) dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{ID: id, OrgID: orgID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc) return resolveDashboardScope(ctx, orgID, dashboard, folderSvc)
})
}) })
} }
// NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:" // NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:"
// into uid based scopes for both dashboard and folder // into uid based scopes for both dashboard and folder
func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { func NewDashboardUIDScopeResolver(ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) {
prefix := ScopeDashboardsProvider.GetResourceScopeUID("") prefix := ScopeDashboardsProvider.GetResourceScopeUID("")
return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.NewDashboardUIDScopeResolver") ctx, span := tracer.Start(ctx, "dashboards.NewDashboardUIDScopeResolver")
@ -148,36 +153,26 @@ func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardServi
return nil, err return nil, err
} }
return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) {
dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{UID: uid, OrgID: orgID}) dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{UID: uid, OrgID: orgID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc) return resolveDashboardScope(ctx, orgID, dashboard, folderSvc)
})
}) })
} }
func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) { func resolveDashboardScope(ctx context.Context, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) {
ctx, span := tracer.Start(ctx, "dashboards.resolveDashboardScope") ctx, span := tracer.Start(ctx, "dashboards.resolveDashboardScope")
span.End() span.End()
var folderUID string var folderUID string
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() if dashboard.FolderUID == "" {
// nolint:staticcheck
if dashboard.FolderID < 0 {
return []string{ScopeDashboardsProvider.GetResourceScopeUID(dashboard.UID)}, nil
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
// nolint:staticcheck
if dashboard.FolderID == 0 {
folderUID = ac.GeneralFolderUID folderUID = ac.GeneralFolderUID
} else { } else {
folder, err := folderDB.GetFolderByID(ctx, orgID, dashboard.FolderID) folderUID = dashboard.FolderUID
if err != nil {
return nil, err
}
folderUID = folder.UID
} }
result, err := GetInheritedScopes(ctx, orgID, folderUID, folderSvc) result, err := GetInheritedScopes(ctx, orgID, folderUID, folderSvc)

@ -60,12 +60,12 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
func TestNewDashboardIDScopeResolver(t *testing.T) { func TestNewDashboardIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
require.Equal(t, "dashboards:id:", prefix) require.Equal(t, "dashboards:id:", prefix)
}) })
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) _, resolver := NewDashboardIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123") _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123")
require.ErrorIs(t, err, ac.ErrInvalidScope) require.ErrorIs(t, err, ac.ErrInvalidScope)
}) })
@ -73,12 +73,12 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
func TestNewDashboardUIDScopeResolver(t *testing.T) { func TestNewDashboardUIDScopeResolver(t *testing.T) {
t.Run("prefix should be expected", func(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) {
prefix, _ := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
require.Equal(t, "dashboards:uid:", prefix) require.Equal(t, "dashboards:uid:", prefix)
}) })
t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { t.Run("resolver should fail if input scope is not expected", func(t *testing.T) {
_, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService())
_, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123") _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123")
require.ErrorIs(t, err, ac.ErrInvalidScope) require.ErrorIs(t, err, ac.ErrInvalidScope)
}) })

@ -123,8 +123,8 @@ func ProvideDashboardServiceImpl(
return nil, err return nil, err
} }
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(dashSvc, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, dashSvc, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashSvc, folderSvc))
if err := folderSvc.RegisterService(dashSvc); err != nil { if err := folderSvc.RegisterService(dashSvc); err != nil {
return nil, err return nil, err

@ -962,7 +962,7 @@ func setupAccessControlGuardianTest(
folderStore := foldertest.NewFakeFolderStore(t) folderStore := foldertest.NewFakeFolderStore(t)
ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, fakeDashboardService, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(fakeDashboardService, folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(folderSvc))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, folderSvc))

Loading…
Cancel
Save