mirror of
https://github.com/langgenius/dify.git
synced 2026-11-11 22:02:23 +08:00
Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
203 lines
7.8 KiB
Python
203 lines
7.8 KiB
Python
"""Public bearer-only file upload transport for v2 approval grants."""
|
|
|
|
from flask import request
|
|
from flask_restx import Resource
|
|
from pydantic import BaseModel, ConfigDict, HttpUrl, ValidationError
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
from controllers.common.errors import (
|
|
BlockedFileExtensionError,
|
|
FilenameNotExistsError,
|
|
FileTooLargeError,
|
|
NoFileUploadedError,
|
|
RemoteFileAccessDeniedError,
|
|
RemoteFileInvalidResponseError,
|
|
RemoteFileInvalidUrlError,
|
|
RemoteFileNotFoundError,
|
|
RemoteFileUnavailableError,
|
|
RemoteFileUrlBlockedError,
|
|
TooManyFilesError,
|
|
UnsupportedFileTypeError,
|
|
)
|
|
from controllers.common.human_input_v2_errors import (
|
|
PublicHumanInputV2ExpiredError,
|
|
PublicHumanInputV2NotFoundError,
|
|
PublicHumanInputV2SubmittedError,
|
|
)
|
|
from controllers.common.schema import register_response_schema_models, register_schema_models
|
|
from controllers.web import web_ns
|
|
from controllers.web.human_input_file_upload import (
|
|
HumanInputFileUploadFormPayload,
|
|
InvalidUploadTokenBadRequestError,
|
|
InvalidUploadTokenForbiddenError,
|
|
InvalidUploadTokenUnauthorizedError,
|
|
)
|
|
from controllers.web.human_input_form_access_request import _raise_public_form_error
|
|
from fields.file_fields import FileWithSignedUrl
|
|
from libs.external_api import error_responses
|
|
from libs.helper import dump_response
|
|
from services import remote_file_service as remote_errors
|
|
from services.errors import file as file_errors
|
|
from services.human_input_file_upload_service import InvalidUploadTokenError
|
|
from services.human_input_v2.file_upload_service import V2_UPLOAD_TOKEN_PREFIX, build_file_upload_service
|
|
from services.human_input_v2.submission_service import HumanInputFormError
|
|
|
|
register_response_schema_models(web_ns, FileWithSignedUrl)
|
|
|
|
|
|
@web_ns.route("/human-input/v2/files")
|
|
class HumanInputV2FileUploadApi(Resource):
|
|
@error_responses(
|
|
InvalidUploadTokenUnauthorizedError,
|
|
InvalidUploadTokenBadRequestError,
|
|
InvalidUploadTokenForbiddenError,
|
|
PublicHumanInputV2NotFoundError,
|
|
PublicHumanInputV2SubmittedError,
|
|
PublicHumanInputV2ExpiredError,
|
|
NoFileUploadedError,
|
|
FilenameNotExistsError,
|
|
TooManyFilesError,
|
|
FileTooLargeError,
|
|
UnsupportedFileTypeError,
|
|
BlockedFileExtensionError,
|
|
RemoteFileAccessDeniedError,
|
|
RemoteFileInvalidResponseError,
|
|
RemoteFileInvalidUrlError,
|
|
RemoteFileNotFoundError,
|
|
RemoteFileUnavailableError,
|
|
RemoteFileUrlBlockedError,
|
|
)
|
|
@web_ns.doc(
|
|
description="Upload a file or a remote URL using a v2 upload grant.",
|
|
consumes=["multipart/form-data"],
|
|
security=[],
|
|
params={
|
|
"Authorization": {
|
|
"in": "header",
|
|
"type": "string",
|
|
"required": True,
|
|
"description": "Bearer v2 upload grant",
|
|
},
|
|
"file": {"in": "formData", "type": "file", "required": False},
|
|
"url": {"in": "formData", "type": "string", "required": False, "description": "Remote HTTP(S) file URL"},
|
|
},
|
|
responses={
|
|
400: "Invalid upload",
|
|
401: "Grant required",
|
|
403: "Invalid grant",
|
|
404: "Form not found",
|
|
412: "Form inactive",
|
|
},
|
|
)
|
|
@web_ns.response(201, "File uploaded", web_ns.models[FileWithSignedUrl.__name__])
|
|
def post(self):
|
|
return _upload_file(remote=False)
|
|
|
|
|
|
class HumanInputV2RemoteUploadPayload(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
url: HttpUrl
|
|
|
|
|
|
register_schema_models(web_ns, HumanInputV2RemoteUploadPayload)
|
|
|
|
|
|
@web_ns.route("/human-input/v2/files/remote")
|
|
class HumanInputV2RemoteFileUploadApi(Resource):
|
|
@error_responses(
|
|
InvalidUploadTokenUnauthorizedError,
|
|
InvalidUploadTokenBadRequestError,
|
|
InvalidUploadTokenForbiddenError,
|
|
PublicHumanInputV2NotFoundError,
|
|
PublicHumanInputV2SubmittedError,
|
|
PublicHumanInputV2ExpiredError,
|
|
NoFileUploadedError,
|
|
FilenameNotExistsError,
|
|
TooManyFilesError,
|
|
FileTooLargeError,
|
|
UnsupportedFileTypeError,
|
|
BlockedFileExtensionError,
|
|
RemoteFileAccessDeniedError,
|
|
RemoteFileInvalidResponseError,
|
|
RemoteFileInvalidUrlError,
|
|
RemoteFileNotFoundError,
|
|
RemoteFileUnavailableError,
|
|
RemoteFileUrlBlockedError,
|
|
)
|
|
@web_ns.doc(
|
|
description="Fetch a remote file using a v2 upload grant.",
|
|
security=[],
|
|
params={"Authorization": {"in": "header", "type": "string", "required": True}},
|
|
responses={
|
|
400: "Invalid upload",
|
|
401: "Grant required",
|
|
403: "Invalid grant",
|
|
404: "Form not found",
|
|
412: "Form inactive",
|
|
},
|
|
)
|
|
@web_ns.expect(web_ns.models[HumanInputV2RemoteUploadPayload.__name__])
|
|
@web_ns.response(201, "File uploaded", web_ns.models[FileWithSignedUrl.__name__])
|
|
def post(self):
|
|
return _upload_file(remote=True)
|
|
|
|
|
|
def _upload_file(*, remote: bool):
|
|
authorization = request.headers.get("Authorization", "").split()
|
|
if len(authorization) != 2:
|
|
raise InvalidUploadTokenUnauthorizedError()
|
|
scheme, token = authorization
|
|
if scheme.lower() != "bearer" or not token.startswith(V2_UPLOAD_TOKEN_PREFIX):
|
|
raise InvalidUploadTokenBadRequestError()
|
|
service = build_file_upload_service()
|
|
try:
|
|
context = service.validate_upload_token(token)
|
|
except InvalidUploadTokenError as error:
|
|
raise InvalidUploadTokenForbiddenError() from error
|
|
except HumanInputFormError as error:
|
|
_raise_public_form_error(error)
|
|
try:
|
|
if remote:
|
|
payload = HumanInputV2RemoteUploadPayload.model_validate(web_ns.payload or {})
|
|
form = HumanInputFileUploadFormPayload(url=payload.url)
|
|
else:
|
|
form = HumanInputFileUploadFormPayload.model_validate(request.form.to_dict(flat=True))
|
|
except ValidationError as error:
|
|
raise RemoteFileInvalidUrlError() from error
|
|
if len(request.files.getlist("file")) > 1 or len(request.files) > 1:
|
|
raise TooManyFilesError()
|
|
if form.url is not None and request.files:
|
|
raise BadRequest("Provide either a file or a URL.")
|
|
try:
|
|
if form.url is not None:
|
|
uploaded = service.upload_remote_file(context=context, url=str(form.url))
|
|
else:
|
|
file = request.files.get("file")
|
|
if file is None:
|
|
raise NoFileUploadedError()
|
|
if not file.filename:
|
|
raise FilenameNotExistsError()
|
|
uploaded = service.upload_local_file(
|
|
context=context, filename=file.filename, content=file.read(), mimetype=file.mimetype
|
|
)
|
|
except file_errors.FileTooLargeError as error:
|
|
raise FileTooLargeError(error.description or "File size exceeded.") from error
|
|
except file_errors.UnsupportedFileTypeError as error:
|
|
raise UnsupportedFileTypeError() from error
|
|
except file_errors.BlockedFileExtensionError as error:
|
|
raise BlockedFileExtensionError() from error
|
|
except remote_errors.RemoteFileInvalidUrlError as error:
|
|
raise RemoteFileInvalidUrlError() from error
|
|
except remote_errors.RemoteFileUrlBlockedError as error:
|
|
raise RemoteFileUrlBlockedError() from error
|
|
except remote_errors.RemoteFileNotFoundError as error:
|
|
raise RemoteFileNotFoundError() from error
|
|
except remote_errors.RemoteFileAccessDeniedError as error:
|
|
raise RemoteFileAccessDeniedError() from error
|
|
except remote_errors.RemoteFileUnavailableError as error:
|
|
raise RemoteFileUnavailableError() from error
|
|
except remote_errors.RemoteFileInvalidResponseError as error:
|
|
raise RemoteFileInvalidResponseError() from error
|
|
return dump_response(FileWithSignedUrl, uploaded), 201
|