diff --git a/docs/apidoc/swagger.json b/docs/apidoc/swagger.json index a628c1761..7cdbb3d3c 100644 --- a/docs/apidoc/swagger.json +++ b/docs/apidoc/swagger.json @@ -3910,7 +3910,7 @@ "parameters": [ { "type": "string", - "description": "identifier of the application ", + "description": "identifier of the environment", "name": "envName", "in": "path", "required": true @@ -3950,7 +3950,7 @@ "parameters": [ { "type": "string", - "description": "identifier of the application ", + "description": "identifier of the environment", "name": "envName", "in": "path", "required": true @@ -4013,7 +4013,7 @@ "tags": [ "rbac" ], - "summary": "list all project level perm policies", + "summary": "list all platform level perm policies", "operationId": "listPlatformPermissions", "responses": { "200": { @@ -4026,6 +4026,73 @@ } } } + }, + "post": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "rbac" + ], + "summary": "create the platform perm policy", + "operationId": "createPlatformPermission", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.CreatePermissionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.PermissionBase" + } + } + } + } + }, + "/api/v1/permissions/{permissionName}": { + "delete": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "rbac" + ], + "summary": "delete a platform perm policy", + "operationId": "deletePlatformPermission", + "parameters": [ + { + "type": "string", + "description": "identifier of the permission", + "name": "permissionName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.EmptyResponse" + } + } + } } }, "/api/v1/projects": { @@ -4276,6 +4343,85 @@ } } } + }, + "post": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "project" + ], + "summary": "create a project level perm policy", + "operationId": "createProjectPermission", + "parameters": [ + { + "type": "string", + "description": "identifier of the project", + "name": "projectName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.PermissionBase" + } + } + } + } + } + }, + "/api/v1/projects/{projectName}/permissions/{permissionName}": { + "delete": { + "consumes": [ + "application/xml", + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "tags": [ + "project" + ], + "summary": "delete a project level perm policy", + "operationId": "deleteProjectPermission", + "parameters": [ + { + "type": "string", + "description": "identifier of the project", + "name": "projectName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "identifier of the permission", + "name": "permissionName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.PermissionBase" + } + } + } + } } }, "/api/v1/projects/{projectName}/roles": { @@ -4990,6 +5136,13 @@ "summary": "update platform level role", "operationId": "updatePlatformRole", "parameters": [ + { + "type": "string", + "description": "identifier of the role", + "name": "roleName", + "in": "path", + "required": true + }, { "name": "body", "in": "body", @@ -5022,6 +5175,15 @@ ], "summary": "update platform level role", "operationId": "deletePlatformRole", + "parameters": [ + { + "type": "string", + "description": "identifier of the role", + "name": "roleName", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", @@ -6456,20 +6618,34 @@ } } }, - "common.SubStepsStatus": { + "common.StepStatus": { + "required": [ + "id" + ], "properties": { - "mode": { + "firstExecuteTime": { "type": "string" }, - "stepIndex": { - "type": "integer", - "format": "int32" + "id": { + "type": "string" }, - "steps": { - "type": "array", - "items": { - "$ref": "#/definitions/common.WorkflowSubStepStatus" - } + "lastExecuteTime": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" } } }, @@ -6571,7 +6747,45 @@ "type": "string" }, "subSteps": { - "$ref": "#/definitions/common.SubStepsStatus" + "type": "array", + "items": { + "$ref": "#/definitions/common.WorkflowSubStepStatus" + } + }, + "type": { + "type": "string" + } + } + }, + "common.WorkflowSubStep": { + "required": [ + "name", + "type" + ], + "properties": { + "dependsOn": { + "type": "array", + "items": { + "type": "string" + } + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/definitions/common.inputItem" + } + }, + "name": { + "type": "string" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/definitions/common.outputItem" + } + }, + "properties": { + "type": "string" }, "type": { "type": "string" @@ -6583,9 +6797,15 @@ "id" ], "properties": { + "firstExecuteTime": { + "type": "string" + }, "id": { "type": "string" }, + "lastExecuteTime": { + "type": "string" + }, "message": { "type": "string" }, @@ -6897,8 +7117,8 @@ }, "model.Cluster": { "required": [ - "createTime", "updateTime", + "createTime", "name", "alias", "description", @@ -7449,8 +7669,8 @@ }, "v1.AddonStatusResponse": { "required": [ - "name", "phase", + "name", "args" ], "properties": { @@ -7606,12 +7826,12 @@ }, "v1.ApplicationDeployResponse": { "required": [ + "status", "note", - "envName", "triggerType", "createTime", "version", - "status" + "envName" ], "properties": { "codeInfo": { @@ -8580,6 +8800,38 @@ } } }, + "v1.CreatePermissionRequest": { + "required": [ + "name", + "alias", + "resources", + "actions", + "effect" + ], + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "string" + }, + "effect": { + "type": "string" + }, + "name": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "v1.CreatePolicyRequest": { "required": [ "name", @@ -8776,11 +9028,11 @@ }, "v1.DetailAddonResponse": { "required": [ - "name", - "version", - "description", - "icon", "invisible", + "name", + "description", + "version", + "icon", "schema", "uiSchema", "definitions", @@ -8860,13 +9112,13 @@ }, "v1.DetailApplicationResponse": { "required": [ + "name", "project", "description", - "createTime", "icon", "alias", + "createTime", "updateTime", - "name", "policies", "envBindings", "resourceInfo" @@ -8923,20 +9175,20 @@ }, "v1.DetailClusterResponse": { "required": [ - "kubeConfig", "name", "description", - "apiServerURL", + "createTime", + "icon", "status", "reason", - "icon", "provider", - "kubeConfigSecret", - "labels", - "dashboardURL", - "createTime", - "updateTime", + "apiServerURL", "alias", + "dashboardURL", + "updateTime", + "labels", + "kubeConfig", + "kubeConfigSecret", "resourceInfo" ], "properties": { @@ -8994,14 +9246,14 @@ }, "v1.DetailComponentResponse": { "required": [ + "creator", + "appPrimaryKey", "alias", - "updateTime", - "name", "type", "main", "createTime", - "appPrimaryKey", - "creator", + "updateTime", + "name", "definition" ], "properties": { @@ -9089,11 +9341,11 @@ }, "v1.DetailDefinitionResponse": { "required": [ - "name", - "status", - "labels", "alias", "description", + "status", + "labels", + "name", "icon", "schema", "uiSchema" @@ -9148,14 +9400,14 @@ }, "v1.DetailPolicyResponse": { "required": [ - "creator", - "properties", "createTime", "updateTime", "envName", "name", "type", - "description" + "description", + "creator", + "properties" ], "properties": { "createTime": { @@ -9188,16 +9440,16 @@ }, "v1.DetailRevisionResponse": { "required": [ - "updateTime", - "note", - "triggerType", - "reason", - "envName", "createTime", - "deployUser", + "updateTime", + "reason", "appPrimaryKey", "version", "status", + "envName", + "deployUser", + "note", + "triggerType", "workflowName" ], "properties": { @@ -9252,10 +9504,10 @@ }, "v1.DetailTargetResponse": { "required": [ - "name", "createTime", + "updateTime", "project", - "updateTime" + "name" ], "properties": { "alias": { @@ -9295,11 +9547,11 @@ }, "v1.DetailUserResponse": { "required": [ + "disabled", "createTime", "lastLoginTime", "name", "email", - "disabled", "projects", "roles" ], @@ -9340,12 +9592,12 @@ }, "v1.DetailWorkflowRecordResponse": { "required": [ - "namespace", - "workflowName", - "workflowAlias", "applicationRevision", "status", "name", + "namespace", + "workflowName", + "workflowAlias", "deployTime", "deployUser", "note", @@ -9397,14 +9649,14 @@ }, "v1.DetailWorkflowResponse": { "required": [ - "createTime", "alias", - "description", "enable", - "updateTime", + "createTime", "name", "default", - "envName" + "envName", + "updateTime", + "description" ], "properties": { "alias": { @@ -9599,8 +9851,8 @@ }, "v1.EnvBindingTarget": { "required": [ - "name", - "alias" + "alias", + "name" ], "properties": { "alias": { @@ -9983,11 +10235,11 @@ }, "v1.LoginUserInfoResponse": { "required": [ + "createTime", "lastLoginTime", "name", "email", "disabled", - "createTime", "projects", "platformPermissions", "projectPermissions" @@ -10992,6 +11244,12 @@ "properties": { "type": "string" }, + "subSteps": { + "type": "array", + "items": { + "$ref": "#/definitions/common.WorkflowSubStep" + } + }, "type": { "type": "string" } @@ -11006,22 +11264,6 @@ "$ref": "#/definitions/common.Schematic" } } - }, - "v1beta2.BaseConfigurationSpec": { - "properties": { - "customRegion": { - "type": "string" - }, - "deleteResource": { - "type": "boolean" - }, - "providerRef": { - "$ref": "#/definitions/types.Reference" - }, - "writeConnectionSecretToRef": { - "$ref": "#/definitions/types.SecretReference" - } - } } } } \ No newline at end of file diff --git a/pkg/apiserver/domain/repository/grpc.go b/pkg/apiserver/domain/repository/grpc.go new file mode 100644 index 000000000..4dfb8e540 --- /dev/null +++ b/pkg/apiserver/domain/repository/grpc.go @@ -0,0 +1,54 @@ +/* +Copyright 2022 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package repository + +import ( + "context" + + "github.com/oam-dev/kubevela/pkg/apiserver/domain/model" + "github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" +) + +// ListRoles list roles from store +func ListRoles(ctx context.Context, store datastore.DataStore, projectName string, page, pageSize int) ([]*model.Role, int64, error) { + var role = model.Role{ + Project: projectName, + } + var filter datastore.FilterOptions + if projectName == "" { + filter.IsNotExist = append(filter.IsNotExist, datastore.IsNotExistQueryOption{ + Key: "project", + }) + } + entities, err := store.List(ctx, &role, &datastore.ListOptions{FilterOptions: filter, Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}}) + if err != nil { + return nil, 0, err + } + var roles []*model.Role + for i := range entities { + roles = append(roles, entities[i].(*model.Role)) + } + count := int64(len(roles)) + if page > 0 && pageSize > 0 { + var err error + count, err = store.Count(ctx, &role, &filter) + if err != nil { + return nil, 0, err + } + } + return roles, count, nil +} diff --git a/pkg/apiserver/domain/service/rbac.go b/pkg/apiserver/domain/service/rbac.go index 359064edd..581a3b20e 100644 --- a/pkg/apiserver/domain/service/rbac.go +++ b/pkg/apiserver/domain/service/rbac.go @@ -27,7 +27,9 @@ import ( "github.com/emicklei/go-restful/v3" "github.com/oam-dev/kubevela/pkg/apiserver/domain/model" + "github.com/oam-dev/kubevela/pkg/apiserver/domain/repository" "github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore" + assembler "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/assembler/v1" apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1" apiserverutils "github.com/oam-dev/kubevela/pkg/apiserver/utils" "github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode" @@ -52,7 +54,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{ { Name: "app-management", Alias: "App Management", - Resources: []string{"project:{projectName}/application:*/*", "definition:list", "definition:detail"}, + Resources: []string{"project:{projectName}/application:*/*"}, Actions: []string{"*"}, Effect: "Allow", Scope: "project", @@ -135,7 +137,7 @@ var defaultPlatformPermission = []*model.PermissionTemplate{ { Name: "integration-management", Alias: "Integration Management", - Resources: []string{"config:*", "configType:*"}, + Resources: []string{"configType:*/*"}, Actions: []string{"*"}, Effect: "Allow", Scope: "platform", @@ -222,8 +224,10 @@ var ResourceMaps = map[string]resourceMetadata{ "user": { pathName: "userName", }, - "role": {}, - "permission": {}, + "role": {}, + "permission": { + pathName: "permissionName", + }, "systemSetting": {}, "definition": { pathName: "definitionName", @@ -342,6 +346,7 @@ type RBACService interface { ListRole(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListRolesResponse, error) ListPermissionTemplate(ctx context.Context, projectName string) ([]apisv1.PermissionTemplateBase, error) ListPermissions(ctx context.Context, projectName string) ([]apisv1.PermissionBase, error) + CreatePermission(ctx context.Context, projectName string, req apisv1.CreatePermissionRequest) (*apisv1.PermissionBase, error) DeletePermission(ctx context.Context, projectName, permName string) error InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error Init(ctx context.Context) error @@ -446,9 +451,9 @@ func (p *rbacServiceImpl) GetUserPermissions(ctx context.Context, user *model.Us return perms, nil } -func (p *rbacServiceImpl) UpdatePermission(ctx context.Context, projetName string, permissionName string, req *apisv1.UpdatePermissionRequest) (*apisv1.PermissionBase, error) { +func (p *rbacServiceImpl) UpdatePermission(ctx context.Context, projectName string, permissionName string, req *apisv1.UpdatePermissionRequest) (*apisv1.PermissionBase, error) { perm := &model.Permission{ - Project: projetName, + Project: projectName, Name: permissionName, } err := p.Store.Get(ctx, perm) @@ -556,6 +561,7 @@ func (p *rbacServiceImpl) CheckPerm(resource string, actions ...string) func(req } return req.PathParameter(name) }) + ra.SetActions(actions) // get user's perm list. projectName := getProjectName() @@ -603,7 +609,7 @@ func (p *rbacServiceImpl) CreateRole(ctx context.Context, projectName string, re } return nil, err } - return ConvertRole2Model(&role, policies), nil + return assembler.ConvertRole2DTO(&role, policies), nil } func (p *rbacServiceImpl) DeleteRole(ctx context.Context, projectName, roleName string) error { @@ -621,6 +627,19 @@ func (p *rbacServiceImpl) DeleteRole(ctx context.Context, projectName, roleName } func (p *rbacServiceImpl) DeletePermission(ctx context.Context, projectName, permName string) error { + roles, _, err := repository.ListRoles(ctx, p.Store, projectName, 0, 0) + if err != nil { + log.Logger.Errorf("fail to list the roles: %s", err.Error()) + return bcode.ErrPermissionIsUsed + } + for _, role := range roles { + for _, p := range role.Permissions { + if p == permName { + return bcode.ErrPermissionIsUsed + } + } + } + var perm = model.Permission{ Name: permName, Project: projectName, @@ -665,26 +684,17 @@ func (p *rbacServiceImpl) UpdateRole(ctx context.Context, projectName, roleName if err := p.Store.Put(ctx, &role); err != nil { return nil, err } - return ConvertRole2Model(&role, policies), nil + return assembler.ConvertRole2DTO(&role, policies), nil } func (p *rbacServiceImpl) ListRole(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListRolesResponse, error) { - var role = model.Role{ - Project: projectName, - } - var filter datastore.FilterOptions - if projectName == "" { - filter.IsNotExist = append(filter.IsNotExist, datastore.IsNotExistQueryOption{ - Key: "project", - }) - } - entities, err := p.Store.List(ctx, &role, &datastore.ListOptions{FilterOptions: filter, Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}}) + roles, count, err := repository.ListRoles(ctx, p.Store, projectName, 0, 0) if err != nil { return nil, err } var policySet = make(map[string]string) - for _, entity := range entities { - for _, p := range entity.(*model.Role).Permissions { + for _, role := range roles { + for _, p := range role.Permissions { policySet[p] = p } } @@ -698,17 +708,12 @@ func (p *rbacServiceImpl) ListRole(ctx context.Context, projectName string, page policyMap[policy.Name] = policies[i] } var res apisv1.ListRolesResponse - for _, entity := range entities { - role := entity.(*model.Role) + for _, role := range roles { var rolePolicies []*model.Permission for _, perm := range role.Permissions { rolePolicies = append(rolePolicies, policyMap[perm]) } - res.Roles = append(res.Roles, ConvertRole2Model(entity.(*model.Role), rolePolicies)) - } - count, err := p.Store.Count(ctx, &role, &filter) - if err != nil { - return nil, err + res.Roles = append(res.Roles, assembler.ConvertRole2DTO(role, rolePolicies)) } res.Total = count return &res, nil @@ -746,11 +751,50 @@ func (p *rbacServiceImpl) ListPermissions(ctx context.Context, projectName strin return perms, nil } +func (p *rbacServiceImpl) CreatePermission(ctx context.Context, projectName string, req apisv1.CreatePermissionRequest) (*apisv1.PermissionBase, error) { + if projectName != "" { + var project = model.Project{ + Name: projectName, + } + if err := p.Store.Get(ctx, &project); err != nil { + return nil, bcode.ErrProjectIsNotExist + } + } + if len(req.Resources) == 0 { + return nil, bcode.ErrRolePermissionCheckFailure + } + + if len(req.Actions) == 0 { + req.Actions = []string{"*"} + } + + if req.Effect == "" { + req.Effect = "Allow" + } + + var permission = model.Permission{ + Name: req.Name, + Alias: req.Alias, + Project: projectName, + Resources: req.Resources, + Actions: req.Actions, + Effect: req.Effect, + } + + if err := p.Store.Add(ctx, &permission); err != nil { + if errors.Is(err, datastore.ErrRecordExist) { + return nil, bcode.ErrPermissionIsExist + } + return nil, err + } + return assembler.ConvertPermission2DTO(&permission), nil +} + func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error { var batchData []datastore.Entity for _, permissionTemp := range defaultProjectPermissionTemplate { var rra = RequestResourceAction{} - var formatedResource []string + var formattedResource []string for _, resource := range permissionTemp.Resources { rra.SetResourceWithName(resource, func(name string) string { if name == ResourceMaps["project"].pathName { @@ -758,13 +802,13 @@ func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, } return "" }) - formatedResource = append(formatedResource, rra.GetResource().String()) + formattedResource = append(formattedResource, rra.GetResource().String()) } batchData = append(batchData, &model.Permission{ Name: permissionTemp.Name, Alias: permissionTemp.Alias, Project: project.Name, - Resources: formatedResource, + Resources: formattedResource, Actions: permissionTemp.Actions, Effect: permissionTemp.Effect, }) @@ -791,24 +835,6 @@ func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, return p.Store.BatchAdd(ctx, batchData) } -// ConvertRole2Model convert role model to role base struct -func ConvertRole2Model(role *model.Role, policies []*model.Permission) *apisv1.RoleBase { - return &apisv1.RoleBase{ - CreateTime: role.CreateTime, - UpdateTime: role.UpdateTime, - Name: role.Name, - Alias: role.Alias, - Permissions: func() (list []apisv1.NameAlias) { - for _, policy := range policies { - if policy != nil { - list = append(list, apisv1.NameAlias{Name: policy.Name, Alias: policy.Alias}) - } - } - return - }(), - } -} - // ResourceName it is similar to ARNs // :/: type ResourceName struct { diff --git a/pkg/apiserver/domain/service/rbac_test.go b/pkg/apiserver/domain/service/rbac_test.go index 9c802784c..5590959ba 100644 --- a/pkg/apiserver/domain/service/rbac_test.go +++ b/pkg/apiserver/domain/service/rbac_test.go @@ -51,6 +51,10 @@ var _ = Describe("Test rbac service", func() { Expect(err).Should(BeNil()) Expect(path).Should(BeEquivalentTo("project:{projectName}/application:{appName}")) + path, err = checkResourcePath("environment") + Expect(err).Should(BeNil()) + Expect(path).Should(BeEquivalentTo("project:{projectName}/environment:{envName}")) + _, err = checkResourcePath("applications") Expect(err).ShouldNot(BeNil()) @@ -263,6 +267,21 @@ func TestRequestResourceActionMatch(t *testing.T) { ra5.SetActions([]string{"list"}) assert.Equal(t, ra5.Match([]*model.Permission{{Resources: []string{"project:*/application:*"}, Actions: []string{"list"}, Effect: "Allow"}}), true) + ra6 := &RequestResourceAction{} + path, err := checkResourcePath("environment") + assert.Equal(t, err, nil) + ra6.SetResourceWithName(path, func(name string) string { + if name == "projectName" { + return "default" + } + return "" + }) + ra6.SetActions([]string{"create"}) + assert.Equal(t, ra6.Match([]*model.Permission{{Resources: []string{ + "project:*/*", "addon:* addonRegistry:*", "target:*", "cluster:*/namespace:*", "user:*", "role:*", "permission:*", "configType:*/*", "project:*", + "project:default/config:*", "project:default/role:*", "project:default/projectUser:*", "project:default/permission:*", "project:default/environment:*", "project:default/application:*/*", "project:default", + }, Actions: []string{"list", "detail"}, Effect: "Allow"}}), false) + } func TestRegisterResourceAction(t *testing.T) { diff --git a/pkg/apiserver/interfaces/api/assembler/v1/do2dto.go b/pkg/apiserver/interfaces/api/assembler/v1/do2dto.go index 3093d136e..79358ac44 100644 --- a/pkg/apiserver/interfaces/api/assembler/v1/do2dto.go +++ b/pkg/apiserver/interfaces/api/assembler/v1/do2dto.go @@ -201,6 +201,40 @@ func ConvertPolicyModelToBase(policy *model.ApplicationPolicy) *apisv1.PolicyBas return pb } +// ConvertRole2DTO convert role model to role base struct +func ConvertRole2DTO(role *model.Role, policies []*model.Permission) *apisv1.RoleBase { + return &apisv1.RoleBase{ + CreateTime: role.CreateTime, + UpdateTime: role.UpdateTime, + Name: role.Name, + Alias: role.Alias, + Permissions: func() (list []apisv1.NameAlias) { + for _, policy := range policies { + if policy != nil { + list = append(list, apisv1.NameAlias{Name: policy.Name, Alias: policy.Alias}) + } + } + return + }(), + } +} + +// ConvertPermission2DTO convert permission model to the DTO +func ConvertPermission2DTO(permission *model.Permission) *apisv1.PermissionBase { + if permission == nil { + return nil + } + return &apisv1.PermissionBase{ + Name: permission.Name, + Alias: permission.Alias, + Resources: permission.Resources, + Actions: permission.Actions, + Effect: permission.Effect, + CreateTime: permission.CreateTime, + UpdateTime: permission.UpdateTime, + } +} + func convertBool(b *bool) bool { if b == nil { return false diff --git a/pkg/apiserver/interfaces/api/dto/v1/types.go b/pkg/apiserver/interfaces/api/dto/v1/types.go index b0a2924b1..a334c6447 100644 --- a/pkg/apiserver/interfaces/api/dto/v1/types.go +++ b/pkg/apiserver/interfaces/api/dto/v1/types.go @@ -1345,7 +1345,7 @@ type PermissionBase struct { UpdateTime time.Time `json:"updateTime"` } -// UpdatePermissionRequest the request body that update permission policy +// UpdatePermissionRequest the request body that updating a permission policy type UpdatePermissionRequest struct { Alias string `json:"alias" validate:"checkalias"` Resources []string `json:"resources"` @@ -1353,6 +1353,15 @@ type UpdatePermissionRequest struct { Effect string `json:"effect" validate:"oneof=Allow Deny"` } +// CreatePermissionRequest the request body that creating a permission policy +type CreatePermissionRequest struct { + Name string `json:"name" validate:"checkname"` + Alias string `json:"alias" validate:"checkalias"` + Resources []string `json:"resources"` + Actions []string `json:"actions"` + Effect string `json:"effect" validate:"oneof=Allow Deny"` +} + // LoginUserInfoResponse the response body of login user info type LoginUserInfoResponse struct { UserBase diff --git a/pkg/apiserver/interfaces/api/env.go b/pkg/apiserver/interfaces/api/env.go index 7b24b9619..1b55f6d19 100644 --- a/pkg/apiserver/interfaces/api/env.go +++ b/pkg/apiserver/interfaces/api/env.go @@ -70,7 +70,7 @@ func (n *envAPIInterface) GetWebServiceRoute() *restful.WebService { Doc("update an env"). Metadata(restfulspec.KeyOpenAPITags, tags). Filter(n.RBACService.CheckPerm("environment", "update")). - Param(ws.PathParameter("envName", "identifier of the application ").DataType("string")). + Param(ws.PathParameter("envName", "identifier of the environment").DataType("string")). Reads(apis.CreateEnvRequest{}). Returns(200, "OK", apis.Env{}). Writes(apis.Env{})) @@ -80,7 +80,7 @@ func (n *envAPIInterface) GetWebServiceRoute() *restful.WebService { Doc("delete one env"). Metadata(restfulspec.KeyOpenAPITags, tags). Filter(n.RBACService.CheckPerm("environment", "delete")). - Param(ws.PathParameter("envName", "identifier of the application ").DataType("string")). + Param(ws.PathParameter("envName", "identifier of the environment").DataType("string")). Returns(200, "OK", apis.EmptyResponse{}). Returns(400, "Bad Request", bcode.Bcode{}). Writes(apis.EmptyResponse{})) diff --git a/pkg/apiserver/interfaces/api/project.go b/pkg/apiserver/interfaces/api/project.go index df31fa932..b56c3a36c 100644 --- a/pkg/apiserver/interfaces/api/project.go +++ b/pkg/apiserver/interfaces/api/project.go @@ -176,6 +176,23 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService { Returns(200, "OK", []apis.PermissionBase{}). Writes([]apis.PermissionBase{})) + ws.Route(ws.POST("/{projectName}/permissions").To(n.createProjectPermission). + Doc("create a project level perm policy"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")). + Filter(n.RbacService.CheckPerm("project/permission", "list")). + Returns(200, "OK", []apis.PermissionBase{}). + Writes([]apis.PermissionBase{})) + + ws.Route(ws.DELETE("/{projectName}/permissions/{permissionName}").To(n.deleteProjectPermission). + Doc("delete a project level perm policy"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")). + Param(ws.PathParameter("permissionName", "identifier of the permission").DataType("string")). + Filter(n.RbacService.CheckPerm("project/permission", "list")). + Returns(200, "OK", []apis.PermissionBase{}). + Writes([]apis.PermissionBase{})) + ws.Route(ws.GET("/{projectName}/configs").To(n.getConfigs). Doc("get configs which are in a project"). Metadata(restfulspec.KeyOpenAPITags, tags). @@ -524,6 +541,45 @@ func (n *projectAPIInterface) listProjectPermissions(req *restful.Request, res * } } +func (n *projectAPIInterface) createProjectPermission(req *restful.Request, res *restful.Response) { + // Verify the validity of parameters + var createReq apis.CreatePermissionRequest + if err := req.ReadEntity(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := validate.Struct(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + // Call the domain layer code + permissionBase, err := n.RbacService.CreatePermission(req.Request.Context(), req.PathParameter("projectName"), createReq) + if err != nil { + log.Logger.Errorf("create the permission failure %s", err.Error()) + bcode.ReturnError(req, res, err) + return + } + + // Write back response data + if err := res.WriteEntity(permissionBase); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + +func (n *projectAPIInterface) deleteProjectPermission(req *restful.Request, res *restful.Response) { + err := n.RbacService.DeletePermission(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("permissionName")) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + // Write back response data + if err := res.WriteEntity(apis.EmptyResponse{}); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Response) { configs, err := n.ProjectService.GetConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("configType")) if err != nil { diff --git a/pkg/apiserver/interfaces/api/rbac.go b/pkg/apiserver/interfaces/api/rbac.go index fa7961b1f..5a81e7a14 100644 --- a/pkg/apiserver/interfaces/api/rbac.go +++ b/pkg/apiserver/interfaces/api/rbac.go @@ -63,6 +63,7 @@ func (r *rbacAPIInterface) GetWebServiceRoute() *restful.WebService { ws.Route(ws.PUT("/roles/{roleName}").To(r.updatePlatformRole). Doc("update platform level role"). Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("roleName", "identifier of the role").DataType("string")). Filter(r.RbacService.CheckPerm("role", "update")). Reads(apis.UpdateRoleRequest{}). Returns(200, "OK", apis.RoleBase{}). @@ -71,17 +72,34 @@ func (r *rbacAPIInterface) GetWebServiceRoute() *restful.WebService { ws.Route(ws.DELETE("/roles/{roleName}").To(r.deletePlatformRole). Doc("update platform level role"). Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("roleName", "identifier of the role").DataType("string")). Filter(r.RbacService.CheckPerm("role", "delete")). Returns(200, "OK", apis.EmptyResponse{}). Writes(apis.EmptyResponse{})) ws.Route(ws.GET("/permissions").To(r.listPlatformPermissions). - Doc("list all project level perm policies"). + Doc("list all platform level perm policies"). Metadata(restfulspec.KeyOpenAPITags, tags). Filter(r.RbacService.CheckPerm("permission", "list")). Returns(200, "OK", []apis.PermissionBase{}). Writes([]apis.PermissionBase{})) + ws.Route(ws.POST("/permissions").To(r.createPlatformPermission). + Doc("create the platform perm policy"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(apis.CreatePermissionRequest{}). + Filter(r.RbacService.CheckPerm("permission", "create")). + Returns(200, "OK", apis.PermissionBase{}). + Writes(apis.PermissionBase{})) + + ws.Route(ws.DELETE("/permissions/{permissionName}").To(r.deletePlatformPermission). + Doc("delete a platform perm policy"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("permissionName", "identifier of the permission").DataType("string")). + Filter(r.RbacService.CheckPerm("permission", "delete")). + Returns(200, "OK", apis.EmptyResponse{}). + Writes(apis.EmptyResponse{})) + ws.Filter(authCheckFilter) return ws } @@ -179,3 +197,42 @@ func (r *rbacAPIInterface) listPlatformPermissions(req *restful.Request, res *re return } } + +func (r *rbacAPIInterface) createPlatformPermission(req *restful.Request, res *restful.Response) { + // Verify the validity of parameters + var createReq apis.CreatePermissionRequest + if err := req.ReadEntity(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + if err := validate.Struct(&createReq); err != nil { + bcode.ReturnError(req, res, err) + return + } + // Call the domain layer code + permissionBase, err := r.RbacService.CreatePermission(req.Request.Context(), "", createReq) + if err != nil { + log.Logger.Errorf("create the permission failure %s", err.Error()) + bcode.ReturnError(req, res, err) + return + } + + // Write back response data + if err := res.WriteEntity(permissionBase); err != nil { + bcode.ReturnError(req, res, err) + return + } +} + +func (r *rbacAPIInterface) deletePlatformPermission(req *restful.Request, res *restful.Response) { + err := r.RbacService.DeletePermission(req.Request.Context(), "", req.PathParameter("permissionName")) + if err != nil { + bcode.ReturnError(req, res, err) + return + } + // Write back response data + if err := res.WriteEntity(apis.EmptyResponse{}); err != nil { + bcode.ReturnError(req, res, err) + return + } +} diff --git a/pkg/apiserver/utils/bcode/015_rbac.go b/pkg/apiserver/utils/bcode/015_rbac.go index 86d8adc3c..f77e3ce53 100644 --- a/pkg/apiserver/utils/bcode/015_rbac.go +++ b/pkg/apiserver/utils/bcode/015_rbac.go @@ -25,4 +25,8 @@ var ( ErrRoleIsNotExist = NewBcode(400, 15003, "the role is not exist") // ErrPermissionNotExist means the permission is not exist ErrPermissionNotExist = NewBcode(404, 15004, "the permission is not exist") + // ErrPermissionIsExist means the he permission is exist + ErrPermissionIsExist = NewBcode(400, 15005, "the permission name is exist") + // ErrPermissionIsUsed means the permission is bound by role, can not be deleted + ErrPermissionIsUsed = NewBcode(400, 15006, "the permission have been used") )