diff --git a/airflow/config_templates/config.yml b/airflow/config_templates/config.yml index 5890e43de6c87..3018f1a64a5f4 100644 --- a/airflow/config_templates/config.yml +++ b/airflow/config_templates/config.yml @@ -2052,6 +2052,22 @@ webserver: type: boolean example: ~ default: "False" + max_form_memory_size: + description: | + The maximum size in bytes any non-file form field may be in a multipart/form-data body. + If this limit is exceeded, a 413 RequestEntityTooLarge error is raised by webserver. + version_added: 2.10.5 + type: integer + example: ~ + default: "500000" + max_form_parts: + description: | + The maximum number of fields that may be present in a multipart/form-data body. + If this limit is exceeded, a 413 RequestEntityTooLarge error is raised by webserver. + version_added: 2.10.5 + type: integer + example: ~ + default: "1000" email: description: | Configuration email backend and whether to diff --git a/airflow/www/app.py b/airflow/www/app.py index 91b23875dcda1..2656045e84ba5 100644 --- a/airflow/www/app.py +++ b/airflow/www/app.py @@ -72,6 +72,8 @@ def create_app(config=None, testing=False): flask_app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=settings.get_session_lifetime_config()) flask_app.config["MAX_CONTENT_LENGTH"] = conf.getfloat("webserver", "allowed_payload_size") * 1024 * 1024 + flask_app.config["MAX_FORM_PARTS"] = conf.getint("webserver", "max_form_parts") + flask_app.config["MAX_FORM_MEMORY_SIZE"] = conf.getint("webserver", "max_form_memory_size") webserver_config = conf.get_mandatory_value("webserver", "config_file") # Enable customizations in webserver_config.py to be applied via Flask.current_app. diff --git a/airflow/www/extensions/init_views.py b/airflow/www/extensions/init_views.py index a540bdd3a6765..a45e008d77356 100644 --- a/airflow/www/extensions/init_views.py +++ b/airflow/www/extensions/init_views.py @@ -25,6 +25,7 @@ from connexion.decorators.validation import RequestBodyValidator from connexion.exceptions import BadRequestProblem from flask import request +from werkzeug import Request from airflow.api_connexion.exceptions import common_error_handler from airflow.api_fastapi.app import get_auth_manager @@ -189,6 +190,21 @@ def set_cors_headers_on_response(response): return response +def init_data_form_parameters(): + """ + Initialize custom values for data form parameters. + + This is a workaround for Flask versions prior to 3.1.0. + In order to allow users customizing form data parameters, we need these two fields to be configurable. + Starting from Flask 3.1.0 these two parameters can be configured through Flask config, but unfortunately, + current version of flask supported in Airflow is way older. That's why this workaround was introduced. + See https://flask.palletsprojects.com/en/stable/api/#flask.Request.max_form_memory_size + # TODO: remove it when Flask upgraded to version 3.1.0 or higher. + """ + Request.max_form_parts = conf.getint("webserver", "max_form_parts") + Request.max_form_memory_size = conf.getint("webserver", "max_form_memory_size") + + class _LazyResolution: """ OpenAPI endpoint that lazily resolves the function on first use. @@ -281,6 +297,7 @@ def init_api_connexion(app: Flask) -> None: validate_responses=True, validator_map={"body": _CustomErrorRequestBodyValidator}, ).blueprint + api_bp.before_app_request(init_data_form_parameters) api_bp.after_request(set_cors_headers_on_response) app.register_blueprint(api_bp)