access.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package auth
  2. import (
  3. "context"
  4. "errors"
  5. "math"
  6. "strings"
  7. "perms-system-server/internal/consts"
  8. "perms-system-server/internal/loaders"
  9. "perms-system-server/internal/middleware"
  10. "perms-system-server/internal/response"
  11. "perms-system-server/internal/svc"
  12. "github.com/zeromicro/go-zero/core/stores/sqlx"
  13. )
  14. func memberTypePriority(memberType string) int {
  15. switch memberType {
  16. case consts.MemberTypeSuperAdmin:
  17. return 0
  18. case consts.MemberTypeAdmin:
  19. return 1
  20. case consts.MemberTypeDeveloper:
  21. return 2
  22. case consts.MemberTypeMember:
  23. return 3
  24. default:
  25. return math.MaxInt32
  26. }
  27. }
  28. // CheckManageAccess 检查当前操作者是否有权管理目标用户。
  29. // 规则:
  30. // 1. SUPER_ADMIN 完全豁免
  31. // 2. 操作自己豁免
  32. // 3. 部门检查:目标用户须在操作者本部门或下级子部门(ADMIN 豁免)
  33. // 4. 权限级别检查:操作者的级别必须严格高于目标用户
  34. // - 先比 memberType 优先级(SUPER_ADMIN > ADMIN > DEVELOPER > MEMBER)
  35. // - 同 memberType 时比 permsLevel(数值越小权限越高)
  36. func CheckManageAccess(ctx context.Context, svcCtx *svc.ServiceContext, targetUserId int64, productCode string) error {
  37. caller := middleware.GetUserDetails(ctx)
  38. if caller == nil {
  39. return response.ErrUnauthorized("未登录")
  40. }
  41. if caller.IsSuperAdmin {
  42. return nil
  43. }
  44. if caller.UserId == targetUserId {
  45. return nil
  46. }
  47. if err := checkDeptHierarchy(ctx, svcCtx, caller, targetUserId); err != nil {
  48. return err
  49. }
  50. return checkPermLevel(ctx, svcCtx, caller, targetUserId, productCode)
  51. }
  52. // CheckMemberTypeAssignment 检查操作者是否有权分配指定的 memberType。
  53. func CheckMemberTypeAssignment(ctx context.Context, assignedType string) error {
  54. caller := middleware.GetUserDetails(ctx)
  55. if caller == nil {
  56. return response.ErrUnauthorized("未登录")
  57. }
  58. if caller.IsSuperAdmin {
  59. return nil
  60. }
  61. if caller.MemberType == "" {
  62. return response.ErrForbidden("缺少产品成员上下文")
  63. }
  64. if memberTypePriority(caller.MemberType) >= memberTypePriority(assignedType) {
  65. return response.ErrForbidden("无权分配该成员类型,不能分配与自己同级或更高级别的类型")
  66. }
  67. return nil
  68. }
  69. // RequireSuperAdmin 要求当前操作者必须是超级管理员。
  70. func RequireSuperAdmin(ctx context.Context) error {
  71. caller := middleware.GetUserDetails(ctx)
  72. if caller == nil {
  73. return response.ErrUnauthorized("未登录")
  74. }
  75. if !caller.IsSuperAdmin {
  76. return response.ErrForbidden("仅超级管理员可执行此操作")
  77. }
  78. return nil
  79. }
  80. // RequireProductAdminFor 要求当前操作者是超级管理员或指定产品的管理员。
  81. func RequireProductAdminFor(ctx context.Context, targetProductCode string) error {
  82. caller := middleware.GetUserDetails(ctx)
  83. if caller == nil {
  84. return response.ErrUnauthorized("未登录")
  85. }
  86. if caller.IsSuperAdmin {
  87. return nil
  88. }
  89. if caller.MemberType == consts.MemberTypeAdmin && caller.ProductCode == targetProductCode {
  90. return nil
  91. }
  92. return response.ErrForbidden("仅超级管理员或该产品的管理员可执行此操作")
  93. }
  94. // GuardRoleLevelAssignable 校验调用者能否把 rolePermsLevel 这一等级的角色分配给他人。
  95. // 约束:"只能分配严格低于自身的等级"(数字更大 = 更低),与 checkPermLevel 的 ">=" 拦截口径对齐,
  96. // 避免调用者把下属拉到与自己平级后彻底失去管控(见审计 H-3)。
  97. // 拥有产品全权(SuperAdmin / ADMIN / DEVELOPER)的调用者直接放行。
  98. func GuardRoleLevelAssignable(caller *loaders.UserDetails, rolePermsLevel int64) error {
  99. if HasFullProductPerms(caller) {
  100. return nil
  101. }
  102. if caller == nil || caller.MinPermsLevel == math.MaxInt64 {
  103. return response.ErrForbidden("您没有可分配的角色等级")
  104. }
  105. if rolePermsLevel <= caller.MinPermsLevel {
  106. return response.ErrForbidden("不能分配权限级别高于自身的角色(含同级)")
  107. }
  108. return nil
  109. }
  110. // HasFullProductPerms 判断调用者是否拥有当前产品的全部权限(无需做 permsLevel 校验)。
  111. // SuperAdmin / ADMIN / DEVELOPER 均视为全权;loadPerms 对此三者走全权分支。
  112. // 所有依赖"调用者已拥有全权"的短路逻辑应复用此函数,变更只需改一处。
  113. func HasFullProductPerms(caller *loaders.UserDetails) bool {
  114. if caller == nil {
  115. return false
  116. }
  117. return caller.IsSuperAdmin ||
  118. caller.MemberType == consts.MemberTypeAdmin ||
  119. caller.MemberType == consts.MemberTypeDeveloper
  120. }
  121. // ValidateStatusChange 校验状态变更的合法性(不允许自改状态、不允许冻结超管)。
  122. // UpdateUser 和 UpdateUserStatus 共用此函数以确保校验逻辑一致。
  123. func ValidateStatusChange(ctx context.Context, svcCtx *svc.ServiceContext, callerId, targetUserId int64) error {
  124. if callerId == targetUserId {
  125. return response.ErrBadRequest("不能修改自己的状态")
  126. }
  127. target, err := svcCtx.SysUserModel.FindOne(ctx, targetUserId)
  128. if err != nil {
  129. return response.ErrNotFound("用户不存在")
  130. }
  131. if target.IsSuperAdmin == consts.IsSuperAdminYes {
  132. return response.ErrForbidden("不能修改超级管理员的状态")
  133. }
  134. return nil
  135. }
  136. func checkDeptHierarchy(ctx context.Context, svcCtx *svc.ServiceContext, caller *loaders.UserDetails, targetUserId int64) error {
  137. if caller.MemberType == consts.MemberTypeAdmin {
  138. return nil
  139. }
  140. if caller.DeptId == 0 {
  141. return response.ErrForbidden("您未归属任何部门,无权管理其他用户")
  142. }
  143. if caller.DeptPath == "" {
  144. return response.ErrForbidden("您的部门信息异常,无法执行此操作")
  145. }
  146. target, err := svcCtx.SysUserModel.FindOne(ctx, targetUserId)
  147. if err != nil {
  148. return response.ErrNotFound("目标用户不存在")
  149. }
  150. if target.DeptId == 0 {
  151. return response.ErrForbidden("目标用户未归属部门,仅超管或产品管理员可管理")
  152. }
  153. targetDept, err := svcCtx.SysDeptModel.FindOne(ctx, target.DeptId)
  154. if err != nil {
  155. return response.ErrForbidden("无权操作")
  156. }
  157. if !strings.HasPrefix(targetDept.Path, caller.DeptPath) {
  158. return response.ErrForbidden("无权管理其他部门的用户")
  159. }
  160. return nil
  161. }
  162. func checkPermLevel(ctx context.Context, svcCtx *svc.ServiceContext, caller *loaders.UserDetails, targetUserId int64, productCode string) error {
  163. if productCode == "" {
  164. return response.ErrBadRequest("缺少产品上下文,无法进行权限级别判定")
  165. }
  166. targetMember, err := svcCtx.SysProductMemberModel.FindOneByProductCodeUserId(ctx, productCode, targetUserId)
  167. if err != nil {
  168. return response.ErrForbidden("目标用户不是当前产品的成员,无法执行管理操作")
  169. }
  170. targetMemberType := targetMember.MemberType
  171. callerPri := memberTypePriority(caller.MemberType)
  172. targetPri := memberTypePriority(targetMemberType)
  173. if callerPri > targetPri {
  174. return response.ErrForbidden("无权管理权限级别高于您的用户")
  175. }
  176. if callerPri < targetPri {
  177. return nil
  178. }
  179. // memberType 相同,比较 permsLevel
  180. targetLevel, err := svcCtx.SysRoleModel.FindMinPermsLevelByUserIdAndProductCode(ctx, targetUserId, productCode)
  181. if err != nil {
  182. // 区分"无角色 → 等价最低等级"与"DB 抖动 → 未知":只有 ErrNotFound 语义的场景才允许
  183. // 降级为 MaxInt64 放行管辖;其余错误一律视作不确定,fail-close 返回 500,避免 DB 抖动
  184. // 被同化成"目标无角色"造成越权放行(见审计 L-4)。
  185. if !errors.Is(err, sqlx.ErrNotFound) {
  186. return response.NewCodeError(500, "校验权限级别失败,请稍后重试")
  187. }
  188. targetLevel = math.MaxInt64
  189. }
  190. if caller.MinPermsLevel >= targetLevel {
  191. return response.ErrForbidden("无权管理权限级别高于或等于您的用户")
  192. }
  193. return nil
  194. }