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]

Reply via email to