mirror of
https://github.com/langgenius/dify.git
synced 2026-05-27 04:16:16 +08:00
Replace the single mutable-context Pipeline with a two-phase, condition-driven system dispatched by token type. New architecture: - TokenType(StrEnum) replaces source: str on AuthContext / TokenKind - AuthPipeline: pure prepare→auth step runner; no guard() - PipelineRoute: binds AuthPipeline to an optional required_edition gate - PipelineRouter: single guard() entry point; runs edition/license/token-type pre-gates then dispatches to the registered pipeline for the token type - Cond / When: composable predicates for conditional step dispatch - AuthData: frozen Pydantic model produced by the prepare phase; carries token_id so endpoints don't need to call get_auth_ctx() for identity fields - Edition enum + current_edition(): CE / EE / SAAS discriminator Two pipelines in composition.py: - account_pipeline — OAUTH_ACCOUNT tokens - external_sso_pipeline — OAUTH_EXTERNAL_SSO tokens (EE enforced at route level) All /openapi/v1 endpoints migrated to auth_router.guard(). Old context.py, steps.py, strategies.py, surface_gate.py deleted. WORKSPACE_READ scope added; cached_verdicts renamed to membership_cache.
76 lines
2.8 KiB
Python
76 lines
2.8 KiB
Python
"""POST /openapi/v1/apps/<app_id>/files/upload — upload a file for use in app inputs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from flask import request
|
|
from flask_restx import Resource
|
|
from flask_restx.api import HTTPStatus
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
import services
|
|
from controllers.common.errors import (
|
|
BlockedFileExtensionError,
|
|
FilenameNotExistsError,
|
|
FileTooLargeError,
|
|
NoFileUploadedError,
|
|
TooManyFilesError,
|
|
UnsupportedFileTypeError,
|
|
)
|
|
from controllers.openapi import openapi_ns
|
|
from controllers.openapi.auth.composition import auth_router
|
|
from controllers.openapi.auth.data import AuthData
|
|
from extensions.ext_database import db
|
|
from fields.file_fields import FileResponse
|
|
from libs.oauth_bearer import Scope
|
|
from services.file_service import FileService
|
|
|
|
|
|
@openapi_ns.route("/apps/<string:app_id>/files/upload")
|
|
class AppFileUploadApi(Resource):
|
|
@openapi_ns.doc("upload_file_for_app_input")
|
|
@openapi_ns.doc(description="Upload a file to use as an input variable when running the app")
|
|
@openapi_ns.doc(
|
|
responses={
|
|
201: "File uploaded successfully",
|
|
400: "Bad request — no file or filename missing",
|
|
401: "Unauthorized — invalid or expired bearer token",
|
|
413: "File too large",
|
|
415: "Unsupported file type or blocked extension",
|
|
}
|
|
)
|
|
@openapi_ns.response(HTTPStatus.CREATED, "File uploaded", openapi_ns.models[FileResponse.__name__])
|
|
@auth_router.guard(scope=Scope.APPS_RUN)
|
|
def post(self, app_id: str, *, auth_data: AuthData):
|
|
app_model = auth_data.app
|
|
caller = auth_data.caller
|
|
caller_kind = auth_data.caller_kind
|
|
if "file" not in request.files:
|
|
raise NoFileUploadedError()
|
|
if len(request.files) > 1:
|
|
raise TooManyFilesError()
|
|
|
|
file = request.files["file"]
|
|
if not file.mimetype:
|
|
raise UnsupportedFileTypeError()
|
|
if not file.filename:
|
|
raise FilenameNotExistsError()
|
|
|
|
try:
|
|
upload_file = FileService(db.engine).upload_file(
|
|
filename=file.filename,
|
|
content=file.stream.read(),
|
|
mimetype=file.mimetype,
|
|
user=caller,
|
|
)
|
|
except ValueError as exc:
|
|
raise BadRequest(str(exc))
|
|
except services.errors.file.FileTooLargeError as exc:
|
|
raise FileTooLargeError(exc.description)
|
|
except services.errors.file.UnsupportedFileTypeError:
|
|
raise UnsupportedFileTypeError()
|
|
except services.errors.file.BlockedFileExtensionError as exc:
|
|
raise BlockedFileExtensionError(exc.description)
|
|
|
|
response = FileResponse.model_validate(upload_file, from_attributes=True)
|
|
return response.model_dump(mode="json"), 201
|