admin_permission.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """管理类权限判定:分类/机构/用户编辑开关与管理范围锚点。
  2. 语义(管理权限矩阵化后):
  3. - 三管理位(manage_category_edit / manage_org_edit / manage_user_edit)挂在
  4. 角色上,判定读"生效角色"(用户显式角色,未挂角色回退默认角色),
  5. 且仅对 ADMIN 类别生效;分类编辑全局生效,机构/用户编辑受管理范围约束;
  6. - 角色上 managed_org_id 为管理范围锚点:NULL=不受范围约束(全局),否则
  7. 机构/用户编辑限定锚点部门及其下级子树(沿启用边BFS);
  8. - 内置 admin 账号恒为全权超管,不受开关与范围约束。
  9. """
  10. from __future__ import annotations
  11. from sqlalchemy import select
  12. from dms.common.enums import EnabledStatus, RoleCode
  13. from dms.common.errors import ForbiddenError
  14. from dms.models import Organization, Role, User
  15. from dms.security.auth_context import get_auth_context
  16. SUPER_ADMIN_USERNAME = "admin"
  17. # managed_org_id 哨兵值 0 = "SELF"(锚点跟随用户所在部门):
  18. # 0 不是合法机构ID,机构/用户编辑范围按各用户自身 organization_id 收敛。
  19. MANAGED_ORG_SELF_ANCHOR = 0
  20. MANAGED_ORG_SELF = "SELF"
  21. def effective_manage_role(session, user: User) -> Role | None:
  22. """生效角色:显式角色(活跃且启用)优先,否则回退默认角色(活跃且启用)。"""
  23. if user.role_id is not None:
  24. role = session.get(Role, user.role_id)
  25. if (
  26. role is not None
  27. and not role.is_deleted
  28. and role.status == EnabledStatus.ENABLED.value
  29. ):
  30. return role
  31. return session.scalar(
  32. select(Role).where(
  33. Role.is_default.is_(True),
  34. Role.is_deleted.is_(False),
  35. Role.status == EnabledStatus.ENABLED.value,
  36. )
  37. )
  38. def is_super_admin(user: User) -> bool:
  39. """内置 admin 账号恒为超管,不受三管理位与管理范围约束。"""
  40. return user.username == SUPER_ADMIN_USERNAME
  41. def _has_manage_flag(session, user: User, flag: str) -> bool:
  42. if user.role_code != RoleCode.ADMIN.value:
  43. return False
  44. role = effective_manage_role(session, user)
  45. return role is not None and bool(getattr(role, flag))
  46. def can_edit_categories(session, user: User) -> bool:
  47. if is_super_admin(user):
  48. return True
  49. return _has_manage_flag(session, user, "manage_category_edit")
  50. def can_edit_organizations(session, user: User) -> bool:
  51. if is_super_admin(user):
  52. return True
  53. return _has_manage_flag(session, user, "manage_org_edit")
  54. def can_edit_users(session, user: User) -> bool:
  55. if is_super_admin(user):
  56. return True
  57. return _has_manage_flag(session, user, "manage_user_edit")
  58. def require_category_editor(session) -> User:
  59. """取当前认证用户并校验分类编辑权限。"""
  60. actor = session.get(User, get_auth_context().user_id)
  61. if actor is None or not can_edit_categories(session, actor):
  62. raise ForbiddenError("无分类编辑权限")
  63. return actor
  64. def require_organization_editor(session) -> User:
  65. """取当前认证用户并校验机构编辑权限(范围按目标另行判定)。"""
  66. actor = session.get(User, get_auth_context().user_id)
  67. if actor is None or not can_edit_organizations(session, actor):
  68. raise ForbiddenError("无机构编辑权限")
  69. return actor
  70. def require_user_editor(session) -> User:
  71. """取当前认证用户并校验用户编辑权限(范围按目标另行判定)。"""
  72. actor = session.get(User, get_auth_context().user_id)
  73. if actor is None or not can_edit_users(session, actor):
  74. raise ForbiddenError("无用户编辑权限")
  75. return actor
  76. def managed_scope_ids(session, user: User) -> set[int] | None:
  77. """操作者可管理的机构ID集合;None 表示不受范围约束(全局)。
  78. super_admin 与生效角色锚点为 NULL 的管理员不受约束;锚点为 SELF
  79. 哨兵(0)时按用户自身所在部门收敛(无部门则无可管理范围);其余
  80. 按锚点部门子树(仅沿启用边)收敛。
  81. """
  82. if is_super_admin(user):
  83. return None
  84. role = effective_manage_role(session, user)
  85. anchor = role.managed_org_id if role is not None else None
  86. if anchor is None:
  87. return None
  88. if anchor == MANAGED_ORG_SELF_ANCHOR:
  89. if user.organization_id is None:
  90. return set()
  91. anchor = user.organization_id
  92. organizations = session.scalars(
  93. select(Organization).where(Organization.is_deleted.is_(False))
  94. ).all()
  95. children: dict[int | None, list[int]] = {}
  96. for item in organizations:
  97. if item.status == EnabledStatus.ENABLED.value:
  98. children.setdefault(item.parent_id, []).append(item.id)
  99. scope = {anchor}
  100. pending = [anchor]
  101. while pending:
  102. current = pending.pop()
  103. for child_id in children.get(current, []):
  104. if child_id not in scope:
  105. scope.add(child_id)
  106. pending.append(child_id)
  107. return scope
  108. def ensure_org_in_managed_scope(session, actor: User, org_id: int | None) -> None:
  109. """校验目标机构在操作者管理范围内。
  110. org_id 为 None 表示顶级机构(或无部门用户):仅全局管理员可操作。
  111. """
  112. scope = managed_scope_ids(session, actor)
  113. if scope is None:
  114. return
  115. if org_id is None or org_id not in scope:
  116. raise ForbiddenError("目标超出管理范围")