You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/api/middleware/policy.go

225 lines
4.9 KiB
Go

1 week ago
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.
//
package middleware
import (
"strings"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/models"
"github.com/veypi/vigo"
)
// PolicyEvaluator 策略评估器
type PolicyEvaluator struct {
checker *Checker
}
// NewPolicyEvaluator 创建策略评估器
func (c *Checker) NewPolicyEvaluator() *PolicyEvaluator {
return &PolicyEvaluator{checker: c}
}
// PermissionCheck 权限检查参数
type PermissionCheck struct {
Resource string // 资源: user, org, role, policy, project, etc.
Action string // 操作: create, read, update, delete, list, etc.
OrgID string // 组织ID可选
OwnerID string // 资源所有者ID用于条件判断
}
// HasPermission 检查是否有权限
func (pe *PolicyEvaluator) HasPermission(check PermissionCheck) bool {
if pe.checker.userID == "" {
return false
}
// 平台超级管理员拥有所有权限
if pe.isPlatformAdmin() {
return true
}
// 获取用户所有策略
policies := pe.getUserPolicies(check.OrgID)
// 按优先级排序deny > allow
// 先检查 deny 策略
for _, policy := range policies {
if policy.Effect != models.PolicyEffectDeny {
continue
}
if pe.matchPolicy(policy, check) {
return false
}
}
// 再检查 allow 策略
for _, policy := range policies {
if policy.Effect != models.PolicyEffectAllow {
continue
}
if pe.matchPolicy(policy, check) {
return true
}
}
return false
}
// isPlatformAdmin 检查是否为平台管理员
func (pe *PolicyEvaluator) isPlatformAdmin() bool {
return pe.checker.IsOrgOwner()
}
// getUserPolicies 获取用户拥有的所有策略
func (pe *PolicyEvaluator) getUserPolicies(orgID string) []models.Policy {
var policies []models.Policy
roles := pe.checker.GetUserRoles()
if len(roles) == 0 {
return policies
}
for _, roleID := range roles {
var role models.Role
if err := cfg.DB().First(&role, "id = ?", roleID).Error; err != nil {
continue
}
if role.PolicyIDs == "" {
continue
}
policyIDs := strings.Split(role.PolicyIDs, ",")
var rolePolicies []models.Policy
if err := cfg.DB().Where("id IN ?", policyIDs).Find(&rolePolicies).Error; err != nil {
continue
}
policies = append(policies, rolePolicies...)
}
return policies
}
// matchPolicy 匹配策略
func (pe *PolicyEvaluator) matchPolicy(policy models.Policy, check PermissionCheck) bool {
if !matchPattern(policy.Resource, check.Resource) {
return false
}
if !matchPattern(policy.Action, check.Action) {
return false
}
if policy.Condition != "" {
if !pe.checkCondition(policy.Condition, check) {
return false
}
}
return true
}
// matchPattern 模式匹配(支持通配符 *
func matchPattern(pattern, value string) bool {
if pattern == "*" {
return true
}
if pattern == value {
return true
}
if strings.HasSuffix(pattern, ":*") {
prefix := strings.TrimSuffix(pattern, ":*")
return strings.HasPrefix(value, prefix+":")
}
return false
}
// checkCondition 检查条件
func (pe *PolicyEvaluator) checkCondition(condition string, check PermissionCheck) bool {
conditions := strings.Split(condition, ",")
for _, c := range conditions {
c = strings.TrimSpace(c)
switch c {
case "owner":
if check.OwnerID != "" && pe.checker.UserID() == check.OwnerID {
return true
}
case "org_member":
if pe.checker.OrgID() != "" {
return true
}
case "admin":
if pe.checker.IsOrgAdmin() {
return true
}
}
}
return false
}
// RequirePermission 要求特定权限
func (c *Checker) RequirePermission(resource, action string) error {
pe := c.NewPolicyEvaluator()
if !pe.HasPermission(PermissionCheck{
Resource: resource,
Action: action,
OrgID: c.OrgID(),
}) {
return vigo.ErrForbidden.WithString("permission denied: " + resource + ":" + action)
}
return nil
}
// Permission 基于策略的权限检查中间件
func Permission(resource, action string) func(*vigo.X) error {
return func(x *vigo.X) error {
checker := NewChecker(x)
return checker.RequirePermission(resource, action)
}
}
// PermissionWithOwner 带资源所有者检查的权限中间件
func PermissionWithOwner(resource, action, ownerIDKey string) func(*vigo.X) error {
return func(x *vigo.X) error {
checker := NewChecker(x)
ownerID := ""
if oid, ok := x.Get(ownerIDKey).(string); ok {
ownerID = oid
}
if ownerID != "" && checker.UserID() == ownerID {
return nil
}
return checker.RequirePermission(resource, action)
}
}
// AdminOrOwner 管理员或所有者权限
func AdminOrOwner(ownerIDKey string) func(*vigo.X) error {
return func(x *vigo.X) error {
checker := NewChecker(x)
if checker.IsOrgAdmin() {
return nil
}
ownerID := ""
if oid, ok := x.Get(ownerIDKey).(string); ok {
ownerID = oid
}
if ownerID != "" && checker.UserID() == ownerID {
return nil
}
return vigo.ErrForbidden.WithString("admin or owner permission required")
}
}