From b00e36ca80da9acb27bd7fef397fbd0aa85d90f6 Mon Sep 17 00:00:00 2001 From: veypi Date: Wed, 25 Feb 2026 18:13:02 +0800 Subject: [PATCH] refactor(auth): Add permission validation and update design docs - Add validatePermission function to check depth/level consistency - Validate permission codes in Perm, Grant, and Check methods - LevelCreate requires odd depth, other levels require even depth - Update design.md examples from org to app/role model - Add RoleID field to Permission struct documentation --- auth/auth.go | 78 ++++++++++++++++++++--- auth/design.md | 167 ++++++++++++++++++++++--------------------------- 2 files changed, 145 insertions(+), 100 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index f3c2933..a42e549 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -226,6 +226,10 @@ func (a *appAuth) Perm(code string, level int) PermFunc { } // 检查权限 + if err := validatePermission(permID, level); err != nil { + panic(err) + } + if !a.Check(x.Context(), userID, permID, level) { return vigo.ErrNoPermission.WithString(fmt.Sprintf("requires permission: %s (level %d)", permID, level)) } @@ -234,13 +238,39 @@ func (a *appAuth) Perm(code string, level int) PermFunc { } } -func (a *appAuth) PermCreate(code string) PermFunc { return a.Perm(code, LevelCreate) } -func (a *appAuth) PermRead(code string) PermFunc { return a.Perm(code, LevelRead) } -func (a *appAuth) PermWrite(code string) PermFunc { return a.Perm(code, LevelWrite) } -func (a *appAuth) PermAdmin(code string) PermFunc { return a.Perm(code, LevelAdmin) } +func (a *appAuth) PermCreate(code string) PermFunc { + if err := validatePermission(code, LevelCreate); err != nil { + panic(err) + } + return a.Perm(code, LevelCreate) +} + +func (a *appAuth) PermRead(code string) PermFunc { + if err := validatePermission(code, LevelRead); err != nil { + panic(err) + } + return a.Perm(code, LevelRead) +} + +func (a *appAuth) PermWrite(code string) PermFunc { + if err := validatePermission(code, LevelWrite); err != nil { + panic(err) + } + return a.Perm(code, LevelWrite) +} + +func (a *appAuth) PermAdmin(code string) PermFunc { + if err := validatePermission(code, LevelAdmin); err != nil { + panic(err) + } + return a.Perm(code, LevelAdmin) +} // Grant 授予权限 func (a *appAuth) Grant(ctx context.Context, userID, permissionID string, level int) error { + if err := validatePermission(permissionID, level); err != nil { + return err + } // 检查是否存在 var count int64 cfg.DB().Model(&models.Permission{}). @@ -305,6 +335,9 @@ func (a *appAuth) RevokeRole(ctx context.Context, userID, roleCode string) error // Check 检查权限 func (a *appAuth) Check(ctx context.Context, userID, permissionID string, level int) bool { + if err := validatePermission(permissionID, level); err != nil { + panic(err) + } // 1. 获取用户在该作用域下的所有权限(直接权限 + 角色权限) perms, err := a.getUserPermissions(userID) if err != nil { @@ -357,11 +390,18 @@ func (a *appAuth) ListUsers(ctx context.Context, permissionID string) (map[strin var perms []models.Permission db := cfg.DB().Where("scope = ?", a.scope) - query := db.Where("permission_id = ?", permissionID) + conditions := []string{"permission_id = ?"} + args := []interface{}{permissionID} + if len(parents) > 0 { - query = query.Or("permission_id IN ? AND level = ?", parents, LevelAdmin) + conditions = append(conditions, "(permission_id IN ? AND level = ?)") + args = append(args, parents, LevelAdmin) } - query = query.Or("permission_id = ? AND level = ?", "*", LevelAdmin) + + conditions = append(conditions, "(permission_id = ? AND level = ?)") + args = append(args, "*", LevelAdmin) + + query := db.Where(strings.Join(conditions, " OR "), args...) if err := query.Find(&perms).Error; err != nil { return nil, err @@ -575,3 +615,27 @@ func extractToken(x *vigo.X) string { } return x.Request.URL.Query().Get("access_token") } + +func validatePermission(code string, level int) error { + if code == "*" { + if level != LevelAdmin { + return fmt.Errorf("wildcard * requires LevelAdmin") + } + return nil + } + + parts := strings.Split(code, ":") + depth := len(parts) + + if level == LevelCreate { + if depth%2 == 0 { + return fmt.Errorf("LevelCreate requires odd depth (resource type), got %d for %s", depth, code) + } + } else { + // Level 2, 4, 6, 7 + if depth%2 != 0 { + return fmt.Errorf("Level %d requires even depth (resource instance), got %d for %s", level, depth, code) + } + } + return nil +} diff --git a/auth/design.md b/auth/design.md index 558d978..ed9bd37 100644 --- a/auth/design.md +++ b/auth/design.md @@ -15,10 +15,10 @@ ### 1.2 示例 ``` -org → 第1层 (奇数) - 资源类型 -org:orgA → 第2层 (偶数) - 实例 -org:orgA:project → 第3层 (奇数) - 资源类型 -org:orgA:project:projB → 第4层 (偶数) - 实例 +app → 第1层 (奇数) - 资源类型 +app:vbase → 第2层 (偶数) - 实例 +app:vbase:role → 第3层 (奇数) - 资源类型 +app:vbase:role:admin → 第4层 (偶数) - 实例 ``` --- @@ -61,59 +61,48 @@ org:orgA:project:projB → 第4层 (偶数) - 实例 ``` 创建资源 (level 1) → 检查当前 permissionID 对应的奇数层 - → 例: "org:{orgID}:project" 检查 "org:{orgID}:project" 层 + → 例: "app:{appID}:role" 检查 "app:{appID}:role" 层 读取/更新/删除资源 (level 2,4,6,7) → 检查当前 permissionID 对应的偶数层 → 如无权限,递归向上检查父实例层 → 注意:只有 Level 7 (管理员) 权限才会向下继承,Level 2,4,6 不会继承 - → 例: "org:{orgID}:project:{projectID}" 先检查实例层,再检查 "org:{orgID}" + → 例: "app:{appID}:role:{roleID}" 先检查实例层,再检查 "app:{appID}" ``` --- ## 四、权限流程示例 -### 场景一:用户 A 创建组织 +### 场景一:用户 A 创建应用 ``` -1. 用户A创建组织 "公司A" +1. 用户A创建应用 "VBase" 2. 自动创建权限: - - PermissionID: "org:org_companyA" + - PermissionID: "app:vbase" - Level: 7 (创建者完全控制) ``` -### 场景二:用户 A 邀请用户 B 加入组织 +### 场景二:用户 A 邀请用户 B 加入应用 ``` -1. 用户A授予用户B: org:org_companyA level 2 (读) +1. 用户A授予用户B: app:vbase level 2 (读) 2. 用户B权限表: - - org:org_companyA level 2 + - app:vbase level 2 3. 用户B可执行: - - ✓ 读取 org_companyA + - ✓ 读取 vbase 信息 - ✗ 修改/删除 ``` -### 场景三:用户 B 创建项目 +### 场景三:用户 B 创建角色 ``` -前置: 用户B有 org:org_companyA level 2 (读),需要额外授权 +前置: 用户B有 app:vbase level 2 (读),需要额外授权 -1. 用户A授予用户B: org:org_companyA:project level 1 (创建项目) -2. 用户B创建项目 "项目X" +1. 用户A授予用户B: app:vbase:role level 1 (创建角色) +2. 用户B创建角色 "Editor" 3. 自动创建权限: - - PermissionID: "org:org_companyA:project:project_X" - - Level: 7 -``` - -### 场景四:用户 C 加入项目并创建文档 - -``` -1. 用户B授予用户C: org:org_companyA:project:project_X level 2 (读) -2. 用户C需要额外授权才能创建文档 -3. 用户C创建文档 "文档Y" -4. 自动创建权限: - - PermissionID: "org:org_companyA:project:project_X:doc:doc_Y" + - PermissionID: "app:vbase:role:editor" - Level: 7 ``` @@ -159,11 +148,11 @@ type Auth interface { // Perm 检查权限 // code: 权限码,支持动态解析 - // - 固定写法: "org:orgA" - // - 动态解析: "org:{orgID}" 从 path 获取 - // "org:{orgID@query}" 从 query 获取 - // "org:{orgID@header}" 从 header 获取 - // "org:{orgID@ctx}" 从 ctx 获取 + // - 固定写法: "app:vbase" + // - 动态解析: "app:{appID}" 从 path 获取 + // "app:{appID@query}" 从 query 获取 + // "app:{appID@header}" 从 header 获取 + // "app:{appID@ctx}" 从 ctx 获取 // level: 需要的权限等级 Perm(code string, level int) PermFunc @@ -185,7 +174,7 @@ type Auth interface { // Grant 授予权限 // 在创建资源、被授权等业务逻辑中调用 - // permissionID: 权限码,如 "org:orgA" + // permissionID: 权限码,如 "app:vbase" // level: 权限等级 Grant(ctx context.Context, userID, permissionID string, level int) error @@ -195,21 +184,21 @@ type Auth interface { // ========== 权限查询 ========== // Check 检查权限 不支持动态解析 - // permissionID: 完整的权限码,如 "org:orgA" + // permissionID: 完整的权限码,如 "app:vbase" Check(ctx context.Context, userID, permissionID string, level int) bool // ========== 资源列表查询 ========== // ListResources 查询用户在特定资源类型下的详细权限信息 - // 用于解决 "查询我有权限的 org 列表" 等场景 + // 用于解决 "查询我有权限的 app 列表" 等场景 // userID: 用户ID - // resourceType: 资源类型 (奇数层),如 "org" 或 "org:{orgID}:project" - // 返回: map[实例ID]权限等级 (如 {"orgA": 2, "orgB": 7}) + // resourceType: 资源类型 (奇数层),如 "app" 或 "app:{appID}:role" + // 返回: map[实例ID]权限等级 (如 {"vbase": 2, "other": 7}) ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error) // ListUsers 查询特定资源的所有协作者及其权限 - // 用于解决 "查看这个项目有哪些成员" 等场景 - // permissionID: 资源实例权限码,如 "org:orgA" + // 用于解决 "查看这个应用有哪些成员" 等场景 + // permissionID: 资源实例权限码,如 "app:vbase" // 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7}) ListUsers(ctx context.Context, permissionID string) (map[string]int, error) } @@ -220,6 +209,7 @@ type Auth interface { type Permission struct { ID string `json:"id"` UserID string `json:"user_id"` + RoleID string `json:"role_id"` PermissionID string `json:"permission_id"` Level int `json:"level"` CreatedAt int64 `json:"created_at"` @@ -237,8 +227,8 @@ type Permission struct { var Router = vigo.NewRouter() func init() { - // 创建组织 - 需要系统级 org 权限 - Router.Post("/orgs", cfg.Auth.PermCreate("org"), CreateOrg) + // 创建应用 - 需要系统级 app 权限 + Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) // 超级管理员接口 Router.Get("/admin/users", cfg.Auth.PermAdmin("*"), AdminListUsers) @@ -249,19 +239,19 @@ func init() { ```go func init() { - // 从路径参数获取 orgID (默认) - // GET /orgs/{orgID} - Router.Get("/orgs/{orgID}", cfg.Auth.PermRead("org:{orgID}"), GetOrg) + // 从路径参数获取 appID (默认) + // GET /apps/{appID} + Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) // 从 query 参数获取 - // GET /orgs?orgID=xxx - Router.Get("/orgs", cfg.Auth.PermRead("org:{orgID@query}"), GetOrg) + // GET /apps?appID=xxx + Router.Get("/apps", cfg.Auth.PermRead("app:{appID@query}"), GetApp) // 多层嵌套 - // GET /orgs/{orgID}/projects/{projectID} - Router.Get("/orgs/{orgID}/projects/{projectID}", - cfg.Auth.PermRead("org:{orgID}:project:{projectID}"), - GetProject, + // GET /apps/{appID}/roles/{roleID} + Router.Get("/apps/{appID}/roles/{roleID}", + cfg.Auth.PermRead("app:{appID}:role:{roleID}"), + GetRole, ) } ``` @@ -272,26 +262,22 @@ func init() { var Router = vigo.NewRouter().Use(cfg.Auth.Login()) func init() { - // 创建组织 - 系统级权限 - Router.Post("/orgs", cfg.Auth.PermCreate("org"), CreateOrg) - - // 列出我的组织 - 只需登录 - Router.Get("/orgs", ListMyOrgs) - - // 组织操作 - 从路径获取 - Router.Get("/orgs/{orgID}", cfg.Auth.PermRead("org:{orgID}"), GetOrg) - Router.Put("/orgs/{orgID}", cfg.Auth.PermWrite("org:{orgID}"), UpdateOrg) - Router.Delete("/orgs/{orgID}", cfg.Auth.PermAdmin("org:{orgID}"), DeleteOrg) - - // 项目操作 - 嵌套资源 - Router.Post("/orgs/{orgID}/projects", cfg.Auth.PermCreate("org:{orgID}:project"), CreateProject) - Router.Get("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermRead("org:{orgID}:project:{projectID}"), GetProject) - Router.Put("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermWrite("org:{orgID}:project:{projectID}"), UpdateProject) - Router.Delete("/orgs/{orgID}/projects/{projectID}", cfg.Auth.PermAdmin("org:{orgID}:project:{projectID}"), DeleteProject) - - // 文档操作 - Router.Post("/orgs/{orgID}/projects/{projectID}/docs", cfg.Auth.PermCreate("org:{orgID}:project:{projectID}:doc"), CreateDoc) - Router.Get("/orgs/{orgID}/projects/{projectID}/docs/{docID}", cfg.Auth.PermRead("org:{orgID}:project:{projectID}:doc:{docID}"), GetDoc) + // 创建应用 - 系统级权限 + Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp) + + // 列出我的应用 - 只需登录 + Router.Get("/apps", ListMyApps) + + // 应用操作 - 从路径获取 + Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp) + Router.Put("/apps/{appID}", cfg.Auth.PermWrite("app:{appID}"), UpdateApp) + Router.Delete("/apps/{appID}", cfg.Auth.PermAdmin("app:{appID}"), DeleteApp) + + // 角色操作 - 嵌套资源 + Router.Post("/apps/{appID}/roles", cfg.Auth.PermCreate("app:{appID}:role"), CreateRole) + Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.PermRead("app:{appID}:role:{roleID}"), GetRole) + Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.PermWrite("app:{appID}:role:{roleID}"), UpdateRole) + Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.PermAdmin("app:{appID}:role:{roleID}"), DeleteRole) } ``` @@ -299,10 +285,10 @@ func init() { | 语法 | 来源 | 示例 | |------|------|------| -| `{key}` | path 参数 | `{orgID}` | -| `{key@query}` | query 参数 | `{orgID@query}` | -| `{key@header}` | header | `{orgID@header}` | -| `{key@ctx}` | context | `{orgID@ctx}` | +| `{key}` | path 参数 | `{appID}` | +| `{key@query}` | query 参数 | `{appID@query}` | +| `{key@header}` | header | `{appID@header}` | +| `{key@ctx}` | context | `{appID@ctx}` | --- @@ -319,15 +305,15 @@ func init() { ### 7.2 Grant 调用时机 ```go -// 创建组织时 -func CreateOrg(x *vigo.X, req *CreateOrgReq) (*OrgResp, error) { - org := models.Org{Name: req.Name} - db.Create(&org) +// 创建应用时 +func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) { + app := models.App{Name: req.Name} + db.Create(&app) // 授予创建者完全控制权限 - cfg.Auth.Grant(x.Context(), userID, "org:"+org.ID, auth.LevelAdmin) + cfg.Auth.Grant(x.Context(), userID, "app:"+app.ID, auth.LevelAdmin) - return &OrgResp{Org: org}, nil + return &AppResp{App: app}, nil } ``` @@ -336,25 +322,20 @@ func CreateOrg(x *vigo.X, req *CreateOrgReq) (*OrgResp, error) { 对于资源列表(List)或搜索接口,推荐以下设计模式: 1. **全量管理接口**(如后台管理系统): - - 使用 `PermAdmin("*")` 或 `PermAdmin("org:*")`。 + - 使用 `PermAdmin("*")` 或 `PermAdmin("app:*")`。 - 这类接口返回所有数据,必须严格控制权限。 -2. **用户侧列表/搜索**(如“我的项目”): +2. **用户侧列表/搜索**(如“我的应用”): - **方式一(仅所有者)**: - 使用 `Login()` 确保登录。 - - 业务层:`db.Where("owner_id = ?", userID).Find(&orgs)` + - 业务层:`db.Where("owner_id = ?", userID).Find(&apps)` - **方式二(协作模式 - 使用 ListResources)**: - 调用 Auth 接口获取有权限的 ID 列表。 - - `perms, _ := auth.ListResources(ctx, userID, "org")` + - `perms, _ := auth.ListResources(ctx, userID, "app")` - `ids := keys(perms)` - - 业务层:`db.Where("id IN ?", ids).Find(&orgs)` + - 业务层:`db.Where("id IN ?", ids).Find(&apps)` - **方式三(混合模式)**: - 同时查询 owner_id 和 授权列表。 - `perms, _ := auth.ListResources(...)` - `ids := keys(perms)` - - `db.Where("owner_id = ? OR id IN ?", userID, ids).Find(&orgs)` - -3. **`PermXXX` 适用场景**: - - 针对 **特定资源实例** 的操作(URL 中包含 ID,如 `/projects/{id}`)。 - - 针对 **共享资源** 的访问控制。 - - 针对 **管理功能** 的鉴权。 + - `db.Where("owner_id = ? OR id IN ?", userID, ids).Find(&apps)`