user_hierarchy_test.go 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. package middleware
  2. import (
  3. "context"
  4. "net/http"
  5. "net/http/httptest"
  6. "strings"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/gin-gonic/gin"
  10. "github.com/stretchr/testify/require"
  11. )
  12. type hierarchyTestUsers map[int64]*service.User
  13. func (s hierarchyTestUsers) GetByID(_ context.Context, id int64) (*service.User, error) {
  14. if u := s[id]; u != nil {
  15. return u, nil
  16. }
  17. return nil, service.ErrUserNotFound
  18. }
  19. func TestUserHierarchyGuardsEveryMutationAndBatch(t *testing.T) {
  20. for _, tt := range []struct {
  21. name, role, path, body string
  22. want int
  23. }{
  24. {"admin_balance_super", service.RoleAdmin, "/api/v1/admin/users/2/balance", `{"balance":9}`, 403},
  25. {"admin_totp_super", service.RoleAdmin, "/api/v1/admin/users/2/totp", `{}`, 403},
  26. {"admin_batch_super", service.RoleAdmin, "/api/v1/admin/users/batch", `{"user_ids":[3,2]}`, 403},
  27. {"admin_batch_all", service.RoleAdmin, "/api/v1/admin/users/batch", `{"all":true}`, 403},
  28. {"admin_promote_self", service.RoleAdmin, "/api/v1/admin/users/1", `{"role":"super_admin"}`, 403},
  29. {"admin_create_admin", service.RoleAdmin, "/api/v1/admin/users", `{"role":"admin"}`, 403},
  30. {"admin_update_user", service.RoleAdmin, "/api/v1/admin/users/3", `{"notes":"updated"}`, 200},
  31. {"super_update_admin", service.RoleSuperAdmin, "/api/v1/admin/users/1", `{"notes":"updated"}`, 200},
  32. {"user_forbidden", service.RoleUser, "/api/v1/admin/users/3", `{}`, 403},
  33. } {
  34. t.Run(tt.name, func(t *testing.T) {
  35. gin.SetMode(gin.TestMode)
  36. r := gin.New()
  37. r.Use(func(c *gin.Context) { c.Set(string(ContextKeyUserRole), tt.role) })
  38. r.Use(userHierarchyGuard(hierarchyTestUsers{1: {ID: 1, Role: service.RoleAdmin}, 2: {ID: 2, Role: service.RoleSuperAdmin}, 3: {ID: 3, Role: service.RoleUser}}))
  39. ok := func(c *gin.Context) { c.Status(http.StatusOK) }
  40. r.POST("/api/v1/admin/users", ok)
  41. r.POST("/api/v1/admin/users/batch", ok)
  42. r.POST("/api/v1/admin/users/:id", ok)
  43. r.POST("/api/v1/admin/users/:id/balance", ok)
  44. r.POST("/api/v1/admin/users/:id/totp", ok)
  45. w := httptest.NewRecorder()
  46. req := httptest.NewRequest(http.MethodPost, tt.path, strings.NewReader(tt.body))
  47. req.Header.Set("Content-Type", "application/json")
  48. r.ServeHTTP(w, req)
  49. require.Equal(t, tt.want, w.Code, w.Body.String())
  50. })
  51. }
  52. }
  53. func TestUserHierarchyPreventsGlobalKeyAndRestoreEscalation(t *testing.T) {
  54. for _, route := range []struct{ method, path string }{
  55. {"GET", "/api/v1/admin/settings/admin-api-key"},
  56. {"POST", "/api/v1/admin/settings/admin-api-key/regenerate"},
  57. {"PUT", "/api/v1/admin/settings"},
  58. {"POST", "/api/v1/admin/backups/8/restore"},
  59. {"GET", "/api/v1/admin/backups/8/download-url"},
  60. {"POST", "/api/v1/admin/data-management/backups"},
  61. {"PUT", "/api/v1/admin/plugins/8"},
  62. } {
  63. for _, role := range []string{service.RoleAdmin, service.RoleSuperAdmin} {
  64. t.Run(role+route.path, func(t *testing.T) {
  65. r := gin.New()
  66. r.Use(func(c *gin.Context) { c.Set(string(ContextKeyUserRole), role) })
  67. r.Use(userHierarchyGuard(nil))
  68. r.Handle(route.method, route.path, func(c *gin.Context) { c.Status(200) })
  69. w := httptest.NewRecorder()
  70. r.ServeHTTP(w, httptest.NewRequest(route.method, route.path, nil))
  71. want := 403
  72. if role == service.RoleSuperAdmin {
  73. want = 200
  74. }
  75. require.Equal(t, want, w.Code)
  76. })
  77. }
  78. }
  79. }