diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 69fa72bf479..80444b8d3fb 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -36,6 +36,14 @@ var resourceInfo = v0alpha1.FolderResourceInfo var errNoUser = errors.New("valid user is required") var errNoResource = errors.New("resource name is required") +var folderValidationRules = struct { + maxDepth int + invalidNames []string +}{ + maxDepth: 4, + invalidNames: []string{"general"}, +} + // This is used just so wire has something unique to return type FolderAPIBuilder struct { gv schema.GroupVersion @@ -183,10 +191,6 @@ func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer { }) } -func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - return nil -} - func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) { verb := attr.GetVerb() name := attr.GetName() @@ -220,3 +224,38 @@ func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorize } return &authorizerParams{evaluator: eval, user: user}, nil } + +func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error { + // name cannot be called "general" + id := a.GetName() + for _, invalidName := range folderValidationRules.invalidNames { + if id == invalidName { + return dashboards.ErrFolderInvalidUID + } + } + + obj := a.GetObject() + + for i := 0; i <= folderValidationRules.maxDepth+1; i++ { + parent := getParent(obj) + if parent == "" { + break + } + if i == folderValidationRules.maxDepth+1 { + return folder.ErrMaximumDepthReached + } + + // TODO: investigate the best way to get a folder by name, considering validate is called before the storage layer + // parent, err := getFolderByNameSomehow() + // obj = parent + } + return nil +} + +func getParent(o runtime.Object) string { + meta, err := utils.MetaAccessor(o) + if err != nil { + return "" + } + return meta.GetFolder() +} diff --git a/pkg/registry/apis/folders/register_test.go b/pkg/registry/apis/folders/register_test.go index 068bfafdce5..dcd9e96aef3 100644 --- a/pkg/registry/apis/folders/register_test.go +++ b/pkg/registry/apis/folders/register_test.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" @@ -13,6 +14,8 @@ import ( "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/user" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -114,3 +117,58 @@ func TestFolderAPIBuilder_getAuthorizerFunc(t *testing.T) { }) } } + +func TestFolderAPIBuilder_Validate(t *testing.T) { + type input struct { + obj *unstructured.Unstructured + name string + } + tests := []struct { + name string + input input + err error + }{ + { + name: "should return error when nameis invalid", + input: input{ + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "meta": map[string]interface{}{"name": folderValidationRules.invalidNames[0]}, + }, + }, + name: folderValidationRules.invalidNames[0], + }, + err: dashboards.ErrFolderInvalidUID, + }, + } + + b := &FolderAPIBuilder{ + gv: resourceInfo.GroupVersion(), + features: nil, + namespacer: func(_ int64) string { return "123" }, + folderSvc: foldertest.NewFakeService(), + accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures("nestedFolders"), zanzana.NewNoopClient()), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := b.Validate(context.Background(), admission.NewAttributesRecord( + tt.input.obj, + nil, + v0alpha1.SchemeGroupVersion.WithKind("folder"), + "stacks-123", + tt.input.name, + v0alpha1.SchemeGroupVersion.WithResource("folders"), + "", + "create", + nil, + true, + &user.SignedInUser{}, + ), nil) + + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + return + } + }) + } +}