| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- package middleware
- import (
- "bytes"
- "context"
- "encoding/json"
- "io"
- "net/http"
- "strconv"
- "strings"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- type hierarchyUserLookup interface {
- GetByID(context.Context, int64) (*service.User, error)
- }
- // UserHierarchyGuard belongs after AdminAuth on the admin route group. It
- // protects all user writes, including balance, TOTP, identities and bulk limits.
- func UserHierarchyGuard(userService *service.UserService) gin.HandlerFunc {
- if userService == nil {
- return userHierarchyGuard(nil)
- }
- return userHierarchyGuard(userService)
- }
- func userHierarchyGuard(users hierarchyUserLookup) gin.HandlerFunc {
- return func(c *gin.Context) {
- path := c.Request.URL.Path
- role, ok := GetUserRoleFromContext(c)
- if subject, present := GetAuthSubjectFromContext(c); present && subject.UserID > 0 {
- c.Request = c.Request.WithContext(service.WithUserManagementActor(c.Request.Context(), subject.UserID))
- }
- if superAdminOperation(path, c.Request.Method) && (!ok || role != service.RoleSuperAdmin) {
- AbortWithError(c, 403, "SUPER_ADMIN_REQUIRED", "只有超级管理员可以管理全局安全设置、备份和插件")
- return
- }
- if !strings.HasPrefix(path, "/api/v1/admin/users") || c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead || c.Request.Method == http.MethodOptions {
- c.Next()
- return
- }
- if !ok || !service.IsAdminRole(role) {
- AbortWithError(c, 403, "FORBIDDEN", "Admin access required")
- return
- }
- if role == service.RoleSuperAdmin {
- c.Next()
- return
- }
- var body struct {
- Role string `json:"role"`
- UserIDs []int64 `json:"user_ids"`
- All bool `json:"all"`
- }
- if c.Request.Body != nil && c.Request.Body != http.NoBody {
- data, err := io.ReadAll(io.LimitReader(c.Request.Body, 2<<20))
- if err != nil || len(data) >= 2<<20 {
- AbortWithError(c, 400, "INVALID_REQUEST", "User management request is too large")
- return
- }
- c.Request.Body = io.NopCloser(bytes.NewReader(data))
- if len(data) > 0 && json.Unmarshal(data, &body) != nil {
- AbortWithError(c, 400, "INVALID_REQUEST", "Invalid JSON request")
- return
- }
- }
- // All=true includes administrators. The super-admin may use that legacy
- // operation; an ordinary admin selects specific ordinary users instead.
- if body.All || service.IsAdminRole(body.Role) {
- AbortWithError(c, 403, "SUPER_ADMIN_REQUIRED", "只有超级管理员可以修改角色或管理管理员账号")
- return
- }
- ids := body.UserIDs
- if len(ids) > 500 {
- AbortWithError(c, 400, "INVALID_REQUEST", "At most 500 users may be selected")
- return
- }
- if raw := c.Param("id"); raw != "" {
- if id, err := strconv.ParseInt(raw, 10, 64); err == nil && id > 0 {
- ids = append(ids, id)
- }
- }
- for _, id := range ids {
- if users == nil {
- AbortWithError(c, 503, "UNAVAILABLE", "User authorization unavailable")
- return
- }
- u, err := users.GetByID(c.Request.Context(), id)
- if err != nil {
- AbortWithError(c, 403, "FORBIDDEN", "Cannot authorize target user")
- return
- }
- if u.IsAdmin() {
- AbortWithError(c, 403, "SUPER_ADMIN_REQUIRED", "只有超级管理员可以管理管理员账号")
- return
- }
- }
- c.Next()
- }
- }
- func superAdminOperation(path, method string) bool {
- for _, prefix := range []string{"/api/v1/admin/settings/admin-api-key", "/api/v1/admin/backups", "/api/v1/admin/data-management"} {
- if path == prefix || strings.HasPrefix(path, prefix+"/") {
- return true
- }
- }
- write := method != http.MethodGet && method != http.MethodHead && method != http.MethodOptions
- return write && (strings.HasPrefix(path, "/api/v1/admin/system/") || path == "/api/v1/admin/settings" || strings.HasPrefix(path, "/api/v1/admin/settings/") || path == "/api/v1/admin/plugins" || strings.HasPrefix(path, "/api/v1/admin/plugins/"))
- }
|