PayrollManagementSystem/backend/app/services/authorization_service.py

93 lines
3.7 KiB
Python
Raw Normal View History

"""授权查询与计算,权限每次从数据库解析,不采用 JWT 中的角色作为依据。"""
from sqlalchemy import select
from sqlalchemy.orm import object_session
from app.core.exceptions import BadRequestError, ForbiddenError
from app.models import (
AuthorizationSetting,
Permission,
Role,
RolePermission,
User,
UserRole,
)
from app.services.permission_catalog import CATALOG, PAYROLL_WRITE_PERMISSIONS
def setting(session, *, lock=False):
statement = select(AuthorizationSetting).where(AuthorizationSetting.id == 1)
if lock:
statement = statement.with_for_update().execution_options(populate_existing=True)
row = session.scalar(statement)
if row is None:
raise ForbiddenError("授权配置尚未初始化,请先完成数据库迁移")
return row
def role_codes(session, user_id, *, for_update=False):
statement = select(Role.role_code).join(UserRole).where(
UserRole.user_id == user_id, Role.status == 1
)
if for_update:
statement = statement.with_for_update()
return list(session.scalars(statement))
def calculate(session, user, *, roles=None, overrides=None):
"""用户继承启用角色权限的并集;管理员固定全权限,操作依赖模块查看。"""
if overrides:
raise BadRequestError("已改为角色授权,请在角色管理中配置权限")
if user is None or user.status != 1:
return {"permissions": [], "sources": {}, "roles": []}
roles = roles if roles is not None else role_codes(session, user.id)
if "admin" in roles:
return {"permissions": sorted(CATALOG), "sources": {
code: {"roles": ["admin"], "override": None, "effective": True}
for code in CATALOG
}, "roles": roles}
inherited = {}
for code, role in session.execute(
select(Permission.perm_code, Role.role_code)
.join(RolePermission, RolePermission.permission_id == Permission.id)
.join(Role, Role.id == RolePermission.role_id)
.where(Role.role_code.in_(roles), Role.status == 1, Permission.status == 1)
):
inherited.setdefault(code, []).append(role)
allowed = set(inherited)
allowed &= set(CATALOG)
allowed = {c for c in allowed if not CATALOG[c]["admin_only"]}
allowed = {c for c in allowed if CATALOG[c]["view_code"] in allowed}
sources = {c: {"roles": inherited.get(c, []), "override": None,
"effective": c in allowed} for c in CATALOG}
return {"permissions": sorted(allowed), "sources": sources, "roles": roles}
def is_maker(session, user):
"""兼容旧会话字段:现表示账号具备工资写权限,不再查询全局绑定。"""
return bool(set(calculate(session, user)["permissions"]) & PAYROLL_WRITE_PERMISSIONS)
def require_permission(session, user, code, *, check_maker=True):
item = CATALOG.get(code)
if item is None or code not in calculate(session, user)["permissions"]:
label = f"{item['module_name']}:{item['name']}" if item else "此操作"
raise ForbiddenError(f"当前账号没有「{label}」权限,请联系管理员授权")
return user
def session_info(session, user):
result = calculate(session, user)
maker = bool(set(result["permissions"]) & PAYROLL_WRITE_PERMISSIONS)
return {"permissions": result["permissions"], "authorization_revision": setting(session).revision,
"is_payroll_maker": maker, "can_write_payroll": maker,
"maker_required_permissions": [],
"roles": result["roles"]}
def user_session(user):
session = object_session(user) if isinstance(user, User) else None
if session is None:
raise ForbiddenError("账号状态不可用,请重新登录")
return session