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. 81
      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.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
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
if tt.body != "" {

@ -32,7 +32,7 @@ func checkNilRequester(r Requester) bool {
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
// static permissions so it can be used in legacy code paths.
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
}
// 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 {
permissions := make(map[string][]string, len(actions))
for _, a := range actions {

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

@ -60,12 +60,12 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
func TestNewDashboardIDScopeResolver(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)
})
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")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})
@ -73,12 +73,12 @@ func TestNewDashboardIDScopeResolver(t *testing.T) {
func TestNewDashboardUIDScopeResolver(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)
})
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")
require.ErrorIs(t, err, ac.ErrInvalidScope)
})

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

@ -962,7 +962,7 @@ func setupAccessControlGuardianTest(
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.NewFolderIDScopeResolver(folderStore, folderSvc))

Loading…
Cancel
Save