user_hierarchy.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "entgo.io/ent/dialect"
  6. dbent "github.com/Wei-Shaw/sub2api/ent"
  7. dbuser "github.com/Wei-Shaw/sub2api/ent/user"
  8. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. )
  11. const userHierarchyLockKey = "user-management:administrator-roles:v1"
  12. // The supplied client must be transaction-bound. PostgreSQL owns the lock until
  13. // commit/rollback, including an outer transaction; no process-local mutex can
  14. // substitute for this across application replicas.
  15. func lockUserHierarchy(ctx context.Context, client *dbent.Client) (func(), error) {
  16. if client.Driver().Dialect() == dialect.Postgres {
  17. if err := client.Driver().Exec(ctx, "SELECT pg_advisory_xact_lock($1)", []any{advisoryLockHash(userHierarchyLockKey)}, nil); err != nil {
  18. return nil, err
  19. }
  20. return func() {}, nil
  21. }
  22. return repositoryScopedKeyLocks.lock(userHierarchyLockKey), nil
  23. }
  24. func lockHierarchyUsers(ctx context.Context, client *dbent.Client, ids ...int64) (map[int64]*dbent.User, error) {
  25. query := client.User.Query().Where(dbuser.IDIn(ids...), dbuser.DeletedAtIsNil()).Order(dbent.Asc(dbuser.FieldID))
  26. if client.Driver().Dialect() == dialect.Postgres {
  27. query = query.ForUpdate()
  28. }
  29. rows, err := query.All(ctx)
  30. if err != nil {
  31. return nil, err
  32. }
  33. users := make(map[int64]*dbent.User, len(rows))
  34. for _, user := range rows {
  35. users[user.ID] = user
  36. }
  37. return users, nil
  38. }
  39. func verifyHierarchyActor(actor *dbent.User, needsSuper bool) error {
  40. if actor == nil || actor.Status != service.StatusActive || !service.IsAdminRole(actor.Role) || (needsSuper && actor.Role != service.RoleSuperAdmin) {
  41. return infraerrors.Forbidden("SUPER_ADMIN_REQUIRED", "操作者权限已变更或不足,请刷新后重试")
  42. }
  43. return nil
  44. }
  45. func guardUserHierarchyUpdate(ctx context.Context, client *dbent.Client, incoming *service.User, fields service.UserUpdateFields) (func(), error) {
  46. actorID, managed := service.UserManagementActor(ctx)
  47. if !fields.Role && !fields.Status && !managed {
  48. return func() {}, nil
  49. }
  50. release, err := lockUserHierarchy(ctx, client)
  51. if err != nil {
  52. return nil, err
  53. }
  54. check := func() error {
  55. users, err := lockHierarchyUsers(ctx, client, incoming.ID, actorID)
  56. if err != nil {
  57. return err
  58. }
  59. current := users[incoming.ID]
  60. if current == nil {
  61. return service.ErrUserNotFound
  62. }
  63. roleChange := fields.Role && incoming.Role != current.Role
  64. if managed || roleChange {
  65. if err := verifyHierarchyActor(users[actorID], roleChange || service.IsAdminRole(current.Role)); err != nil {
  66. return err
  67. }
  68. if roleChange && incoming.ID == actorID {
  69. return infraerrors.Forbidden("SELF_DEMOTION_FORBIDDEN", "不能修改自己的管理员角色")
  70. }
  71. }
  72. if fields.Role && incoming.Role != service.RoleUser && !service.IsAdminRole(incoming.Role) {
  73. return infraerrors.BadRequest("INVALID_USER_ROLE", "无效的用户角色")
  74. }
  75. if fields.Status && incoming.Status != service.StatusActive && service.IsAdminRole(current.Role) {
  76. return infraerrors.Forbidden("ADMIN_PROTECTED", "不能禁用管理员账号")
  77. }
  78. if !roleChange || !service.IsAdminRole(current.Role) {
  79. return nil
  80. }
  81. remaining := client.User.Query().Where(dbuser.IDNEQ(current.ID), dbuser.StatusEQ(service.StatusActive), dbuser.DeletedAtIsNil())
  82. if current.Role == service.RoleSuperAdmin {
  83. remaining = remaining.Where(dbuser.RoleEQ(service.RoleSuperAdmin))
  84. } else {
  85. remaining = remaining.Where(dbuser.RoleIn(service.RoleSuperAdmin, service.RoleAdmin))
  86. }
  87. exists, err := remaining.Exist(ctx)
  88. if err != nil {
  89. return err
  90. }
  91. if !exists {
  92. return infraerrors.Forbidden("LAST_SUPER_ADMIN", "不能降级最后一个有效管理员")
  93. }
  94. return nil
  95. }
  96. if err := check(); err != nil {
  97. release()
  98. return nil, err
  99. }
  100. return release, nil
  101. }
  102. func guardUserHierarchyCreate(ctx context.Context, client *dbent.Client, user *service.User) (func(), error) {
  103. if !service.IsAdminRole(user.Role) {
  104. return func() {}, nil
  105. }
  106. release, err := lockUserHierarchy(ctx, client)
  107. if err != nil {
  108. return nil, err
  109. }
  110. if actorID, managed := service.UserManagementActor(ctx); managed {
  111. actors, err := lockHierarchyUsers(ctx, client, actorID)
  112. if err == nil {
  113. err = verifyHierarchyActor(actors[actorID], true)
  114. }
  115. if err != nil {
  116. release()
  117. return nil, err
  118. }
  119. }
  120. return release, nil
  121. }
  122. func guardUserHierarchyDelete(ctx context.Context, client *dbent.Client, id int64) (func(), error) {
  123. release, err := lockUserHierarchy(ctx, client)
  124. if err != nil {
  125. return nil, err
  126. }
  127. actorID, managed := service.UserManagementActor(ctx)
  128. ids := []int64{id}
  129. if managed {
  130. ids = append(ids, actorID)
  131. }
  132. users, err := lockHierarchyUsers(ctx, client, ids...)
  133. if err == nil {
  134. if users[id] == nil {
  135. err = service.ErrUserNotFound
  136. } else if managed {
  137. err = verifyHierarchyActor(users[actorID], false)
  138. } else if service.IsAdminRole(users[id].Role) {
  139. err = infraerrors.Forbidden("ADMIN_PROTECTED", fmt.Sprintf("不能删除管理员账号 %d", id))
  140. }
  141. if err == nil && service.IsAdminRole(users[id].Role) {
  142. err = infraerrors.Forbidden("ADMIN_PROTECTED", fmt.Sprintf("不能删除管理员账号 %d", id))
  143. }
  144. }
  145. if err != nil {
  146. release()
  147. return nil, err
  148. }
  149. return release, nil
  150. }