diff --git a/internal/domain/usecase/opa.go b/internal/domain/usecase/opa.go index 34c0e72..1a1f3a1 100644 --- a/internal/domain/usecase/opa.go +++ b/internal/domain/usecase/opa.go @@ -40,6 +40,8 @@ type RuleRequest struct { } type CheckOPAResp struct { - Allow bool `json:"allow"` - Request RuleRequest `json:"request"` + Allow bool `json:"allow"` + PolicyName string `json:"policy_name"` + PlainCode bool `json:"plain_code"` // 是否為明碼顯示 + Request RuleRequest `json:"request"` } diff --git a/internal/logic/permissionservice/check_permission_by_role_logic.go b/internal/logic/permissionservice/check_permission_by_role_logic.go index 288ceef..4dd7f63 100644 --- a/internal/logic/permissionservice/check_permission_by_role_logic.go +++ b/internal/logic/permissionservice/check_permission_by_role_logic.go @@ -1,10 +1,11 @@ package permissionservicelogic import ( - "context" - "ark-permission/gen_result/pb/permission" + "ark-permission/internal/domain/usecase" "ark-permission/internal/svc" + ers "code.30cm.net/wanderland/library-go/errors" + "context" "github.com/zeromicro/go-zero/core/logx" ) @@ -23,9 +24,36 @@ func NewCheckPermissionByRoleLogic(ctx context.Context, svcCtx *svc.ServiceConte } } +type checkPermissionReq struct { + Role string `json:"role" validate:"required"` + Method string `json:"method" validate:"required"` + Path string `json:"path" validate:"required"` +} + // CheckPermissionByRole 透過角色 ID 來檢視權限 func (l *CheckPermissionByRoleLogic) CheckPermissionByRole(in *permission.CheckPermissionByRoleReq) (*permission.PermissionResp, error) { - // todo: add your logic here and delete this line + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&checkPermissionReq{ + Role: in.GetRole(), + Method: in.GetMethod(), + Path: in.GetPath(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } - return &permission.PermissionResp{}, nil + rbacPermission, err := l.svcCtx.PolicyAgent.CheckRBACPermission(l.ctx, usecase.CheckReq{ + Roles: []string{in.GetRole()}, + Method: in.GetMethod(), + Path: in.GetPath(), + }) + + if err != nil { + return nil, ers.Forbidden(err.Error()) + } + + return &permission.PermissionResp{ + Allow: rbacPermission.Allow, + PermissionName: rbacPermission.PolicyName, + PlainCode: rbacPermission.PlainCode, + }, nil } diff --git a/internal/logic/permissionservice/list_permission_status_logic.go b/internal/logic/permissionservice/list_permission_status_logic.go index f63dd12..8492c5c 100644 --- a/internal/logic/permissionservice/list_permission_status_logic.go +++ b/internal/logic/permissionservice/list_permission_status_logic.go @@ -42,7 +42,7 @@ func (l *ListPermissionStatusLogic) ListPermissionStatus(in *permission.NoneReq) Id: v.Id, // 權限 ID ParentId: v.Parent.Int64, // 上級權限的ID Name: v.Name, // 權限名稱 - Status: permission.PermissionStatus(v.Status), // 權限開啟或關閉,判斷時上級權限如果關閉,下級也應該關閉對此人關閉 + Status: permission.PermissionStatus(v.Status), // 權限開啟或關閉,判斷時上級權限如果關閉,下級也應該關閉對此人關閉 // TODO 還沒做到,目前忠實呈現 Type: t.ToString(), // 前台權限,還是後台權限,還是其他中台之類的 }) exist[v.Name] = struct{}{} diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index 99e615f..f13e9fe 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -3,11 +3,14 @@ package svc import ( "ark-permission/internal/config" "ark-permission/internal/domain/repository" + domainUseCase "ark-permission/internal/domain/usecase" "ark-permission/internal/lib/required" "ark-permission/internal/model" repo "ark-permission/internal/repository" + "ark-permission/internal/usecase" ers "code.30cm.net/wanderland/library-go/errors" "code.30cm.net/wanderland/library-go/errors/code" + "context" "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/sqlx" ) @@ -18,8 +21,8 @@ type ServiceContext struct { Validate required.Validate Redis redis.Redis TokenRedisRepo repository.TokenRepository - - Permission model.PermissionModel + Permission model.PermissionModel + PolicyAgent domainUseCase.OpaUseCase } func NewServiceContext(c config.Config) *ServiceContext { @@ -29,9 +32,18 @@ func NewServiceContext(c config.Config) *ServiceContext { } ers.Scope = code.CloudEPPermission - sqlConn := sqlx.NewMysql(c.DB.DsnString) + pa, err := usecase.NewOpaUseCase(usecase.OpaUseCaseParam{}) + if err != nil { + panic(err) + } + // TODO policy 權限還要再組合過,我的角度會把 UID 當成一種 RoleID 這樣就可以針對每一個人克制權限,,初期也可以使用最簡安的來做統一,再想一下 + err = pa.LoadPolicy(context.Background(), []domainUseCase.Policy{}) + if err != nil { + panic(err) + } + return &ServiceContext{ Config: c, Validate: required.MustValidator(), @@ -39,6 +51,7 @@ func NewServiceContext(c config.Config) *ServiceContext { TokenRedisRepo: repo.NewTokenRepository(repo.TokenRepositoryParam{ Store: newRedis, }), - Permission: model.NewPermissionModel(sqlConn), + Permission: model.NewPermissionModel(sqlConn), + PolicyAgent: pa, } } diff --git a/internal/usecase/opa.go b/internal/usecase/opa.go index ecd4260..5e3cd6a 100644 --- a/internal/usecase/opa.go +++ b/internal/usecase/opa.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/open-policy-agent/opa/rego" "github.com/zeromicro/go-zero/core/logx" + "strings" ) //go:embed "rule.rego" @@ -114,49 +115,83 @@ func policiesToMap(policy usecase.Policy) map[string]any { func convertToCheckOPAResp(data map[string]any) (usecase.CheckOPAResp, error) { var response usecase.CheckOPAResp - if allow, ok := data["allow"].(bool); ok { - response.Allow = allow - } else { + // 解析 allow 欄位 + allow, ok := data["allow"].(bool) + if !ok { return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'allow' field") } + response.Allow = allow + // 解析 policy_name 欄位 + policyData, ok := data["policy_name"].(map[string]any) + if ok { + if name, ok := policyData["name"].(string); ok { + response.PolicyName = name + response.PlainCode = strings.HasSuffix(name, ".plan_code") + } + } else { + return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'policy_name' field") + } + + // 解析 request 欄位 requestData, ok := data["request"].(map[string]any) if !ok { return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'request' field") } - response.Request.Method = requestData["method"].(string) - response.Request.Path = requestData["path"].(string) + // 解析 method 和 path + response.Request.Method, ok = requestData["method"].(string) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'method' field") + } + response.Request.Path, ok = requestData["path"].(string) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'path' field") + } + // 解析 policies 欄位 policiesData, ok := requestData["policies"].([]any) if !ok { return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'policies' field") } - response.Request.Policies = make([]usecase.Policy, 0, len(policiesData)) - for _, policyData := range policiesData { - p := policyData.(map[string]any) - methodsData := p["methods"].([]any) + response.Request.Policies = make([]usecase.Policy, len(policiesData)) + for i, policyData := range policiesData { + policyMap, ok := policyData.(map[string]any) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("invalid policy format") + } + // 解析 methods + methodsData, ok := policyMap["methods"].([]any) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'methods' field in policy") + } methods := make([]string, len(methodsData)) - for i, m := range methodsData { - methods[i] = m.(string) + for j, m := range methodsData { + methods[j], ok = m.(string) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("invalid method format in policy") + } } - - policy := usecase.Policy{ + // 組裝 policy + response.Request.Policies[i] = usecase.Policy{ Methods: methods, - Name: p["name"].(string), - Path: p["path"].(string), - Role: p["role"].(string), + Name: policyMap["name"].(string), + Path: policyMap["path"].(string), + Role: policyMap["role"].(string), } - response.Request.Policies = append(response.Request.Policies, policy) } + // 解析 roles 欄位 rolesData, ok := requestData["roles"].([]any) if !ok { return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'roles' field") } response.Request.Roles = make([]string, len(rolesData)) for i, r := range rolesData { - response.Request.Roles[i] = r.(string) + response.Request.Roles[i], ok = r.(string) + if !ok { + return usecase.CheckOPAResp{}, fmt.Errorf("invalid role format") + } } return response, nil diff --git a/internal/usecase/opa_test.go b/internal/usecase/opa_test.go index c9d42ad..9fab5c8 100644 --- a/internal/usecase/opa_test.go +++ b/internal/usecase/opa_test.go @@ -40,7 +40,7 @@ func TestMustOpaUseCase(t *testing.T) { }) assert.NoError(t, err) - // 定义测试用例表 + // 定義測試表 tests := []struct { name string req usecase.CheckReq diff --git a/internal/usecase/rule.rego b/internal/usecase/rule.rego index 3aa4aab..5058a00 100644 --- a/internal/usecase/rule.rego +++ b/internal/usecase/rule.rego @@ -32,3 +32,12 @@ allow if { valid_role(input.roles, policy.role) method_match(input.method, policy.methods) } + +# 返回當前符合的策略名稱 +policy_name := { + "name": policy.name| + policy := input.policies[_] + key_match(input.path, policy.path); + valid_role(input.roles, policy.role); + method_match(input.method, policy.methods) +} \ No newline at end of file