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

pierrejeambrun 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 68c8dc8701b Add REPARSE_ALL permission for reparsing files with no 
registered Dag (#73274)
68c8dc8701b is described below

commit 68c8dc8701b7074490df7f17cf14f1d73aef2962
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Sep 17 16:25:00 2026 +0200

    Add REPARSE_ALL permission for reparsing files with no registered Dag 
(#73274)
    
    * Add REPARSE_ALL permission for reparsing files with no registered Dag
    
    Reparsing a file that has no registered Dag needs a permission of its own: 
such a file has no per-Dag key to authorize the reparse against, and the 
existing IMPORT_ERRORS_ALL view only governs seeing those errors. Gating the 
reparse action on that view would let anyone who can view an error also trigger 
its reparse. A dedicated admin-by-default, team-scoped permission keeps seeing 
and acting separate. It is consumed by a follow-up that adds reparse of 
unregistered files from the UI.
    
    * Mark fab's common-compat dependency to use the next version
    
    This PR extends the common-compat access-view shim with REPARSE_ALL, which 
the
    FAB auth manager consumes, so FAB needs the upcoming common-compat release. 
The
    "# use next version" marker records that intent and satisfies the provider
    dependency CI guard that fires when common-compat changes alongside a 
provider.
---
 .../api_fastapi/auth/managers/models/resource_details.py      |  3 +++
 .../api_fastapi/auth/managers/simple/simple_auth_manager.py   |  7 ++++---
 .../auth/managers/simple/test_simple_auth_manager.py          |  9 +++++----
 .../airflow/providers/common/compat/security/access_view.py   | 11 +++++++----
 .../tests/unit/common/compat/security/test_access_view.py     |  3 ++-
 providers/fab/pyproject.toml                                  |  2 +-
 .../airflow/providers/fab/auth_manager/fab_auth_manager.py    |  8 ++++++--
 .../providers/fab/auth_manager/security_manager/override.py   |  1 +
 .../fab/src/airflow/providers/fab/www/security/permissions.py |  1 +
 9 files changed, 30 insertions(+), 15 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py 
b/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
index cd8b0a7f55c..330dc3d607c 100644
--- 
a/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
+++ 
b/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
@@ -113,6 +113,9 @@ class AccessView(Enum):
     JOBS = "JOBS"
     PLUGINS = "PLUGINS"
     PROVIDERS = "PROVIDERS"
+    # Reparsing a file with no registered Dag: there is no per-Dag key to
+    # authorize on, so it gets its own admin-by-default view.
+    REPARSE_ALL = "REPARSE_ALL"
     TRIGGERS = "TRIGGERS"
     WEBSITE = "WEBSITE"
 
diff --git 
a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/simple_auth_manager.py
 
b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/simple_auth_manager.py
index 542fde46670..8f15e734d2c 100644
--- 
a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/simple_auth_manager.py
+++ 
b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/simple_auth_manager.py
@@ -355,11 +355,12 @@ class 
SimpleAuthManager(BaseAuthManager[SimpleAuthManagerUser]):
         self, *, access_view: AccessView, user: SimpleAuthManagerUser, 
team_name: str | None = None
     ) -> bool:
         # Views covering records that have no per-Dag key to authorize on are 
admin-only --
-        # import errors for files with no registered Dag, and audit log rows 
not tied to a
-        # Dag. Every other view stays readable by viewers.
+        # import errors for files with no registered Dag, audit log rows not 
tied to a Dag, and
+        # reparsing a file with no registered Dag. Every other view stays 
readable by viewers.
         allow_role = (
             SimpleAuthManagerRole.ADMIN
-            if access_view in (AccessView.IMPORT_ERRORS_ALL, 
AccessView.AUDIT_LOGS_ALL)
+            if access_view
+            in (AccessView.IMPORT_ERRORS_ALL, AccessView.AUDIT_LOGS_ALL, 
AccessView.REPARSE_ALL)
             else SimpleAuthManagerRole.VIEWER
         )
         return self._is_authorized(method="GET", allow_role=allow_role, 
user=user, team_name=team_name)
diff --git 
a/airflow-core/tests/unit/api_fastapi/auth/managers/simple/test_simple_auth_manager.py
 
b/airflow-core/tests/unit/api_fastapi/auth/managers/simple/test_simple_auth_manager.py
index 8c65d1e8266..5cdb5248a12 100644
--- 
a/airflow-core/tests/unit/api_fastapi/auth/managers/simple/test_simple_auth_manager.py
+++ 
b/airflow-core/tests/unit/api_fastapi/auth/managers/simple/test_simple_auth_manager.py
@@ -349,7 +349,7 @@ class TestSimpleAuthManager:
 
     @pytest.mark.parametrize(
         "access_view",
-        [AccessView.IMPORT_ERRORS_ALL, AccessView.AUDIT_LOGS_ALL],
+        [AccessView.IMPORT_ERRORS_ALL, AccessView.AUDIT_LOGS_ALL, 
AccessView.REPARSE_ALL],
     )
     @pytest.mark.parametrize(
         ("role", "result"),
@@ -363,9 +363,10 @@ class TestSimpleAuthManager:
     def test_is_authorized_view_admin_only_views(self, auth_manager, 
access_view, role, result):
         """The views covering records with no per-Dag key to authorize on are 
admin-only.
 
-        Every other view is readable by a viewer (asserted above); these two 
gate records
-        that carry no other authorization key -- import errors for files with 
no registered
-        Dag, and audit log rows not tied to a Dag -- so they must not ride on 
viewer access.
+        Every other view is readable by a viewer (asserted above); these gate 
records that
+        carry no other authorization key -- import errors for files with no 
registered Dag,
+        audit log rows not tied to a Dag, and reparsing a file with no 
registered Dag -- so
+        they must not ride on viewer access.
         """
         assert (
             auth_manager.is_authorized_view(
diff --git 
a/providers/common/compat/src/airflow/providers/common/compat/security/access_view.py
 
b/providers/common/compat/src/airflow/providers/common/compat/security/access_view.py
index 23209b68ad9..cd811fb8926 100644
--- 
a/providers/common/compat/src/airflow/providers/common/compat/security/access_view.py
+++ 
b/providers/common/compat/src/airflow/providers/common/compat/security/access_view.py
@@ -16,16 +16,19 @@
 # under the License.
 from __future__ import annotations
 
-# ``AccessView.IMPORT_ERRORS_ALL`` and ``AccessView.AUDIT_LOGS_ALL`` were 
added in
-# Airflow 3.4.0, and providers are released independently of core. ``None`` 
signals the
-# view is unavailable on the running core and callers should skip mapping it.
+# ``AccessView.IMPORT_ERRORS_ALL`` and ``AccessView.AUDIT_LOGS_ALL`` were 
added in Airflow
+# 3.4.0 and ``AccessView.REPARSE_ALL`` after it, and providers are released 
independently of
+# core. ``None`` signals the view is unavailable on the running core and 
callers should skip
+# mapping it.
 try:
     from airflow.api_fastapi.auth.managers.models.resource_details import 
AccessView
 
     IMPORT_ERRORS_ALL_ACCESS_VIEW: AccessView | None = getattr(AccessView, 
"IMPORT_ERRORS_ALL", None)
     AUDIT_LOGS_ALL_ACCESS_VIEW: AccessView | None = getattr(AccessView, 
"AUDIT_LOGS_ALL", None)
+    REPARSE_ALL_ACCESS_VIEW: AccessView | None = getattr(AccessView, 
"REPARSE_ALL", None)
 except ImportError:
     IMPORT_ERRORS_ALL_ACCESS_VIEW = None
     AUDIT_LOGS_ALL_ACCESS_VIEW = None
+    REPARSE_ALL_ACCESS_VIEW = None
 
-__all__ = ["AUDIT_LOGS_ALL_ACCESS_VIEW", "IMPORT_ERRORS_ALL_ACCESS_VIEW"]
+__all__ = ["AUDIT_LOGS_ALL_ACCESS_VIEW", "IMPORT_ERRORS_ALL_ACCESS_VIEW", 
"REPARSE_ALL_ACCESS_VIEW"]
diff --git 
a/providers/common/compat/tests/unit/common/compat/security/test_access_view.py 
b/providers/common/compat/tests/unit/common/compat/security/test_access_view.py
index 9798ff9330a..2350b151397 100644
--- 
a/providers/common/compat/tests/unit/common/compat/security/test_access_view.py
+++ 
b/providers/common/compat/tests/unit/common/compat/security/test_access_view.py
@@ -27,7 +27,7 @@ RESOURCE_DETAILS_MODULE = 
"airflow.api_fastapi.auth.managers.models.resource_det
 ACCESS_VIEW_SHIM_MODULE = 
"airflow.providers.common.compat.security.access_view"
 
 
[email protected]("member_name", ["IMPORT_ERRORS_ALL", 
"AUDIT_LOGS_ALL"])
[email protected]("member_name", ["IMPORT_ERRORS_ALL", 
"AUDIT_LOGS_ALL", "REPARSE_ALL"])
 def test_resolves_to_the_core_access_view_member_or_none(member_name):
     """The shim mirrors the running core: the ``AccessView`` member on a core 
that
     defines it (>= 3.4.0), otherwise ``None``. Kept version-agnostic so it 
holds
@@ -59,6 +59,7 @@ def test_is_none_on_older_core_without_the_member():
         reloaded = 
importlib.reload(importlib.import_module(ACCESS_VIEW_SHIM_MODULE))
         assert reloaded.IMPORT_ERRORS_ALL_ACCESS_VIEW is None
         assert reloaded.AUDIT_LOGS_ALL_ACCESS_VIEW is None
+        assert reloaded.REPARSE_ALL_ACCESS_VIEW is None
 
     # Restore the module against the real core so later imports see the real 
value.
     importlib.reload(importlib.import_module(ACCESS_VIEW_SHIM_MODULE))
diff --git a/providers/fab/pyproject.toml b/providers/fab/pyproject.toml
index ee61c5891a0..c77b580c593 100644
--- a/providers/fab/pyproject.toml
+++ b/providers/fab/pyproject.toml
@@ -68,7 +68,7 @@ requires-python = ">=3.10"
 # After you modify the dependencies, and rebuild your Breeze CI image with 
``breeze ci-image build``
 dependencies = [
     "apache-airflow>=3.0.2",
-    "apache-airflow-providers-common-compat>=1.18.0",
+    "apache-airflow-providers-common-compat>=1.18.0",  # use next version
     # Blinker use for signals in Flask, this is an optional dependency in 
Flask 2.2 and lower.
     # In Flask 2.3 it becomes a mandatory dependency, and flask signals are 
always available.
     "blinker>=1.6.2",
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py 
b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
index a281a1b6584..02819c5b110 100644
--- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
+++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py
@@ -60,6 +60,7 @@ from airflow.providers.common.compat.sdk import 
AirflowException, conf
 from airflow.providers.common.compat.security.access_view import (
     AUDIT_LOGS_ALL_ACCESS_VIEW,
     IMPORT_ERRORS_ALL_ACCESS_VIEW,
+    REPARSE_ALL_ACCESS_VIEW,
 )
 from airflow.providers.fab.auth_manager.models import Permission, Role, User
 from airflow.providers.fab.auth_manager.models.anonymous_user import 
AnonymousUser
@@ -88,6 +89,7 @@ from airflow.providers.fab.www.security.permissions import (
     RESOURCE_PLUGIN,
     RESOURCE_POOL,
     RESOURCE_PROVIDER,
+    RESOURCE_REPARSE_ALL,
     RESOURCE_TASK_INSTANCE,
     RESOURCE_TASK_LOG,
     RESOURCE_TRIGGER,
@@ -154,12 +156,14 @@ _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE = {
 }
 
 # ``AccessView.IMPORT_ERRORS_ALL`` and ``AccessView.AUDIT_LOGS_ALL`` only 
exist on
-# core >= 3.4.0; the compat shim yields ``None`` on older core so this 
provider still
-# imports there.
+# core >= 3.4.0, and ``AccessView.REPARSE_ALL`` after it; the compat shim 
yields ``None``
+# on older core so this provider still imports there.
 if IMPORT_ERRORS_ALL_ACCESS_VIEW is not None:
     _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[IMPORT_ERRORS_ALL_ACCESS_VIEW] = 
RESOURCE_IMPORT_ERROR_ALL
 if AUDIT_LOGS_ALL_ACCESS_VIEW is not None:
     _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[AUDIT_LOGS_ALL_ACCESS_VIEW] = 
RESOURCE_AUDIT_LOG_ALL
+if REPARSE_ALL_ACCESS_VIEW is not None:
+    _MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE[REPARSE_ALL_ACCESS_VIEW] = 
RESOURCE_REPARSE_ALL
 
 _MAP_MENU_ITEM_TO_FAB_RESOURCE_TYPE = {
     MenuItem.ASSETS: RESOURCE_ASSET,
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
index 0ac69dab596..bd1c9c6ab14 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
@@ -356,6 +356,7 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         (permissions.ACTION_CAN_ACCESS_MENU, permissions.RESOURCE_AUDIT_LOG),
         (permissions.ACTION_CAN_READ, permissions.RESOURCE_AUDIT_LOG_ALL),
         (permissions.ACTION_CAN_READ, permissions.RESOURCE_IMPORT_ERROR_ALL),
+        (permissions.ACTION_CAN_READ, permissions.RESOURCE_REPARSE_ALL),
         (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_RESCHEDULE),
         (permissions.ACTION_CAN_ACCESS_MENU, 
permissions.RESOURCE_TASK_RESCHEDULE),
         (permissions.ACTION_CAN_READ, permissions.RESOURCE_TRIGGER),
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 7fcf1ea357a..a92b94de8a5 100644
--- a/providers/fab/src/airflow/providers/fab/www/security/permissions.py
+++ b/providers/fab/src/airflow/providers/fab/www/security/permissions.py
@@ -50,6 +50,7 @@ RESOURCE_PERMISSION = "Permission Views"  # Refers to a Perm 
<-> View mapping, n
 RESOURCE_PLUGIN = "Plugins"
 RESOURCE_POOL = "Pools"
 RESOURCE_PROVIDER = "Providers"
+RESOURCE_REPARSE_ALL = "All Reparses"
 RESOURCE_RESOURCE = "View Menus"
 RESOURCE_ROLE = "Roles"
 RESOURCE_SLA_MISS = "SLA Misses"

Reply via email to