Skip to content

Commit 2f8fe65

Browse files
committed
refactor: cleanups
1 parent 7b64d3a commit 2f8fe65

2 files changed

Lines changed: 36 additions & 10 deletions

File tree

airflow/opa-auth-manager/airflow-3/opa_auth_manager/opa_fab_auth_manager.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ def _init_opa_resources(self) -> None:
103103
"""
104104
Set up the OPA cache and HTTP session.
105105
106-
Idempotent so it can be called from both ``init`` (FastAPI api-server)
107-
and ``init_flask_resources`` (Flask AppBuilder) — only one of the two
108-
runs depending on the Airflow component.
106+
Called from both ``init`` (FastAPI api-server) and
107+
``init_flask_resources`` (Flask AppBuilder). In Airflow 3 both run
108+
during a single api-server startup but on *different*
109+
OpaFabAuthManager instances — ``init_appbuilder`` calls
110+
``create_auth_manager()`` again, which constructs a fresh instance.
111+
The api-server instance is reachable via
112+
``request.app.state.auth_manager``; the FAB instance is returned by
113+
``get_auth_manager()``. Both need their own cache and session.
109114
"""
110115

111-
if getattr(self, "opa_cache", None) is not None:
112-
return
113-
114116
Stats.incr(METRIC_NAME_OPA_CACHE_LIMIT_REACHED, count=0)
115117

116118
self.opa_cache = Cache(
@@ -590,11 +592,18 @@ def get_authorized_dag_ids(
590592
# FabAuthManager's implementation consults the user's FAB DB role
591593
# permissions and bypasses is_authorized_dag entirely, which makes any
592594
# user without a FAB role (e.g. the default Public role) see an empty
593-
# DAG list even when OPA would allow them. Re-implement the
594-
# BaseAuthManager default: list all DAG ids and filter them via
595-
# is_authorized_dag → OPA.
595+
# DAG list even when OPA would allow them. List all DAG ids and filter
596+
# them via is_authorized_dag → OPA directly, without going through
597+
# filter_authorized_dag_ids — the Fab base class may add a DB-backed
598+
# override of that method in the future.
596599
dag_ids = {dag.dag_id for dag in session.execute(select(DagModel.dag_id))}
597-
return self.filter_authorized_dag_ids(dag_ids=dag_ids, method=method, user=user)
600+
return {
601+
dag_id
602+
for dag_id in dag_ids
603+
if self.is_authorized_dag(
604+
method=method, details=DagDetails(id=dag_id), user=user
605+
)
606+
}
598607

599608
@override
600609
def filter_authorized_menu_items(

airflow/opa-auth-manager/airflow-3/tests/test_opa_fab_auth_manager.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,23 @@ def test_filter_authorized_menu_items_routes_through_opa(
389389
)
390390
assert denied == []
391391

392+
def test_filter_authorized_menu_items_denies_unknown(
393+
self, auth_manager_with_appbuilder, mock_opa
394+
):
395+
# A MenuItem value not handled by _is_menu_item_authorized (e.g. one
396+
# introduced in a future Airflow version) must fail closed: denied
397+
# without consulting OPA, so a new UI surface isn't silently exposed
398+
# before the dispatch table is updated.
399+
unknown = Mock(spec=MenuItem)
400+
unknown.name = "FUTURE_THING"
401+
402+
result = auth_manager_with_appbuilder.filter_authorized_menu_items(
403+
[unknown], user=_make_user()
404+
)
405+
406+
assert result == []
407+
assert mock_opa.calls == []
408+
392409
def test_filter_authorized_menu_items_preserves_order_and_filters(
393410
self, auth_manager_with_appbuilder, mock_opa
394411
):

0 commit comments

Comments
 (0)