Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

wraps.py 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import contextlib
  2. import json
  3. import os
  4. import time
  5. from collections.abc import Callable
  6. from functools import wraps
  7. from typing import ParamSpec, TypeVar
  8. from flask import abort, request
  9. from flask_login import current_user
  10. from configs import dify_config
  11. from controllers.console.workspace.error import AccountNotInitializedError
  12. from extensions.ext_database import db
  13. from extensions.ext_redis import redis_client
  14. from models.account import AccountStatus
  15. from models.dataset import RateLimitLog
  16. from models.model import DifySetup
  17. from services.feature_service import FeatureService, LicenseStatus
  18. from services.operation_service import OperationService
  19. from .error import NotInitValidateError, NotSetupError, UnauthorizedAndForceLogout
  20. P = ParamSpec("P")
  21. R = TypeVar("R")
  22. def account_initialization_required(view: Callable[P, R]):
  23. @wraps(view)
  24. def decorated(*args: P.args, **kwargs: P.kwargs):
  25. # check account initialization
  26. account = current_user
  27. if account.status == AccountStatus.UNINITIALIZED:
  28. raise AccountNotInitializedError()
  29. return view(*args, **kwargs)
  30. return decorated
  31. def only_edition_cloud(view: Callable[P, R]):
  32. @wraps(view)
  33. def decorated(*args: P.args, **kwargs: P.kwargs):
  34. if dify_config.EDITION != "CLOUD":
  35. abort(404)
  36. return view(*args, **kwargs)
  37. return decorated
  38. def only_edition_enterprise(view: Callable[P, R]):
  39. @wraps(view)
  40. def decorated(*args: P.args, **kwargs: P.kwargs):
  41. if not dify_config.ENTERPRISE_ENABLED:
  42. abort(404)
  43. return view(*args, **kwargs)
  44. return decorated
  45. def only_edition_self_hosted(view: Callable[P, R]):
  46. @wraps(view)
  47. def decorated(*args: P.args, **kwargs: P.kwargs):
  48. if dify_config.EDITION != "SELF_HOSTED":
  49. abort(404)
  50. return view(*args, **kwargs)
  51. return decorated
  52. def cloud_edition_billing_enabled(view: Callable[P, R]):
  53. @wraps(view)
  54. def decorated(*args: P.args, **kwargs: P.kwargs):
  55. features = FeatureService.get_features(current_user.current_tenant_id)
  56. if not features.billing.enabled:
  57. abort(403, "Billing feature is not enabled.")
  58. return view(*args, **kwargs)
  59. return decorated
  60. def cloud_edition_billing_resource_check(resource: str):
  61. def interceptor(view: Callable[P, R]):
  62. @wraps(view)
  63. def decorated(*args: P.args, **kwargs: P.kwargs):
  64. features = FeatureService.get_features(current_user.current_tenant_id)
  65. if features.billing.enabled:
  66. members = features.members
  67. apps = features.apps
  68. vector_space = features.vector_space
  69. documents_upload_quota = features.documents_upload_quota
  70. annotation_quota_limit = features.annotation_quota_limit
  71. if resource == "members" and 0 < members.limit <= members.size:
  72. abort(403, "The number of members has reached the limit of your subscription.")
  73. elif resource == "apps" and 0 < apps.limit <= apps.size:
  74. abort(403, "The number of apps has reached the limit of your subscription.")
  75. elif resource == "vector_space" and 0 < vector_space.limit <= vector_space.size:
  76. abort(
  77. 403, "The capacity of the knowledge storage space has reached the limit of your subscription."
  78. )
  79. elif resource == "documents" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:
  80. # The api of file upload is used in the multiple places,
  81. # so we need to check the source of the request from datasets
  82. source = request.args.get("source")
  83. if source == "datasets":
  84. abort(403, "The number of documents has reached the limit of your subscription.")
  85. else:
  86. return view(*args, **kwargs)
  87. elif resource == "workspace_custom" and not features.can_replace_logo:
  88. abort(403, "The workspace custom feature has reached the limit of your subscription.")
  89. elif resource == "annotation" and 0 < annotation_quota_limit.limit < annotation_quota_limit.size:
  90. abort(403, "The annotation quota has reached the limit of your subscription.")
  91. else:
  92. return view(*args, **kwargs)
  93. return view(*args, **kwargs)
  94. return decorated
  95. return interceptor
  96. def cloud_edition_billing_knowledge_limit_check(resource: str):
  97. def interceptor(view: Callable[P, R]):
  98. @wraps(view)
  99. def decorated(*args: P.args, **kwargs: P.kwargs):
  100. features = FeatureService.get_features(current_user.current_tenant_id)
  101. if features.billing.enabled:
  102. if resource == "add_segment":
  103. if features.billing.subscription.plan == "sandbox":
  104. abort(
  105. 403,
  106. "To unlock this feature and elevate your Dify experience, please upgrade to a paid plan.",
  107. )
  108. else:
  109. return view(*args, **kwargs)
  110. return view(*args, **kwargs)
  111. return decorated
  112. return interceptor
  113. def cloud_edition_billing_rate_limit_check(resource: str):
  114. def interceptor(view: Callable[P, R]):
  115. @wraps(view)
  116. def decorated(*args: P.args, **kwargs: P.kwargs):
  117. if resource == "knowledge":
  118. knowledge_rate_limit = FeatureService.get_knowledge_rate_limit(current_user.current_tenant_id)
  119. if knowledge_rate_limit.enabled:
  120. current_time = int(time.time() * 1000)
  121. key = f"rate_limit_{current_user.current_tenant_id}"
  122. redis_client.zadd(key, {current_time: current_time})
  123. redis_client.zremrangebyscore(key, 0, current_time - 60000)
  124. request_count = redis_client.zcard(key)
  125. if request_count > knowledge_rate_limit.limit:
  126. # add ratelimit record
  127. rate_limit_log = RateLimitLog(
  128. tenant_id=current_user.current_tenant_id,
  129. subscription_plan=knowledge_rate_limit.subscription_plan,
  130. operation="knowledge",
  131. )
  132. db.session.add(rate_limit_log)
  133. db.session.commit()
  134. abort(
  135. 403, "Sorry, you have reached the knowledge base request rate limit of your subscription."
  136. )
  137. return view(*args, **kwargs)
  138. return decorated
  139. return interceptor
  140. def cloud_utm_record(view: Callable[P, R]):
  141. @wraps(view)
  142. def decorated(*args: P.args, **kwargs: P.kwargs):
  143. with contextlib.suppress(Exception):
  144. features = FeatureService.get_features(current_user.current_tenant_id)
  145. if features.billing.enabled:
  146. utm_info = request.cookies.get("utm_info")
  147. if utm_info:
  148. utm_info_dict: dict = json.loads(utm_info)
  149. OperationService.record_utm(current_user.current_tenant_id, utm_info_dict)
  150. return view(*args, **kwargs)
  151. return decorated
  152. def setup_required(view: Callable[P, R]):
  153. @wraps(view)
  154. def decorated(*args: P.args, **kwargs: P.kwargs):
  155. # check setup
  156. if (
  157. dify_config.EDITION == "SELF_HOSTED"
  158. and os.environ.get("INIT_PASSWORD")
  159. and not db.session.query(DifySetup).first()
  160. ):
  161. raise NotInitValidateError()
  162. elif dify_config.EDITION == "SELF_HOSTED" and not db.session.query(DifySetup).first():
  163. raise NotSetupError()
  164. return view(*args, **kwargs)
  165. return decorated
  166. def enterprise_license_required(view: Callable[P, R]):
  167. @wraps(view)
  168. def decorated(*args: P.args, **kwargs: P.kwargs):
  169. settings = FeatureService.get_system_features()
  170. if settings.license.status in [LicenseStatus.INACTIVE, LicenseStatus.EXPIRED, LicenseStatus.LOST]:
  171. raise UnauthorizedAndForceLogout("Your license is invalid. Please contact your administrator.")
  172. return view(*args, **kwargs)
  173. return decorated
  174. def email_password_login_enabled(view: Callable[P, R]):
  175. @wraps(view)
  176. def decorated(*args: P.args, **kwargs: P.kwargs):
  177. features = FeatureService.get_system_features()
  178. if features.enable_email_password_login:
  179. return view(*args, **kwargs)
  180. # otherwise, return 403
  181. abort(403)
  182. return decorated
  183. def enable_change_email(view: Callable[P, R]):
  184. @wraps(view)
  185. def decorated(*args: P.args, **kwargs: P.kwargs):
  186. features = FeatureService.get_system_features()
  187. if features.enable_change_email:
  188. return view(*args, **kwargs)
  189. # otherwise, return 403
  190. abort(403)
  191. return decorated
  192. def is_allow_transfer_owner(view: Callable[P, R]):
  193. @wraps(view)
  194. def decorated(*args: P.args, **kwargs: P.kwargs):
  195. features = FeatureService.get_features(current_user.current_tenant_id)
  196. if features.is_allow_transfer_workspace:
  197. return view(*args, **kwargs)
  198. # otherwise, return 403
  199. abort(403)
  200. return decorated
  201. def knowledge_pipeline_publish_enabled(view):
  202. @wraps(view)
  203. def decorated(*args, **kwargs):
  204. features = FeatureService.get_features(current_user.current_tenant_id)
  205. if features.knowledge_pipeline.publish_enabled:
  206. return view(*args, **kwargs)
  207. abort(403)
  208. return decorated