feat/all-pa #3
|
@ -41,5 +41,7 @@ type RuleRequest struct {
|
||||||
|
|
||||||
type CheckOPAResp struct {
|
type CheckOPAResp struct {
|
||||||
Allow bool `json:"allow"`
|
Allow bool `json:"allow"`
|
||||||
|
PolicyName string `json:"policy_name"`
|
||||||
|
PlainCode bool `json:"plain_code"` // 是否為明碼顯示
|
||||||
Request RuleRequest `json:"request"`
|
Request RuleRequest `json:"request"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
return &permission.PermissionResp{}, nil
|
Role: in.GetRole(),
|
||||||
|
Method: in.GetMethod(),
|
||||||
|
Path: in.GetPath(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
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{}{}
|
||||||
|
|
|
@ -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(),
|
||||||
|
@ -40,5 +52,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
Store: newRedis,
|
Store: newRedis,
|
||||||
}),
|
}),
|
||||||
Permission: model.NewPermissionModel(sqlConn),
|
Permission: model.NewPermissionModel(sqlConn),
|
||||||
|
PolicyAgent: pa,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 := usecase.Policy{
|
// 組裝 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue