decorators.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """DMS Bearer 认证与角色授权装饰器。"""
  2. from __future__ import annotations
  3. from functools import wraps
  4. from typing import Any, Callable, TypeVar, cast
  5. from flask import g, request
  6. from sqlalchemy import select
  7. from dms.common.enums import EnabledStatus, RoleCode, SecurityLevel
  8. from dms.common.errors import (
  9. AuthenticationError,
  10. AuthVersionMismatchError,
  11. ForbiddenError,
  12. UserDisabledError,
  13. )
  14. from dms.extensions import db
  15. from dms.models import User
  16. from dms.security.auth_context import AuthContext, get_auth_context
  17. from dms.security.jwt_tokens import decode_access_token
  18. F = TypeVar("F", bound=Callable[..., Any])
  19. def extract_bearer_token() -> str:
  20. authorization = request.headers.get("Authorization", "")
  21. scheme, separator, token = authorization.partition(" ")
  22. if not separator or scheme.lower() != "bearer" or not token.strip():
  23. raise AuthenticationError()
  24. return token.strip()
  25. def authenticate_request(token: str) -> AuthContext:
  26. claims = decode_access_token(token)
  27. try:
  28. user_id = int(claims["sub"])
  29. token_auth_version = int(claims["authVersion"])
  30. token_username = str(claims["username"])
  31. token_role = str(claims["roleCode"])
  32. except (KeyError, TypeError, ValueError) as exc:
  33. raise AuthenticationError() from exc
  34. user = db.session.scalar(select(User).where(User.id == user_id))
  35. if user is None or user.is_deleted:
  36. raise AuthenticationError()
  37. if user.status != EnabledStatus.ENABLED:
  38. raise UserDisabledError()
  39. if user.auth_version != token_auth_version:
  40. raise AuthVersionMismatchError()
  41. if user.username != token_username or user.role_code != token_role:
  42. raise AuthenticationError()
  43. context = AuthContext(
  44. user_id=user.id,
  45. username=user.username,
  46. real_name=user.real_name,
  47. organization_id=user.organization_id,
  48. organization_name=user.organization_name,
  49. role_code=RoleCode(user.role_code),
  50. security_level=SecurityLevel(user.security_level),
  51. auth_version=user.auth_version,
  52. )
  53. g.dms_auth_context = context
  54. return context
  55. def bearer_auth_required(view: F) -> F:
  56. @wraps(view)
  57. def wrapped(*args: Any, **kwargs: Any):
  58. authenticate_request(extract_bearer_token())
  59. return view(*args, **kwargs)
  60. return cast(F, wrapped)
  61. def admin_required(view: F) -> F:
  62. @wraps(view)
  63. def wrapped(*args: Any, **kwargs: Any):
  64. if get_auth_context().role_code != RoleCode.ADMIN:
  65. raise ForbiddenError()
  66. return view(*args, **kwargs)
  67. return cast(F, wrapped)