From d6b6a9da7bf292407df55612d5ecc90c5adac72d Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:10:32 +0100 Subject: [PATCH] FolderAPI: Use different authorizer for multi-tenant api (#101372) Use different authorizers depening on if we are running multi-tenant or single-tenant --- pkg/registry/apis/folders/authorizer.go | 106 +++++++ pkg/registry/apis/folders/authorizer_test.go | 277 +++++++++++++++++++ pkg/registry/apis/folders/register.go | 70 +---- pkg/registry/apis/folders/register_test.go | 257 ++--------------- 4 files changed, 423 insertions(+), 287 deletions(-) create mode 100644 pkg/registry/apis/folders/authorizer.go create mode 100644 pkg/registry/apis/folders/authorizer_test.go diff --git a/pkg/registry/apis/folders/authorizer.go b/pkg/registry/apis/folders/authorizer.go new file mode 100644 index 00000000000..b4d5c3fdc1c --- /dev/null +++ b/pkg/registry/apis/folders/authorizer.go @@ -0,0 +1,106 @@ +package folders + +import ( + "context" + "errors" + "slices" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" +) + +// newLegacyAuthorizer creates an authorizer using legacy access control, this is only usable for single tenant api. +func newLegacyAuthorizer(ac accesscontrol.AccessControl) authorizer.Authorizer { + return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { + in, err := authorizerFunc(ctx, attr) + if err != nil { + if errors.Is(err, errNoUser) { + return authorizer.DecisionDeny, "", nil + } + return authorizer.DecisionNoOpinion, "", nil + } + + ok, err := ac.Evaluate(ctx, in.user, in.evaluator) + if ok { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "folder", err + }) +} + +func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) { + allowedVerbs := []string{utils.VerbCreate, utils.VerbDelete, utils.VerbList} + verb := attr.GetVerb() + name := attr.GetName() + if (!attr.IsResourceRequest()) || (name == "" && verb != utils.VerbCreate && slices.Contains(allowedVerbs, verb)) { + return nil, errNoResource + } + + // require a user + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, errNoUser + } + + scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(name) + var eval accesscontrol.Evaluator + + // "get" is used for sub-resources with GET http (parents, access, count) + switch verb { + case utils.VerbCreate: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate) + case utils.VerbPatch: + fallthrough + case utils.VerbUpdate: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scope) + case utils.VerbDeleteCollection: + fallthrough + case utils.VerbDelete: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, scope) + case utils.VerbList: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead) + default: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead, scope) + } + return &authorizerParams{evaluator: eval, user: user}, nil +} + +// newMultiTenantAuthorizer creates an authorizer sutiable to multi-tenant setup. +// For now it only allow authorization of access tokens. +func newMultiTenantAuthorizer(ac types.AccessClient) authorizer.Authorizer { + return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + info, ok := types.AuthInfoFrom(ctx) + if !ok { + return authorizer.DecisionDeny, "missing auth info", nil + } + + // For now we only allow access policy to authorize with multi-tenant setup + if !types.IsIdentityType(info.GetIdentityType(), types.TypeAccessPolicy) { + return authorizer.DecisionDeny, "permission denied", nil + } + + res, err := ac.Check(ctx, info, types.CheckRequest{ + Verb: a.GetVerb(), + Group: a.GetAPIGroup(), + Resource: a.GetResource(), + Namespace: a.GetNamespace(), + Name: a.GetNamespace(), + Subresource: a.GetSubresource(), + }) + + if err != nil { + return authorizer.DecisionDeny, "faild to perform authorization", err + } + + if !res.Allowed { + return authorizer.DecisionDeny, "permission denied", nil + } + + return authorizer.DecisionAllow, "", nil + }) +} diff --git a/pkg/registry/apis/folders/authorizer_test.go b/pkg/registry/apis/folders/authorizer_test.go new file mode 100644 index 00000000000..eced4071112 --- /dev/null +++ b/pkg/registry/apis/folders/authorizer_test.go @@ -0,0 +1,277 @@ +package folders + +import ( + "context" + "testing" + + "github.com/go-jose/go-jose/v3/jwt" + "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/authz" + "github.com/grafana/authlib/types" + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" + folderv0aplha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/user" +) + +func TestLegacyAuthorizer(t *testing.T) { + type input struct { + user identity.Requester + verb string + } + type expect struct { + authorized authorizer.Decision + err error + } + var orgID int64 = 1 + + tests := []struct { + name string + input input + expect expect + }{ + { + name: "user with create permissions should be able to create a folder", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, + }, + }, + verb: string(utils.VerbCreate), + }, + expect: expect{ + authorized: authorizer.DecisionAllow, + }, + }, + { + name: "not possible to create a folder without a user", + input: input{ + user: nil, + verb: string(utils.VerbCreate), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + { + name: "user without permissions should not be able to create a folder", + input: input{ + user: &user.SignedInUser{}, + verb: string(utils.VerbCreate), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + { + name: "user in another orgId should not be able to create a folder ", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: 2, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, + }, + }, + verb: string(utils.VerbCreate), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + { + name: "user with read permissions should be able to list folders", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {}, + }, + }, + verb: string(utils.VerbList), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + { + name: "user with delete permissions should be able to delete a folder", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {dashboards.ActionFoldersDelete: {dashboards.ScopeFoldersAll}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, + }, + }, + verb: string(utils.VerbDelete), + }, + expect: expect{authorized: authorizer.DecisionAllow}, + }, + { + name: "user without delete permissions should NOT be able to delete a folder", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {}, + }, + }, + verb: string(utils.VerbDelete), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + { + name: "user with write permissions should be able to update a folder", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, + }, + }, + verb: string(utils.VerbUpdate), + }, + expect: expect{authorized: authorizer.DecisionAllow}, + }, + { + name: "user without write permissions should NOT be able to update a folder", + input: input{ + user: &user.SignedInUser{ + UserID: 1, + OrgID: orgID, + Name: "123", + Permissions: map[int64]map[string][]string{ + orgID: {}, + }, + }, + verb: string(utils.VerbUpdate), + }, + expect: expect{authorized: authorizer.DecisionDeny}, + }, + } + + authz := newLegacyAuthorizer(acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders"))) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + authorized, _, err := authz.Authorize( + identity.WithRequester(context.Background(), tt.input.user), + authorizer.AttributesRecord{User: tt.input.user, Verb: tt.input.verb, Resource: "folders", ResourceRequest: true, Name: "123"}, + ) + if tt.expect.err != nil { + require.Error(t, err) + require.Equal(t, authorizer.DecisionDeny, authorized) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expect.authorized, authorized) + }) + } +} + +func TestMultiTenantAuthorizer(t *testing.T) { + type input struct { + verb string + info types.AuthInfo + client types.AccessClient + } + + type expected struct { + authorized authorizer.Decision + err bool + } + + tests := []struct { + name string + input input + expeted expected + }{ + { + name: "non access policy idenity should not be able to authorize", + input: input{ + verb: utils.VerbGet, + info: &identity.StaticRequester{ + Type: types.TypeUser, + UserID: 1, + UserUID: "1", + }, + }, + expeted: expected{ + authorized: authorizer.DecisionDeny, + }, + }, + { + name: "access policy identity with correct permissions should be able to authorize", + input: input{ + verb: utils.VerbGet, + info: authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{ + Claims: jwt.Claims{ + Subject: "access-policy:123", + }, + Rest: authn.AccessTokenClaims{ + Namespace: "stacks-1", + Permissions: []string{ + "folder.grafana.app/folders:get", + }, + }, + }), + client: authz.NewClient(nil), + }, + expeted: expected{ + authorized: authorizer.DecisionAllow, + }, + }, + { + name: "access policy identity without correct permissions should not be able to authorize", + input: input{ + verb: utils.VerbGet, + info: authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{ + Claims: jwt.Claims{ + Subject: "access-policy:123", + }, + Rest: authn.AccessTokenClaims{ + Namespace: "stacks-1", + Permissions: []string{ + "folder.grafana.app/folders:create", + }, + }, + }), + client: authz.NewClient(nil), + }, + expeted: expected{ + authorized: authorizer.DecisionDeny, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + authz := newMultiTenantAuthorizer(tt.input.client) + authorized, _, err := authz.Authorize( + types.WithAuthInfo(context.Background(), tt.input.info), + authorizer.AttributesRecord{User: tt.input.info, Verb: tt.input.verb, APIGroup: folderv0aplha1.GROUP, Resource: "folders", ResourceRequest: true, Name: "123", Namespace: "stacks-1"}, + ) + + if tt.expeted.err { + require.Error(t, err) + require.Equal(t, authorizer.DecisionDeny, authorized) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expeted.authorized, authorized) + }) + } +} diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 1ca84a49a4f..4e5e144d50e 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -4,9 +4,9 @@ import ( "context" "errors" "fmt" - "slices" "strings" + authtypes "github.com/grafana/authlib/types" "github.com/prometheus/client_golang/prometheus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -50,10 +50,12 @@ type FolderAPIBuilder struct { folderSvc folder.Service folderPermissionsSvc accesscontrol.FolderPermissionsService storage grafanarest.Storage - accessControl accesscontrol.AccessControl - searcher resource.ResourceIndexClient - cfg *setting.Cfg - ignoreLegacy bool // skip legacy storage and only use unified storage + + authorizer authorizer.Authorizer + + searcher resource.ResourceIndexClient + cfg *setting.Cfg + ignoreLegacy bool // skip legacy storage and only use unified storage } func RegisterAPIService(cfg *setting.Cfg, @@ -79,17 +81,18 @@ func RegisterAPIService(cfg *setting.Cfg, folderSvc: folderSvc, folderPermissionsSvc: folderPermissionsSvc, cfg: cfg, - accessControl: accessControl, + authorizer: newLegacyAuthorizer(accessControl), searcher: unified, } apiregistration.RegisterAPI(builder) return builder } -func NewAPIService() *FolderAPIBuilder { +func NewAPIService(ac authtypes.AccessClient) *FolderAPIBuilder { return &FolderAPIBuilder{ gv: resourceInfo.GroupVersion(), namespacer: request.GetNamespaceMapper(nil), + authorizer: newMultiTenantAuthorizer(ac), ignoreLegacy: true, } } @@ -201,58 +204,7 @@ type authorizerParams struct { } func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { - in, err := authorizerFunc(ctx, attr) - if err != nil { - if errors.Is(err, errNoUser) { - return authorizer.DecisionDeny, "", nil - } - return authorizer.DecisionNoOpinion, "", nil - } - - ok, err := b.accessControl.Evaluate(ctx, in.user, in.evaluator) - if ok { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "folder", err - }) -} - -func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) { - allowedVerbs := []string{utils.VerbCreate, utils.VerbDelete, utils.VerbList} - verb := attr.GetVerb() - name := attr.GetName() - if (!attr.IsResourceRequest()) || (name == "" && verb != utils.VerbCreate && slices.Contains(allowedVerbs, verb)) { - return nil, errNoResource - } - - // require a user - user, err := identity.GetRequester(ctx) - if err != nil { - return nil, errNoUser - } - - scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(name) - var eval accesscontrol.Evaluator - - // "get" is used for sub-resources with GET http (parents, access, count) - switch verb { - case utils.VerbCreate: - eval = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate) - case utils.VerbPatch: - fallthrough - case utils.VerbUpdate: - eval = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scope) - case utils.VerbDeleteCollection: - fallthrough - case utils.VerbDelete: - eval = accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, scope) - case utils.VerbList: - eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead) - default: - eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead, scope) - } - return &authorizerParams{evaluator: eval, user: user}, nil + return b.authorizer } var folderValidationRules = struct { diff --git a/pkg/registry/apis/folders/register_test.go b/pkg/registry/apis/folders/register_test.go index 4f1aae986f7..5d80a2c2369 100644 --- a/pkg/registry/apis/folders/register_test.go +++ b/pkg/registry/apis/folders/register_test.go @@ -8,211 +8,17 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/authorization/authorizer" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/unified/resource" ) -func TestFolderAPIBuilder_getAuthorizerFunc(t *testing.T) { - type input struct { - user identity.Requester - verb string - } - type expect struct { - eval string - allow bool - err error - } - var orgID int64 = 1 - - tests := []struct { - name string - input input - expect expect - }{ - { - name: "user with create permissions should be able to create a folder", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, - }, - }, - verb: string(utils.VerbCreate), - }, - expect: expect{ - eval: "folders:create", - allow: true, - }, - }, - { - name: "not possible to create a folder without a user", - input: input{ - user: nil, - verb: string(utils.VerbCreate), - }, - expect: expect{ - eval: "folders:create", - err: errNoUser, - }, - }, - { - name: "user without permissions should not be able to create a folder", - input: input{ - user: &user.SignedInUser{}, - verb: string(utils.VerbCreate), - }, - expect: expect{ - eval: "folders:create", - }, - }, - { - name: "user in another orgId should not be able to create a folder ", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: 2, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, - }, - }, - verb: string(utils.VerbCreate), - }, - expect: expect{ - eval: "folders:create", - }, - }, - { - name: "user with read permissions should be able to list folders", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {}, - }, - }, - verb: string(utils.VerbList), - }, - expect: expect{ - eval: "folders:read", - allow: false, - }, - }, - { - name: "user with delete permissions should be able to delete a folder", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {dashboards.ActionFoldersDelete: {dashboards.ScopeFoldersAll}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, - }, - }, - verb: string(utils.VerbDelete), - }, - expect: expect{ - eval: "folders:delete", - allow: true, - }, - }, - { - name: "user without delete permissions should NOT be able to delete a folder", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {}, - }, - }, - verb: string(utils.VerbDelete), - }, - expect: expect{ - eval: "folders:delete", - allow: false, - }, - }, - { - name: "user with write permissions should be able to update a folder", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, - }, - }, - verb: string(utils.VerbUpdate), - }, - expect: expect{ - eval: "folders:write", - allow: true, - }, - }, - { - name: "user without write permissions should NOT be able to update a folder", - input: input{ - user: &user.SignedInUser{ - UserID: 1, - OrgID: orgID, - Name: "123", - Permissions: map[int64]map[string][]string{ - orgID: {}, - }, - }, - verb: string(utils.VerbUpdate), - }, - expect: expect{ - eval: "folders:write", - allow: false, - }, - }, - } - - b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - out, err := authorizerFunc(identity.WithRequester(ctx, tt.input.user), authorizer.AttributesRecord{User: tt.input.user, Verb: tt.input.verb, Resource: "folders", ResourceRequest: true, Name: "123"}) - if tt.expect.err != nil { - require.Error(t, err) - return - } - allow, _ := b.accessControl.Evaluate(ctx, out.user, out.evaluator) - require.NoError(t, err) - require.Equal(t, tt.expect.eval, out.evaluator.String()) - require.Equal(t, tt.expect.allow, allow) - }) - } -} - func TestFolderAPIBuilder_Validate_Create(t *testing.T) { type input struct { obj *v0alpha1.Folder @@ -294,12 +100,11 @@ func TestFolderAPIBuilder_Validate_Create(t *testing.T) { us := storageMock{m, s} b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - storage: us, - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + storage: us, } for _, tt := range tests { @@ -379,13 +184,12 @@ func TestFolderAPIBuilder_Validate_Delete(t *testing.T) { setupFn(m, tt.statsResponse) b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - storage: us, - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), - searcher: sm, + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + storage: us, + searcher: sm, } err := b.Validate(context.Background(), admission.NewAttributesRecord( @@ -545,13 +349,12 @@ func TestFolderAPIBuilder_Validate_Update(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - storage: us, - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), - searcher: sm, + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + storage: us, + searcher: sm, } err := b.Validate(context.Background(), admission.NewAttributesRecord( @@ -641,13 +444,12 @@ func TestFolderAPIBuilder_Mutate_Create(t *testing.T) { us := storageMock{m, s} sm := searcherMock{Mock: m} b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - storage: us, - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), - searcher: sm, + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + storage: us, + searcher: sm, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -748,13 +550,12 @@ func TestFolderAPIBuilder_Mutate_Update(t *testing.T) { us := storageMock{m, s} sm := searcherMock{Mock: m} b := &FolderAPIBuilder{ - gv: resourceInfo.GroupVersion(), - features: nil, - namespacer: func(_ int64) string { return "123" }, - folderSvc: foldertest.NewFakeService(), - storage: us, - accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders")), - searcher: sm, + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + storage: us, + searcher: sm, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {