feat/all-pa #3
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{}{}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestMustOpaUseCase(t *testing.T) {
|
|||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 定义测试用例表
|
||||
// 定義測試表
|
||||
tests := []struct {
|
||||
name string
|
||||
req usecase.CheckReq
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue