diff --git a/devcloud/.vscode/launch.json b/devcloud/.vscode/launch.json new file mode 100644 index 0000000..8806066 --- /dev/null +++ b/devcloud/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "mcenter api server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "cwd": "${workspaceFolder}", + "args": ["start","-f","${workspaceFolder}/etc/application.toml"] + } + ] +} \ No newline at end of file diff --git a/devcloud/Makefile b/devcloud/Makefile new file mode 100644 index 0000000..7f97c43 --- /dev/null +++ b/devcloud/Makefile @@ -0,0 +1,47 @@ +PKG := "122.51.31.227/go-course/go18/devcloud" +MOD_DIR := $(shell go env GOPATH)/pkg/mod +PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/ | grep -v redis) +GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go) + +GIT_TAG := $() +BUILD_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +BUILD_COMMIT := ${shell git rev-parse HEAD} +BUILD_TIME := ${shell date '+%Y-%m-%d %H:%M:%S'} +BUILD_GO_VERSION := $(shell go version | grep -o 'go[0-9].[0-9].*') +VERSION_PATH := "github.com/infraboard/mcube/v2/ioc/config/application" +OUTPUT_NAME := "devcloud-api" + +.PHONY: all dep lint vet test test-coverage build clean + +all: build + +dep: ## Get the dependencies + @go mod tidy + +lint: ## Lint Golang files + @golint -set_exit_status ${PKG_LIST} + +vet: ## Run go vet + @go vet ${PKG_LIST} + +run: ## Run Devcloud + @go run main.go start + +build: dep ## Build the binary file + @go build -a -o dist/${OUTPUT_NAME} -ldflags "-s -w" -ldflags "-X '${VERSION_PATH}.GIT_BRANCH=${BUILD_BRANCH}' -X '${VERSION_PATH}.GIT_COMMIT=${BUILD_COMMIT}' -X '${VERSION_PATH}.BUILD_TIME=${BUILD_TIME}' -X '${VERSION_PATH}.GO_VERSION=${BUILD_GO_VERSION}' -X '${VERSION_PATH}.GIT_TAG=${GIT_TAG}'" ${MAIN_FILE} + +linux: dep ## Build the linux binary file + @GOOS=linux GOARCH=amd64 go build -a -o dist/${OUTPUT_NAME} -ldflags "-s -w" -ldflags "-X '${VERSION_PATH}.GIT_BRANCH=${BUILD_BRANCH}' -X '${VERSION_PATH}.GIT_COMMIT=${BUILD_COMMIT}' -X '${VERSION_PATH}.BUILD_TIME=${BUILD_TIME}' -X '${VERSION_PATH}.GO_VERSION=${BUILD_GO_VERSION}' -X '${VERSION_PATH}.GIT_TAG=${GIT_TAG}'" ${MAIN_FILE} + +image: dep ## Build the docker image + docker build -t ${IMAGE_VERSION} -f Dockerfile . + +test: ## Run unittests + @go test -short ${PKG_LIST} + +test-coverage: ## Run tests with coverage + @go test -short -coverprofile cover.out -covermode=atomic ${PKG_LIST} + @cat cover.out >> coverage.txt + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' \ No newline at end of file diff --git a/devcloud/README.md b/devcloud/README.md index 7debaca..f388387 100644 --- a/devcloud/README.md +++ b/devcloud/README.md @@ -9,4 +9,5 @@ devcloud: 研发云, 给产研团队(技术团队), 产品经理, 项目经理, + 应用构建: CI, 流水线发布, 应用的持续构建, Jenkins, 新一代的流程, 基于K8s Job自己设计 + 发布中心(Dev/Test/Pre/Pro)CD: 发布, 应用维护, 部署集群的维护 -多业务模块组成, 渐进式微服务开发方式 \ No newline at end of file +多业务模块组成, 渐进式微服务开发方式 + diff --git a/devcloud/dist/devcloud-api b/devcloud/dist/devcloud-api new file mode 100755 index 0000000..9144466 Binary files /dev/null and b/devcloud/dist/devcloud-api differ diff --git a/devcloud/mcenter/main.go b/devcloud/main.go similarity index 93% rename from devcloud/mcenter/main.go rename to devcloud/main.go index ec94f61..5f87654 100644 --- a/devcloud/mcenter/main.go +++ b/devcloud/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/infraboard/mcube/v2/ioc/server/cmd" - // 加载的业务对象 + // mcenter 业务对象 _ "122.51.31.227/go-course/go18/devcloud/mcenter/apps" // 非功能性模块 diff --git a/devcloud/mcenter/apps/policy/impl/permission_test.go b/devcloud/mcenter/apps/policy/impl/permission_test.go index a01b4dd..ca124a1 100644 --- a/devcloud/mcenter/apps/policy/impl/permission_test.go +++ b/devcloud/mcenter/apps/policy/impl/permission_test.go @@ -18,7 +18,7 @@ func TestQueryNamespace(t *testing.T) { func TestQueryEndpoint(t *testing.T) { req := policy.NewQueryEndpointRequest() - req.UserId = 1 + req.UserId = 2 req.NamespaceId = 1 set, err := impl.QueryEndpoint(ctx, req) if err != nil { diff --git a/devcloud/mcenter/apps/policy/impl/policy.go b/devcloud/mcenter/apps/policy/impl/policy.go index bbc68ef..d7ba982 100644 --- a/devcloud/mcenter/apps/policy/impl/policy.go +++ b/devcloud/mcenter/apps/policy/impl/policy.go @@ -36,6 +36,16 @@ func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPol set := types.New[*policy.Policy]() query := datasource.DBFromCtx(ctx).Model(&policy.Policy{}).Order("created_at desc") + if in.UserId != nil { + query = query.Where("user_id = ?", in.UserId) + } + if in.NamespaceId != nil { + query = query.Where("namespace_id = ?", in.NamespaceId) + } + if in.Enabled != nil { + query = query.Where("enabled = ?", in.Enabled) + } + err := query.Count(&set.Total).Error if err != nil { return nil, err diff --git a/devcloud/mcenter/apps/policy/impl/policy_test.go b/devcloud/mcenter/apps/policy/impl/policy_test.go index 1a95f0a..aa6abd8 100644 --- a/devcloud/mcenter/apps/policy/impl/policy_test.go +++ b/devcloud/mcenter/apps/policy/impl/policy_test.go @@ -21,7 +21,7 @@ func TestQueryPolicy(t *testing.T) { func TestCreatePolicy(t *testing.T) { req := policy.NewCreatePolicyRequest() req.SetNamespaceId(1) - req.UserId = 1 + req.UserId = 2 req.RoleId = 1 set, err := impl.CreatePolicy(ctx, req) if err != nil { diff --git a/devcloud/mcenter/apps/token/impl/token.go b/devcloud/mcenter/apps/token/impl/token.go index da95ffa..3975b4b 100644 --- a/devcloud/mcenter/apps/token/impl/token.go +++ b/devcloud/mcenter/apps/token/impl/token.go @@ -49,6 +49,10 @@ func (i *TokenServiceImpl) IssueToken(ctx context.Context, in *token.IssueTokenR } } + if tk.NamespaceId == 0 { + tk.NamespaceId = 1 + } + // 保持Token if err := datasource.DBFromCtx(ctx). Create(tk). diff --git a/devcloud/mcenter/apps/user/api/api.go b/devcloud/mcenter/apps/user/api/api.go index b45cd97..7ca65d6 100644 --- a/devcloud/mcenter/apps/user/api/api.go +++ b/devcloud/mcenter/apps/user/api/api.go @@ -37,6 +37,7 @@ func (h *UserRestfulApiHandler) Init() error { // 这个开关怎么生效 // 中间件需求读取接口的描述信息,来决定是否需要认证 Metadata(permission.Auth(true)). + Metadata(permission.Permission(true)). Metadata(permission.Resource("user")). Metadata(permission.Action("list")). Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")). diff --git a/devcloud/mcenter/apps/user/enum.go b/devcloud/mcenter/apps/user/enum.go index caffafd..4053c81 100644 --- a/devcloud/mcenter/apps/user/enum.go +++ b/devcloud/mcenter/apps/user/enum.go @@ -29,6 +29,7 @@ const ( type TYPE int32 const ( + // 普通用户 TYPE_SUB TYPE = 0 ) diff --git a/devcloud/mcenter/permission/README.md b/devcloud/mcenter/permission/README.md index 76e2704..9ca262c 100644 --- a/devcloud/mcenter/permission/README.md +++ b/devcloud/mcenter/permission/README.md @@ -4,16 +4,51 @@ 1. 路有装饰, 路有配置 ```go // required_auth=true/false -ws.Route(ws.GET("").To(h.QueryUser). - Doc("用户列表查询"). - Metadata(restfulspec.KeyOpenAPITags, tags). - // 这个开关怎么生效 - // 中间件需求读取接口的描述信息,来决定是否需要认证 - Metadata(permission.Auth(true)). - Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")). - Param(restful.QueryParameter("page_number", "页码").DataType("integer")). - Writes(Set{}). - Returns(200, "OK", Set{})) + // required_auth=true/false + ws.Route(ws.GET("").To(h.QueryUser). + Doc("用户列表查询"). + Metadata(restfulspec.KeyOpenAPITags, tags). + // 这个开关怎么生效 + // 中间件需求读取接口的描述信息,来决定是否需要认证 + Metadata(permission.Auth(true)). + Metadata(permission.Permission(true)). + Metadata(permission.Resource("user")). + Metadata(permission.Action("list")). + Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")). + Param(restful.QueryParameter("page_number", "页码").DataType("integer")). + Writes(Set{}). + Returns(200, "OK", Set{})) ``` -2. 加载鉴权处理逻辑(中间件) \ No newline at end of file +2. 加载鉴权处理逻辑(中间件) + + +```go +// 中间件的函数里面 +func (c *Checker) Check(r *restful.Request, w *restful.Response, next *restful.FilterChain) { + // 请求处理前, 对接口进行保护 + // 1. 知道用户当前访问的是哪个接口, 当前url 匹配到的路由是哪个 + // SelectedRoute, 它可以返回当前URL适配哪个路有, RouteReader + // 封装了一个函数 来获取Meta信息 NewEntryFromRestRouteReader + route := endpoint.NewEntryFromRestRouteReader(r.SelectedRoute()) + if route.RequiredAuth { + // 校验身份 + tk, err := c.CheckToken(r) + if err != nil { + response.Failed(w, err) + return + } + + // 校验权限 + if err := c.CheckPolicy(r, tk, route); err != nil { + response.Failed(w, err) + return + } + } + + // 请求处理 + next.ProcessFilter(r, w) + + // 请求处理后 +} +``` \ No newline at end of file diff --git a/devcloud/mcenter/permission/checker.go b/devcloud/mcenter/permission/checker.go index ed96a80..4c6a20c 100644 --- a/devcloud/mcenter/permission/checker.go +++ b/devcloud/mcenter/permission/checker.go @@ -4,11 +4,13 @@ import ( "context" "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint" + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy" "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token" "github.com/emicklei/go-restful/v3" "github.com/infraboard/mcube/v2/exception" "github.com/infraboard/mcube/v2/http/restful/response" "github.com/infraboard/mcube/v2/ioc" + "github.com/infraboard/mcube/v2/ioc/config/application" "github.com/infraboard/mcube/v2/ioc/config/gorestful" "github.com/infraboard/mcube/v2/ioc/config/log" "github.com/rs/zerolog" @@ -40,8 +42,8 @@ type Checker struct { ioc.ObjectImpl log *zerolog.Logger - token token.Service - // policy policy.Service + token token.Service + policy policy.Service } // 中间件对象名称 @@ -59,7 +61,7 @@ func (c *Checker) Priority() int { func (c *Checker) Init() error { c.log = log.Sub(c.Name()) c.token = token.GetService() - // c.policy = policy.GetService() + c.policy = policy.GetService() // 注册认证中间件 gorestful.RootRouter().Filter(c.Check) @@ -116,5 +118,53 @@ func (c *Checker) CheckToken(r *restful.Request) (*token.Token, error) { } func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoint.RouteEntry) error { + // 判断用户是否是超级管理员 + if tk.IsAdmin { + return nil + } + + // 角色校验 @Required('admin', '') + if route.HasRequiredRole() { + set, err := c.policy.QueryPolicy(r.Request.Context(), + policy.NewQueryPolicyRequest(). + SetNamespaceId(tk.NamespaceId). + SetUserId(tk.UserId). + SetExpired(false). + SetEnabled(true). + SetWithRole(true), + ) + if err != nil { + return exception.NewInternalServerError("%s", err.Error()) + } + hasPerm := false + for i := range set.Items { + p := set.Items[i] + if route.IsRequireRole(p.Role.Name) { + hasPerm = true + break + } + } + if !hasPerm { + return exception.NewPermissionDeny("无权限访问") + } + } + + // API权限校验 + if route.RequiredPerm { + validateReq := policy.NewValidateEndpointPermissionRequest() + validateReq.UserId = tk.UserId + validateReq.NamespaceId = tk.NamespaceId + validateReq.Service = application.Get().GetAppName() + validateReq.Method = route.Method + validateReq.Path = route.Path + resp, err := c.policy.ValidateEndpointPermission(r.Request.Context(), validateReq) + if err != nil { + return exception.NewInternalServerError("%s", err.Error()) + } + if !resp.HasPermission { + return exception.NewPermissionDeny("无权限访问") + } + } + return nil }