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

vincbeck 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 447c20fa2cf Fix DAG named "DAGs" colliding with the global DAGs 
permission resource (#69106)
447c20fa2cf is described below

commit 447c20fa2cf702ae3f76e5e7695187f8fa934f19
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Jul 6 15:28:28 2026 +0200

    Fix DAG named "DAGs" colliding with the global DAGs permission resource 
(#69106)
    
    resource_name() returned the dag_id unchanged when it equalled a reserved
    resource name. Because a real dag_id can be "DAGs" (it passes the dag_id
    validator), a DAG named "DAGs" resolved to the global DAGs resource instead
    of its own per-DAG "DAG:DAGs" resource, so permissions for that DAG were
    applied to the wrong resource.
    
    Drop the reserved-name short-circuit so a dag_id is always prefixed. The
    already-prefixed branch (e.g. "DAG:foo") still short-circuits, which is safe
    because the "DAG:" / "DAG Run:" prefixes contain a colon that the dag_id
    validator (KEY_REGEX = ^[\w.-]+$) forbids. Mirrored in the airflow-core 
copy.
---
 airflow-core/src/airflow/security/permissions.py   |  2 --
 .../tests/unit/security/test_permissions.py        | 26 ++++++++++++++++++++++
 .../providers/fab/www/security/permissions.py      |  2 --
 .../tests/unit/fab/auth_manager/test_security.py   |  6 +++++
 4 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/security/permissions.py 
b/airflow-core/src/airflow/security/permissions.py
index 867ea7b6bdf..cb6aa7fe889 100644
--- a/airflow-core/src/airflow/security/permissions.py
+++ b/airflow-core/src/airflow/security/permissions.py
@@ -106,8 +106,6 @@ PREFIX_RESOURCES_MAP = {details["prefix"]: resource for 
resource, details in RES
 
 def resource_name(dag_id: str, resource: str) -> str:
     """Return the resource name for a DAG id."""
-    if dag_id in RESOURCE_DETAILS_MAP.keys():
-        return dag_id
     if dag_id.startswith(tuple(PREFIX_RESOURCES_MAP.keys())):
         return dag_id
     return f"{RESOURCE_DETAILS_MAP[resource]['prefix']}{dag_id}"
diff --git a/airflow-core/tests/unit/security/test_permissions.py 
b/airflow-core/tests/unit/security/test_permissions.py
new file mode 100644
index 00000000000..b212bd1e2d0
--- /dev/null
+++ b/airflow-core/tests/unit/security/test_permissions.py
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from airflow.security import permissions
+
+
+def test_resource_name_does_not_collide_with_reserved_resource_names():
+    # Regression: a Dag literally named "DAGs" (the global resource name, and 
a valid
+    # dag_id) must resolve to its own per-DAG resource, never the global one.
+    assert permissions.resource_name(permissions.RESOURCE_DAG, 
permissions.RESOURCE_DAG) == "DAG:DAGs"
diff --git 
a/providers/fab/src/airflow/providers/fab/www/security/permissions.py 
b/providers/fab/src/airflow/providers/fab/www/security/permissions.py
index 37ce89ba18c..630a95ea541 100644
--- a/providers/fab/src/airflow/providers/fab/www/security/permissions.py
+++ b/providers/fab/src/airflow/providers/fab/www/security/permissions.py
@@ -96,8 +96,6 @@ PREFIX_RESOURCES_MAP = {details["prefix"]: resource for 
resource, details in RES
 
 def resource_name(dag_id: str, resource: str) -> str:
     """Return the resource name for a DAG id."""
-    if dag_id in RESOURCE_DETAILS_MAP.keys():
-        return dag_id
     if dag_id.startswith(tuple(PREFIX_RESOURCES_MAP.keys())):
         return dag_id
     return f"{RESOURCE_DETAILS_MAP[resource]['prefix']}{dag_id}"
diff --git a/providers/fab/tests/unit/fab/auth_manager/test_security.py 
b/providers/fab/tests/unit/fab/auth_manager/test_security.py
index e80e5f3b077..f2520fbd884 100644
--- a/providers/fab/tests/unit/fab/auth_manager/test_security.py
+++ b/providers/fab/tests/unit/fab/auth_manager/test_security.py
@@ -1273,3 +1273,9 @@ def test_add_user_uses_configured_hash_method(mock_hash, 
app, security_manager):
                 mock_hash.assert_called_with("plaintext", 
method="pbkdf2:sha256")
             finally:
                 delete_user(app, "hash_method_add_test")
+
+
+def test_resource_name_does_not_collide_with_reserved_resource_names():
+    # Regression: a Dag literally named "DAGs" (the global resource name, and 
a valid
+    # dag_id) must resolve to its own per-DAG resource, never the global one.
+    assert permissions.resource_name(permissions.RESOURCE_DAG, 
permissions.RESOURCE_DAG) == "DAG:DAGs"

Reply via email to