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:
@@ -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
|
||||||
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
@@ -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"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user