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.

upload.py 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from mimetypes import guess_extension
  2. from typing import Optional
  3. from flask_restx import Resource, reqparse
  4. from flask_restx.api import HTTPStatus
  5. from werkzeug.datastructures import FileStorage
  6. from werkzeug.exceptions import Forbidden
  7. import services
  8. from controllers.common.errors import (
  9. FileTooLargeError,
  10. UnsupportedFileTypeError,
  11. )
  12. from controllers.console.wraps import setup_required
  13. from controllers.files import files_ns
  14. from controllers.inner_api.plugin.wraps import get_user
  15. from core.file.helpers import verify_plugin_file_signature
  16. from core.tools.tool_file_manager import ToolFileManager
  17. from fields.file_fields import build_file_model
  18. # Define parser for both documentation and validation
  19. upload_parser = reqparse.RequestParser()
  20. upload_parser.add_argument("file", location="files", type=FileStorage, required=True, help="File to upload")
  21. upload_parser.add_argument(
  22. "timestamp", type=str, required=True, location="args", help="Unix timestamp for signature verification"
  23. )
  24. upload_parser.add_argument(
  25. "nonce", type=str, required=True, location="args", help="Random string for signature verification"
  26. )
  27. upload_parser.add_argument(
  28. "sign", type=str, required=True, location="args", help="HMAC signature for request validation"
  29. )
  30. upload_parser.add_argument("tenant_id", type=str, required=True, location="args", help="Tenant identifier")
  31. upload_parser.add_argument("user_id", type=str, required=False, location="args", help="User identifier")
  32. @files_ns.route("/upload/for-plugin")
  33. class PluginUploadFileApi(Resource):
  34. @setup_required
  35. @files_ns.expect(upload_parser)
  36. @files_ns.doc("upload_plugin_file")
  37. @files_ns.doc(description="Upload a file for plugin usage with signature verification")
  38. @files_ns.doc(
  39. responses={
  40. 201: "File uploaded successfully",
  41. 400: "Invalid request parameters",
  42. 403: "Forbidden - Invalid signature or missing parameters",
  43. 413: "File too large",
  44. 415: "Unsupported file type",
  45. }
  46. )
  47. @files_ns.marshal_with(build_file_model(files_ns), code=HTTPStatus.CREATED)
  48. def post(self):
  49. """Upload a file for plugin usage.
  50. Accepts a file upload with signature verification for security.
  51. The file must be accompanied by valid timestamp, nonce, and signature parameters.
  52. Returns:
  53. dict: File metadata including ID, URLs, and properties
  54. int: HTTP status code (201 for success)
  55. Raises:
  56. Forbidden: Invalid signature or missing required parameters
  57. FileTooLargeError: File exceeds size limit
  58. UnsupportedFileTypeError: File type not supported
  59. """
  60. # Parse and validate all arguments
  61. args = upload_parser.parse_args()
  62. file: FileStorage = args["file"]
  63. timestamp: str = args["timestamp"]
  64. nonce: str = args["nonce"]
  65. sign: str = args["sign"]
  66. tenant_id: str = args["tenant_id"]
  67. user_id: Optional[str] = args.get("user_id")
  68. user = get_user(tenant_id, user_id)
  69. filename: Optional[str] = file.filename
  70. mimetype: Optional[str] = file.mimetype
  71. if not filename or not mimetype:
  72. raise Forbidden("Invalid request.")
  73. if not verify_plugin_file_signature(
  74. filename=filename,
  75. mimetype=mimetype,
  76. tenant_id=tenant_id,
  77. user_id=user.id,
  78. timestamp=timestamp,
  79. nonce=nonce,
  80. sign=sign,
  81. ):
  82. raise Forbidden("Invalid request.")
  83. try:
  84. tool_file = ToolFileManager().create_file_by_raw(
  85. user_id=user.id,
  86. tenant_id=tenant_id,
  87. file_binary=file.read(),
  88. mimetype=mimetype,
  89. filename=filename,
  90. conversation_id=None,
  91. )
  92. extension = guess_extension(tool_file.mimetype) or ".bin"
  93. preview_url = ToolFileManager.sign_file(tool_file_id=tool_file.id, extension=extension)
  94. # Create a dictionary with all the necessary attributes
  95. result = {
  96. "id": tool_file.id,
  97. "user_id": tool_file.user_id,
  98. "tenant_id": tool_file.tenant_id,
  99. "conversation_id": tool_file.conversation_id,
  100. "file_key": tool_file.file_key,
  101. "mimetype": tool_file.mimetype,
  102. "original_url": tool_file.original_url,
  103. "name": tool_file.name,
  104. "size": tool_file.size,
  105. "mime_type": mimetype,
  106. "extension": extension,
  107. "preview_url": preview_url,
  108. }
  109. return result, 201
  110. except services.errors.file.FileTooLargeError as file_too_large_error:
  111. raise FileTooLargeError(file_too_large_error.description)
  112. except services.errors.file.UnsupportedFileTypeError:
  113. raise UnsupportedFileTypeError()