You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by as...@apache.org on 2022/04/25 11:26:46 UTC

[airflow] branch main updated: Fix dag_id extraction for dag level access checks in web ui (#23015)

This is an automated email from the ASF dual-hosted git repository.

ash pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new dd623016ae Fix dag_id extraction for dag level access checks in web ui (#23015)
dd623016ae is described below

commit dd623016aececfa7e0644e7da901902a7a629f44
Author: npodewitz <10...@users.noreply.github.com>
AuthorDate: Mon Apr 25 13:26:34 2022 +0200

    Fix dag_id extraction for dag level access checks in web ui (#23015)
    
    Properly extract dag_id from post form or json body for dag level access
    permissions.
    
    Added test case for dag level access.
    Fixed test_success_fail_for_read_only_task_instance_access to succeed
    due to the right reasons.
---
 airflow/www/auth.py               |  5 ++-
 tests/www/views/test_views_acl.py | 82 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/airflow/www/auth.py b/airflow/www/auth.py
index cbb9582526..9d40c00a5c 100644
--- a/airflow/www/auth.py
+++ b/airflow/www/auth.py
@@ -36,7 +36,10 @@ def has_access(permissions: Optional[Sequence[Tuple[str, str]]] = None) -> Calla
 
             appbuilder = current_app.appbuilder
 
-            if appbuilder.sm.check_authorization(permissions, request.args.get('dag_id', None)):
+            dag_id = (
+                request.args.get("dag_id") or request.form.get("dag_id") or (request.json or {}).get("dag_id")
+            )
+            if appbuilder.sm.check_authorization(permissions, dag_id):
                 return func(*args, **kwargs)
             elif not g.user.is_anonymous and not g.user.perms:
                 return (
diff --git a/tests/www/views/test_views_acl.py b/tests/www/views/test_views_acl.py
index a37d384539..85c787f492 100644
--- a/tests/www/views/test_views_acl.py
+++ b/tests/www/views/test_views_acl.py
@@ -736,6 +736,7 @@ def user_only_dags_tis(acl_app):
         username="user_only_dags_tis",
         role_name="role_only_dags_tis",
         permissions=[
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
             (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
             (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE),
         ],
@@ -756,7 +757,7 @@ def test_success_fail_for_read_only_task_instance_access(client_only_dags_tis):
     form = dict(
         task_id="run_this_last",
         dag_id="example_bash_operator",
-        execution_date=DEFAULT_DATE,
+        dag_run_id=DEFAULT_RUN_ID,
         upstream="false",
         downstream="false",
         future="false",
@@ -844,3 +845,82 @@ def client_anonymous(acl_app):
 def test_no_roles_permissions(request, client, url, status_code, expected_content):
     resp = request.getfixturevalue(client).get(url, follow_redirects=True)
     check_content_in_response(expected_content, resp, status_code)
+
+
+@pytest.fixture(scope="module")
+def user_dag_level_access_with_ti_edit(acl_app):
+    with create_user_scope(
+        acl_app,
+        username="user_dag_level_access_with_ti_edit",
+        role_name="role_dag_level_access_with_ti_edit",
+        permissions=[
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE),
+            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
+            (permissions.ACTION_CAN_EDIT, permissions.resource_name_for_dag("example_bash_operator")),
+        ],
+    ) as user:
+        yield user
+
+
+@pytest.fixture()
+def client_dag_level_access_with_ti_edit(acl_app, user_dag_level_access_with_ti_edit):
+    return client_with_login(
+        acl_app,
+        username="user_dag_level_access_with_ti_edit",
+        password="user_dag_level_access_with_ti_edit",
+    )
+
+
+def test_success_edit_ti_with_dag_level_access_only(client_dag_level_access_with_ti_edit):
+    form = dict(
+        task_id="run_this_last",
+        dag_id="example_bash_operator",
+        dag_run_id=DEFAULT_RUN_ID,
+        upstream="false",
+        downstream="false",
+        future="false",
+        past="false",
+    )
+    resp = client_dag_level_access_with_ti_edit.post('/success', data=form, follow_redirects=True)
+    check_content_in_response('Marked success on 1 task instances', resp)
+
+
+@pytest.fixture(scope="module")
+def user_ti_edit_without_dag_level_access(acl_app):
+    with create_user_scope(
+        acl_app,
+        username="user_ti_edit_without_dag_level_access",
+        role_name="role_ti_edit_without_dag_level_access",
+        permissions=[
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG),
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE),
+            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
+        ],
+    ) as user:
+        yield user
+
+
+@pytest.fixture()
+def client_ti_edit_without_dag_level_access(acl_app, user_ti_edit_without_dag_level_access):
+    return client_with_login(
+        acl_app,
+        username="user_ti_edit_without_dag_level_access",
+        password="user_ti_edit_without_dag_level_access",
+    )
+
+
+def test_failure_edit_ti_without_dag_level_access(client_ti_edit_without_dag_level_access):
+    form = dict(
+        task_id="run_this_last",
+        dag_id="example_bash_operator",
+        dag_run_id=DEFAULT_RUN_ID,
+        upstream="false",
+        downstream="false",
+        future="false",
+        past="false",
+    )
+    resp = client_ti_edit_without_dag_level_access.post('/success', data=form, follow_redirects=True)
+    check_content_not_in_response('Marked success on 1 task instances', resp)