mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 13:12:08 +08:00
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
This commit is contained in:
@ -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))
|
||||
|
||||
|
Reference in New Issue
Block a user