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"