From dd63a8d175f8f0f905271cd373b584eb7ef7d53e Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 14 Dec 2021 17:07:55 +0800 Subject: [PATCH 01/12] feat(api/open): add subject groups query api --- docs/docs.go | 212 ++++++++++++++++++++-- docs/swagger.json | 202 ++++++++++++++++++++- docs/swagger.yaml | 162 +++++++++++++++-- pkg/api/open/handler/subject_group.go | 117 ++++++++++++ pkg/api/open/handler/subject_group_slz.go | 15 ++ pkg/api/open/router.go | 7 + 6 files changed, 667 insertions(+), 48 deletions(-) create mode 100644 pkg/api/open/handler/subject_group.go create mode 100644 pkg/api/open/handler/subject_group_slz.go diff --git a/docs/docs.go b/docs/docs.go index 4d9298a5..7274e075 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,13 +1,3 @@ -/* - * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. - * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at http://opensource.org/licenses/MIT - * 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. - */ - // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag @@ -120,23 +110,27 @@ var doc = `{ "operationId": "api-engine-policies-list", "parameters": [ { - "type": "integer", - "name": "timestamp", + "type": "string", + "example": "1,2,3", + "name": "ids", "in": "query" }, { "type": "integer", - "name": "min_id", + "example": 10001, + "name": "max_id", "in": "query" }, { "type": "integer", - "name": "max_id", + "example": 1, + "name": "min_id", "in": "query" }, { - "type": "string", - "name": "ids", + "type": "integer", + "example": 1592899208, + "name": "timestamp", "in": "query" } ], @@ -193,11 +187,13 @@ var doc = `{ "parameters": [ { "type": "integer", + "example": 1592899208, "name": "begin_updated_at", "in": "query" }, { "type": "integer", + "example": 1592899208, "name": "end_updated_at", "in": "query" } @@ -255,6 +251,7 @@ var doc = `{ "parameters": [ { "type": "integer", + "example": 1592899208, "name": "updated_at", "in": "query" } @@ -1444,22 +1441,26 @@ var doc = `{ }, { "type": "string", + "example": "edit_host", "name": "actionID", "in": "query", "required": true }, { "type": "integer", - "name": "pageSize", + "example": 1, + "name": "page", "in": "query" }, { "type": "integer", - "name": "page", + "example": 100, + "name": "pageSize", "in": "query" }, { "type": "integer", + "example": 1592899208, "name": "timestamp", "in": "query" } @@ -1524,6 +1525,7 @@ var doc = `{ }, { "type": "string", + "example": "1,2,3", "name": "ids", "in": "query", "required": true @@ -1929,6 +1931,86 @@ var doc = `{ } } }, + "/api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a subject's groups, include the inherit groups from department", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "subject group", + "operationId": "api-open-subject-groups-get", + "parameters": [ + { + "type": "string", + "description": "System ID", + "name": "system_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Subject Type", + "name": "subject_type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Subject ID", + "name": "subject_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "get subject's inherit groups from it's departments", + "name": "inherit", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v1/systems/{system_id}/token": { "get": { "security": [ @@ -2807,6 +2889,10 @@ var doc = `{ }, "handler.appCodeAppSecretSerializer": { "type": "object", + "required": [ + "app_code", + "app_secret" + ], "properties": { "app_code": { "type": "string" @@ -2974,6 +3060,10 @@ var doc = `{ }, "handler.credentialsVerifySerializer": { "type": "object", + "required": [ + "data", + "type" + ], "properties": { "data": { "type": "object", @@ -3279,6 +3369,7 @@ var doc = `{ "type": "string" }, "environment": { + "description": "NOTE: this field not used!", "type": "string" }, "expired_at": { @@ -3508,6 +3599,27 @@ var doc = `{ } } }, + "handler.referenceInstanceSelection": { + "type": "object", + "required": [ + "id", + "system_id" + ], + "properties": { + "id": { + "type": "string", + "example": "host_view" + }, + "ignore_iam_path": { + "type": "boolean", + "example": false + }, + "system_id": { + "type": "string", + "example": "bk_cmdb" + } + } + }, "handler.referenceResourceType": { "type": "object", "required": [ @@ -3525,6 +3637,40 @@ var doc = `{ } } }, + "handler.relatedResourceType": { + "type": "object", + "required": [ + "id", + "system_id" + ], + "properties": { + "id": { + "type": "string", + "example": "host" + }, + "name_alias": { + "type": "string" + }, + "name_alias_en": { + "type": "string" + }, + "related_instance_selections": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.referenceInstanceSelection" + } + }, + "selection_mode": { + "description": "实例选择方式/范围: [\"all\", \"instance\", \"attribute\"]", + "type": "string", + "example": "instance" + }, + "system_id": { + "type": "string", + "example": "bk_cmdb" + } + } + }, "handler.resource": { "type": "object", "required": [ @@ -3648,6 +3794,23 @@ var doc = `{ } } }, + "handler.responseSubject": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "admin" + }, + "name": { + "type": "string", + "example": "Administer" + }, + "type": { + "type": "string", + "example": "user" + } + } + }, "handler.subject": { "type": "object", "required": [ @@ -3656,13 +3819,21 @@ var doc = `{ ], "properties": { "id": { - "type": "string" + "type": "string", + "example": "admin" }, "type": { - "type": "string" + "type": "string", + "example": "user" } } }, + "handler.subjectGroupsResponse": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.responseSubject" + } + }, "handler.subjectTemplateSerializer": { "type": "object", "required": [ @@ -3887,6 +4058,7 @@ var doc = `{ "type": "string" }, "environment": { + "description": "NOTE: this field not used!", "type": "string" }, "expired_at": { diff --git a/docs/swagger.json b/docs/swagger.json index fb2ee8c0..33fc59e2 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -93,23 +93,27 @@ "operationId": "api-engine-policies-list", "parameters": [ { - "type": "integer", - "name": "timestamp", + "type": "string", + "example": "1,2,3", + "name": "ids", "in": "query" }, { "type": "integer", - "name": "min_id", + "example": 10001, + "name": "max_id", "in": "query" }, { "type": "integer", - "name": "max_id", + "example": 1, + "name": "min_id", "in": "query" }, { - "type": "string", - "name": "ids", + "type": "integer", + "example": 1592899208, + "name": "timestamp", "in": "query" } ], @@ -166,11 +170,13 @@ "parameters": [ { "type": "integer", + "example": 1592899208, "name": "begin_updated_at", "in": "query" }, { "type": "integer", + "example": 1592899208, "name": "end_updated_at", "in": "query" } @@ -228,6 +234,7 @@ "parameters": [ { "type": "integer", + "example": 1592899208, "name": "updated_at", "in": "query" } @@ -1417,22 +1424,26 @@ }, { "type": "string", + "example": "edit_host", "name": "actionID", "in": "query", "required": true }, { "type": "integer", - "name": "pageSize", + "example": 1, + "name": "page", "in": "query" }, { "type": "integer", - "name": "page", + "example": 100, + "name": "pageSize", "in": "query" }, { "type": "integer", + "example": 1592899208, "name": "timestamp", "in": "query" } @@ -1497,6 +1508,7 @@ }, { "type": "string", + "example": "1,2,3", "name": "ids", "in": "query", "required": true @@ -1902,6 +1914,86 @@ } } }, + "/api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a subject's groups, include the inherit groups from department", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "subject group", + "operationId": "api-open-subject-groups-get", + "parameters": [ + { + "type": "string", + "description": "System ID", + "name": "system_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Subject Type", + "name": "subject_type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Subject ID", + "name": "subject_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "get subject's inherit groups from it's departments", + "name": "inherit", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v1/systems/{system_id}/token": { "get": { "security": [ @@ -2780,6 +2872,10 @@ }, "handler.appCodeAppSecretSerializer": { "type": "object", + "required": [ + "app_code", + "app_secret" + ], "properties": { "app_code": { "type": "string" @@ -2947,6 +3043,10 @@ }, "handler.credentialsVerifySerializer": { "type": "object", + "required": [ + "data", + "type" + ], "properties": { "data": { "type": "object", @@ -3252,6 +3352,7 @@ "type": "string" }, "environment": { + "description": "NOTE: this field not used!", "type": "string" }, "expired_at": { @@ -3481,6 +3582,27 @@ } } }, + "handler.referenceInstanceSelection": { + "type": "object", + "required": [ + "id", + "system_id" + ], + "properties": { + "id": { + "type": "string", + "example": "host_view" + }, + "ignore_iam_path": { + "type": "boolean", + "example": false + }, + "system_id": { + "type": "string", + "example": "bk_cmdb" + } + } + }, "handler.referenceResourceType": { "type": "object", "required": [ @@ -3498,6 +3620,40 @@ } } }, + "handler.relatedResourceType": { + "type": "object", + "required": [ + "id", + "system_id" + ], + "properties": { + "id": { + "type": "string", + "example": "host" + }, + "name_alias": { + "type": "string" + }, + "name_alias_en": { + "type": "string" + }, + "related_instance_selections": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.referenceInstanceSelection" + } + }, + "selection_mode": { + "description": "实例选择方式/范围: [\"all\", \"instance\", \"attribute\"]", + "type": "string", + "example": "instance" + }, + "system_id": { + "type": "string", + "example": "bk_cmdb" + } + } + }, "handler.resource": { "type": "object", "required": [ @@ -3621,6 +3777,23 @@ } } }, + "handler.responseSubject": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "admin" + }, + "name": { + "type": "string", + "example": "Administer" + }, + "type": { + "type": "string", + "example": "user" + } + } + }, "handler.subject": { "type": "object", "required": [ @@ -3629,13 +3802,21 @@ ], "properties": { "id": { - "type": "string" + "type": "string", + "example": "admin" }, "type": { - "type": "string" + "type": "string", + "example": "user" } } }, + "handler.subjectGroupsResponse": { + "type": "array", + "items": { + "$ref": "#/definitions/handler.responseSubject" + } + }, "handler.subjectTemplateSerializer": { "type": "object", "required": [ @@ -3860,6 +4041,7 @@ "type": "string" }, "environment": { + "description": "NOTE: this field not used!", "type": "string" }, "expired_at": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 017f776e..80c09f21 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -122,6 +122,9 @@ definitions: type: string app_secret: type: string + required: + - app_code + - app_secret type: object handler.authByActionsRequest: properties: @@ -240,6 +243,9 @@ definitions: type: object type: type: string + required: + - data + - type type: object handler.deleteViaID: properties: @@ -443,6 +449,7 @@ definitions: action_id: type: string environment: + description: 'NOTE: this field not used!' type: string expired_at: type: integer @@ -608,6 +615,21 @@ definitions: example: base_info,resource_types,actions type: string type: object + handler.referenceInstanceSelection: + properties: + id: + example: host_view + type: string + ignore_iam_path: + example: false + type: boolean + system_id: + example: bk_cmdb + type: string + required: + - id + - system_id + type: object handler.referenceResourceType: properties: id: @@ -620,6 +642,30 @@ definitions: - id - system_id type: object + handler.relatedResourceType: + properties: + id: + example: host + type: string + name_alias: + type: string + name_alias_en: + type: string + related_instance_selections: + items: + $ref: '#/definitions/handler.referenceInstanceSelection' + type: array + selection_mode: + description: '实例选择方式/范围: ["all", "instance", "attribute"]' + example: instance + type: string + system_id: + example: bk_cmdb + type: string + required: + - id + - system_id + type: object handler.resource: properties: attribute: @@ -710,16 +756,34 @@ definitions: example: 1 type: integer type: object + handler.responseSubject: + properties: + id: + example: admin + type: string + name: + example: Administer + type: string + type: + example: user + type: string + type: object handler.subject: properties: id: + example: admin type: string type: + example: user type: string required: - id - type type: object + handler.subjectGroupsResponse: + items: + $ref: '#/definitions/handler.responseSubject' + type: array handler.subjectTemplateSerializer: properties: subject_id: @@ -876,6 +940,7 @@ definitions: action_id: type: string environment: + description: 'NOTE: this field not used!' type: string expired_at: type: integer @@ -997,18 +1062,22 @@ paths: description: list policies of ids or between min-max ids operationId: api-engine-policies-list parameters: - - in: query - name: timestamp + - example: 1,2,3 + in: query + name: ids + type: string + - example: 10001 + in: query + name: max_id type: integer - - in: query + - example: 1 + in: query name: min_id type: integer - - in: query - name: max_id + - example: 1592899208 + in: query + name: timestamp type: integer - - in: query - name: ids - type: string produces: - application/json responses: @@ -1038,10 +1107,12 @@ paths: description: list policy pks by condition operationId: api-engine-policy-id-list parameters: - - in: query + - example: 1592899208 + in: query name: begin_updated_at type: integer - - in: query + - example: 1592899208 + in: query name: end_updated_at type: integer produces: @@ -1073,7 +1144,8 @@ paths: description: get max policy id by condition operationId: api-engine-policy-id-max parameters: - - in: query + - example: 1592899208 + in: query name: updated_at type: integer produces: @@ -1804,17 +1876,21 @@ paths: name: system_id required: true type: string - - in: query + - example: edit_host + in: query name: actionID required: true type: string - - in: query - name: pageSize - type: integer - - in: query + - example: 1 + in: query name: page type: integer - - in: query + - example: 100 + in: query + name: pageSize + type: integer + - example: 1592899208 + in: query name: timestamp type: integer produces: @@ -1851,7 +1927,8 @@ paths: name: system_id required: true type: string - - in: query + - example: 1,2,3 + in: query name: ids required: true type: string @@ -2107,6 +2184,55 @@ paths: summary: resource type update tags: - model + /api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups: + get: + consumes: + - application/json + description: get a subject's groups, include the inherit groups from department + operationId: api-open-subject-groups-get + parameters: + - description: System ID + in: path + name: system_id + required: true + type: string + - description: Subject Type + in: path + name: subject_type + required: true + type: string + - description: Subject ID + in: path + name: subject_id + required: true + type: string + - description: get subject's inherit groups from it's departments + in: query + name: inherit + required: true + type: boolean + produces: + - application/json + responses: + "200": + description: OK + headers: + X-Request-Id: + description: the request id + type: string + schema: + allOf: + - $ref: '#/definitions/util.Response' + - properties: + data: + $ref: '#/definitions/handler.subjectGroupsResponse' + type: object + security: + - AppCode: [] + - AppSecret: [] + summary: subject group + tags: + - open /api/v1/systems/{system_id}/token: get: consumes: diff --git a/pkg/api/open/handler/subject_group.go b/pkg/api/open/handler/subject_group.go new file mode 100644 index 00000000..a26d0117 --- /dev/null +++ b/pkg/api/open/handler/subject_group.go @@ -0,0 +1,117 @@ +package handler + +import ( + "database/sql" + "errors" + "time" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + + "iam/pkg/cacheimpls" + "iam/pkg/errorx" + "iam/pkg/util" +) + +// TODO: +// 1. url怎么设置? +// 2. 直接插 subject-relation表好, 还是全部走缓存? +// 3. group被删除的时候, subjectDetail引用的并不会清理 => 一个group 被删除, 可能 1min 之内, 还会出现在列表中 + +// SubjectGroups godoc +// @Summary subject group +// @Description get a subject's groups, include the inherit groups from department +// @ID api-open-subject-groups-get +// @Tags open +// @Accept json +// @Produce json +// @Param system_id path string true "System ID" +// @Param subject_type path string true "Subject Type" +// @Param subject_id path string true "Subject ID" +// @Param inherit query bool true "get subject's inherit groups from it's departments" +// @Success 200 {object} util.Response{data=subjectGroupsResponse} +// @Header 200 {string} X-Request-Id "the request id" +// @Security AppCode +// @Security AppSecret +// @Router /api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups [get] +func SubjectGroups(c *gin.Context) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "subject_groups") + + var pathParams subjectGroupsSerializer + if err := c.ShouldBindUri(&pathParams); err != nil { + util.BadRequestErrorJSONResponse(c, util.ValidationErrorMessage(err)) + return + } + _, inherit := c.GetQuery("inherit") + + subjectPK, err := cacheimpls.GetLocalSubjectPK(pathParams.SubjectType, pathParams.SubjectID) + if err != nil { + // 不存在的情况, 404 + if errors.Is(err, sql.ErrNoRows) { + util.NotFoundJSONResponse(c, "subject not exist") + return + } + + util.SystemErrorJSONResponse(c, err) + return + } + + // NOTE: group被删除的时候, subjectDetail引用的并不会清理=> how to? + subjectDetail, err := cacheimpls.GetSubjectDetail(subjectPK) + if err != nil { + util.SystemErrorJSONResponse(c, err) + return + } + + nowUnix := time.Now().Unix() + + // 1. get the subject's groups + groups := subjectDetail.SubjectGroups + groupPKs := util.NewFixedLengthInt64Set(len(groups)) + for _, group := range groups { + // 仅仅在有效期内才需要 + if group.PolicyExpiredAt > nowUnix { + groupPKs.Add(group.PK) + } + } + + // 2. get the subject-department's groups + deptPKs := subjectDetail.DepartmentPKs + if inherit && len(deptPKs) > 0 { + subjectGroups, newErr := cacheimpls.ListSubjectEffectGroups(deptPKs) + if newErr != nil { + newErr = errorWrapf(newErr, "ListSubjectEffectGroups deptPKs=`%+v` fail", deptPKs) + util.SystemErrorJSONResponse(c, newErr) + return + } + for _, sg := range subjectGroups { + if sg.PolicyExpiredAt > nowUnix { + groupPKs.Add(sg.PK) + } + } + } + + // 3. build the response + data := subjectGroupsResponse{} + for _, pk := range groupPKs.ToSlice() { + // NOTE: 一个group 被删除, 可能 1min 之内, 还会出现在列表中 + subj, err := cacheimpls.GetSubjectByPK(pk) + if err != nil { + // no log + if errors.Is(err, sql.ErrNoRows) { + continue + } + // get subject fail, continue + log.Info(errorWrapf(err, "subject_groups GetSubjectByPK subject_pk=`%d` fail", pk)) + continue + } + + data = append(data, responseSubject{ + Type: subj.Type, + ID: subj.ID, + Name: subj.Name, + }) + } + + util.SuccessJSONResponse(c, "ok", data) +} diff --git a/pkg/api/open/handler/subject_group_slz.go b/pkg/api/open/handler/subject_group_slz.go new file mode 100644 index 00000000..cae724bf --- /dev/null +++ b/pkg/api/open/handler/subject_group_slz.go @@ -0,0 +1,15 @@ +package handler + +type subjectGroupsSerializer struct { + // NOTE: currently only support subject_type=user, maybe support subject_type=department later + SubjectType string `uri:"subject_type" binding:"required,oneof=user" example:"user"` + SubjectID string `uri:"subject_id" binding:"required" example:"admin"` +} + +type responseSubject struct { + Type string `json:"type" example:"user"` + ID string `json:"id" example:"admin"` + Name string `json:"name" example:"Administer"` +} + +type subjectGroupsResponse []responseSubject diff --git a/pkg/api/open/router.go b/pkg/api/open/router.go index a4a3c0ee..5aacf737 100644 --- a/pkg/api/open/router.go +++ b/pkg/api/open/router.go @@ -32,4 +32,11 @@ func Register(r *gin.RouterGroup) { // GET /api/v1/systems/:system/policies/-/subjects?ids=1,2,3,4 policies.GET("/:policy_id/subjects", handler.Subjects) } + + subjects := r.Group("/:system_id/subjects") + subjects.Use(common.SystemExistsAndClientValid()) + { + subjects.GET("/:subject_type/:subject_id/groups", handler.SubjectGroups) + } + } From 338dee14cf776f8e5fa908b60ddb83bf0925a835 Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 29 Dec 2021 15:38:21 +0800 Subject: [PATCH 02/12] feat(api/open): do some renames --- pkg/api/open/handler/policies_get.go | 4 +- pkg/api/open/handler/policies_list.go | 4 +- .../open/handler/policies_list_slz_test.go | 4 +- ...query_subjects.go => policies_subjects.go} | 8 ++-- ...bjects_slz.go => policies_subjects_slz.go} | 0 pkg/api/open/handler/subject_group_members.go | 19 ++++++++++ pkg/api/open/handler/subject_group_slz.go | 15 -------- .../{subject_group.go => subject_groups.go} | 20 +++++++--- pkg/api/open/handler/subject_groups_slz.go | 25 +++++++++++++ pkg/api/open/router.go | 37 +++++++++++++++---- pkg/api/open/router_test.go | 2 +- pkg/server/router.go | 13 ++++++- 12 files changed, 111 insertions(+), 40 deletions(-) rename pkg/api/open/handler/{policies_query_subjects.go => policies_subjects.go} (95%) rename pkg/api/open/handler/{policies_query_subjects_slz.go => policies_subjects_slz.go} (100%) create mode 100644 pkg/api/open/handler/subject_group_members.go delete mode 100644 pkg/api/open/handler/subject_group_slz.go rename pkg/api/open/handler/{subject_group.go => subject_groups.go} (76%) create mode 100644 pkg/api/open/handler/subject_groups_slz.go diff --git a/pkg/api/open/handler/policies_get.go b/pkg/api/open/handler/policies_get.go index 7aab2776..f560405b 100644 --- a/pkg/api/open/handler/policies_get.go +++ b/pkg/api/open/handler/policies_get.go @@ -23,7 +23,7 @@ import ( "iam/pkg/util" ) -// Get godoc +// PolicyGet godoc // @Summary policy get // @Description get a policy // @ID api-open-system-policies-get @@ -37,7 +37,7 @@ import ( // @Security AppCode // @Security AppSecret // @Router /api/v1/systems/{system_id}/policies/{policy_id} [get] -func Get(c *gin.Context) { +func PolicyGet(c *gin.Context) { var pathParams policyGetSerializer if err := c.ShouldBindUri(&pathParams); err != nil { util.BadRequestErrorJSONResponse(c, util.ValidationErrorMessage(err)) diff --git a/pkg/api/open/handler/policies_list.go b/pkg/api/open/handler/policies_list.go index 71e52431..33978249 100644 --- a/pkg/api/open/handler/policies_list.go +++ b/pkg/api/open/handler/policies_list.go @@ -28,7 +28,7 @@ import ( "iam/pkg/util" ) -// List godoc +// PolicyList godoc // @Summary policy list // @Description list policies of a action // @ID api-open-system-policies-list @@ -42,7 +42,7 @@ import ( // @Security AppCode // @Security AppSecret // @Router /api/v1/systems/{system_id}/policies [get] -func List(c *gin.Context) { +func PolicyList(c *gin.Context) { // TODO: 翻页接口是否有性能问题 / 限制调用并发, 用drl var query listQuerySerializer if err := c.ShouldBindQuery(&query); err != nil { diff --git a/pkg/api/open/handler/policies_list_slz_test.go b/pkg/api/open/handler/policies_list_slz_test.go index 40d2738b..0fd368b8 100644 --- a/pkg/api/open/handler/policies_list_slz_test.go +++ b/pkg/api/open/handler/policies_list_slz_test.go @@ -13,9 +13,9 @@ package handler import ( "testing" - "iam/pkg/util" - "github.com/stretchr/testify/assert" + + "iam/pkg/util" ) func Test_listQuerySerializer_validate(t *testing.T) { diff --git a/pkg/api/open/handler/policies_query_subjects.go b/pkg/api/open/handler/policies_subjects.go similarity index 95% rename from pkg/api/open/handler/policies_query_subjects.go rename to pkg/api/open/handler/policies_subjects.go index 227aaf26..1f4943d2 100644 --- a/pkg/api/open/handler/policies_query_subjects.go +++ b/pkg/api/open/handler/policies_subjects.go @@ -22,7 +22,7 @@ import ( "iam/pkg/util" ) -// Subjects godoc +// PoliciesSubjects godoc // @Summary query subjects/获取有权限的用户列表 // @Description system+action+policy_ids => subjects // @ID api-open-system-policies-subjects @@ -36,8 +36,8 @@ import ( // @Security AppCode // @Security AppSecret // @Router /api/v1/systems/{system_id}/policies/-/subjects [get] -func Subjects(c *gin.Context) { - errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "policy_list.Subjects") +func PoliciesSubjects(c *gin.Context) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "policy_list.PoliciesSubjects") // TODO: maybe add cache here; // key is subjectsSerializer md5 => value is []subjects; search from git history LocalPolicySubjectsCache @@ -83,7 +83,7 @@ func Subjects(c *gin.Context) { // if get subject fail, continue if err1 != nil { log.Info(errorWrapf(err1, - "policy_list.Subjects GetSubjectByPK subject_pk=`%d` fail", + "policy_list.PoliciesSubjects GetSubjectByPK subject_pk=`%d` fail", policy.SubjectPK)) continue } diff --git a/pkg/api/open/handler/policies_query_subjects_slz.go b/pkg/api/open/handler/policies_subjects_slz.go similarity index 100% rename from pkg/api/open/handler/policies_query_subjects_slz.go rename to pkg/api/open/handler/policies_subjects_slz.go diff --git a/pkg/api/open/handler/subject_group_members.go b/pkg/api/open/handler/subject_group_members.go new file mode 100644 index 00000000..ab8e2ba2 --- /dev/null +++ b/pkg/api/open/handler/subject_group_members.go @@ -0,0 +1,19 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. + * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * 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 handler + +import ( + "github.com/gin-gonic/gin" +) + +func GroupMembers(c *gin.Context) { + +} diff --git a/pkg/api/open/handler/subject_group_slz.go b/pkg/api/open/handler/subject_group_slz.go deleted file mode 100644 index cae724bf..00000000 --- a/pkg/api/open/handler/subject_group_slz.go +++ /dev/null @@ -1,15 +0,0 @@ -package handler - -type subjectGroupsSerializer struct { - // NOTE: currently only support subject_type=user, maybe support subject_type=department later - SubjectType string `uri:"subject_type" binding:"required,oneof=user" example:"user"` - SubjectID string `uri:"subject_id" binding:"required" example:"admin"` -} - -type responseSubject struct { - Type string `json:"type" example:"user"` - ID string `json:"id" example:"admin"` - Name string `json:"name" example:"Administer"` -} - -type subjectGroupsResponse []responseSubject diff --git a/pkg/api/open/handler/subject_group.go b/pkg/api/open/handler/subject_groups.go similarity index 76% rename from pkg/api/open/handler/subject_group.go rename to pkg/api/open/handler/subject_groups.go index a26d0117..7d046867 100644 --- a/pkg/api/open/handler/subject_group.go +++ b/pkg/api/open/handler/subject_groups.go @@ -1,8 +1,19 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. + * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * 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 handler import ( "database/sql" "errors" + "strings" "time" "github.com/gin-gonic/gin" @@ -14,18 +25,16 @@ import ( ) // TODO: -// 1. url怎么设置? // 2. 直接插 subject-relation表好, 还是全部走缓存? // 3. group被删除的时候, subjectDetail引用的并不会清理 => 一个group 被删除, 可能 1min 之内, 还会出现在列表中 // SubjectGroups godoc -// @Summary subject group +// @Summary subject groups // @Description get a subject's groups, include the inherit groups from department // @ID api-open-subject-groups-get // @Tags open // @Accept json // @Produce json -// @Param system_id path string true "System ID" // @Param subject_type path string true "Subject Type" // @Param subject_id path string true "Subject ID" // @Param inherit query bool true "get subject's inherit groups from it's departments" @@ -33,7 +42,7 @@ import ( // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups [get] +// @Router /api/v1/open/subjects/{subject_type}/{subject_id}/groups [get] func SubjectGroups(c *gin.Context) { errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "subject_groups") @@ -42,7 +51,8 @@ func SubjectGroups(c *gin.Context) { util.BadRequestErrorJSONResponse(c, util.ValidationErrorMessage(err)) return } - _, inherit := c.GetQuery("inherit") + inheritValue, ok := c.GetQuery("inherit") + inherit := ok && strings.ToLower(inheritValue) == "true" subjectPK, err := cacheimpls.GetLocalSubjectPK(pathParams.SubjectType, pathParams.SubjectID) if err != nil { diff --git a/pkg/api/open/handler/subject_groups_slz.go b/pkg/api/open/handler/subject_groups_slz.go new file mode 100644 index 00000000..ad77cf6c --- /dev/null +++ b/pkg/api/open/handler/subject_groups_slz.go @@ -0,0 +1,25 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. + * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * 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 handler + +type subjectGroupsSerializer struct { + // NOTE: currently only support subject_type=user, maybe support subject_type=department later + SubjectType string `uri:"subject_type" binding:"required,oneof=user" example:"user"` + SubjectID string `uri:"subject_id" binding:"required" example:"admin"` +} + +type responseSubject struct { + Type string `json:"type" example:"user"` + ID string `json:"id" example:"admin"` + Name string `json:"name" example:"Administer"` +} + +type subjectGroupsResponse []responseSubject diff --git a/pkg/api/open/router.go b/pkg/api/open/router.go index 5aacf737..e07145a0 100644 --- a/pkg/api/open/router.go +++ b/pkg/api/open/router.go @@ -17,26 +17,49 @@ import ( "iam/pkg/api/open/handler" ) -// Register the urls: /api/v1/systems -func Register(r *gin.RouterGroup) { +// RegisterLegacySystemAPIs the urls: /api/v1/systems +// NOTE: should not add more apis here, move to /api/v1/open/systems/{system_id}/ +func RegisterLegacySystemAPIs(r *gin.RouterGroup) { policies := r.Group("/:system_id/policies") policies.Use(common.SystemExistsAndClientValid()) { // GET /api/v1/systems/:system/policies?action=x 拉取某个操作的所有策略列表 - policies.GET("", handler.List) + policies.GET("", handler.PolicyList) // GET /api/v1/systems/:system/policies/:policy_id 查询某个策略详情(这个策略必须属于本系统) - policies.GET("/:policy_id", handler.Get) + policies.GET("/:policy_id", handler.PolicyGet) // https://cloud.google.com/apis/design/design_patterns#list_sub-collections // GET /api/v1/systems/:system/policies/-/subjects?ids=1,2,3,4 - policies.GET("/:policy_id/subjects", handler.Subjects) + policies.GET("/-/subjects", handler.PoliciesSubjects) + } +} + +// Register the urls: /api/v1/open +func Register(r *gin.RouterGroup) { + // 1. system scope /api/v1/open/systems + + // 1.1 policies + policies := r.Group("/systems/:system_id/policies") + policies.Use(common.SystemExistsAndClientValid()) + { + // GET /api/v1/open/systems/:system_id/policies?action=x 拉取某个操作的所有策略列表 + policies.GET("", handler.PolicyList) + + // GET /api/v1/open/systems/:system_id/policies/:policy_id 查询某个策略详情(这个策略必须属于本系统) + policies.GET("/:policy_id", handler.PolicyGet) + + // https://cloud.google.com/apis/design/design_patterns#list_sub-collections + // GET /api/v1/open/systems/:system_id/policies/-/subjects?ids=1,2,3,4 + policies.GET("/-/subjects", handler.PoliciesSubjects) } - subjects := r.Group("/:system_id/subjects") - subjects.Use(common.SystemExistsAndClientValid()) + // 2. subjects + subjects := r.Group("/subjects") { + // GET /api/v1/ subjects.GET("/:subject_type/:subject_id/groups", handler.SubjectGroups) + subjects.GET("/group/:group_id/members", handler.GroupMembers) } } diff --git a/pkg/api/open/router_test.go b/pkg/api/open/router_test.go index c0515431..9f1becb0 100644 --- a/pkg/api/open/router_test.go +++ b/pkg/api/open/router_test.go @@ -23,7 +23,7 @@ func TestRegister(t *testing.T) { r := util.SetupRouter() g := r.Group("/test") - Register(g) + RegisterLegacySystemAPIs(g) assert.NotNil(t, g) } diff --git a/pkg/server/router.go b/pkg/server/router.go index d7c5df7f..2a915322 100644 --- a/pkg/server/router.go +++ b/pkg/server/router.go @@ -61,7 +61,16 @@ func NewRouter(cfg *config.Config) *gin.Engine { policy.Register(policyRouter) // restful apis for open api - openAPIRouter := router.Group("/api/v1/systems") + // 1. legacy apis, will be removed in the future + openLegacyAPIRouter := router.Group("/api/v1/systems") + openLegacyAPIRouter.Use(middleware.Metrics()) + openLegacyAPIRouter.Use(middleware.APILogger()) + openLegacyAPIRouter.Use(middleware.NewClientAuthMiddleware(cfg)) + policyRouter.Use(middleware.NewRateLimitMiddleware(cfg)) + open.RegisterLegacySystemAPIs(openLegacyAPIRouter) + + // 2. open apis + openAPIRouter := router.Group("/api/v1/open") openAPIRouter.Use(middleware.Metrics()) openAPIRouter.Use(middleware.APILogger()) openAPIRouter.Use(middleware.NewClientAuthMiddleware(cfg)) @@ -86,7 +95,7 @@ func NewRouter(cfg *config.Config) *gin.Engine { engineRouter := router.Group("/api/v1/engine") engineRouter.Use(middleware.Metrics()) // NOTE: disable the log - //engineRouter.Use(middleware.WebLogger()) + // engineRouter.Use(middleware.WebLogger()) engineRouter.Use(middleware.NewClientAuthMiddleware(cfg)) engineRouter.Use(middleware.SuperClientMiddleware()) engine.Register(engineRouter) From ab9b5bb747c34e67ef178e7d2b1459a3128dcf11 Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 29 Dec 2021 16:43:48 +0800 Subject: [PATCH 03/12] feat(api/open): add user_groups and department_groups --- docs/docs.go | 301 ++++++++++-------- docs/swagger.json | 301 ++++++++++-------- docs/swagger.yaml | 190 ++++++----- pkg/api/open/handler/subject_group_members.go | 19 -- pkg/api/open/handler/subject_groups.go | 57 ++-- pkg/api/open/handler/subject_groups_slz.go | 6 - pkg/api/open/router.go | 23 +- 7 files changed, 497 insertions(+), 400 deletions(-) delete mode 100644 pkg/api/open/handler/subject_group_members.go diff --git a/docs/docs.go b/docs/docs.go index 7274e075..8a811a66 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -334,6 +334,131 @@ var doc = `{ } } }, + "/api/v1/open/departments/{user_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a department's groups", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "department groups", + "operationId": "api-open-department-groups-get", + "parameters": [ + { + "type": "string", + "description": "Department ID", + "name": "department_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, + "/api/v1/open/users/{user_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a user's groups, include the inherit groups from department", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "user groups", + "operationId": "api-open-user-groups-get", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "get subject's inherit groups from it's departments", + "name": "inherit", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v1/policy/auth": { "post": { "security": [ @@ -1931,86 +2056,6 @@ var doc = `{ } } }, - "/api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups": { - "get": { - "security": [ - { - "AppCode": [] - }, - { - "AppSecret": [] - } - ], - "description": "get a subject's groups, include the inherit groups from department", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "open" - ], - "summary": "subject group", - "operationId": "api-open-subject-groups-get", - "parameters": [ - { - "type": "string", - "description": "System ID", - "name": "system_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Subject Type", - "name": "subject_type", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Subject ID", - "name": "subject_id", - "in": "path", - "required": true - }, - { - "type": "boolean", - "description": "get subject's inherit groups from it's departments", - "name": "inherit", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/util.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handler.subjectGroupsResponse" - } - } - } - ] - }, - "headers": { - "X-Request-Id": { - "type": "string", - "description": "the request id" - } - } - } - } - } - }, "/api/v1/systems/{system_id}/token": { "get": { "security": [ @@ -3086,6 +3131,46 @@ var doc = `{ } } }, + "handler.enginePolicyResponse": { + "type": "object", + "properties": { + "action": { + "type": "object", + "$ref": "#/definitions/handler.policyResponseAction" + }, + "expired_at": { + "type": "integer", + "example": 4102444800 + }, + "expression": { + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "integer", + "example": 100 + }, + "subject": { + "type": "object", + "$ref": "#/definitions/handler.policyResponseSubject" + }, + "system": { + "type": "string", + "example": "bk_cmdb" + }, + "template_id": { + "type": "integer" + }, + "updated_at": { + "type": "integer", + "example": 4102444800 + }, + "version": { + "type": "string", + "example": "1" + } + } + }, "handler.extResource": { "type": "object", "required": [ @@ -3429,39 +3514,18 @@ var doc = `{ "handler.policyListResponse": { "type": "object", "properties": { - "count": { - "type": "integer", - "example": 120 - }, "metadata": { "type": "object", - "$ref": "#/definitions/handler.policyListResponseMetadata" + "$ref": "#/definitions/handler.listPolicySerializer" }, "results": { "type": "array", "items": { - "$ref": "#/definitions/handler.thinPolicyResponse" + "$ref": "#/definitions/handler.enginePolicyResponse" } } } }, - "handler.policyListResponseMetadata": { - "type": "object", - "properties": { - "action": { - "type": "object", - "$ref": "#/definitions/handler.policyResponseAction" - }, - "system": { - "type": "string", - "example": "bk_test" - }, - "timestamp": { - "type": "integer", - "example": 1592899208 - } - } - }, "handler.policyResponseAction": { "type": "object", "properties": { @@ -3819,12 +3883,10 @@ var doc = `{ ], "properties": { "id": { - "type": "string", - "example": "admin" + "type": "string" }, "type": { - "type": "string", - "example": "user" + "type": "string" } } }, @@ -4011,31 +4073,6 @@ var doc = `{ } } }, - "handler.thinPolicyResponse": { - "type": "object", - "properties": { - "expired_at": { - "type": "integer", - "example": 4102444800 - }, - "expression": { - "type": "object", - "additionalProperties": true - }, - "id": { - "type": "integer", - "example": 100 - }, - "subject": { - "type": "object", - "$ref": "#/definitions/handler.policyResponseSubject" - }, - "version": { - "type": "string", - "example": "1" - } - } - }, "handler.tokenResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 33fc59e2..aea0f1a0 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -317,6 +317,131 @@ } } }, + "/api/v1/open/departments/{user_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a department's groups", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "department groups", + "operationId": "api-open-department-groups-get", + "parameters": [ + { + "type": "string", + "description": "Department ID", + "name": "department_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, + "/api/v1/open/users/{user_id}/groups": { + "get": { + "security": [ + { + "AppCode": [] + }, + { + "AppSecret": [] + } + ], + "description": "get a user's groups, include the inherit groups from department", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "open" + ], + "summary": "user groups", + "operationId": "api-open-user-groups-get", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "get subject's inherit groups from it's departments", + "name": "inherit", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/handler.subjectGroupsResponse" + } + } + } + ] + }, + "headers": { + "X-Request-Id": { + "type": "string", + "description": "the request id" + } + } + } + } + } + }, "/api/v1/policy/auth": { "post": { "security": [ @@ -1914,86 +2039,6 @@ } } }, - "/api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups": { - "get": { - "security": [ - { - "AppCode": [] - }, - { - "AppSecret": [] - } - ], - "description": "get a subject's groups, include the inherit groups from department", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "open" - ], - "summary": "subject group", - "operationId": "api-open-subject-groups-get", - "parameters": [ - { - "type": "string", - "description": "System ID", - "name": "system_id", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Subject Type", - "name": "subject_type", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Subject ID", - "name": "subject_id", - "in": "path", - "required": true - }, - { - "type": "boolean", - "description": "get subject's inherit groups from it's departments", - "name": "inherit", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/util.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/handler.subjectGroupsResponse" - } - } - } - ] - }, - "headers": { - "X-Request-Id": { - "type": "string", - "description": "the request id" - } - } - } - } - } - }, "/api/v1/systems/{system_id}/token": { "get": { "security": [ @@ -3069,6 +3114,46 @@ } } }, + "handler.enginePolicyResponse": { + "type": "object", + "properties": { + "action": { + "type": "object", + "$ref": "#/definitions/handler.policyResponseAction" + }, + "expired_at": { + "type": "integer", + "example": 4102444800 + }, + "expression": { + "type": "object", + "additionalProperties": true + }, + "id": { + "type": "integer", + "example": 100 + }, + "subject": { + "type": "object", + "$ref": "#/definitions/handler.policyResponseSubject" + }, + "system": { + "type": "string", + "example": "bk_cmdb" + }, + "template_id": { + "type": "integer" + }, + "updated_at": { + "type": "integer", + "example": 4102444800 + }, + "version": { + "type": "string", + "example": "1" + } + } + }, "handler.extResource": { "type": "object", "required": [ @@ -3412,39 +3497,18 @@ "handler.policyListResponse": { "type": "object", "properties": { - "count": { - "type": "integer", - "example": 120 - }, "metadata": { "type": "object", - "$ref": "#/definitions/handler.policyListResponseMetadata" + "$ref": "#/definitions/handler.listPolicySerializer" }, "results": { "type": "array", "items": { - "$ref": "#/definitions/handler.thinPolicyResponse" + "$ref": "#/definitions/handler.enginePolicyResponse" } } } }, - "handler.policyListResponseMetadata": { - "type": "object", - "properties": { - "action": { - "type": "object", - "$ref": "#/definitions/handler.policyResponseAction" - }, - "system": { - "type": "string", - "example": "bk_test" - }, - "timestamp": { - "type": "integer", - "example": 1592899208 - } - } - }, "handler.policyResponseAction": { "type": "object", "properties": { @@ -3802,12 +3866,10 @@ ], "properties": { "id": { - "type": "string", - "example": "admin" + "type": "string" }, "type": { - "type": "string", - "example": "user" + "type": "string" } } }, @@ -3994,31 +4056,6 @@ } } }, - "handler.thinPolicyResponse": { - "type": "object", - "properties": { - "expired_at": { - "type": "integer", - "example": 4102444800 - }, - "expression": { - "type": "object", - "additionalProperties": true - }, - "id": { - "type": "integer", - "example": 100 - }, - "subject": { - "type": "object", - "$ref": "#/definitions/handler.policyResponseSubject" - }, - "version": { - "type": "string", - "example": "1" - } - } - }, "handler.tokenResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 80c09f21..4e42718d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -255,6 +255,35 @@ definitions: required: - id type: object + handler.enginePolicyResponse: + properties: + action: + $ref: '#/definitions/handler.policyResponseAction' + type: object + expired_at: + example: 4102444800 + type: integer + expression: + additionalProperties: true + type: object + id: + example: 100 + type: integer + subject: + $ref: '#/definitions/handler.policyResponseSubject' + type: object + system: + example: bk_cmdb + type: string + template_id: + type: integer + updated_at: + example: 4102444800 + type: integer + version: + example: "1" + type: string + type: object handler.extResource: properties: ids: @@ -495,29 +524,14 @@ definitions: type: object handler.policyListResponse: properties: - count: - example: 120 - type: integer metadata: - $ref: '#/definitions/handler.policyListResponseMetadata' + $ref: '#/definitions/handler.listPolicySerializer' type: object results: items: - $ref: '#/definitions/handler.thinPolicyResponse' + $ref: '#/definitions/handler.enginePolicyResponse' type: array type: object - handler.policyListResponseMetadata: - properties: - action: - $ref: '#/definitions/handler.policyResponseAction' - type: object - system: - example: bk_test - type: string - timestamp: - example: 1592899208 - type: integer - type: object handler.policyResponseAction: properties: id: @@ -771,10 +785,8 @@ definitions: handler.subject: properties: id: - example: admin type: string type: - example: user type: string required: - id @@ -911,24 +923,6 @@ definitions: $ref: '#/definitions/handler.systemProviderConfig' type: object type: object - handler.thinPolicyResponse: - properties: - expired_at: - example: 4102444800 - type: integer - expression: - additionalProperties: true - type: object - id: - example: 100 - type: integer - subject: - $ref: '#/definitions/handler.policyResponseSubject' - type: object - version: - example: "1" - type: string - type: object handler.tokenResponse: properties: token: @@ -1198,6 +1192,79 @@ paths: summary: system info tags: - engine + /api/v1/open/departments/{user_id}/groups: + get: + consumes: + - application/json + description: get a department's groups + operationId: api-open-department-groups-get + parameters: + - description: Department ID + in: path + name: department_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + headers: + X-Request-Id: + description: the request id + type: string + schema: + allOf: + - $ref: '#/definitions/util.Response' + - properties: + data: + $ref: '#/definitions/handler.subjectGroupsResponse' + type: object + security: + - AppCode: [] + - AppSecret: [] + summary: department groups + tags: + - open + /api/v1/open/users/{user_id}/groups: + get: + consumes: + - application/json + description: get a user's groups, include the inherit groups from department + operationId: api-open-user-groups-get + parameters: + - description: User ID + in: path + name: user_id + required: true + type: string + - description: get subject's inherit groups from it's departments + in: query + name: inherit + required: true + type: boolean + produces: + - application/json + responses: + "200": + description: OK + headers: + X-Request-Id: + description: the request id + type: string + schema: + allOf: + - $ref: '#/definitions/util.Response' + - properties: + data: + $ref: '#/definitions/handler.subjectGroupsResponse' + type: object + security: + - AppCode: [] + - AppSecret: [] + summary: user groups + tags: + - open /api/v1/policy/auth: post: consumes: @@ -2184,55 +2251,6 @@ paths: summary: resource type update tags: - model - /api/v1/systems/{system_id}/subjects/{subject_type}/{subject_id}/groups: - get: - consumes: - - application/json - description: get a subject's groups, include the inherit groups from department - operationId: api-open-subject-groups-get - parameters: - - description: System ID - in: path - name: system_id - required: true - type: string - - description: Subject Type - in: path - name: subject_type - required: true - type: string - - description: Subject ID - in: path - name: subject_id - required: true - type: string - - description: get subject's inherit groups from it's departments - in: query - name: inherit - required: true - type: boolean - produces: - - application/json - responses: - "200": - description: OK - headers: - X-Request-Id: - description: the request id - type: string - schema: - allOf: - - $ref: '#/definitions/util.Response' - - properties: - data: - $ref: '#/definitions/handler.subjectGroupsResponse' - type: object - security: - - AppCode: [] - - AppSecret: [] - summary: subject group - tags: - - open /api/v1/systems/{system_id}/token: get: consumes: diff --git a/pkg/api/open/handler/subject_group_members.go b/pkg/api/open/handler/subject_group_members.go deleted file mode 100644 index ab8e2ba2..00000000 --- a/pkg/api/open/handler/subject_group_members.go +++ /dev/null @@ -1,19 +0,0 @@ -/* - * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. - * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at http://opensource.org/licenses/MIT - * 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 handler - -import ( - "github.com/gin-gonic/gin" -) - -func GroupMembers(c *gin.Context) { - -} diff --git a/pkg/api/open/handler/subject_groups.go b/pkg/api/open/handler/subject_groups.go index 7d046867..776ac944 100644 --- a/pkg/api/open/handler/subject_groups.go +++ b/pkg/api/open/handler/subject_groups.go @@ -21,40 +21,56 @@ import ( "iam/pkg/cacheimpls" "iam/pkg/errorx" + "iam/pkg/service/types" "iam/pkg/util" ) -// TODO: -// 2. 直接插 subject-relation表好, 还是全部走缓存? -// 3. group被删除的时候, subjectDetail引用的并不会清理 => 一个group 被删除, 可能 1min 之内, 还会出现在列表中 - -// SubjectGroups godoc -// @Summary subject groups -// @Description get a subject's groups, include the inherit groups from department -// @ID api-open-subject-groups-get +// UserGroups godoc +// @Summary user groups +// @Description get a user's groups, include the inherit groups from department +// @ID api-open-user-groups-get // @Tags open // @Accept json // @Produce json -// @Param subject_type path string true "Subject Type" -// @Param subject_id path string true "Subject ID" +// @Param user_id path string true "User ID" // @Param inherit query bool true "get subject's inherit groups from it's departments" // @Success 200 {object} util.Response{data=subjectGroupsResponse} // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/open/subjects/{subject_type}/{subject_id}/groups [get] -func SubjectGroups(c *gin.Context) { - errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "subject_groups") +// @Router /api/v1/open/users/{user_id}/groups [get] +func UserGroups(c *gin.Context) { + subjectID := c.Param("user_id") - var pathParams subjectGroupsSerializer - if err := c.ShouldBindUri(&pathParams); err != nil { - util.BadRequestErrorJSONResponse(c, util.ValidationErrorMessage(err)) - return - } inheritValue, ok := c.GetQuery("inherit") inherit := ok && strings.ToLower(inheritValue) == "true" - subjectPK, err := cacheimpls.GetLocalSubjectPK(pathParams.SubjectType, pathParams.SubjectID) + handleSubjectGroups(c, types.UserType, subjectID, inherit) +} + +// DepartmentGroups godoc +// @Summary department groups +// @Description get a department's groups +// @ID api-open-department-groups-get +// @Tags open +// @Accept json +// @Produce json +// @Param department_id path string true "Department ID" +// @Success 200 {object} util.Response{data=subjectGroupsResponse} +// @Header 200 {string} X-Request-Id "the request id" +// @Security AppCode +// @Security AppSecret +// @Router /api/v1/open/departments/{user_id}/groups [get] +func DepartmentGroups(c *gin.Context) { + subjectID := c.Param("department_id") + + handleSubjectGroups(c, types.DepartmentType, subjectID, false) +} + +func handleSubjectGroups(c *gin.Context, subjectType, subjectID string, inherit bool) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler", "subject_groups") + + subjectPK, err := cacheimpls.GetLocalSubjectPK(subjectType, subjectID) if err != nil { // 不存在的情况, 404 if errors.Is(err, sql.ErrNoRows) { @@ -66,7 +82,7 @@ func SubjectGroups(c *gin.Context) { return } - // NOTE: group被删除的时候, subjectDetail引用的并不会清理=> how to? + // NOTE: group被删除的时候, subjectDetail引用的并不会清理 subjectDetail, err := cacheimpls.GetSubjectDetail(subjectPK) if err != nil { util.SystemErrorJSONResponse(c, err) @@ -111,6 +127,7 @@ func SubjectGroups(c *gin.Context) { if errors.Is(err, sql.ErrNoRows) { continue } + // get subject fail, continue log.Info(errorWrapf(err, "subject_groups GetSubjectByPK subject_pk=`%d` fail", pk)) continue diff --git a/pkg/api/open/handler/subject_groups_slz.go b/pkg/api/open/handler/subject_groups_slz.go index ad77cf6c..ca9c5975 100644 --- a/pkg/api/open/handler/subject_groups_slz.go +++ b/pkg/api/open/handler/subject_groups_slz.go @@ -10,12 +10,6 @@ package handler -type subjectGroupsSerializer struct { - // NOTE: currently only support subject_type=user, maybe support subject_type=department later - SubjectType string `uri:"subject_type" binding:"required,oneof=user" example:"user"` - SubjectID string `uri:"subject_id" binding:"required" example:"admin"` -} - type responseSubject struct { Type string `json:"type" example:"user"` ID string `json:"id" example:"admin"` diff --git a/pkg/api/open/router.go b/pkg/api/open/router.go index e07145a0..f1b0fe2f 100644 --- a/pkg/api/open/router.go +++ b/pkg/api/open/router.go @@ -54,12 +54,25 @@ func Register(r *gin.RouterGroup) { policies.GET("/-/subjects", handler.PoliciesSubjects) } - // 2. subjects - subjects := r.Group("/subjects") + // 2. subjects: users, departments, groups + users := r.Group("/users") { - // GET /api/v1/ - subjects.GET("/:subject_type/:subject_id/groups", handler.SubjectGroups) - subjects.GET("/group/:group_id/members", handler.GroupMembers) + // GET /user/123/groups?inherit=true + users.GET("/:user_id/groups", handler.UserGroups) } + departments := r.Group("/departments") + { + // GET /department/456/groups + departments.GET("/:department_id/groups", handler.DepartmentGroups) + } + + // 3. groups + // groups := r.Group("/groups") + // { + // groups.GET("/:group_id/members", handler.GroupMembers) + // groups.GET("/", handler.Groups) + // groups.GET("/:group_id", handler.GroupGet) + // } + } From 94c286ff07552e32f27f89617def5fe9072824d5 Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 29 Dec 2021 16:48:21 +0800 Subject: [PATCH 04/12] fix(sync): sync with upstream/develop --- pkg/api/open/handler/subject_groups.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/api/open/handler/subject_groups.go b/pkg/api/open/handler/subject_groups.go index 776ac944..1a6c1c63 100644 --- a/pkg/api/open/handler/subject_groups.go +++ b/pkg/api/open/handler/subject_groups.go @@ -16,11 +16,12 @@ import ( "strings" "time" + "github.com/TencentBlueKing/gopkg/collection/set" + "github.com/TencentBlueKing/gopkg/errorx" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "iam/pkg/cacheimpls" - "iam/pkg/errorx" "iam/pkg/service/types" "iam/pkg/util" ) @@ -93,7 +94,7 @@ func handleSubjectGroups(c *gin.Context, subjectType, subjectID string, inherit // 1. get the subject's groups groups := subjectDetail.SubjectGroups - groupPKs := util.NewFixedLengthInt64Set(len(groups)) + groupPKs := set.NewFixedLengthInt64Set(len(groups)) for _, group := range groups { // 仅仅在有效期内才需要 if group.PolicyExpiredAt > nowUnix { From 9d5963ef01d36792cf5e9453f1c4415969ac2e9d Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 29 Dec 2021 17:40:42 +0800 Subject: [PATCH 05/12] fix(lints): fix lints --- pkg/api/open/handler/subject_groups.go | 5 ++--- pkg/api/open/router.go | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/api/open/handler/subject_groups.go b/pkg/api/open/handler/subject_groups.go index 1a6c1c63..d39e72bc 100644 --- a/pkg/api/open/handler/subject_groups.go +++ b/pkg/api/open/handler/subject_groups.go @@ -43,8 +43,7 @@ import ( func UserGroups(c *gin.Context) { subjectID := c.Param("user_id") - inheritValue, ok := c.GetQuery("inherit") - inherit := ok && strings.ToLower(inheritValue) == "true" + inherit := strings.ToLower(c.Query("inherit")) == "true" handleSubjectGroups(c, types.UserType, subjectID, inherit) } @@ -61,7 +60,7 @@ func UserGroups(c *gin.Context) { // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/open/departments/{user_id}/groups [get] +// @Router /api/v1/open/departments/{department_id}/groups [get] func DepartmentGroups(c *gin.Context) { subjectID := c.Param("department_id") diff --git a/pkg/api/open/router.go b/pkg/api/open/router.go index f1b0fe2f..48f93999 100644 --- a/pkg/api/open/router.go +++ b/pkg/api/open/router.go @@ -74,5 +74,4 @@ func Register(r *gin.RouterGroup) { // groups.GET("/", handler.Groups) // groups.GET("/:group_id", handler.GroupGet) // } - } From f5d16b9f5c63f2e0bc49132bb211e1f02b58579b Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 29 Dec 2021 17:48:55 +0800 Subject: [PATCH 06/12] chore(version): upgrade version to 1.9.3 --- VERSION | 2 +- release.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8fdcf386..77fee73a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.2 +1.9.3 diff --git a/release.md b/release.md index d326c7fa..a2da06ef 100644 --- a/release.md +++ b/release.md @@ -1,3 +1,9 @@ +# 1.9.3 + +- update: replace some lib with https://github.com/TencentBlueKing/gopkg +- move: API /api/v1/systems/ to /api/v1/open/systems/ +- add: API /api/v1/open/users/:user_id/groups + # 1.9.2 - hotfix: condition StringPrefix eval wrong when key is _bk_iam_path_ From 94bdcca9ec59110c6a5a2f4217f03f22bd3d46d4 Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 11 Jan 2022 15:54:46 +0800 Subject: [PATCH 07/12] feat(support/bkauth): add bkauth support, check app_code/app_secret --- cmd/init.go | 26 +++- pkg/api/model/handler/system_test.go | 4 +- pkg/cacheimpls/init.go | 4 + pkg/cacheimpls/local_app_code_secret.go | 30 ++++ pkg/cacheimpls/local_remote_resource_list.go | 4 +- pkg/cacheimpls/remote_resource_test.go | 2 +- pkg/component/auth.go | 145 +++++++++++++++++++ pkg/component/init.go | 14 +- pkg/component/types.go | 16 ++ pkg/config/config.go | 39 +++-- pkg/database/init.go | 2 +- pkg/middleware/client.go | 11 +- pkg/middleware/client_test.go | 2 +- 13 files changed, 267 insertions(+), 32 deletions(-) create mode 100644 pkg/component/auth.go diff --git a/cmd/init.go b/cmd/init.go index cda99330..33c5f109 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -76,13 +76,19 @@ func initDatabase() { panic("database bk-iam should be configured") } + if globalConfig.EnableBkAuth { + database.InitDBClients(&defaultDBConfig, nil) + log.Info("init Database success") + return + } + // TODO: 不应该成为强依赖 bkPaaSDBConfig, ok := globalConfig.DatabaseMap["open_paas"] if !ok { - panic("database open_paas should be configured") + panic("bkauth is not enabled, so database open_paas should be configured") } - database.InitDBClients(&defaultDBConfig, &bkPaaSDBConfig) + log.Info("init Database success") } @@ -142,7 +148,21 @@ func initSupportShieldFeatures() { } func initComponents() { - component.InitComponentClients() + component.InitBkRemoteResourceClient() + + if globalConfig.EnableBkAuth { + bkAuthHost, ok := globalConfig.HostMap["bkauth"] + if !ok { + panic("bkauth is enabled, so host bkauth should be configured") + } + + if globalConfig.BkAppCode == "" || globalConfig.BkAppSecret == "" { + panic("bkauth is enabled, but iam's bkAppCode and bkAppSecret is not configured") + } + + component.InitBkAuthClient(bkAuthHost.Addr, globalConfig.BkAppCode, globalConfig.BkAppSecret) + log.Infof("init bkauth client success, host = %s", bkAuthHost.Addr) + } } func initQuota() { diff --git a/pkg/api/model/handler/system_test.go b/pkg/api/model/handler/system_test.go index 23ac5a0a..0e6db01c 100644 --- a/pkg/api/model/handler/system_test.go +++ b/pkg/api/model/handler/system_test.go @@ -90,7 +90,7 @@ func TestCreateSystem(t *testing.T) { // init the router r := util.SetupRouter() - r.Use(middleware.ClientAuthMiddleware([]byte(""))) + r.Use(middleware.ClientAuthMiddleware([]byte(""), false)) url := "/api/v1/systems" r.POST(url, CreateSystem) @@ -287,7 +287,7 @@ func TestUpdateSystem(t *testing.T) { // init the router r := util.SetupRouter() - r.Use(middleware.ClientAuthMiddleware([]byte(""))) + r.Use(middleware.ClientAuthMiddleware([]byte(""), false)) url := "/api/v1/systems/test" r.POST(url, UpdateSystem) diff --git a/pkg/cacheimpls/init.go b/pkg/cacheimpls/init.go index 294df534..34e951bb 100644 --- a/pkg/cacheimpls/init.go +++ b/pkg/cacheimpls/init.go @@ -30,6 +30,7 @@ const CacheLayer = "Cache" // LocalAppCodeAppSecretCache ... var ( LocalAppCodeAppSecretCache memory.Cache + LocalAuthAppAccessKeyCache *gocache.Cache LocalSubjectCache memory.Cache LocalSubjectRoleCache memory.Cache LocalSystemClientsCache memory.Cache @@ -81,6 +82,9 @@ func InitCaches(disabled bool) { nil, ) + // auth app_code/app_secret cache + LocalAuthAppAccessKeyCache = gocache.New(12*time.Hour, 5*time.Minute) + // 影响: engine增量同步 LocalSubjectCache = memory.NewCache( diff --git a/pkg/cacheimpls/local_app_code_secret.go b/pkg/cacheimpls/local_app_code_secret.go index 055ef850..6c082eb7 100644 --- a/pkg/cacheimpls/local_app_code_secret.go +++ b/pkg/cacheimpls/local_app_code_secret.go @@ -11,9 +11,13 @@ package cacheimpls import ( + "time" + "github.com/TencentBlueKing/gopkg/cache" + gocache "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" + "iam/pkg/component" "iam/pkg/database/edao" ) @@ -48,3 +52,29 @@ func VerifyAppCodeAppSecret(appCode, appSecret string) bool { } return exists } + +func VerifyAppCodeAppSecretFromAuth(appCode, appSecret string) bool { + // 1. get from cache + key := appCode + ":" + appSecret + + value, found := LocalAuthAppAccessKeyCache.Get(key) + if found { + return value.(bool) + } + + // 2. get from auth + valid, err := component.BkAuth.Verify(appCode, appSecret) + if err != nil { + log.Errorf("verify app_code_app_secret from auth fail, key=%s, err=%s", key, err) + return false + } + + // 3. set to cache, default 12 hours, if not valid, only keep in cache for 1 minutes + // in case of auth server down, we can still get the valid matched accessKeys from cache + ttl := gocache.DefaultExpiration + if !valid { + ttl = 1 * time.Minute + } + LocalAuthAppAccessKeyCache.Set(key, valid, ttl) + return valid +} diff --git a/pkg/cacheimpls/local_remote_resource_list.go b/pkg/cacheimpls/local_remote_resource_list.go index dbc9dbb1..d157f71e 100644 --- a/pkg/cacheimpls/local_remote_resource_list.go +++ b/pkg/cacheimpls/local_remote_resource_list.go @@ -81,10 +81,10 @@ func listRemoteResources(systemID, _type string, ids []string, fields []string) return nil, err } - resources, err := component.BKRemoteResource.GetResources(req, systemID, _type, ids, fields) + resources, err := component.BkRemoteResource.GetResources(req, systemID, _type, ids, fields) if err != nil { err = errorWrapf( - err, "BKRemoteResource.GetResource systemID=`%s`, resourceTypeID=`%s`, ids length=`%d`, fields=`%s` fail", + err, "BkRemoteResource.GetResource systemID=`%s`, resourceTypeID=`%s`, ids length=`%d`, fields=`%s` fail", systemID, _type, len(ids), fields) return nil, err } diff --git a/pkg/cacheimpls/remote_resource_test.go b/pkg/cacheimpls/remote_resource_test.go index 693fe590..c5d585ab 100644 --- a/pkg/cacheimpls/remote_resource_test.go +++ b/pkg/cacheimpls/remote_resource_test.go @@ -84,7 +84,7 @@ func TestGetCMDBResource(t *testing.T) { "id": "checklist", }}, nil).AnyTimes() - component.BKRemoteResource = mockService + component.BkRemoteResource = mockService mockCache := redis.NewMockCache("mockCache", expiration) diff --git a/pkg/component/auth.go b/pkg/component/auth.go new file mode 100644 index 00000000..d4afb2db --- /dev/null +++ b/pkg/component/auth.go @@ -0,0 +1,145 @@ +package component + +import ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/TencentBlueKing/gopkg/errorx" + "github.com/parnurzeal/gorequest" + + "iam/pkg/logging" +) + +// AuthResponse is the struct of iam backend response +type AuthResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data map[string]interface{} `json:"data"` +} + +// Error will check if the response with error +func (r *AuthResponse) Error() error { + if r.Code == 0 { + return nil + } + + return fmt.Errorf("response error[code=`%d`, message=`%s`]", r.Code, r.Message) +} + +// String will return the detail text of the response +func (r *AuthResponse) String() string { + return fmt.Sprintf("response[code=`%d`, message=`%s`, data=`%v`]", r.Code, r.Message, r.Data) +} + +// AuthClient is the interface of auth client +type AuthClient interface { + Verify(bkAppCode, bkAppSecret string) (bool, error) +} + +type authClient struct { + Host string + + appCode string + appSecret string +} + +// NewAuthClient will create a auth client +func NewAuthClient(host string, appCode string, appSecret string) AuthClient { + host = strings.TrimRight(host, "/") + return &authClient{ + Host: host, + appCode: appCode, + appSecret: appSecret, + } +} + +func (c *authClient) call( + method Method, + path string, + data interface{}, + timeout int64, +) (map[string]interface{}, error) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf("component", "authClient.call") + + callTimeout := time.Duration(timeout) * time.Second + if timeout == 0 { + callTimeout = defaultTimeout + } + + url := fmt.Sprintf("%s%s", c.Host, path) + result := AuthResponse{} + start := time.Now() + callbackFunc := NewMetricCallback("Auth", start) + + request := gorequest.New() + switch method { + case POST: + request = request.Post(url) + case GET: + request = request.Get(url) + } + request = request.Timeout(callTimeout).Type("json") + + // set headers + request.Header.Set("X-BK-APP-CODE", c.appCode) + request.Header.Set("X-BK-APP-SECRET", c.appSecret) + + // do request + resp, _, errs := request. + Send(data). + EndStruct(&result, callbackFunc) + + // NOTE: it's a sensitive api, so, no log request detail! + // logFailHTTPRequest(start, request, resp, respBody, errs, &result) + logger := logging.GetComponentLogger() + + var err error + if len(errs) != 0 { + // 敏感信息泄漏 ip+端口号, 替换为 *.*.*.* + errsMessage := fmt.Sprintf("gorequest errorx=`%s`", errs) + errsMessage = ipRegex.ReplaceAllString(errsMessage, replaceToIP) + err = errors.New(errsMessage) + + err = errorWrapf(err, "errsCount=`%d`", len(errs)) + logger.Errorf("call auth api %s fail, err=%s", path, err.Error()) + return nil, err + } + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("gorequest statusCode is %d not 200", resp.StatusCode) + logger.Errorf("call auth api %s fail , err=%s", path, err.Error()) + return nil, errorWrapf(err, "status=%d", resp.StatusCode) + } + if result.Code != 0 { + err = errors.New(result.Message) + err = errorWrapf(err, "result.Code=%d", result.Code) + logger.Errorf("call auth api %s ok but code in response is not 0, err=%s", path, err.Error()) + return nil, err + } + + return result.Data, nil +} + +// Verify will check bkAppCode, bkAppSecret is valid +func (c *authClient) Verify(bkAppCode, bkAppSecret string) (bool, error) { + path := fmt.Sprintf("/api/v1/apps/%s/access-keys/verify", bkAppCode) + + data, err := c.call(GET, path, map[string]interface{}{ + "bk_app_secret": bkAppSecret, + }, 5) + if err != nil { + return false, err + } + matchI, ok := data["is_match"] + if !ok { + return false, errors.New("no is_match in response body") + } + + match, ok := matchI.(bool) + if !ok { + return false, errors.New("is_match is not a valid bool") + } + return match, nil +} diff --git a/pkg/component/init.go b/pkg/component/init.go index e9cf463e..4b7658f4 100644 --- a/pkg/component/init.go +++ b/pkg/component/init.go @@ -29,14 +29,18 @@ const ( maxResponseBodyLength = 10240 ) -// BKRemoteResource ... var ( - BKRemoteResource RemoteResourceClient + BkRemoteResource RemoteResourceClient + BkAuth AuthClient ) -// InitComponentClients ... -func InitComponentClients() { - BKRemoteResource = NewRemoteResourceClient() +// InitBkRemoteResourceClient ... +func InitBkRemoteResourceClient() { + BkRemoteResource = NewRemoteResourceClient() +} + +func InitBkAuthClient(bkAuthHost, appCode, appSecret string) { + BkAuth = NewAuthClient(bkAuthHost, appCode, appSecret) } // CallbackFunc ... diff --git a/pkg/component/types.go b/pkg/component/types.go index d3ac1234..7c21a157 100644 --- a/pkg/component/types.go +++ b/pkg/component/types.go @@ -10,6 +10,22 @@ package component +import "time" + +const ( + defaultTimeout = 5 * time.Second +) + +// Method is the type of http method +type Method string + +var ( + // POST http post + POST Method = "POST" + // GET http get + GET Method = "GET" +) + type responseStruct interface { Error() error } diff --git a/pkg/config/config.go b/pkg/config/config.go index 5a357a1d..8ea29408 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -109,10 +109,10 @@ type SystemQuota struct { Quota Quota } -// type Host struct { -// ID string -// Addr string -// } +type Host struct { + ID string + Addr string +} // Crypto store the keys for crypto type Crypto struct { @@ -127,6 +127,10 @@ type Config struct { Server Server Sentry Sentry + // iam's app_code and app_secret + BkAppCode string + BkAppSecret string + SuperAppCode string // default superuser SuperUser string @@ -139,13 +143,15 @@ type Config struct { Redis []Redis RedisMap map[string]Redis + EnableBkAuth bool + Hosts []Host + HostMap map[string]Host + Quota Quota CustomQuotas []SystemQuota CustomQuotasMap map[string]Quota - // Hosts []Host - // HostMap map[string]Host Switch map[string]bool Cache Cache @@ -155,7 +161,7 @@ type Config struct { Cryptos map[string]*Crypto } -// Load: 从viper中读取配置文件 +// Load 从viper中读取配置文件 func Load(v *viper.Viper) (*Config, error) { var cfg Config // 将配置信息绑定到结构体上 @@ -180,17 +186,22 @@ func Load(v *viper.Viper) (*Config, error) { cfg.RedisMap[rds.ID] = rds } - // 3. init quota + // 3. hosts + cfg.HostMap = make(map[string]Host) + for _, host := range cfg.Hosts { + cfg.HostMap[host.ID] = host + } + if cfg.EnableBkAuth { + if len(cfg.HostMap) == 0 { + return nil, errors.New("hosts cannot be empty") + } + } + + // 4. init quota cfg.CustomQuotasMap = make(map[string]Quota) for _, q := range cfg.CustomQuotas { cfg.CustomQuotasMap[q.ID] = q.Quota } - // 3. hosts - // cfg.HostMap = make(map[string]Host) - // for _, host := range cfg.Hosts { - // cfg.HostMap[host.ID] = host - // } - return &cfg, nil } diff --git a/pkg/database/init.go b/pkg/database/init.go index fe601811..7d4b7028 100644 --- a/pkg/database/init.go +++ b/pkg/database/init.go @@ -51,7 +51,7 @@ func InitDBClients(defaultDBConfig, bkPaaSDBConfig *config.Database) { } // NOTE: change to app_code/app_secret verify api in the future - if BKPaaSDBClient == nil { + if BKPaaSDBClient == nil && bkPaaSDBConfig != nil { bkPaaSDBClientOnce.Do(func() { BKPaaSDBClient = NewDBClient(bkPaaSDBConfig) if err := BKPaaSDBClient.Connect(); err != nil { diff --git a/pkg/middleware/client.go b/pkg/middleware/client.go index 2a301658..462eca61 100644 --- a/pkg/middleware/client.go +++ b/pkg/middleware/client.go @@ -33,11 +33,11 @@ func NewClientAuthMiddleware(c *config.Config) gin.HandlerFunc { apiGatewayPublicKey = []byte(apigwCrypto.Key) } - return ClientAuthMiddleware(apiGatewayPublicKey) + return ClientAuthMiddleware(apiGatewayPublicKey, c.EnableBkAuth) } // ClientAuthMiddleware ... -func ClientAuthMiddleware(apiGatewayPublicKey []byte) gin.HandlerFunc { +func ClientAuthMiddleware(apiGatewayPublicKey []byte, enableBkAuth bool) gin.HandlerFunc { return func(c *gin.Context) { log.Debug("Middleware: ClientAuthMiddleware") @@ -80,7 +80,12 @@ func ClientAuthMiddleware(apiGatewayPublicKey []byte) gin.HandlerFunc { } // 2. validate from cache -> database - valid := cacheimpls.VerifyAppCodeAppSecret(appCode, appSecret) + var valid bool + if enableBkAuth { + valid = cacheimpls.VerifyAppCodeAppSecretFromAuth(appCode, appSecret) + } else { + valid = cacheimpls.VerifyAppCodeAppSecret(appCode, appSecret) + } if !valid { util.UnauthorizedJSONResponse(c, "app code or app secret wrong") c.Abort() diff --git a/pkg/middleware/client_test.go b/pkg/middleware/client_test.go index 33629c6f..dbebeb7f 100644 --- a/pkg/middleware/client_test.go +++ b/pkg/middleware/client_test.go @@ -29,7 +29,7 @@ func TestClientAuthMiddleware(t *testing.T) { // 1. without appCode appSecret r := gin.Default() - r.Use(ClientAuthMiddleware([]byte(""))) + r.Use(ClientAuthMiddleware([]byte(""), false)) util.NewTestRouter(r) req, _ := http.NewRequest("GET", "/ping", nil) From e1af85afcffba781818d2a2405f975b2a80c8929 Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 11 Jan 2022 16:49:01 +0800 Subject: [PATCH 08/12] fix(support/auth): a. remove sensitive info from log b.verify from get to post c.more error detail --- pkg/cacheimpls/local_app_code_secret.go | 11 +++++++++-- pkg/component/auth.go | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkg/cacheimpls/local_app_code_secret.go b/pkg/cacheimpls/local_app_code_secret.go index 6c082eb7..9ba08634 100644 --- a/pkg/cacheimpls/local_app_code_secret.go +++ b/pkg/cacheimpls/local_app_code_secret.go @@ -14,6 +14,7 @@ import ( "time" "github.com/TencentBlueKing/gopkg/cache" + "github.com/TencentBlueKing/gopkg/stringx" gocache "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" @@ -47,7 +48,10 @@ func VerifyAppCodeAppSecret(appCode, appSecret string) bool { } exists, err := LocalAppCodeAppSecretCache.GetBool(key) if err != nil { - log.Errorf("get app_code_app_secret from memory cache fail, key=%s, err=%s", key.Key(), err) + log.Errorf("get app_code_app_secret from memory cache fail, app_code=%s, app_secret=%s, err=%s", + appCode, + stringx.Truncate(appSecret, 6)+"******", + err) return false } return exists @@ -65,7 +69,10 @@ func VerifyAppCodeAppSecretFromAuth(appCode, appSecret string) bool { // 2. get from auth valid, err := component.BkAuth.Verify(appCode, appSecret) if err != nil { - log.Errorf("verify app_code_app_secret from auth fail, key=%s, err=%s", key, err) + log.Errorf("verify app_code_app_secret from auth fail, app_code=%s, app_secret=%s, err=%s", + appCode, + stringx.Truncate(appSecret, 6)+"******", + err) return false } diff --git a/pkg/component/auth.go b/pkg/component/auth.go index d4afb2db..ef7d54e3 100644 --- a/pkg/component/auth.go +++ b/pkg/component/auth.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/TencentBlueKing/gopkg/conv" "github.com/TencentBlueKing/gopkg/errorx" "github.com/parnurzeal/gorequest" @@ -88,7 +89,7 @@ func (c *authClient) call( request.Header.Set("X-BK-APP-SECRET", c.appSecret) // do request - resp, _, errs := request. + resp, respBody, errs := request. Send(data). EndStruct(&result, callbackFunc) @@ -108,28 +109,35 @@ func (c *authClient) call( return nil, err } if resp.StatusCode != http.StatusOK { - err = fmt.Errorf("gorequest statusCode is %d not 200", resp.StatusCode) + err = fmt.Errorf("gorequest statusCode is %d not 200, respBody=%s", + resp.StatusCode, conv.BytesToString(respBody)) logger.Errorf("call auth api %s fail , err=%s", path, err.Error()) return nil, errorWrapf(err, "status=%d", resp.StatusCode) } if result.Code != 0 { err = errors.New(result.Message) err = errorWrapf(err, "result.Code=%d", result.Code) - logger.Errorf("call auth api %s ok but code in response is not 0, err=%s", path, err.Error()) + logger.Errorf("call auth api %s ok but code in response is not 0, respBody=%s, err=%s", + path, conv.BytesToString(respBody), err.Error()) return nil, err } + fmt.Println("result.Data", result.Data) return result.Data, nil } // Verify will check bkAppCode, bkAppSecret is valid func (c *authClient) Verify(bkAppCode, bkAppSecret string) (bool, error) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf("component", "authClient.Verify") + path := fmt.Sprintf("/api/v1/apps/%s/access-keys/verify", bkAppCode) - data, err := c.call(GET, path, map[string]interface{}{ + data, err := c.call(POST, path, map[string]interface{}{ "bk_app_secret": bkAppSecret, }, 5) if err != nil { + err = errorWrapf(err, "verify app_code=`%s` fail", bkAppCode) + return false, err } matchI, ok := data["is_match"] From 2ccd043d82ee563b253cbd9c28baa624158023eb Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 11 Jan 2022 16:55:40 +0800 Subject: [PATCH 09/12] fix(lint): fix lints --- pkg/component/auth.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/component/auth.go b/pkg/component/auth.go index ef7d54e3..013776cb 100644 --- a/pkg/component/auth.go +++ b/pkg/component/auth.go @@ -121,7 +121,6 @@ func (c *authClient) call( path, conv.BytesToString(respBody), err.Error()) return nil, err } - fmt.Println("result.Data", result.Data) return result.Data, nil } From 4f3d734292ea6135cd22a6f5897b3fd76062d041 Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 11 Jan 2022 17:07:52 +0800 Subject: [PATCH 10/12] chore(version): to 1.9.4 --- VERSION | 2 +- release.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 77fee73a..d615fd0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.3 +1.9.4 diff --git a/release.md b/release.md index a2da06ef..06982985 100644 --- a/release.md +++ b/release.md @@ -1,3 +1,7 @@ +# 1.9.4 + +- add: bkauth support + # 1.9.3 - update: replace some lib with https://github.com/TencentBlueKing/gopkg From ed8cb08f510dd8dd20fa35fa94f8b78181c06e28 Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 12 Jan 2022 16:47:51 +0800 Subject: [PATCH 11/12] fix(cr/comments): fix cr comments --- pkg/component/auth.go | 27 ++++++++++++++------------- pkg/component/init.go | 4 ++-- pkg/config/config.go | 5 ----- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/pkg/component/auth.go b/pkg/component/auth.go index 013776cb..9a3342e5 100644 --- a/pkg/component/auth.go +++ b/pkg/component/auth.go @@ -37,23 +37,24 @@ func (r *AuthResponse) String() string { // AuthClient is the interface of auth client type AuthClient interface { - Verify(bkAppCode, bkAppSecret string) (bool, error) + Verify(appCode, appSecret string) (bool, error) } type authClient struct { Host string - appCode string - appSecret string + // iam's app_code/app_secret, credentials for bkauth + bkAppCode string + bkAppSecret string } // NewAuthClient will create a auth client -func NewAuthClient(host string, appCode string, appSecret string) AuthClient { +func NewAuthClient(host string, bkAppCode string, bkAppSecret string) AuthClient { host = strings.TrimRight(host, "/") return &authClient{ - Host: host, - appCode: appCode, - appSecret: appSecret, + Host: host, + bkAppCode: bkAppCode, + bkAppSecret: bkAppSecret, } } @@ -85,8 +86,8 @@ func (c *authClient) call( request = request.Timeout(callTimeout).Type("json") // set headers - request.Header.Set("X-BK-APP-CODE", c.appCode) - request.Header.Set("X-BK-APP-SECRET", c.appSecret) + request.Header.Set("X-BK-APP-CODE", c.bkAppCode) + request.Header.Set("X-BK-APP-SECRET", c.bkAppSecret) // do request resp, respBody, errs := request. @@ -126,16 +127,16 @@ func (c *authClient) call( } // Verify will check bkAppCode, bkAppSecret is valid -func (c *authClient) Verify(bkAppCode, bkAppSecret string) (bool, error) { +func (c *authClient) Verify(appCode, appSecret string) (bool, error) { errorWrapf := errorx.NewLayerFunctionErrorWrapf("component", "authClient.Verify") - path := fmt.Sprintf("/api/v1/apps/%s/access-keys/verify", bkAppCode) + path := fmt.Sprintf("/api/v1/apps/%s/access-keys/verify", appCode) data, err := c.call(POST, path, map[string]interface{}{ - "bk_app_secret": bkAppSecret, + "bk_app_secret": appSecret, }, 5) if err != nil { - err = errorWrapf(err, "verify app_code=`%s` fail", bkAppCode) + err = errorWrapf(err, "verify app_code=`%s` fail", appCode) return false, err } diff --git a/pkg/component/init.go b/pkg/component/init.go index 4b7658f4..33d4492a 100644 --- a/pkg/component/init.go +++ b/pkg/component/init.go @@ -39,8 +39,8 @@ func InitBkRemoteResourceClient() { BkRemoteResource = NewRemoteResourceClient() } -func InitBkAuthClient(bkAuthHost, appCode, appSecret string) { - BkAuth = NewAuthClient(bkAuthHost, appCode, appSecret) +func InitBkAuthClient(bkAuthHost, bkAppCode, bkAppSecret string) { + BkAuth = NewAuthClient(bkAuthHost, bkAppCode, bkAppSecret) } // CallbackFunc ... diff --git a/pkg/config/config.go b/pkg/config/config.go index 8ea29408..58ef5f25 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -191,11 +191,6 @@ func Load(v *viper.Viper) (*Config, error) { for _, host := range cfg.Hosts { cfg.HostMap[host.ID] = host } - if cfg.EnableBkAuth { - if len(cfg.HostMap) == 0 { - return nil, errors.New("hosts cannot be empty") - } - } // 4. init quota cfg.CustomQuotasMap = make(map[string]Quota) From afaab8ad7c8176e32fa751e86b8f079025bdf1da Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 12 Jan 2022 16:52:12 +0800 Subject: [PATCH 12/12] chore(copyright): add copyright --- pkg/component/auth.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/component/auth.go b/pkg/component/auth.go index 9a3342e5..30170893 100644 --- a/pkg/component/auth.go +++ b/pkg/component/auth.go @@ -1,3 +1,13 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available. + * Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * 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 component import (