gaurav0107 commented on code in PR #65685:
URL: https://github.com/apache/airflow/pull/65685#discussion_r3126686129
##########
providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py:
##########
@@ -300,11 +300,61 @@ def is_logged_in(self) -> bool:
"""Return whether the user is logged in."""
user = self.get_user()
return bool(
- self.appbuilder
- and self.appbuilder.app.config.get("AUTH_ROLE_PUBLIC", None)
- or (not user.is_anonymous and user.is_active)
+ (self.appbuilder and self._get_auth_role_public()) or (not
user.is_anonymous and user.is_active)
)
+ def _get_auth_role_public(self) -> str | None:
+ """
+ Return the role granted to anonymous users, or ``None`` if public
access is disabled.
+
+ Prefers the Airflow ``[fab] auth_role_public`` configuration and falls
back to the legacy
+ ``AUTH_ROLE_PUBLIC`` entry in ``webserver_config.py`` so existing
deployments continue to
+ work.
+ """
+ role = conf.get("fab", "auth_role_public", fallback="") or ""
+ if role:
+ return role
+ if self.appbuilder is not None:
+ return self.appbuilder.app.config.get("AUTH_ROLE_PUBLIC", None)
+ return None
+
+ def build_public_user(self) -> AnonymousUser | None:
+ """
+ Build an :class:`AnonymousUser` pre-populated with the configured
public role.
+
+ Returns ``None`` when ``[fab] auth_role_public`` (or the legacy
+ ``AUTH_ROLE_PUBLIC`` entry in ``webserver_config.py``) is not set.
+
+ :meta private:
+ """
+ public_role_name = self._get_auth_role_public()
+ if not public_role_name:
+ return None
+
+ user = AnonymousUser()
+ flask_app = self.flask_app or (self.appbuilder.app if self.appbuilder
else None)
+ if flask_app is not None:
+ with flask_app.app_context():
+ flask_app.config["AUTH_ROLE_PUBLIC"] = public_role_name
+ role = flask_app.appbuilder.sm.find_role(public_role_name)
+ if role is not None:
+ # ``AnonymousUser.roles`` is a lazy property that calls
+ # ``security_manager.get_public_role()`` on every access,
which needs a Flask
+ # request context we do not have under FastAPI. Writing
``_roles``/``_perms``
+ # directly freezes a snapshot of the public role's
permissions for the
+ # lifetime of a single FastAPI authorization check (see
#60897).
+ user._roles = {role}
+ user._perms = {(perm.action.name, perm.resource.name) for
perm in role.permissions}
Review Comment:
Switched to the same shape as simple auth manager — no Flask context, no
`app_context()`, no `find_role` hop through the security manager. The new
version queries the `Role` via `@provide_session` (with permissions joined
eager-loaded on the model) and snapshots `_roles`/`_perms` on the
`AnonymousUser` before the session closes, so downstream `_is_authorized`
checks only need `user.perms` (already materialized as tuples) and don't hit
Flask at all.
One small caveat: unlike the simple auth manager, FAB's `User` is a
SQLAlchemy model and its real login flow expects DB-backed roles, so I'm still
using the existing `AnonymousUser` class rather than constructing a `User(...)`
by hand — the pre-populated `_roles`/`_perms` path is exactly what
`AnonymousUser` was designed for. Happy to go further if you'd prefer a
purpose-built anonymous model though.
##########
providers/fab/src/airflow/providers/fab/www/app.py:
##########
@@ -84,6 +84,15 @@ def remove_duplicate_date_header(response):
with flask_app.app_context():
flask_app.config.from_pyfile(webserver_config, silent=True)
+ # Bridge ``[fab] auth_role_public`` Airflow config into the Flask app
config so legacy FAB
+ # code paths that read ``AUTH_ROLE_PUBLIC`` from ``current_app.config``
(e.g.
+ # ``AnonymousUser.roles``, basic_auth, kerberos_auth, security manager)
stay in sync with
+ # the FastAPI-based auth flow. The Airflow config takes precedence over
+ # ``webserver_config.py`` when both are set so there is a single source of
truth.
+ auth_role_public_conf = conf.get("fab", "auth_role_public", fallback="")
or ""
+ if auth_role_public_conf:
+ flask_app.config["AUTH_ROLE_PUBLIC"] = auth_role_public_conf
Review Comment:
Yes — cleaned up. The only other caller was `_get_auth_role_public` in the
FAB auth manager, and it now reads `appbuilder.app.config["AUTH_ROLE_PUBLIC"]`
directly. The www/app.py bridge is the single place that reads the Airflow
config, and everything else downstream reads the Flask config. There were no
other `conf.get("fab", "auth_role_public")` call sites.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]