From 9494175984cca2481fc5cec397305378e680ee32 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 23 Sep 2025 18:35:26 +0300 Subject: [PATCH] Folders: Use access checker for /access endpoint (#110745) --- pkg/registry/apis/folders/register.go | 6 +- pkg/registry/apis/folders/sub_access.go | 86 ++++++++++++++++++------- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 708c32257b2..c49e9c224db 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -55,6 +55,7 @@ type FolderAPIBuilder struct { ac accesscontrol.AccessControl storage grafanarest.Storage permissionStore reconcilers.PermissionStore + accessClient authlib.AccessClient authorizer authorizer.Authorizer parents parentsGetter @@ -83,6 +84,7 @@ func RegisterAPIService(cfg *setting.Cfg, folderPermissionsSvc: folderPermissionsSvc, acService: acService, ac: accessControl, + accessClient: accessClient, permissionsOnCreate: cfg.RBAC.PermissionsOnCreation("folder"), authorizer: newLegacyAuthorizer(accessControl), searcher: unified, @@ -206,8 +208,8 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API searcher: b.searcher, } storage[resourceInfo.StoragePath("access")] = &subAccessREST{ - getter: folderStore, - ac: b.ac, + getter: folderStore, + accessClient: b.accessClient, } // Adds a path to return children of a given folder diff --git a/pkg/registry/apis/folders/sub_access.go b/pkg/registry/apis/folders/sub_access.go index 5f3b11ad361..ff068a14a33 100644 --- a/pkg/registry/apis/folders/sub_access.go +++ b/pkg/registry/apis/folders/sub_access.go @@ -8,22 +8,23 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" - folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + authlib "github.com/grafana/authlib/types" + foldersV1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" ) type subAccessREST struct { - getter rest.Getter - ac accesscontrol.AccessControl + getter rest.Getter + accessClient authlib.AccessClient } var _ = rest.Connecter(&subAccessREST{}) var _ = rest.StorageMetadata(&subAccessREST{}) func (r *subAccessREST) New() runtime.Object { - return &folders.FolderAccessInfo{} + return &foldersV1.FolderAccessInfo{} } func (r *subAccessREST) Destroy() { @@ -38,7 +39,7 @@ func (r *subAccessREST) ProducesMIMETypes(verb string) []string { } func (r *subAccessREST) ProducesObject(verb string) interface{} { - return &folders.FolderAccessInfo{} + return &foldersV1.FolderAccessInfo{} } func (r *subAccessREST) NewConnectOptions() (runtime.Object, bool, string) { @@ -46,28 +47,67 @@ func (r *subAccessREST) NewConnectOptions() (runtime.Object, bool, string) { } func (r *subAccessREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + access, err := r.getAccessInfo(ctx, name) + if err != nil { + responder.Error(err) + } else { + responder.Object(200, access) + } + }), nil +} + +func (r *subAccessREST) getAccessInfo(ctx context.Context, name string) (*foldersV1.FolderAccessInfo, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } user, err := identity.GetRequester(ctx) if err != nil { return nil, err } - // Must be able to get the resource - if _, err = r.getter.Get(ctx, name, &v1.GetOptions{}); err != nil { + // Can view is managed here (and in the Authorizer) + f, err := r.getter.Get(ctx, name, &v1.GetOptions{}) + if err != nil { return nil, err } + obj, err := utils.MetaAccessor(f) + if err != nil { + return nil, err + } + var tmp authlib.CheckResponse + check := func(verb string) bool { + if err != nil { + return false + } + tmp, err = r.accessClient.Check(ctx, user, authlib.CheckRequest{ + Verb: verb, + Group: foldersV1.GROUP, + Resource: foldersV1.RESOURCE, + Namespace: ns.Value, + Name: name, + Folder: obj.GetFolder(), + }) + return tmp.Allowed + } - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - access := &folders.FolderAccessInfo{} - canEditEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)) - access.CanEdit, _ = r.ac.Evaluate(ctx, user, canEditEvaluator) - access.CanSave = access.CanEdit - canAdminEvaluator := accesscontrol.EvalAll( - accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)), - accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)), - ) - access.CanAdmin, _ = r.ac.Evaluate(ctx, user, canAdminEvaluator) - canDeleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)) - access.CanDelete, _ = r.ac.Evaluate(ctx, user, canDeleteEvaluator) - responder.Object(http.StatusOK, access) - }), nil + rsp := &foldersV1.FolderAccessInfo{} + rsp.CanAdmin = check(utils.VerbSetPermissions) + if err != nil { + return nil, err + } + rsp.CanDelete = rsp.CanAdmin || check(utils.VerbDelete) + if err != nil { + return nil, err + } + rsp.CanEdit = rsp.CanAdmin || check(utils.VerbUpdate) + if err != nil { + return nil, err + } + rsp.CanSave = rsp.CanAdmin || check(utils.VerbCreate) // or the same as update? + if err != nil { + return nil, err + } + return rsp, nil }