package usecase import ( "ark-permission/internal/domain" "ark-permission/internal/domain/usecase" ers "code.30cm.net/wanderland/library-go/errors" "context" _ "embed" "fmt" "github.com/open-policy-agent/opa/rego" "github.com/zeromicro/go-zero/core/logx" ) //go:embed "rule.rego" var policy []byte type OpaUseCaseParam struct{} type opaUseCase struct { // 查詢這個角色是否可用 allowQuery rego.PreparedEvalQuery policies []map[string]any } func (o *opaUseCase) GetPolicy(ctx context.Context) []map[string]any { return o.policies } func (o *opaUseCase) CheckRBACPermission(ctx context.Context, req usecase.CheckReq) (usecase.CheckOPAResp, error) { results, err := o.allowQuery.Eval(ctx, rego.EvalInput(map[string]any{ "roles": req.Roles, "path": req.Path, "method": req.Method, "policies": o.policies, })) if err != nil { return usecase.CheckOPAResp{}, domain.PermissionGetDataError(fmt.Sprintf("failed to evaluate policy: %v", err)) } if len(results) == 0 { logx.WithCallerSkip(1).WithFields( logx.Field("roles", req.Roles), logx.Field("path", req.Path), logx.Field("method", req.Method), logx.Field("policies", o.policies), ).Error("empty RBAC policy result, possibly due to an incorrect query string or policy") return usecase.CheckOPAResp{}, domain.PermissionGetDataError("no results returned from policy evaluation") } data, ok := results[0].Expressions[0].Value.(map[string]any) if !ok { return usecase.CheckOPAResp{}, domain.PermissionGetDataError("unexpected data format in policy evaluation result") } resp, err := convertToCheckOPAResp(data) if !ok { return usecase.CheckOPAResp{}, domain.PermissionGetDataError(err.Error()) } return resp, nil } // LoadPolicy 逐一處理 Policy 並且處理超時 func (o *opaUseCase) LoadPolicy(ctx context.Context, input []usecase.Policy) error { mapped := make([]map[string]any, 0, len(input)) for i, policy := range input { select { case <-ctx.Done(): // 監控是否超時或取消 logx.WithCallerSkip(1).WithFields( logx.Field("input", input), ).Error("LoadPolicy context time out") // TODO 部分完成後處理,記錄日誌並返回成功的部分,或應該要重新 Loading.... o.policies = append(o.policies, mapped...) return ers.SystemTimeoutError(fmt.Sprintf("operation timed out after processing %d policies: %v", i, ctx.Err())) default: // 繼續處理 mapped = append(mapped, policiesToMap(policy)) } } // 完成所有更新後紀錄,整個取代 policies o.policies = mapped return nil } func NewOpaUseCase(param OpaUseCaseParam) (usecase.OpaUseCase, error) { module := rego.Module("policy", string(policy)) ctx := context.Background() var allowQueryErr error uc := &opaUseCase{} uc.allowQuery, allowQueryErr = rego.New( rego.Query("data.rbac"), // 要尋找的話 data 必帶, rbac = rego package , allow 是要query 啥 module, ).PrepareForEval(ctx) if allowQueryErr != nil { return &opaUseCase{}, domain.PermissionGetDataError(allowQueryErr.Error()) } return uc, nil } // 內部使用 func policiesToMap(policy usecase.Policy) map[string]any { return map[string]any{ "methods": policy.Methods, "name": policy.Name, "path": policy.Path, "role": policy.Role, } } func convertToCheckOPAResp(data map[string]any) (usecase.CheckOPAResp, error) { var response usecase.CheckOPAResp if allow, ok := data["allow"].(bool); ok { response.Allow = allow } else { return usecase.CheckOPAResp{}, fmt.Errorf("missing or invalid 'allow' field") } 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) 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) methods := make([]string, len(methodsData)) for i, m := range methodsData { methods[i] = m.(string) } policy := usecase.Policy{ Methods: methods, Name: p["name"].(string), Path: p["path"].(string), Role: p["role"].(string), } response.Request.Policies = append(response.Request.Policies, policy) } 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) } return response, nil }