diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index 77f971163..c5353dcb2 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -821,7 +821,7 @@ func (h *iamHandler) CreateWorkspaceMembers(request *restful.Request, response * } } - response.WriteEntity(servererr.None) + response.WriteEntity(members) } func (h *iamHandler) RemoveWorkspaceMember(request *restful.Request, response *restful.Response) { @@ -867,7 +867,7 @@ func (h *iamHandler) UpdateWorkspaceMember(request *restful.Request, response *r return } - response.WriteEntity(servererr.None) + response.WriteEntity(member) } func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response *restful.Response) { @@ -899,7 +899,7 @@ func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response * } } - response.WriteEntity(servererr.None) + response.WriteEntity(members) } func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *restful.Response) { @@ -936,7 +936,7 @@ func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *r return } - response.WriteEntity(servererr.None) + response.WriteEntity(member) } func (h *iamHandler) RemoveNamespaceMember(request *restful.Request, response *restful.Response) { @@ -980,7 +980,7 @@ func (h *iamHandler) CreateClusterMembers(request *restful.Request, response *re } } - response.WriteEntity(servererr.None) + response.WriteEntity(members) } func (h *iamHandler) RemoveClusterMember(request *restful.Request, response *restful.Response) { @@ -1024,7 +1024,7 @@ func (h *iamHandler) UpdateClusterMember(request *restful.Request, response *res return } - response.WriteEntity(servererr.None) + response.WriteEntity(member) } func (h *iamHandler) DescribeClusterMember(request *restful.Request, response *restful.Response) { @@ -1095,6 +1095,105 @@ func (h *iamHandler) resolveNamespace(namespace string, devops string) (string, return h.am.GetControlledNamespace(devops) } +func (h *iamHandler) PatchWorkspaceRole(request *restful.Request, response *restful.Response) { + workspaceName := request.PathParameter("workspace") + workspaceRoleName := request.PathParameter("workspacerole") + + var workspaceRole iamv1alpha2.WorkspaceRole + err := request.ReadEntity(&workspaceRole) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + workspaceRole.Name = workspaceRoleName + + patched, err := h.am.PatchWorkspaceRole(workspaceName, &workspaceRole) + + if err != nil { + handleError(request, response, err) + return + } + + response.WriteEntity(patched) +} + +func (h *iamHandler) PatchGlobalRole(request *restful.Request, response *restful.Response) { + globalRoleName := request.PathParameter("globalrole") + + var globalRole iamv1alpha2.GlobalRole + err := request.ReadEntity(&globalRole) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + globalRole.Name = globalRoleName + + patched, err := h.am.PatchGlobalRole(&globalRole) + + if err != nil { + handleError(request, response, err) + return + } + + response.WriteEntity(patched) +} + +func (h *iamHandler) PatchNamespaceRole(request *restful.Request, response *restful.Response) { + roleName := request.PathParameter("role") + namespaceName, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) + if err != nil { + klog.Error(err) + handleError(request, response, err) + return + } + + var role rbacv1.Role + err = request.ReadEntity(&role) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + role.Name = roleName + + patched, err := h.am.PatchNamespaceRole(namespaceName, &role) + + if err != nil { + handleError(request, response, err) + return + } + + response.WriteEntity(patched) +} + +func (h *iamHandler) PatchClusterRole(request *restful.Request, response *restful.Response) { + clusterRoleName := request.PathParameter("clusterrole") + + var clusterRole rbacv1.ClusterRole + err := request.ReadEntity(&clusterRole) + if err != nil { + klog.Error(err) + api.HandleBadRequest(response, request, err) + return + } + + clusterRole.Name = clusterRoleName + + patched, err := h.am.PatchClusterRole(&clusterRole) + + if err != nil { + handleError(request, response, err) + return + } + + response.WriteEntity(patched) +} + func handleError(request *restful.Request, response *restful.Response, err error) { if errors.IsBadRequest(err) { api.HandleBadRequest(response, request, err) diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index dda7e371d..651bde356 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -47,16 +47,19 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf To(handler.CreateUser). Doc("Create user in global scope."). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). + Reads(iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/users/{user}"). To(handler.DeleteUser). Doc("Delete user."). + Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/users/{user}"). To(handler.UpdateUser). Doc("Update user info."). Reads(iamv1alpha2.User{}). + Param(ws.PathParameter("user", "username")). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/users/{user}"). @@ -67,7 +70,7 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/users"). To(handler.ListUsers). - Doc("List all users."). + Doc("List all users in global scope."). Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) @@ -76,30 +79,30 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf To(handler.CreateClusterMembers). Doc("Add user to current cluster."). Reads([]Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). + Returns(http.StatusOK, api.StatusOK, []Member{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/clustermembers/{clustermember}"). To(handler.RemoveClusterMember). - Doc("Delete user from cluster scope."). + Doc("Delete member in cluster scope."). + Param(ws.PathParameter("clustermember", "cluster member's username")). Returns(http.StatusOK, api.StatusOK, errors.None). - Param(ws.PathParameter("clustermember", "username")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/clustermembers/{clustermember}"). To(handler.UpdateClusterMember). - Doc("Update user cluster role bind."). + Doc("Update cluster member role bind."). Reads(Member{}). - Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). - Param(ws.PathParameter("clustermember", "username")). + Returns(http.StatusOK, api.StatusOK, Member{}). + Param(ws.PathParameter("clustermember", "cluster member's username")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clustermembers/{clustermember}"). To(handler.DescribeClusterMember). - Doc("Retrieve user details in cluster."). - Param(ws.PathParameter("clustermember", "username")). + Doc("Retrieve member details in cluster."). + Param(ws.PathParameter("clustermember", "cluster member's username")). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clustermembers"). To(handler.ListClusterMembers). - Doc("List all users in cluster."). + Doc("List all members in cluster."). Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) @@ -107,105 +110,114 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf To(handler.ListWorkspaceMembers). Doc("List all members in the specified workspace."). Param(ws.PathParameter("workspace", "workspace name")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/workspacemembers/{workspacemember}"). To(handler.DescribeWorkspaceMember). Doc("Retrieve workspace member details."). Param(ws.PathParameter("workspace", "workspace name")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("workspacemember", "workspace member's username")). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.POST("/workspaces/{workspace}/workspacemembers"). To(handler.CreateWorkspaceMembers). Doc("Batch add workspace members."). Reads([]Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). + Returns(http.StatusOK, api.StatusOK, []Member{}). Param(ws.PathParameter("workspace", "workspace name")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/workspaces/{workspace}/workspacemembers/{workspacemember}"). To(handler.UpdateWorkspaceMember). Doc("Update member in workspace."). Reads(Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). + Returns(http.StatusOK, api.StatusOK, Member{}). Param(ws.PathParameter("workspace", "workspace name")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("workspacemember", "workspace member's username")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/workspaces/{workspace}/workspacemembers/{workspacemember}"). To(handler.RemoveWorkspaceMember). - Doc("Remove member in workspace."). + Doc("Delete member in workspace scope."). Param(ws.PathParameter("workspace", "workspace name")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("workspacemember", "workspace member's username")). + Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/members"). To(handler.ListNamespaceMembers). Doc("List all members in the specified namespace."). Param(ws.PathParameter("namespace", "namespace")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/members/{member}"). To(handler.DescribeNamespaceMember). Doc("Retrieve namespace member details."). Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("member", "namespace member's username")). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.POST("/namespaces/{namespace}/members"). To(handler.CreateNamespaceMembers). Doc("Batch add namespace members."). Reads([]Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). + Returns(http.StatusOK, api.StatusOK, []Member{}). Param(ws.PathParameter("namespace", "namespace")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/namespaces/{namespace}/members/{member}"). To(handler.UpdateNamespaceMember). Doc("Update member in namespace."). Reads(Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). + Returns(http.StatusOK, api.StatusOK, Member{}). Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("member", "namespace member's username")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/namespaces/{namespace}/members/{member}"). To(handler.RemoveNamespaceMember). - Doc("Remove member in namespace."). + Doc("Delete member in namespace scope."). Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). + Param(ws.PathParameter("member", "namespace member's username")). + Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/devops/{devops}/members"). To(handler.ListNamespaceMembers). - Doc("List all members in the specified namespace."). - Param(ws.PathParameter("namespace", "namespace")). + Doc("List all members in the specified devops project."). + Param(ws.PathParameter("devops", "devops project name")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.User{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/devops/{devops}/members/{member}"). To(handler.DescribeNamespaceMember). - Doc("Retrieve namespace member details."). - Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). + Doc("Retrieve devops project member details."). + Param(ws.PathParameter("devops", "devops project name")). + Param(ws.PathParameter("member", "devops project member's username")). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.POST("/devops/{devops}/members"). To(handler.CreateNamespaceMembers). - Doc("Batch add namespace members."). + Doc("Batch add devops project members."). Reads([]Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). - Param(ws.PathParameter("namespace", "namespace")). + Returns(http.StatusOK, api.StatusOK, []Member{}). + Param(ws.PathParameter("devops", "devops project name")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/devops/{devops}/members/{member}"). To(handler.UpdateNamespaceMember). - Doc("Update member in namespace."). + Doc("Update member in devops project."). Reads(Member{}). - Returns(http.StatusOK, api.StatusOK, errors.None). - Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("member", "username")). + Returns(http.StatusOK, api.StatusOK, Member{}). + Param(ws.PathParameter("devops", "devops project name")). + Param(ws.PathParameter("member", "devops project member's username")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/devops/{devops}/members/{member}"). To(handler.RemoveNamespaceMember). Doc("Remove member in namespace."). - Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("member", "username")). + Param(ws.PathParameter("devops", "devops project name")). + Param(ws.PathParameter("member", "devops project member's username")). + Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) // globalroles ws.Route(ws.POST("/globalroles"). To(handler.CreateGlobalRole). - Doc("Create global role."). + Doc("Create global role. Automatically aggregate policy rules according to annotation."). Reads(iamv1alpha2.GlobalRole{}). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GlobalRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) @@ -217,7 +229,14 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/globalroles/{globalrole}"). To(handler.UpdateGlobalRole). - Doc("Update global role."). + Doc("Update global role. Automatically aggregate policy rules according to annotation."). + Param(ws.PathParameter("globalrole", "global role name")). + Reads(iamv1alpha2.GlobalRole{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GlobalRole{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PATCH("/globalroles/{globalrole}"). + To(handler.PatchGlobalRole). + Doc("Patch global role. Automatically aggregate policy rules according to annotation."). Param(ws.PathParameter("globalrole", "global role name")). Reads(iamv1alpha2.GlobalRole{}). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GlobalRole{}). @@ -236,7 +255,7 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf // clusterroles ws.Route(ws.POST("/clusterroles"). To(handler.CreateClusterRole). - Doc("Create cluster role."). + Doc("Create cluster role. Automatically aggregate policy rules according to annotation."). Reads(rbacv1.ClusterRole{}). Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) @@ -248,7 +267,14 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/clusterroles/{clusterrole}"). To(handler.UpdateClusterRole). - Doc("Update cluster role."). + Doc("Update cluster role. Automatically aggregate policy rules according to annotation."). + Param(ws.PathParameter("clusterrole", "cluster role name")). + Reads(rbacv1.ClusterRole{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PATCH("/clusterroles/{clusterrole}"). + To(handler.PatchClusterRole). + Doc("Patch cluster role. Automatically aggregate policy rules according to annotation."). Param(ws.PathParameter("clusterrole", "cluster role name")). Reads(rbacv1.ClusterRole{}). Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). @@ -267,38 +293,52 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf // workspaceroles ws.Route(ws.POST("/workspaces/{workspace}/workspaceroles"). To(handler.CreateWorkspaceRole). - Doc("Create workspace role."). + Doc("Create workspace role. Automatically aggregate policy rules according to annotation."). Reads(iamv1alpha2.WorkspaceRole{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.WorkspaceRole{}). Param(ws.PathParameter("workspace", "workspace name")). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/workspaces/{workspace}/workspaceroles/{workspacerole}"). To(handler.DeleteWorkspaceRole). Doc("Delete workspace role."). Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("workspacerole", "workspace role name")). + Returns(http.StatusOK, api.StatusOK, errors.None). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PATCH("/workspaces/{workspace}/workspaceroles/{workspacerole}"). + To(handler.PatchWorkspaceRole). + Doc("Patch workspace role. Automatically aggregate policy rules according to annotation."). + Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("workspacerole", "workspace role name")). + Reads(iamv1alpha2.WorkspaceRole{}). Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/workspaces/{workspace}/workspaceroles/{workspacerole}"). To(handler.UpdateWorkspaceRole). - Doc("Update workspace role."). + Doc("Update workspace role. Automatically aggregate policy rules according to annotation."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("workspacerole", "workspace role name")). + Reads(iamv1alpha2.WorkspaceRole{}). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.WorkspaceRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/workspaceroles"). To(handler.ListWorkspaceRoles). Doc("List all workspace roles."). Param(ws.PathParameter("workspace", "workspace name")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.WorkspaceRole{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/workspaceroles/{workspacerole}"). To(handler.DescribeWorkspaceRole). Doc("Retrieve workspace role details."). Param(ws.PathParameter("workspace", "workspace name")). Param(ws.PathParameter("workspacerole", "workspace role name")). + Returns(http.StatusOK, api.StatusOK, iamv1alpha2.WorkspaceRole{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) // roles ws.Route(ws.POST("/namespaces/{namespace}/roles"). To(handler.CreateNamespaceRole). - Doc("Create role in the specified namespace."). + Doc("Create role in the specified namespace. Automatically aggregate policy rules according to annotation."). Reads(rbacv1.Role{}). Param(ws.PathParameter("namespace", "namespace")). Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). @@ -312,11 +352,19 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/namespaces/{namespace}/roles/{role}"). To(handler.UpdateNamespaceRole). - Doc("Update namespace role."). + Doc("Update namespace role. Automatically aggregate policy rules according to annotation."). Param(ws.PathParameter("namespace", "namespace")). Param(ws.PathParameter("role", "role name")). - Reads(rbacv1.ClusterRole{}). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Reads(rbacv1.Role{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PATCH("/namespaces/{namespace}/roles/{role}"). + To(handler.PatchNamespaceRole). + Doc("Patch namespace role."). + Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("role", "role name")). + Reads(rbacv1.Role{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/roles"). To(handler.ListRoles). @@ -329,78 +377,86 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf Doc("Retrieve role details."). Param(ws.PathParameter("namespace", "namespace")). Param(ws.PathParameter("role", "role name")). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) // roles ws.Route(ws.POST("/devops/{devops}/roles"). To(handler.CreateNamespaceRole). - Doc("Create role in the specified devops project."). + Doc("Create role in the specified devops project. Automatically aggregate policy rules according to annotation."). Reads(rbacv1.Role{}). - Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("devops", "devops project name")). Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.DELETE("/devops/{devops}/roles/{role}"). To(handler.DeleteNamespaceRole). Doc("Delete role in the specified devops project."). - Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("devops", "devops project name")). Param(ws.PathParameter("role", "role name")). Returns(http.StatusOK, api.StatusOK, errors.None). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.PUT("/devops/{devops}/roles/{role}"). To(handler.UpdateNamespaceRole). - Doc("Update devops project role."). - Param(ws.PathParameter("namespace", "namespace")). + Doc("Update devops project role. Automatically aggregate policy rules according to annotation."). + Param(ws.PathParameter("devops", "devops project name")). Param(ws.PathParameter("role", "role name")). - Reads(rbacv1.ClusterRole{}). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Reads(rbacv1.Role{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) + ws.Route(ws.PATCH("/devops/{devops}/roles/{role}"). + To(handler.PatchNamespaceRole). + Doc("Patch devops project role. Automatically aggregate policy rules according to annotation."). + Param(ws.PathParameter("devops", "devops project name")). + Param(ws.PathParameter("role", "role name")). + Reads(rbacv1.Role{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/devops/{devops}/roles"). To(handler.ListRoles). - Doc("List all roles in the specified namespace."). - Param(ws.PathParameter("namespace", "namespace")). + Doc("List all roles in the specified devops project."). + Param(ws.PathParameter("devops", "devops project name")). Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{rbacv1.Role{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/devops/{devops}/roles/{role}"). To(handler.DescribeNamespaceRole). - Doc("Retrieve role details."). - Param(ws.PathParameter("namespace", "namespace")). + Doc("Retrieve devops project role details."). + Param(ws.PathParameter("devops", "devops project name")). Param(ws.PathParameter("role", "role name")). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/users/{user}/globalroles"). To(handler.RetrieveMemberRoleTemplates). Doc("Retrieve user's global role templates."). Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, iamv1alpha2.GlobalRole{}). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.GlobalRole{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/clustermembers/{clustermember}/clusterroles"). To(handler.RetrieveMemberRoleTemplates). Doc("Retrieve user's role templates in cluster."). - Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, rbacv1.ClusterRole{}). + Param(ws.PathParameter("clustermember", "cluster member's username")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{rbacv1.ClusterRole{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/workspaces/{workspace}/workspacemembers/{workspacemember}/workspaceroles"). To(handler.RetrieveMemberRoleTemplates). Doc("Retrieve member's role templates in workspace."). Param(ws.PathParameter("workspace", "workspace")). - Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, iamv1alpha2.WorkspaceRole{}). + Param(ws.PathParameter("workspacemember", "workspace member's username")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{iamv1alpha2.WorkspaceRole{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/namespaces/{namespace}/members/{member}/roles"). To(handler.RetrieveMemberRoleTemplates). Doc("Retrieve member's role templates in namespace."). Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). + Param(ws.PathParameter("member", "namespace member's username")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{rbacv1.Role{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) ws.Route(ws.GET("/devops/{devops}/members/{member}/roles"). To(handler.RetrieveMemberRoleTemplates). Doc("Retrieve member's role templates in devops project."). - Param(ws.PathParameter("namespace", "namespace")). - Param(ws.PathParameter("user", "username")). - Returns(http.StatusOK, api.StatusOK, rbacv1.Role{}). + Param(ws.PathParameter("devops", "devops project name")). + Param(ws.PathParameter("member", "devops project member's username")). + Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{rbacv1.Role{}}}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) container.Add(ws) diff --git a/pkg/kapis/tenant/v1alpha2/handler.go b/pkg/kapis/tenant/v1alpha2/handler.go index 0ed5134e0..527ba2e7e 100644 --- a/pkg/kapis/tenant/v1alpha2/handler.go +++ b/pkg/kapis/tenant/v1alpha2/handler.go @@ -395,12 +395,7 @@ func (h *tenantHandler) PatchNamespace(request *restful.Request, response *restf return } - if namespaceName != namespace.Name { - err := fmt.Errorf("the name of the object (%s) does not match the name on the URL (%s)", namespace.Name, namespaceName) - klog.Errorf("%+v", err) - api.HandleBadRequest(response, request, err) - return - } + namespace.Name = namespaceName patched, err := h.tenant.PatchNamespace(workspaceName, &namespace) @@ -432,12 +427,7 @@ func (h *tenantHandler) PatchWorkspace(request *restful.Request, response *restf return } - if workspaceName != workspace.Name { - err := fmt.Errorf("the name of the object (%s) does not match the name on the URL (%s)", workspace.Name, workspaceName) - klog.Errorf("%+v", err) - api.HandleBadRequest(response, request, err) - return - } + workspace.Name = workspaceName patched, err := h.tenant.PatchWorkspace(&workspace) diff --git a/pkg/models/iam/am/am.go b/pkg/models/iam/am/am.go index 3eb1d74d3..90d1d46cb 100644 --- a/pkg/models/iam/am/am.go +++ b/pkg/models/iam/am/am.go @@ -22,6 +22,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" @@ -52,7 +53,9 @@ type AccessManagementInterface interface { GetWorkspaceRole(workspace string, name string) (*iamv1alpha2.WorkspaceRole, error) CreateGlobalRoleBinding(username string, globalRole string) error CreateOrUpdateWorkspaceRole(workspace string, workspaceRole *iamv1alpha2.WorkspaceRole) (*iamv1alpha2.WorkspaceRole, error) + PatchWorkspaceRole(workspace string, workspaceRole *iamv1alpha2.WorkspaceRole) (*iamv1alpha2.WorkspaceRole, error) CreateOrUpdateGlobalRole(globalRole *iamv1alpha2.GlobalRole) (*iamv1alpha2.GlobalRole, error) + PatchGlobalRole(globalRole *iamv1alpha2.GlobalRole) (*iamv1alpha2.GlobalRole, error) DeleteWorkspaceRole(workspace string, name string) error DeleteGlobalRole(name string) error CreateOrUpdateClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) @@ -69,6 +72,8 @@ type AccessManagementInterface interface { RemoveUserFromCluster(username string) error GetControlledNamespace(devops string) (string, error) GetControlledWorkspace(namespace string) (string, error) + PatchNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error) + PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) } type amOperator struct { @@ -244,7 +249,6 @@ func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.Cluste } result := make([]*rbacv1.ClusterRoleBinding, 0) - for _, obj := range roleBindings.Items { roleBinding := obj.(*rbacv1.ClusterRoleBinding) if contains(roleBinding.Subjects, username) { @@ -334,16 +338,13 @@ func (am *amOperator) GetGlobalRole(globalRole string) (*iamv1alpha2.GlobalRole, } func (am *amOperator) CreateGlobalRoleBinding(username string, role string) error { - _, err := am.GetGlobalRole(role) - if err != nil { klog.Error(err) return err } roleBindings, err := am.ListGlobalRoleBindings(username) - if err != nil { klog.Error(err) return err @@ -398,23 +399,18 @@ func (am *amOperator) CreateOrUpdateWorkspaceRole(workspace string, workspaceRol var aggregateRoles []string if err := json.Unmarshal([]byte(workspaceRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]), &aggregateRoles); err == nil { - for _, roleName := range aggregateRoles { - role, err := am.GetWorkspaceRole("", roleName) - if err != nil { klog.Error(err) return nil, err } - workspaceRole.Rules = append(workspaceRole.Rules, role.Rules...) } } var created *iamv1alpha2.WorkspaceRole var err error - if workspaceRole.ResourceVersion != "" { created, err = am.ksclient.IamV1alpha2().WorkspaceRoles().Update(workspaceRole) } else { @@ -424,17 +420,155 @@ func (am *amOperator) CreateOrUpdateWorkspaceRole(workspace string, workspaceRol return created, err } +func (am *amOperator) PatchGlobalRole(globalRole *iamv1alpha2.GlobalRole) (*iamv1alpha2.GlobalRole, error) { + old, err := am.GetGlobalRole(globalRole.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + // rules cannot be override + globalRole.Rules = old.Rules + + // aggregate roles if annotation has change + if aggregateRolesAnnotation := globalRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]; aggregateRolesAnnotation != "" { + globalRole.Rules = make([]rbacv1.PolicyRule, 0) + var aggregateRoles []string + if err := json.Unmarshal([]byte(aggregateRolesAnnotation), &aggregateRoles); err == nil { + for _, roleName := range aggregateRoles { + role, err := am.GetGlobalRole(roleName) + if err != nil { + klog.Error(err) + return nil, err + } + globalRole.Rules = append(globalRole.Rules, role.Rules...) + } + } + } + + data, err := json.Marshal(globalRole) + if err != nil { + return nil, err + } + + return am.ksclient.IamV1alpha2().GlobalRoles().Patch(globalRole.Name, types.MergePatchType, data) +} + +func (am *amOperator) PatchWorkspaceRole(workspace string, workspaceRole *iamv1alpha2.WorkspaceRole) (*iamv1alpha2.WorkspaceRole, error) { + old, err := am.GetWorkspaceRole(workspace, workspaceRole.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + // workspace label cannot be override + if workspaceRole.Labels[tenantv1alpha1.WorkspaceLabel] != "" { + workspaceRole.Labels[tenantv1alpha1.WorkspaceLabel] = workspace + } + + // rules cannot be override + workspaceRole.Rules = old.Rules + + // aggregate roles if annotation has change + if aggregateRolesAnnotation := workspaceRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]; aggregateRolesAnnotation != "" { + workspaceRole.Rules = make([]rbacv1.PolicyRule, 0) + var aggregateRoles []string + if err := json.Unmarshal([]byte(aggregateRolesAnnotation), &aggregateRoles); err == nil { + for _, roleName := range aggregateRoles { + role, err := am.GetWorkspaceRole("", roleName) + if err != nil { + klog.Error(err) + return nil, err + } + workspaceRole.Rules = append(workspaceRole.Rules, role.Rules...) + } + } + } + + data, err := json.Marshal(workspaceRole) + if err != nil { + return nil, err + } + + return am.ksclient.IamV1alpha2().WorkspaceRoles().Patch(workspaceRole.Name, types.MergePatchType, data) +} + +func (am *amOperator) PatchNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error) { + old, err := am.GetNamespaceRole(namespace, role.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + // rules cannot be override + role.Rules = old.Rules + + // aggregate roles if annotation has change + if aggregateRolesAnnotation := role.Annotations[iamv1alpha2.AggregationRolesAnnotation]; aggregateRolesAnnotation != "" { + role.Rules = make([]rbacv1.PolicyRule, 0) + var aggregateRoles []string + if err := json.Unmarshal([]byte(aggregateRolesAnnotation), &aggregateRoles); err == nil { + for _, roleName := range aggregateRoles { + role, err := am.GetNamespaceRole(namespace, roleName) + if err != nil { + klog.Error(err) + return nil, err + } + role.Rules = append(role.Rules, role.Rules...) + } + } + } + + data, err := json.Marshal(role) + if err != nil { + return nil, err + } + + return am.k8sclient.RbacV1().Roles(namespace).Patch(role.Name, types.MergePatchType, data) +} + +func (am *amOperator) PatchClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) { + old, err := am.GetClusterRole(clusterRole.Name) + if err != nil { + klog.Error(err) + return nil, err + } + + // rules cannot be override + clusterRole.Rules = old.Rules + + // aggregate roles if annotation has change + if aggregateRolesAnnotation := clusterRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]; aggregateRolesAnnotation != "" { + clusterRole.Rules = make([]rbacv1.PolicyRule, 0) + var aggregateRoles []string + if err := json.Unmarshal([]byte(aggregateRolesAnnotation), &aggregateRoles); err == nil { + for _, roleName := range aggregateRoles { + role, err := am.GetClusterRole(roleName) + if err != nil { + klog.Error(err) + return nil, err + } + role.Rules = append(role.Rules, role.Rules...) + } + } + } + + data, err := json.Marshal(clusterRole) + if err != nil { + return nil, err + } + + return am.k8sclient.RbacV1().ClusterRoles().Patch(clusterRole.Name, types.MergePatchType, data) +} + func (am *amOperator) CreateWorkspaceRoleBinding(username string, workspace string, role string) error { - _, err := am.GetWorkspaceRole(workspace, role) - if err != nil { klog.Error(err) return err } roleBindings, err := am.ListWorkspaceRoleBindings(username, workspace) - if err != nil { klog.Error(err) return err @@ -482,9 +616,7 @@ func (am *amOperator) CreateWorkspaceRoleBinding(username string, workspace stri } func (am *amOperator) CreateClusterRoleBinding(username string, role string) error { - _, err := am.GetClusterRole(role) - if err != nil { klog.Error(err) return err @@ -540,14 +672,12 @@ func (am *amOperator) CreateClusterRoleBinding(username string, role string) err func (am *amOperator) CreateNamespaceRoleBinding(username string, namespace string, role string) error { _, err := am.GetNamespaceRole(namespace, role) - if err != nil { klog.Error(err) return err } roleBindings, err := am.ListRoleBindings(username, namespace) - if err != nil { klog.Error(err) return err @@ -596,7 +726,6 @@ func (am *amOperator) CreateNamespaceRoleBinding(username string, namespace stri func (am *amOperator) RemoveUserFromWorkspace(username string, workspace string) error { roleBindings, err := am.ListWorkspaceRoleBindings(username, workspace) - if err != nil { klog.Error(err) return err @@ -619,7 +748,6 @@ func (am *amOperator) RemoveUserFromWorkspace(username string, workspace string) func (am *amOperator) RemoveUserFromNamespace(username string, namespace string) error { roleBindings, err := am.ListRoleBindings(username, namespace) - if err != nil { klog.Error(err) return err @@ -640,9 +768,7 @@ func (am *amOperator) RemoveUserFromNamespace(username string, namespace string) } func (am *amOperator) RemoveUserFromCluster(username string) error { - roleBindings, err := am.ListClusterRoleBindings(username) - if err != nil { klog.Error(err) return err @@ -663,28 +789,22 @@ func (am *amOperator) RemoveUserFromCluster(username string) error { } func (am *amOperator) CreateOrUpdateGlobalRole(globalRole *iamv1alpha2.GlobalRole) (*iamv1alpha2.GlobalRole, error) { - globalRole.Rules = make([]rbacv1.PolicyRule, 0) var aggregateRoles []string if err := json.Unmarshal([]byte(globalRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]), &aggregateRoles); err == nil { - for _, roleName := range aggregateRoles { - role, err := am.GetGlobalRole(roleName) - if err != nil { klog.Error(err) return nil, err } - globalRole.Rules = append(globalRole.Rules, role.Rules...) } } var created *iamv1alpha2.GlobalRole var err error - if globalRole.ResourceVersion != "" { created, err = am.ksclient.IamV1alpha2().GlobalRoles().Update(globalRole) } else { @@ -695,21 +815,15 @@ func (am *amOperator) CreateOrUpdateGlobalRole(globalRole *iamv1alpha2.GlobalRol } func (am *amOperator) CreateOrUpdateClusterRole(clusterRole *rbacv1.ClusterRole) (*rbacv1.ClusterRole, error) { - clusterRole.Rules = make([]rbacv1.PolicyRule, 0) - var aggregateRoles []string if err := json.Unmarshal([]byte(clusterRole.Annotations[iamv1alpha2.AggregationRolesAnnotation]), &aggregateRoles); err == nil { - for _, roleName := range aggregateRoles { - role, err := am.GetClusterRole(roleName) - if err != nil { klog.Error(err) return nil, err } - clusterRole.Rules = append(clusterRole.Rules, role.Rules...) } } @@ -720,30 +834,24 @@ func (am *amOperator) CreateOrUpdateClusterRole(clusterRole *rbacv1.ClusterRole) } else { created, err = am.k8sclient.RbacV1().ClusterRoles().Create(clusterRole) } - return created, err } func (am *amOperator) CreateOrUpdateNamespaceRole(namespace string, role *rbacv1.Role) (*rbacv1.Role, error) { - role.Rules = make([]rbacv1.PolicyRule, 0) role.Namespace = namespace - var aggregateRoles []string if err := json.Unmarshal([]byte(role.Annotations[iamv1alpha2.AggregationRolesAnnotation]), &aggregateRoles); err == nil { - for _, roleName := range aggregateRoles { - role, err := am.GetNamespaceRole(namespace, roleName) - if err != nil { klog.Error(err) return nil, err } - role.Rules = append(role.Rules, role.Rules...) } } + var created *rbacv1.Role var err error if role.ResourceVersion != "" {