1
0
mirror of https://github.com/rancher/steve.git synced 2025-09-15 14:58:52 +00:00

[main&2.10.3] Add schema links and resource methods for resource verb patch (#450)

* Show patch link on the API resource when patch permission is present and add patch ResourceMethod to the schema.

* Added tests for new functionality and corrected disallowed method for patch
This commit is contained in:
Chad Roberts
2025-02-14 06:12:17 -05:00
committed by GitHub
parent f51f89196c
commit 5b5db5c40f
4 changed files with 103 additions and 1 deletions

View File

@@ -98,6 +98,7 @@ func formatter(summarycache *summarycache.SummaryCache, asl accesscontrol.Access
} }
hasUpdate := accessSet.Grants("update", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name()) hasUpdate := accessSet.Grants("update", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name())
hasDelete := accessSet.Grants("delete", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name()) hasDelete := accessSet.Grants("delete", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name())
hasPatch := accessSet.Grants("patch", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name())
selfLink := selfLink(gvr, meta) selfLink := selfLink(gvr, meta)
@@ -118,6 +119,13 @@ func formatter(summarycache *summarycache.SummaryCache, asl accesscontrol.Access
} else { } else {
delete(resource.Links, "remove") delete(resource.Links, "remove")
} }
if hasPatch {
if attributes.DisallowMethods(resource.Schema)[http.MethodPatch] {
resource.Links["patch"] = "blocked"
}
} else {
delete(resource.Links, "patch")
}
if unstr, ok := resource.APIObject.Object.(*unstructured.Unstructured); ok { if unstr, ok := resource.APIObject.Object.(*unstructured.Unstructured); ok {
// with the sql cache, these were already added by the indexer. However, the sql cache // with the sql cache, these were already added by the indexer. However, the sql cache

View File

@@ -634,6 +634,7 @@ func Test_formatterLinks(t *testing.T) {
hasGet bool hasGet bool
hasUpdate bool hasUpdate bool
hasRemove bool hasRemove bool
hasPatch bool
} }
tests := []struct { tests := []struct {
name string name string
@@ -935,6 +936,81 @@ func Test_formatterLinks(t *testing.T) {
"view": "/api/v1/namespaces/example-ns/pods/example-pod", "view": "/api/v1/namespaces/example-ns/pods/example-pod",
}, },
}, },
{
name: "patch permissions",
hasUser: true,
permissions: &permissions{
hasPatch: true,
},
schema: &types.APISchema{
Schema: &schemas.Schema{
ID: "example",
Attributes: map[string]interface{}{
"group": "apps",
"version": "v1",
"resource": "deployments",
},
},
},
apiObject: types.APIObject{
ID: "example",
Object: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example-deployment",
Namespace: "example-ns",
},
},
},
currentLinks: map[string]string{
"default": "defaultVal",
"patch": "/v1/apps.deployments/example-ns/example-deployment",
"view": "/apis/apps/v1/namespaces/example-ns/deployments/example-deployment",
},
wantLinks: map[string]string{
"default": "defaultVal",
"patch": "/v1/apps.deployments/example-ns/example-deployment",
"view": "/apis/apps/v1/namespaces/example-ns/deployments/example-deployment",
},
},
{
name: "patch permissions, but blocked",
hasUser: true,
permissions: &permissions{
hasPatch: true,
},
schema: &types.APISchema{
Schema: &schemas.Schema{
ID: "example",
Attributes: map[string]interface{}{
"group": "apps",
"version": "v1",
"resource": "deployments",
"disallowMethods": map[string]bool{
http.MethodPatch: true,
},
},
},
},
apiObject: types.APIObject{
ID: "example",
Object: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example-deployment",
Namespace: "example-ns",
},
},
},
currentLinks: map[string]string{
"default": "defaultVal",
"patch": "/v1/apps.deployments/example-ns/example-deployment",
"view": "/apis/apps/v1/namespaces/example-ns/deployments/example-deployment",
},
wantLinks: map[string]string{
"default": "defaultVal",
"patch": "blocked",
"view": "/apis/apps/v1/namespaces/example-ns/deployments/example-deployment",
},
},
} }
for _, test := range tests { for _, test := range tests {
@@ -964,6 +1040,12 @@ func Test_formatterLinks(t *testing.T) {
ResourceName: meta.GetName(), ResourceName: meta.GetName(),
}) })
} }
if test.permissions.hasPatch {
accessSet.Add("patch", gvr.GroupResource(), accesscontrol.Access{
Namespace: meta.GetNamespace(),
ResourceName: meta.GetName(),
})
}
asl.EXPECT().AccessFor(&defaultUserInfo).Return(&accessSet) asl.EXPECT().AccessFor(&defaultUserInfo).Return(&accessSet)
} else { } else {
asl.EXPECT().AccessFor(&defaultUserInfo).Return(nil).AnyTimes() asl.EXPECT().AccessFor(&defaultUserInfo).Return(nil).AnyTimes()

View File

@@ -157,6 +157,9 @@ func (c *Collection) schemasForSubject(access *accesscontrol.AccessSet) (*types.
if verbAccess.AnyVerb("create") { if verbAccess.AnyVerb("create") {
s.CollectionMethods = append(s.CollectionMethods, allowed(http.MethodPost)) s.CollectionMethods = append(s.CollectionMethods, allowed(http.MethodPost))
} }
if verbAccess.AnyVerb("patch") {
s.ResourceMethods = append(s.ResourceMethods, allowed(http.MethodPatch))
}
if len(s.CollectionMethods) == 0 && len(s.ResourceMethods) == 0 { if len(s.CollectionMethods) == 0 && len(s.ResourceMethods) == 0 {
continue continue

View File

@@ -38,6 +38,15 @@ func TestSchemas(t *testing.T) {
errDesired: false, errDesired: false,
}, },
}, },
{
name: "basic patch schema test",
config: schemaTestConfig{
permissionVerbs: []string{"patch"},
desiredResourceVerbs: []string{"PATCH"},
desiredCollectionVerbs: []string{},
errDesired: false,
},
},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
@@ -162,7 +171,7 @@ func makeSchema(resourceType string) *types.APISchema {
"group": testGroup, "group": testGroup,
"version": testVersion, "version": testVersion,
"resource": resourceType, "resource": resourceType,
"verbs": []string{"get", "list", "watch", "delete", "update", "create"}, "verbs": []string{"get", "list", "watch", "delete", "update", "create", "patch"},
}, },
}, },
} }