You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ep...@apache.org on 2022/05/17 07:46:11 UTC
[airflow] 03/20: Handle invalid date parsing in webserver views. (#23161)
This is an automated email from the ASF dual-hosted git repository.
ephraimanierobi pushed a commit to branch v2-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 540beb6f2b5f82d9f0b9f71ae0f06ce0f5bc9bdf
Author: Karthikeyan Singaravelan <ti...@gmail.com>
AuthorDate: Tue May 17 00:45:56 2022 +0530
Handle invalid date parsing in webserver views. (#23161)
* Handle invalid date from query parameters in views.
* Add tests.
* Use common parsing helper.
* Add type hint.
* Remove unwanted error check.
* Fix extra_links endpoint.
(cherry picked from commit 9e25bc211f6f7bba1aff133d21fe3865dabda53d)
---
airflow/www/views.py | 46 ++++++++++++++++++++++------------
tests/www/views/test_views.py | 57 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 88 insertions(+), 15 deletions(-)
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 6a4a191c67..fe4ae3ceb1 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -186,13 +186,13 @@ def get_date_time_num_runs_dag_runs_form_data(www_request, session, dag):
"""Get Execution Data, Base Date & Number of runs from a Request"""
date_time = www_request.args.get('execution_date')
if date_time:
- date_time = timezone.parse(date_time)
+ date_time = _safe_parse_datetime(date_time)
else:
date_time = dag.get_latest_execution_date(session=session) or timezone.utcnow()
base_date = www_request.args.get('base_date')
if base_date:
- base_date = timezone.parse(base_date)
+ base_date = _safe_parse_datetime(base_date)
else:
# The DateTimeField widget truncates milliseconds and would loose
# the first dag run. Round to next second.
@@ -231,6 +231,14 @@ def get_date_time_num_runs_dag_runs_form_data(www_request, session, dag):
}
+def _safe_parse_datetime(v):
+ """Parse datetime and return error message for invalid dates"""
+ try:
+ return timezone.parse(v)
+ except (TypeError, ParserError):
+ abort(400, f"Invalid datetime: {v!r}")
+
+
def task_group_to_grid(task_item_or_group, dag, dag_runs, tis, session):
"""
Create a nested dict representation of this TaskGroup and its children used to construct
@@ -1244,7 +1252,7 @@ class Airflow(AirflowBaseView):
task_id = request.args.get('task_id')
map_index = request.args.get('map_index', -1, type=int)
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
form = DateTimeForm(data={'execution_date': dttm})
root = request.args.get('root', '')
@@ -1345,7 +1353,8 @@ class Airflow(AirflowBaseView):
dag_id = request.args.get('dag_id')
task_id = request.args.get('task_id')
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
+
form = DateTimeForm(data={'execution_date': dttm})
root = request.args.get('root', '')
map_index = request.args.get('map_index', -1, type=int)
@@ -1494,7 +1503,12 @@ class Airflow(AirflowBaseView):
task_id = request.args.get('task_id')
map_index = request.args.get('map_index', -1, type=int)
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date) if execution_date else None
+
+ if execution_date:
+ dttm = _safe_parse_datetime(execution_date)
+ else:
+ dttm = None
+
form = DateTimeForm(data={'execution_date': dttm})
dag_model = DagModel.get_dagmodel(dag_id)
@@ -1541,7 +1555,7 @@ class Airflow(AirflowBaseView):
dag_id = request.args.get('dag_id')
task_id = request.args.get('task_id')
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
map_index = request.args.get('map_index', -1, type=int)
try_number = request.args.get('try_number', 1)
@@ -1578,7 +1592,7 @@ class Airflow(AirflowBaseView):
dag_id = request.args.get('dag_id')
task_id = request.args.get('task_id')
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
map_index = request.args.get('map_index', -1, type=int)
form = DateTimeForm(data={'execution_date': dttm})
root = request.args.get('root', '')
@@ -1710,7 +1724,8 @@ class Airflow(AirflowBaseView):
# Carrying execution_date through, even though it's irrelevant for
# this context
execution_date = request.args.get('execution_date')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
+
form = DateTimeForm(data={'execution_date': dttm})
root = request.args.get('root', '')
dag = DagModel.get_dagmodel(dag_id)
@@ -2052,7 +2067,7 @@ class Airflow(AirflowBaseView):
map_indexes = request.form.getlist('map_index', type=int)
execution_date = request.form.get('execution_date')
- execution_date = timezone.parse(execution_date)
+ execution_date = _safe_parse_datetime(execution_date)
confirmed = request.form.get('confirmed') == "true"
upstream = request.form.get('upstream') == "true"
downstream = request.form.get('downstream') == "true"
@@ -2583,7 +2598,7 @@ class Airflow(AirflowBaseView):
num_runs = conf.getint('webserver', 'default_dag_run_display_number')
try:
- base_date = timezone.parse(request.args["base_date"])
+ base_date = _safe_parse_datetime(request.args["base_date"])
except (KeyError, ValueError):
base_date = dag.get_latest_execution_date() or timezone.utcnow()
@@ -2805,6 +2820,7 @@ class Airflow(AirflowBaseView):
edges = dag_edges(dag)
dt_nr_dr_data = get_date_time_num_runs_dag_runs_form_data(request, session, dag)
+
dt_nr_dr_data['arrange'] = arrange
dttm = dt_nr_dr_data['dttm']
dag_run = dag.get_dagrun(execution_date=dttm)
@@ -2911,7 +2927,7 @@ class Airflow(AirflowBaseView):
num_runs = request.args.get('num_runs', default=default_dag_run, type=int)
if base_date:
- base_date = timezone.parse(base_date)
+ base_date = _safe_parse_datetime(base_date)
else:
base_date = dag.get_latest_execution_date() or timezone.utcnow()
@@ -3058,7 +3074,7 @@ class Airflow(AirflowBaseView):
num_runs = request.args.get('num_runs', default=default_dag_run, type=int)
if base_date:
- base_date = timezone.parse(base_date)
+ base_date = _safe_parse_datetime(base_date)
else:
base_date = dag.get_latest_execution_date() or timezone.utcnow()
@@ -3148,7 +3164,7 @@ class Airflow(AirflowBaseView):
num_runs = request.args.get('num_runs', default=default_dag_run, type=int)
if base_date:
- base_date = timezone.parse(base_date)
+ base_date = _safe_parse_datetime(base_date)
else:
base_date = dag.get_latest_execution_date() or timezone.utcnow()
@@ -3382,7 +3398,7 @@ class Airflow(AirflowBaseView):
map_index = request.args.get('map_index', -1, type=int)
execution_date = request.args.get('execution_date')
link_name = request.args.get('link_name')
- dttm = timezone.parse(execution_date)
+ dttm = _safe_parse_datetime(execution_date)
dag = current_app.dag_bag.get_dag(dag_id)
if not dag or task_id not in dag.task_ids:
@@ -3437,7 +3453,7 @@ class Airflow(AirflowBaseView):
dttm = request.args.get('execution_date')
if dttm:
- dttm = timezone.parse(dttm)
+ dttm = _safe_parse_datetime(dttm)
else:
response = jsonify({'error': f"Invalid execution_date {dttm}"})
response.status_code = 400
diff --git a/tests/www/views/test_views.py b/tests/www/views/test_views.py
index c7900d64fd..887bd4898a 100644
--- a/tests/www/views/test_views.py
+++ b/tests/www/views/test_views.py
@@ -373,3 +373,60 @@ def test_get_task_stats_from_query():
data = get_task_stats_from_query(query_data)
assert data == expected_data
+
+
+@pytest.mark.parametrize(
+ "url, content",
+ [
+ (
+ '/rendered-templates?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ '/log?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ '/redirect_to_external_log?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ '/task?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/graph?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/graph?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/duration?base_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/tries?base_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/landing-times?base_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'dags/example_bash_operator/gantt?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ (
+ 'extra_links?execution_date=invalid',
+ "Invalid datetime: 'invalid'",
+ ),
+ ],
+)
+def test_invalid_dates(app, admin_client, url, content):
+ """Test invalid date format doesn't crash page."""
+ resp = admin_client.get(url, follow_redirects=True)
+
+ assert resp.status_code == 400
+ assert content in resp.get_data().decode()