Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

wraps.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. from collections.abc import Callable
  2. from datetime import UTC, datetime
  3. from functools import wraps
  4. from typing import Concatenate, ParamSpec, TypeVar
  5. from flask import request
  6. from flask_restx import Resource
  7. from sqlalchemy import select
  8. from sqlalchemy.orm import Session
  9. from werkzeug.exceptions import BadRequest, NotFound, Unauthorized
  10. from controllers.web.error import WebAppAuthAccessDeniedError, WebAppAuthRequiredError
  11. from extensions.ext_database import db
  12. from libs.passport import PassportService
  13. from models.model import App, EndUser, Site
  14. from services.enterprise.enterprise_service import EnterpriseService, WebAppSettings
  15. from services.feature_service import FeatureService
  16. from services.webapp_auth_service import WebAppAuthService
  17. P = ParamSpec("P")
  18. R = TypeVar("R")
  19. def validate_jwt_token(view: Callable[Concatenate[App, EndUser, P], R] | None = None):
  20. def decorator(view: Callable[Concatenate[App, EndUser, P], R]):
  21. @wraps(view)
  22. def decorated(*args: P.args, **kwargs: P.kwargs):
  23. app_model, end_user = decode_jwt_token()
  24. return view(app_model, end_user, *args, **kwargs)
  25. return decorated
  26. if view:
  27. return decorator(view)
  28. return decorator
  29. def decode_jwt_token():
  30. system_features = FeatureService.get_system_features()
  31. app_code = str(request.headers.get("X-App-Code"))
  32. try:
  33. auth_header = request.headers.get("Authorization")
  34. if auth_header is None:
  35. raise Unauthorized("Authorization header is missing.")
  36. if " " not in auth_header:
  37. raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
  38. auth_scheme, tk = auth_header.split(None, 1)
  39. auth_scheme = auth_scheme.lower()
  40. if auth_scheme != "bearer":
  41. raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
  42. decoded = PassportService().verify(tk)
  43. app_code = decoded.get("app_code")
  44. app_id = decoded.get("app_id")
  45. with Session(db.engine, expire_on_commit=False) as session:
  46. app_model = session.scalar(select(App).where(App.id == app_id))
  47. site = session.scalar(select(Site).where(Site.code == app_code))
  48. if not app_model:
  49. raise NotFound()
  50. if not app_code or not site:
  51. raise BadRequest("Site URL is no longer valid.")
  52. if app_model.enable_site is False:
  53. raise BadRequest("Site is disabled.")
  54. end_user_id = decoded.get("end_user_id")
  55. end_user = session.scalar(select(EndUser).where(EndUser.id == end_user_id))
  56. if not end_user:
  57. raise NotFound()
  58. # for enterprise webapp auth
  59. app_web_auth_enabled = False
  60. webapp_settings = None
  61. if system_features.webapp_auth.enabled:
  62. webapp_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=app_code)
  63. if not webapp_settings:
  64. raise NotFound("Web app settings not found.")
  65. app_web_auth_enabled = webapp_settings.access_mode != "public"
  66. _validate_webapp_token(decoded, app_web_auth_enabled, system_features.webapp_auth.enabled)
  67. _validate_user_accessibility(
  68. decoded, app_code, app_web_auth_enabled, system_features.webapp_auth.enabled, webapp_settings
  69. )
  70. return app_model, end_user
  71. except Unauthorized as e:
  72. if system_features.webapp_auth.enabled:
  73. if not app_code:
  74. raise Unauthorized("Please re-login to access the web app.")
  75. app_web_auth_enabled = (
  76. EnterpriseService.WebAppAuth.get_app_access_mode_by_code(app_code=str(app_code)).access_mode != "public"
  77. )
  78. if app_web_auth_enabled:
  79. raise WebAppAuthRequiredError()
  80. raise Unauthorized(e.description)
  81. def _validate_webapp_token(decoded, app_web_auth_enabled: bool, system_webapp_auth_enabled: bool):
  82. # Check if authentication is enforced for web app, and if the token source is not webapp,
  83. # raise an error and redirect to login
  84. if system_webapp_auth_enabled and app_web_auth_enabled:
  85. source = decoded.get("token_source")
  86. if not source or source != "webapp":
  87. raise WebAppAuthRequiredError()
  88. # Check if authentication is not enforced for web, and if the token source is webapp,
  89. # raise an error and redirect to normal passport login
  90. if not system_webapp_auth_enabled or not app_web_auth_enabled:
  91. source = decoded.get("token_source")
  92. if source and source == "webapp":
  93. raise Unauthorized("webapp token expired.")
  94. def _validate_user_accessibility(
  95. decoded,
  96. app_code,
  97. app_web_auth_enabled: bool,
  98. system_webapp_auth_enabled: bool,
  99. webapp_settings: WebAppSettings | None,
  100. ):
  101. if system_webapp_auth_enabled and app_web_auth_enabled:
  102. # Check if the user is allowed to access the web app
  103. user_id = decoded.get("user_id")
  104. if not user_id:
  105. raise WebAppAuthRequiredError()
  106. if not webapp_settings:
  107. raise WebAppAuthRequiredError("Web app settings not found.")
  108. if WebAppAuthService.is_app_require_permission_check(access_mode=webapp_settings.access_mode):
  109. if not EnterpriseService.WebAppAuth.is_user_allowed_to_access_webapp(user_id, app_code=app_code):
  110. raise WebAppAuthAccessDeniedError()
  111. auth_type = decoded.get("auth_type")
  112. granted_at = decoded.get("granted_at")
  113. if not auth_type:
  114. raise WebAppAuthAccessDeniedError("Missing auth_type in the token.")
  115. if not granted_at:
  116. raise WebAppAuthAccessDeniedError("Missing granted_at in the token.")
  117. # check if sso has been updated
  118. if auth_type == "external":
  119. last_update_time = EnterpriseService.get_app_sso_settings_last_update_time()
  120. if granted_at and datetime.fromtimestamp(granted_at, tz=UTC) < last_update_time:
  121. raise WebAppAuthAccessDeniedError("SSO settings have been updated. Please re-login.")
  122. elif auth_type == "internal":
  123. last_update_time = EnterpriseService.get_workspace_sso_settings_last_update_time()
  124. if granted_at and datetime.fromtimestamp(granted_at, tz=UTC) < last_update_time:
  125. raise WebAppAuthAccessDeniedError("SSO settings have been updated. Please re-login.")
  126. class WebApiResource(Resource):
  127. method_decorators = [validate_jwt_token]