|
|
|
@ -161,79 +161,105 @@ type Role struct {
|
|
|
|
|------|------|----------|
|
|
|
|
|------|------|----------|
|
|
|
|
| "" | 无条件,始终匹配 | 通用权限 |
|
|
|
|
| "" | 无条件,始终匹配 | 通用权限 |
|
|
|
|
| "owner" | 资源所有者 | 只能操作自己的数据 |
|
|
|
|
| "owner" | 资源所有者 | 只能操作自己的数据 |
|
|
|
|
| "org_member" | 组织成员 | 组织内数据访问 |
|
|
|
|
| "org_member" | 项目成员 | 项目内数据访问 |
|
|
|
|
| "org_owner" | 组织所有者 | 所有者权限判断 |
|
|
|
|
| "org_owner" | 项目所有者 | 项目所有者权限判断 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 权限检查流程
|
|
|
|
## 3. 权限检查流程
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 流程图
|
|
|
|
### 3.1 权限检查流程
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
根据是否有项目上下文 (`org_id`),权限检查分为两种方式:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 流程 A: 平台级权限检查 (无项目上下文)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 请求进入 │
|
|
|
|
│ 请求进入 (无 org_id) │
|
|
|
|
│ user, org, │
|
|
|
|
│ user, resource, action │
|
|
|
|
│ resource, action│
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
└────────┬────────┘
|
|
|
|
|
|
|
|
│
|
|
|
|
│
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐ 命中 ┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐ 命中 ┌─────────────────┐
|
|
|
|
│ 检查缓存 │────────────→│ 返回缓存结果 │
|
|
|
|
│ 检查缓存 │────────────→│ 返回缓存结果 │
|
|
|
|
│ Redis │ │ allow/deny │
|
|
|
|
└────────────┬────────────────┘ └─────────────────┘
|
|
|
|
└────────┬────────┘ └─────────────────┘
|
|
|
|
|
|
|
|
│ 未命中
|
|
|
|
│ 未命中
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 1. 检查组织所有者 │────────┐
|
|
|
|
│ 1. 检查是否是资源所有者 │
|
|
|
|
└────────┬────────┘ │
|
|
|
|
│ owner_id == user_id │
|
|
|
|
│ 是 │
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
▼ │
|
|
|
|
│ 是
|
|
|
|
┌──────────┐ │
|
|
|
|
▼
|
|
|
|
│ 返回 Allow│────────────┘
|
|
|
|
┌──────────┐
|
|
|
|
|
|
|
|
│ 返回 Allow│
|
|
|
|
└──────────┘
|
|
|
|
└──────────┘
|
|
|
|
│ 否
|
|
|
|
│ 否
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 2. 获取用户角色 │
|
|
|
|
│ 2. 加载平台级默认策略 │
|
|
|
|
│ 从 OrgMember │
|
|
|
|
│ personal:* 策略 │
|
|
|
|
│ 获取 role_ids │
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
└────────┬────────┘
|
|
|
|
|
|
|
|
│
|
|
|
|
│
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 3. 获取角色策略 │
|
|
|
|
│ 3. 评估策略 (Deny优先) │
|
|
|
|
│ 解析所有策略 │
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
│ 去重合并 │
|
|
|
|
│
|
|
|
|
└────────┬────────┘
|
|
|
|
┌─────┴─────┐
|
|
|
|
|
|
|
|
▼ ▼
|
|
|
|
|
|
|
|
┌───────┐ ┌───────┐
|
|
|
|
|
|
|
|
│ Deny │ │ Allow │
|
|
|
|
|
|
|
|
└───────┘ └───────┘
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 流程 B: 项目级权限检查 (有项目上下文)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
|
|
|
|
│ 请求进入 (有 org_id) │
|
|
|
|
|
|
|
|
│ user, org_id, resource... │
|
|
|
|
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
│
|
|
|
|
│
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐ 命中 ┌─────────────────┐
|
|
|
|
│ 4. 检查 Deny 策略│────────┐
|
|
|
|
│ 检查缓存 │────────────→│ 返回缓存结果 │
|
|
|
|
│ 高优先级优先 │ │
|
|
|
|
└────────────┬────────────────┘ └─────────────────┘
|
|
|
|
└────────┬────────┘ │
|
|
|
|
│ 未命中
|
|
|
|
│ 匹配 Deny │
|
|
|
|
|
|
|
|
▼ │
|
|
|
|
|
|
|
|
┌──────────┐ │
|
|
|
|
|
|
|
|
│返回 Deny │────────────┘
|
|
|
|
|
|
|
|
│ 缓存结果 │
|
|
|
|
|
|
|
|
└──────────┘
|
|
|
|
|
|
|
|
│ 无 Deny
|
|
|
|
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 5. 检查 Allow 策略│────────┐
|
|
|
|
│ 1. 检查是否是项目所有者 │
|
|
|
|
└────────┬────────┘ │
|
|
|
|
│ org.owner_id == user_id │────────┐
|
|
|
|
│ 匹配 Allow │
|
|
|
|
└────────────┬────────────────┘ │
|
|
|
|
|
|
|
|
│ 是 │
|
|
|
|
▼ │
|
|
|
|
▼ │
|
|
|
|
┌──────────┐ │
|
|
|
|
┌──────────┐ │
|
|
|
|
│返回 Allow│────────────┘
|
|
|
|
│ 返回 Allow│───────────────────┘
|
|
|
|
│ 缓存结果 │
|
|
|
|
|
|
|
|
└──────────┘
|
|
|
|
└──────────┘
|
|
|
|
│ 无 Allow
|
|
|
|
│ 否
|
|
|
|
|
|
|
|
▼
|
|
|
|
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
|
|
|
|
│ 2. 获取用户在项目的角色 │
|
|
|
|
|
|
|
|
│ 从 OrgMember 表查询 │
|
|
|
|
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
|
|
|
|
│
|
|
|
|
|
|
|
|
▼
|
|
|
|
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
|
|
|
|
│ 3. 获取角色关联的策略 │
|
|
|
|
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
|
|
|
|
│
|
|
|
|
▼
|
|
|
|
▼
|
|
|
|
┌─────────────────┐
|
|
|
|
┌─────────────────────────────┐
|
|
|
|
│ 6. 默认拒绝 │────────→ 返回 Deny
|
|
|
|
│ 4. 评估策略 (Deny优先) │
|
|
|
|
└─────────────────┘ 缓存结果
|
|
|
|
│ Deny策略 → Allow策略 │
|
|
|
|
|
|
|
|
└────────────┬────────────────┘
|
|
|
|
|
|
|
|
│
|
|
|
|
|
|
|
|
┌─────┴─────┐
|
|
|
|
|
|
|
|
▼ ▼
|
|
|
|
|
|
|
|
┌───────┐ ┌───────┐
|
|
|
|
|
|
|
|
│ Deny │ │ Allow │
|
|
|
|
|
|
|
|
└───────┘ └───────┘
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 决策规则
|
|
|
|
### 3.2 决策规则
|
|
|
|
@ -247,63 +273,134 @@ type Role struct {
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 使用指南
|
|
|
|
## 4. 使用指南
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 检查权限
|
|
|
|
### 4.1 平台级权限检查
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**场景**:用户管理自己的账号、创建新项目
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 检查用户是否可以更新自己的信息
|
|
|
|
|
|
|
|
// orgID 传空字符串,表示平台级检查
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "", "user", "update", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": userID, // 传入自己的ID
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// 结果:允许(满足 owner 条件)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否可以创建新项目
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "", "org", "create", nil)
|
|
|
|
|
|
|
|
// 结果:允许(平台级默认策略允许创建项目)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否可以更新其他用户信息
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "", "user", "update", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": otherUserID, // 传入他人ID
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// 结果:拒绝(不满足 owner 条件)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 项目级权限检查
|
|
|
|
|
|
|
|
|
|
|
|
**代码示例:**
|
|
|
|
**场景**:在项目内操作资源
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
|
import "github.com/veypi/vbase/internal/service"
|
|
|
|
// 检查用户在项目内是否有管理员权限
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "project-123", "member", "invite", nil)
|
|
|
|
|
|
|
|
// 结果取决于用户在 project-123 中的角色
|
|
|
|
|
|
|
|
|
|
|
|
func SomeHandler(x *vigo.X) error {
|
|
|
|
// 项目所有者检查(快速路径)
|
|
|
|
|
|
|
|
// 如果 userID == org.OwnerID,直接返回允许
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 普通成员检查
|
|
|
|
|
|
|
|
// 1. 查询 OrgMember 获取角色
|
|
|
|
|
|
|
|
// 2. 查询 Role 获取策略
|
|
|
|
|
|
|
|
// 3. 评估策略
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 完整示例:更新用户信息
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
func UpdateUser(x *vigo.X, req *UpdateRequest) error {
|
|
|
|
userID := middleware.CurrentUser(x)
|
|
|
|
userID := middleware.CurrentUser(x)
|
|
|
|
orgID := middleware.CurrentOrg(x)
|
|
|
|
targetUserID := req.UserID
|
|
|
|
|
|
|
|
|
|
|
|
checker := service.NewPermissionChecker()
|
|
|
|
checker := service.NewPermissionChecker()
|
|
|
|
result, err := checker.Check(userID, orgID, "user", "update", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": targetUserID,
|
|
|
|
// 场景1: 更新自己的信息(平台级)
|
|
|
|
|
|
|
|
if targetUserID == userID {
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "", "user", "update", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": userID,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil || !result.Allowed {
|
|
|
|
|
|
|
|
return vigo.ErrForbidden.WithString(result.Reason)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 执行更新...
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 场景2: 管理员更新其他用户信息(需要平台级管理员权限)
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, "", "user", "update", nil)
|
|
|
|
|
|
|
|
if err != nil || !result.Allowed {
|
|
|
|
|
|
|
|
return vigo.ErrForbidden.WithString("only admin can update other users")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 执行更新...
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 完整示例:项目内操作
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
func DeleteProjectResource(x *vigo.X, req *DeleteResourceRequest) error {
|
|
|
|
|
|
|
|
userID := middleware.CurrentUser(x)
|
|
|
|
|
|
|
|
orgID := middleware.CurrentOrg(x) // 从请求头 X-Org-ID 获取
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if orgID == "" {
|
|
|
|
|
|
|
|
return vigo.ErrArgInvalid.WithString("org_id required")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checker := service.NewPermissionChecker()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户在项目内是否有删除资源的权限
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, orgID, "resource", "delete", map[string]any{
|
|
|
|
|
|
|
|
"resource_id": req.ResourceID,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil || !result.Allowed {
|
|
|
|
if err != nil || !result.Allowed {
|
|
|
|
return vigo.ErrForbidden.WithString(result.Reason)
|
|
|
|
return vigo.ErrForbidden.WithString(result.Reason)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 继续处理...
|
|
|
|
// 执行删除...
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 创建自定义策略
|
|
|
|
### 4.5 创建自定义策略
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 项目级策略(需要关联到具体项目)
|
|
|
|
policy := &model.Policy{
|
|
|
|
policy := &model.Policy{
|
|
|
|
Code: "custom:resource:action",
|
|
|
|
Code: "custom:db:admin",
|
|
|
|
Name: "自定义策略",
|
|
|
|
Name: "数据库管理员",
|
|
|
|
Description: "允许特定用户操作",
|
|
|
|
Description: "管理项目数据库",
|
|
|
|
Resource: "resource_name",
|
|
|
|
Resource: "database",
|
|
|
|
Action: "action_name", // create/read/update/delete
|
|
|
|
Action: "*", // 所有操作
|
|
|
|
Effect: model.EffectAllow,
|
|
|
|
Effect: model.EffectAllow,
|
|
|
|
Condition: "owner", // owner/org_member/空
|
|
|
|
Condition: "", // 无条件限制
|
|
|
|
Priority: 50,
|
|
|
|
Scope: "project", // 项目级策略
|
|
|
|
|
|
|
|
OrgID: "project-123", // 所属项目
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model.DB.Create(policy)
|
|
|
|
model.DB.Create(policy)
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 创建角色并授权
|
|
|
|
// 将策略添加到角色
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 创建角色
|
|
|
|
|
|
|
|
role := &model.Role{
|
|
|
|
role := &model.Role{
|
|
|
|
OrgID: orgID,
|
|
|
|
OrgID: "project-123",
|
|
|
|
Name: "部门管理员",
|
|
|
|
Name: "DBAdmin",
|
|
|
|
Description: "管理部门成员",
|
|
|
|
PolicyIDs: "custom:db:admin",
|
|
|
|
PolicyIDs: "policy1,policy2,policy3",
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
model.DB.Create(role)
|
|
|
|
model.DB.Create(role)
|
|
|
|
|
|
|
|
|
|
|
|
// 分配给用户
|
|
|
|
// 分配角色给用户
|
|
|
|
member := &model.OrgMember{
|
|
|
|
member := &model.OrgMember{
|
|
|
|
OrgID: orgID,
|
|
|
|
OrgID: "project-123",
|
|
|
|
UserID: userID,
|
|
|
|
UserID: "user-456",
|
|
|
|
RoleIDs: role.ID,
|
|
|
|
RoleIDs: role.ID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
model.DB.Create(member)
|
|
|
|
model.DB.Create(member)
|
|
|
|
@ -381,19 +478,102 @@ Condition: "org_member"
|
|
|
|
SysPolicyRoleManage = "sys:role:manage"
|
|
|
|
SysPolicyRoleManage = "sys:role:manage"
|
|
|
|
Resource: "role"
|
|
|
|
Resource: "role"
|
|
|
|
Action: "*"
|
|
|
|
Action: "*"
|
|
|
|
Condition: ""
|
|
|
|
Condition: "admin"
|
|
|
|
|
|
|
|
|
|
|
|
// 读取策略
|
|
|
|
// 读取策略
|
|
|
|
SysPolicyPolicyRead = "sys:policy:read"
|
|
|
|
SysPolicyPolicyRead = "sys:policy:read"
|
|
|
|
Resource: "policy"
|
|
|
|
Resource: "policy"
|
|
|
|
Action: "read"
|
|
|
|
Action: "read"
|
|
|
|
Condition: ""
|
|
|
|
Condition: "org_member"
|
|
|
|
|
|
|
|
|
|
|
|
// 管理策略
|
|
|
|
// 管理策略
|
|
|
|
SysPolicyPolicyManage = "sys:policy:manage"
|
|
|
|
SysPolicyPolicyManage = "sys:policy:manage"
|
|
|
|
Resource: "policy"
|
|
|
|
Resource: "policy"
|
|
|
|
Action: "*"
|
|
|
|
Action: "*"
|
|
|
|
Condition: ""
|
|
|
|
Condition: "admin"
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.5 资源级策略(BaaS 核心资源)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**数据库资源 (database)**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 查询数据库
|
|
|
|
|
|
|
|
SysPolicyDBRead = "sys:db:read"
|
|
|
|
|
|
|
|
Resource: "database"
|
|
|
|
|
|
|
|
Action: "read"
|
|
|
|
|
|
|
|
Condition: "org_member"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 管理数据库(创建表、修改结构)
|
|
|
|
|
|
|
|
SysPolicyDBManage = "sys:db:manage"
|
|
|
|
|
|
|
|
Resource: "database"
|
|
|
|
|
|
|
|
Action: "*"
|
|
|
|
|
|
|
|
Condition: "developer"
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**存储资源 (storage)**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 读取存储文件
|
|
|
|
|
|
|
|
SysPolicyStorageRead = "sys:storage:read"
|
|
|
|
|
|
|
|
Resource: "storage"
|
|
|
|
|
|
|
|
Action: "read"
|
|
|
|
|
|
|
|
Condition: "org_member"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 上传/删除文件
|
|
|
|
|
|
|
|
SysPolicyStorageWrite = "sys:storage:write"
|
|
|
|
|
|
|
|
Resource: "storage"
|
|
|
|
|
|
|
|
Action: "create,update,delete"
|
|
|
|
|
|
|
|
Condition: "developer"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 管理存储桶
|
|
|
|
|
|
|
|
SysPolicyStorageAdmin = "sys:storage:admin"
|
|
|
|
|
|
|
|
Resource: "storage"
|
|
|
|
|
|
|
|
Action: "*"
|
|
|
|
|
|
|
|
Condition: "admin"
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**函数资源 (function)**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 调用函数
|
|
|
|
|
|
|
|
SysPolicyFunctionCall = "sys:function:call"
|
|
|
|
|
|
|
|
Resource: "function"
|
|
|
|
|
|
|
|
Action: "call"
|
|
|
|
|
|
|
|
Condition: "org_member"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 管理函数(部署、删除)
|
|
|
|
|
|
|
|
SysPolicyFunctionManage = "sys:function:manage"
|
|
|
|
|
|
|
|
Resource: "function"
|
|
|
|
|
|
|
|
Action: "create,update,delete"
|
|
|
|
|
|
|
|
Condition: "developer"
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**API 密钥管理 (apikey)**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 读取 API 密钥(只能读取自己创建的密钥)
|
|
|
|
|
|
|
|
SysPolicyAPIKeyRead = "sys:apikey:read"
|
|
|
|
|
|
|
|
Resource: "apikey"
|
|
|
|
|
|
|
|
Action: "read"
|
|
|
|
|
|
|
|
Condition: "owner"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 管理 API 密钥
|
|
|
|
|
|
|
|
SysPolicyAPIKeyManage = "sys:apikey:manage"
|
|
|
|
|
|
|
|
Resource: "apikey"
|
|
|
|
|
|
|
|
Action: "*"
|
|
|
|
|
|
|
|
Condition: "developer"
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.6 平台超级管理员策略
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 超级管理员 - 拥有所有权限
|
|
|
|
|
|
|
|
SysPolicySuperAdmin = "sys:super:admin"
|
|
|
|
|
|
|
|
Resource: "*"
|
|
|
|
|
|
|
|
Action: "*"
|
|
|
|
|
|
|
|
Condition: "is_super_admin"
|
|
|
|
|
|
|
|
Priority: 999
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
@ -440,18 +620,84 @@ service.ClearOrgPermissionCache(orgID)
|
|
|
|
2. **职责分离**: 敏感操作需要多个角色共同完成
|
|
|
|
2. **职责分离**: 敏感操作需要多个角色共同完成
|
|
|
|
3. **策略命名规范**: `{resource}:{action}:{condition}`
|
|
|
|
3. **策略命名规范**: `{resource}:{action}:{condition}`
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 角色设计
|
|
|
|
### 7.2 BaaS 平台角色设计
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VBase 作为 Supabase-like BaaS 平台,角色设计覆盖 **2C个人开发者** 和 **2B团队/企业** 场景:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 2C 个人开发者场景
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
个人项目角色体系:
|
|
|
|
|
|
|
|
├── owner (项目所有者)
|
|
|
|
|
|
|
|
│ └── 全部权限(可以删除项目)
|
|
|
|
|
|
|
|
└── developer (开发者)
|
|
|
|
|
|
|
|
└── 读写资源、调用 API
|
|
|
|
```
|
|
|
|
```
|
|
|
|
组织层级角色体系:
|
|
|
|
|
|
|
|
|
|
|
|
个人开发者通常自己就是项目所有者,不需要复杂的角色体系。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 2B 团队协作场景
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
团队项目角色体系:
|
|
|
|
├── owner (所有者)
|
|
|
|
├── owner (所有者)
|
|
|
|
│ └── 全部权限
|
|
|
|
│ └── 全部权限、可删除项目、管理账单
|
|
|
|
├── admin (管理员)
|
|
|
|
├── admin (管理员)
|
|
|
|
│ └── 管理成员、角色、策略
|
|
|
|
│ └── 管理成员、角色、资源设置
|
|
|
|
├── manager (部门经理)
|
|
|
|
├── developer (开发者)
|
|
|
|
│ └── 管理部门成员
|
|
|
|
│ ├── 数据库: 创建表、查询、修改结构
|
|
|
|
└── member (普通成员)
|
|
|
|
│ ├── 存储: 上传/下载文件、管理存储桶
|
|
|
|
└── 基础访问权限
|
|
|
|
│ ├── 函数: 部署、调用、删除函数
|
|
|
|
|
|
|
|
│ └── API密钥: 创建、管理
|
|
|
|
|
|
|
|
├── viewer (访客/只读)
|
|
|
|
|
|
|
|
│ └── 查看数据、只读访问
|
|
|
|
|
|
|
|
└── auditor (审计员 - 可选)
|
|
|
|
|
|
|
|
└── 查看日志、审计记录
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 角色权限对照表
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 角色 | 成员管理 | 资源管理 | 数据读写 | API密钥 | 项目设置 |
|
|
|
|
|
|
|
|
|------|----------|----------|----------|---------|----------|
|
|
|
|
|
|
|
|
| **owner** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
|
|
|
|
|
|
| **admin** | ✓ | ✓ | ✓ | ✓ | ✗ |
|
|
|
|
|
|
|
|
| **developer** | ✗ | ✓ | ✓ | ✓ | ✗ |
|
|
|
|
|
|
|
|
| **viewer** | ✗ | ✗ | 只读 | ✗ | ✗ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 系统角色配置示例
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// Owner 角色 - 绑定所有权限
|
|
|
|
|
|
|
|
var RoleOwner = model.Role{
|
|
|
|
|
|
|
|
Name: "owner",
|
|
|
|
|
|
|
|
Description: "项目所有者",
|
|
|
|
|
|
|
|
PolicyIDs: "sys:org:admin,sys:member:manage,sys:role:manage,sys:policy:manage",
|
|
|
|
|
|
|
|
IsSystem: true,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Admin 角色 - 管理权限
|
|
|
|
|
|
|
|
var RoleAdmin = model.Role{
|
|
|
|
|
|
|
|
Name: "admin",
|
|
|
|
|
|
|
|
Description: "项目管理员",
|
|
|
|
|
|
|
|
PolicyIDs: "sys:member:manage,sys:role:read,sys:db:manage,sys:storage:admin,sys:function:manage",
|
|
|
|
|
|
|
|
IsSystem: true,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Developer 角色 - 开发权限
|
|
|
|
|
|
|
|
var RoleDeveloper = model.Role{
|
|
|
|
|
|
|
|
Name: "developer",
|
|
|
|
|
|
|
|
Description: "开发者",
|
|
|
|
|
|
|
|
PolicyIDs: "sys:db:manage,sys:storage:write,sys:function:manage,sys:apikey:manage",
|
|
|
|
|
|
|
|
IsSystem: true,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Viewer 角色 - 只读权限
|
|
|
|
|
|
|
|
var RoleViewer = model.Role{
|
|
|
|
|
|
|
|
Name: "viewer",
|
|
|
|
|
|
|
|
Description: "访客",
|
|
|
|
|
|
|
|
PolicyIDs: "sys:org:read,sys:db:read,sys:storage:read,sys:function:call",
|
|
|
|
|
|
|
|
IsSystem: true,
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.3 权限检查位置
|
|
|
|
### 7.3 权限检查位置
|
|
|
|
@ -478,7 +724,79 @@ func UpdateUser(userID string, data map[string]any) error {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.4 权限测试
|
|
|
|
### 7.4 BaaS 平台权限场景示例
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**场景1: 个人开发者创建项目**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 小明是个人开发者,注册后创建项目
|
|
|
|
|
|
|
|
// 1. 创建项目(平台级权限检查)
|
|
|
|
|
|
|
|
result, _ := checker.Check("xiaoming_user_id", "", "org", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 允许(personal:org:create 策略)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 小明成为项目所有者
|
|
|
|
|
|
|
|
// org.owner_id = "xiaoming_user_id"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 小明在项目内操作数据库(项目级权限)
|
|
|
|
|
|
|
|
result, _ = checker.Check("xiaoming_user_id", "my-project", "database", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 允许(小明是项目所有者)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**场景2: 团队协作开发**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// CEO 创建 WebApp 项目,邀请 CTO 为管理员,员工为开发者
|
|
|
|
|
|
|
|
// - CEO: owner
|
|
|
|
|
|
|
|
// - CTO: admin
|
|
|
|
|
|
|
|
// - 员工A: developer
|
|
|
|
|
|
|
|
// - 外包人员: viewer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CTO 邀请成员(有 member:manage 权限)
|
|
|
|
|
|
|
|
result, _ := checker.Check("cto_user_id", "webapp-project", "member", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 允许(admin 角色有 member:manage 策略)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 员工A 创建数据库表(有 db:manage 权限)
|
|
|
|
|
|
|
|
result, _ = checker.Check("employee_a_id", "webapp-project", "database", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 允许(developer 角色有 db:manage 策略)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 外包人员 尝试删除表(viewer 没有 delete 权限)
|
|
|
|
|
|
|
|
result, _ = checker.Check("contractor_id", "webapp-project", "database", "delete", nil)
|
|
|
|
|
|
|
|
// 结果: 拒绝(viewer 只有 db:read 权限)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**场景3: API 密钥访问控制**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 开发者创建 API 密钥用于后端服务
|
|
|
|
|
|
|
|
// 只能查看自己创建的密钥
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result, _ := checker.Check("developer_id", "project-123", "apikey", "read", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": "developer_id",
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// 结果: 允许(满足 owner 条件)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试查看他人创建的密钥
|
|
|
|
|
|
|
|
result, _ = checker.Check("developer_id", "project-123", "apikey", "read", map[string]any{
|
|
|
|
|
|
|
|
"owner_id": "other_developer_id",
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// 结果: 拒绝(不满足 owner 条件)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**场景4: 跨项目资源访问**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 用户小明在 project-A 是开发者,在 project-B 是访客
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 project-A 上传文件
|
|
|
|
|
|
|
|
result, _ := checker.Check("xiaoming_id", "project-A", "storage", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 允许(developer 角色有 storage:write 权限)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 project-B 上传文件
|
|
|
|
|
|
|
|
result, _ = checker.Check("xiaoming_id", "project-B", "storage", "create", nil)
|
|
|
|
|
|
|
|
// 结果: 拒绝(viewer 角色只有 storage:read 权限)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 7.5 权限测试
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
|
func TestPermissionCheck(t *testing.T) {
|
|
|
|
func TestPermissionCheck(t *testing.T) {
|
|
|
|
@ -531,11 +849,20 @@ func TestPermissionCheck(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
|
|
**检查步骤:**
|
|
|
|
**检查步骤:**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**平台级权限检查失败(org_id 为空):**
|
|
|
|
|
|
|
|
|
|
|
|
1. 确认用户已登录
|
|
|
|
1. 确认用户已登录
|
|
|
|
2. 确认用户是组织成员
|
|
|
|
2. 确认操作的是自己的资源(检查 owner_id 条件)
|
|
|
|
3. 检查用户角色
|
|
|
|
3. 检查用户是否有平台级管理员权限
|
|
|
|
4. 检查角色绑定的策略
|
|
|
|
4. 检查默认个人策略是否生效
|
|
|
|
5. 检查策略的 Effect 和 Condition
|
|
|
|
|
|
|
|
|
|
|
|
**项目级权限检查失败(有 org_id):**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1. 确认用户是项目成员(OrgMember 状态为 active)
|
|
|
|
|
|
|
|
2. 检查用户角色是否正确分配
|
|
|
|
|
|
|
|
3. 检查角色绑定的策略
|
|
|
|
|
|
|
|
4. 检查策略的 Effect 和 Condition
|
|
|
|
|
|
|
|
5. 确认资源所属的项目是否正确
|
|
|
|
|
|
|
|
|
|
|
|
**调试日志:**
|
|
|
|
**调试日志:**
|
|
|
|
|
|
|
|
|
|
|
|
@ -584,7 +911,9 @@ func (pc *PermissionChecker) evaluateCondition(p *model.Policy, userID, orgID st
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 添加资源类型
|
|
|
|
### 9.2 添加 BaaS 资源类型
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**步骤1: 定义资源常量**
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
|
// 在 policy.go 中添加资源常量
|
|
|
|
// 在 policy.go 中添加资源常量
|
|
|
|
@ -592,10 +921,74 @@ const (
|
|
|
|
ResourceUser = "user"
|
|
|
|
ResourceUser = "user"
|
|
|
|
ResourceOrg = "org"
|
|
|
|
ResourceOrg = "org"
|
|
|
|
ResourceMember = "member"
|
|
|
|
ResourceMember = "member"
|
|
|
|
ResourceCustom = "custom_resource" // 新增
|
|
|
|
ResourceDatabase = "database"
|
|
|
|
|
|
|
|
ResourceStorage = "storage"
|
|
|
|
|
|
|
|
ResourceFunction = "function"
|
|
|
|
|
|
|
|
ResourceAPIKey = "apikey"
|
|
|
|
|
|
|
|
ResourceCustom = "custom_resource" // 新增资源类型
|
|
|
|
)
|
|
|
|
)
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**步骤2: 创建资源级策略**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 为新增资源创建策略
|
|
|
|
|
|
|
|
func (pc *PermissionChecker) getCustomResourcePolicies() []model.Policy {
|
|
|
|
|
|
|
|
return []model.Policy{
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "custom:read",
|
|
|
|
|
|
|
|
Name: "读取自定义资源",
|
|
|
|
|
|
|
|
Resource: ResourceCustom,
|
|
|
|
|
|
|
|
Action: "read",
|
|
|
|
|
|
|
|
Effect: model.EffectAllow,
|
|
|
|
|
|
|
|
Condition: "org_member",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Code: "custom:manage",
|
|
|
|
|
|
|
|
Name: "管理自定义资源",
|
|
|
|
|
|
|
|
Resource: ResourceCustom,
|
|
|
|
|
|
|
|
Action: "create,update,delete",
|
|
|
|
|
|
|
|
Effect: model.EffectAllow,
|
|
|
|
|
|
|
|
Condition: "developer",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**步骤3: 在角色中使用**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 创建新角色使用自定义策略
|
|
|
|
|
|
|
|
customRole := &model.Role{
|
|
|
|
|
|
|
|
OrgID: orgID,
|
|
|
|
|
|
|
|
Name: "custom_manager",
|
|
|
|
|
|
|
|
Description: "自定义资源管理员",
|
|
|
|
|
|
|
|
PolicyIDs: "custom:read,custom:manage",
|
|
|
|
|
|
|
|
Scope: "resource",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 多租户资源隔离
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在 BaaS 平台中,资源需要严格的项目隔离:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
// 查询时自动添加 org_id 过滤
|
|
|
|
|
|
|
|
func ListResources(userID, orgID string) ([]Resource, error) {
|
|
|
|
|
|
|
|
// 检查用户在项目中的权限
|
|
|
|
|
|
|
|
checker := service.NewPermissionChecker()
|
|
|
|
|
|
|
|
result, err := checker.Check(userID, orgID, "custom_resource", "read", nil)
|
|
|
|
|
|
|
|
if err != nil || !result.Allowed {
|
|
|
|
|
|
|
|
return nil, vigo.ErrForbidden
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查询该项目的资源(自动隔离)
|
|
|
|
|
|
|
|
var resources []Resource
|
|
|
|
|
|
|
|
err = model.DB.Where("org_id = ?", orgID).Find(&resources).Error
|
|
|
|
|
|
|
|
return resources, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 10. 参考
|
|
|
|
## 10. 参考
|
|
|
|
|