feat/all-pa #3

Merged
daniel.w merged 5 commits from feat/all-pa into main 2024-08-18 14:09:52 +00:00
7 changed files with 117 additions and 30 deletions
Showing only changes of commit ae14cf2b00 - Show all commits

View File

@ -40,6 +40,8 @@ type RuleRequest struct {
} }
type CheckOPAResp struct { type CheckOPAResp struct {
Allow bool `json:"allow"` Allow bool `json:"allow"`
Request RuleRequest `json:"request"` PolicyName string `json:"policy_name"`
PlainCode bool `json:"plain_code"` // 是否為明碼顯示
Request RuleRequest `json:"request"`
} }

View File

@ -1,10 +1,11 @@
package permissionservicelogic package permissionservicelogic
import ( import (
"context"
"ark-permission/gen_result/pb/permission" "ark-permission/gen_result/pb/permission"
"ark-permission/internal/domain/usecase"
"ark-permission/internal/svc" "ark-permission/internal/svc"
ers "code.30cm.net/wanderland/library-go/errors"
"context"
"github.com/zeromicro/go-zero/core/logx" "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 來檢視權限 // CheckPermissionByRole 透過角色 ID 來檢視權限
func (l *CheckPermissionByRoleLogic) CheckPermissionByRole(in *permission.CheckPermissionByRoleReq) (*permission.PermissionResp, error) { 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
} }

View File

@ -42,7 +42,7 @@ func (l *ListPermissionStatusLogic) ListPermissionStatus(in *permission.NoneReq)
Id: v.Id, // 權限 ID Id: v.Id, // 權限 ID
ParentId: v.Parent.Int64, // 上級權限的ID ParentId: v.Parent.Int64, // 上級權限的ID
Name: v.Name, // 權限名稱 Name: v.Name, // 權限名稱
Status: permission.PermissionStatus(v.Status), // 權限開啟或關閉,判斷時上級權限如果關閉,下級也應該關閉對此人關閉 Status: permission.PermissionStatus(v.Status), // 權限開啟或關閉,判斷時上級權限如果關閉,下級也應該關閉對此人關閉 // TODO 還沒做到,目前忠實呈現
Type: t.ToString(), // 前台權限,還是後台權限,還是其他中台之類的 Type: t.ToString(), // 前台權限,還是後台權限,還是其他中台之類的
}) })
exist[v.Name] = struct{}{} exist[v.Name] = struct{}{}

View File

@ -3,11 +3,14 @@ package svc
import ( import (
"ark-permission/internal/config" "ark-permission/internal/config"
"ark-permission/internal/domain/repository" "ark-permission/internal/domain/repository"
domainUseCase "ark-permission/internal/domain/usecase"
"ark-permission/internal/lib/required" "ark-permission/internal/lib/required"
"ark-permission/internal/model" "ark-permission/internal/model"
repo "ark-permission/internal/repository" repo "ark-permission/internal/repository"
"ark-permission/internal/usecase"
ers "code.30cm.net/wanderland/library-go/errors" ers "code.30cm.net/wanderland/library-go/errors"
"code.30cm.net/wanderland/library-go/errors/code" "code.30cm.net/wanderland/library-go/errors/code"
"context"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
) )
@ -18,8 +21,8 @@ type ServiceContext struct {
Validate required.Validate Validate required.Validate
Redis redis.Redis Redis redis.Redis
TokenRedisRepo repository.TokenRepository TokenRedisRepo repository.TokenRepository
Permission model.PermissionModel
Permission model.PermissionModel PolicyAgent domainUseCase.OpaUseCase
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
@ -29,9 +32,18 @@ func NewServiceContext(c config.Config) *ServiceContext {
} }
ers.Scope = code.CloudEPPermission ers.Scope = code.CloudEPPermission
sqlConn := sqlx.NewMysql(c.DB.DsnString) 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{ return &ServiceContext{
Config: c, Config: c,
Validate: required.MustValidator(), Validate: required.MustValidator(),
@ -39,6 +51,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
TokenRedisRepo: repo.NewTokenRepository(repo.TokenRepositoryParam{ TokenRedisRepo: repo.NewTokenRepository(repo.TokenRepositoryParam{
Store: newRedis, Store: newRedis,
}), }),
Permission: model.NewPermissionModel(sqlConn), Permission: model.NewPermissionModel(sqlConn),
PolicyAgent: pa,
} }
} }

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"github.com/open-policy-agent/opa/rego" "github.com/open-policy-agent/opa/rego"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
"strings"
) )
//go:embed "rule.rego" //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) { func convertToCheckOPAResp(data map[string]any) (usecase.CheckOPAResp, error) {
var response usecase.CheckOPAResp var response usecase.CheckOPAResp
if allow, ok := data["allow"].(bool); ok { // 解析 allow 欄位
response.Allow = allow allow, ok := data["allow"].(bool)
} else { if !ok {
return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'allow' field") 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) requestData, ok := data["request"].(map[string]any)
if !ok { if !ok {
return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'request' field") return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'request' field")
} }
response.Request.Method = requestData["method"].(string) // 解析 method 和 path
response.Request.Path = requestData["path"].(string) 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) policiesData, ok := requestData["policies"].([]any)
if !ok { if !ok {
return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'policies' field") return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'policies' field")
} }
response.Request.Policies = make([]usecase.Policy, 0, len(policiesData)) response.Request.Policies = make([]usecase.Policy, len(policiesData))
for _, policyData := range policiesData { for i, policyData := range policiesData {
p := policyData.(map[string]any) policyMap, ok := policyData.(map[string]any)
methodsData := p["methods"].([]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)) methods := make([]string, len(methodsData))
for i, m := range methodsData { for j, m := range methodsData {
methods[i] = m.(string) methods[j], ok = m.(string)
if !ok {
return usecase.CheckOPAResp{}, fmt.Errorf("invalid method format in policy")
}
} }
// 組裝 policy
policy := usecase.Policy{ response.Request.Policies[i] = usecase.Policy{
Methods: methods, Methods: methods,
Name: p["name"].(string), Name: policyMap["name"].(string),
Path: p["path"].(string), Path: policyMap["path"].(string),
Role: p["role"].(string), Role: policyMap["role"].(string),
} }
response.Request.Policies = append(response.Request.Policies, policy)
} }
// 解析 roles 欄位
rolesData, ok := requestData["roles"].([]any) rolesData, ok := requestData["roles"].([]any)
if !ok { if !ok {
return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'roles' field") return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'roles' field")
} }
response.Request.Roles = make([]string, len(rolesData)) response.Request.Roles = make([]string, len(rolesData))
for i, r := range 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 return response, nil

View File

@ -40,7 +40,7 @@ func TestMustOpaUseCase(t *testing.T) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
// 定义测试用例 // 定義測試
tests := []struct { tests := []struct {
name string name string
req usecase.CheckReq req usecase.CheckReq

View File

@ -32,3 +32,12 @@ allow if {
valid_role(input.roles, policy.role) valid_role(input.roles, policy.role)
method_match(input.method, policy.methods) 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)
}