Zingeryo opened a new issue, #57026:
URL: https://github.com/apache/airflow/issues/57026

   ### Apache Airflow Provider(s)
   
   fab
   
   ### Versions of Apache Airflow Providers
   
   ```
   apache-airflow==3.1.0
   apache-airflow-core==3.1.0
   apache-airflow-providers-amazon==9.15.0
   apache-airflow-providers-celery==3.12.4
   apache-airflow-providers-common-compat==1.7.4
   apache-airflow-providers-common-io==1.6.3
   apache-airflow-providers-common-sql==1.28.1
   apache-airflow-providers-docker==4.4.3
   apache-airflow-providers-fab==3.0.0
   apache-airflow-providers-hashicorp==4.3.2
   apache-airflow-providers-http==5.3.4
   apache-airflow-providers-smtp==2.2.1
   apache-airflow-providers-standard==1.8.0
   apache-airflow-providers-trino==6.3.3
   apache-airflow-task-sdk==1.1.0
   ```
   
   ### Apache Airflow version
   
   3.1
   
   ### Operating System
   
   Debian GNU/Linux 12
   
   ### Deployment
   
   Docker-Compose
   
   ### Deployment details
   
   _No response_
   
   ### What happened
   
   After upgrading apache-airflow-providers-fab to 3.0.0, Airflow Api-Server 
refuses to start and fails with error
   ```
   [2025-10-22T05:45:07.641+0000] {cli_parser.py:81} WARNING - cannot load CLI 
commands from auth manager: The package `apache-airflow-providers-fab:3.0.0` 
needs Apache Airflow 3.0.2+
   [2025-10-22T05:45:07.645+0000] {cli_parser.py:82} WARNING - Auth manager is 
not configured and api-server will not be able to start.
   [2025-10-22T05:45:07.650+0000] {cli_parser.py:85} ERROR - The package 
`apache-airflow-providers-fab:3.0.0` needs Apache Airflow 3.0.2+
   Traceback (most recent call last):
     File 
"/home/airflow/.local/lib/python3.12/site-packages/airflow/cli/cli_parser.py", 
line 78, in <module>
       auth_mgr = get_auth_manager_cls()
                  ^^^^^^^^^^^^^^^^^^^^^^
     File 
"/home/airflow/.local/lib/python3.12/site-packages/airflow/api_fastapi/app.py", 
line 125, in get_auth_manager_cls
       auth_manager_cls = conf.getimport(section="core", key="auth_manager")
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File 
"/home/airflow/.local/lib/python3.12/site-packages/airflow/configuration.py", 
line 1229, in getimport
       return import_string(full_qualified_path)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File 
"/home/airflow/.local/lib/python3.12/site-packages/airflow/utils/module_loading.py",
 line 39, in import_string
       module = import_module(module_path)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "/usr/python/lib/python3.12/importlib/__init__.py", line 90, in 
import_module
       return _bootstrap._gcd_import(name[level:], package, level)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
     File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
     File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
     File "<frozen importlib._bootstrap>", line 488, in 
_call_with_frames_removed
     File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
     File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
     File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
     File "<frozen importlib._bootstrap>", line 488, in 
_call_with_frames_removed
     File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
     File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
     File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
     File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
     File "<frozen importlib._bootstrap_external>", line 999, in exec_module
     File "<frozen importlib._bootstrap>", line 488, in 
_call_with_frames_removed
     File 
"/home/airflow/.local/lib/python3.12/site-packages/airflow/providers/fab/__init__.py",
 line 37, in <module>
       raise RuntimeError(
   RuntimeError: The package `apache-airflow-providers-fab:3.0.0` needs Apache 
Airflow 3.0.2+
   ```
   It clearly can't see Airflow 3.1, which is newer than 3.0.2
   
   ### What you think should happen instead
   
   Successful api-server start with fab provider 3.0
   
   ### How to reproduce
   
   Have Airflow 3.1 running and run `pip install 
apache-airflow-providers-fab==3.0.0 --upgrade`. Reboot api-server and the error 
will appear
   
   ### Anything else
   
   If needed, my webserver_config.py file
   ```
   from __future__ import annotations
   
   import os
   import logging
   import jwt
   import requests
   from base64 import b64decode
   from airflow import settings
   from cryptography.hazmat.primitives import serialization
   from flask_appbuilder.security.manager import AUTH_OAUTH
   from airflow.providers.fab.auth_manager.security_manager.override import (
       FabAirflowSecurityManagerOverride,
   )
   
   basedir = os.path.abspath(os.path.dirname(__file__))
   log = logging.getLogger(__name__)
   
   # Flask-WTF flag for CSRF
   WTF_CSRF_ENABLED = True
   WTF_CSRF_TIME_LIMIT = None
   
   # ----------------------------------------------------
   # AUTHENTICATION CONFIG
   # ----------------------------------------------------
   # For details on how to set up each of the following, see
   # 
http://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-methods
   
   # The authentication type
   # AUTH_OID : Is for OpenID
   # AUTH_DB : For Airflow DB (Default)
   # AUTH_LDAP : Is for LDAP
   # AUTH_REMOTE_USER : Is for using REMOTE_USER from web server
   # AUTH_OAUTH : Is for OAuth <- Keycloak uses OAuth
   AUTH_TYPE = AUTH_OAUTH
   
   # Required for Airflow to recognize users on their first time authenticating 
with Keycloak
   AUTH_USER_REGISTRATION = True
   
   # The default role if the user hasn't been assigned one in Keycloak
   AUTH_USER_REGISTRATION_ROLE = "Viewer"
   
   #AUTH_ROLES_SYNC_AT_LOGIN = False
   # airflow_xyz is the arbitrarily-chosen name of the role defined in Keycloak.
   # Whereas "Admin", "Op", etc. are defined by Airflow: 
https://airflow.apache.org/docs/apache-airflow/stable/security/access-control.html
   AUTH_ROLES_MAPPING = {
       "airflow_admin": ["Admin"],
       "airflow_op": ["Op"],
       "airflow_user": ["User"],
       "airflow_viewer": ["Viewer"],
       "airflow_public": ["Public"],
       # add custom roles here 
   }
   
   #AUTH_ROLES_SYNC_AT_LOGIN = True
   
   # Keycloak variables
   PROVIDER_NAME = "keycloak"
   CLIENT_ID = "airflow"
   CLIENT_SECRET = ""
   
   OIDC_ISSUER = "https://idp.test.com/realms/internal";
   OIDC_BASE_URL = 
"{oidc_issuer}/protocol/openid-connect".format(oidc_issuer=OIDC_ISSUER)
   OIDC_TOKEN_URL = "{oidc_base_url}/token".format(oidc_base_url=OIDC_BASE_URL)
   OIDC_AUTH_URL = "{oidc_base_url}/auth".format(oidc_base_url=OIDC_BASE_URL)
   
   OAUTH_PROVIDERS = [
       {
           "name": PROVIDER_NAME,
           "token_key": "access_token",
           "icon": "fa-circle-o",
           "remote_app": {
               "api_base_url": f"{OIDC_BASE_URL}/",
               "access_token_url": OIDC_TOKEN_URL,
               "authorize_url": OIDC_AUTH_URL,
               "request_token_url": None,
               "client_id": CLIENT_ID,
               "client_secret": CLIENT_SECRET,
               "client_kwargs": {"scope": "email profile"},
           },
       }
   ]
   
   # Get public key of your Keycloak instance
   req = requests.get(OIDC_ISSUER)
   key_der_base64 = req.json()["public_key"]
   key_der = b64decode(key_der_base64.encode())
   public_key = serialization.load_der_public_key(key_der)
   
   
   class CustomSecurityManager(FabAirflowSecurityManagerOverride):
       def get_oauth_user_info(self, provider, response):
           if provider == "keycloak":
               token = response["access_token"]
               me = jwt.decode(token, public_key, algorithms=["HS256", 
"RS256"], audience=CLIENT_ID)
               login_user = me.get("preferred_username")
   
               # Check if user exists in Airflow database
               session = settings.Session()
               airflow_users = session.execute("SELECT username FROM 
ab_user").fetchall()
               usernames = [user[0] for user in airflow_users]
   
               if login_user not in usernames:
                   # If user does not exist, create a new user with the default 
role
                   groups = ["Viewer"]
                   userinfo = {
                       "username": login_user,
                       "email": me.get("email"),
                       "first_name": me.get("given_name"),
                       "last_name": me.get("family_name"),
                       "role_keys": groups,
                   }
               else:
                   # If user exists, fetch their roles from the Airflow database
                   user_roles = self.get_user_roles_from_db(session, login_user)
                   userinfo = {
                       "username": login_user,
                       "email": me.get("email"),
                       "first_name": me.get("given_name"),
                       "last_name": me.get("family_name"),
                       "role_keys": user_roles,
                   }
   
               session.close()
               log.info("user info: {0}".format(userinfo))
   
               return userinfo
           else:
               return {}
   
       def get_user_roles_from_db(self, session, username):
           # Query the Airflow database to get the roles for the user
           roles_query = """
           SELECT r.name
           FROM ab_user u
           JOIN ab_user_role ur ON u.id = ur.user_id
           JOIN ab_role r ON ur.role_id = r.id
           WHERE u.username = :username
           """
           roles = session.execute(roles_query, {"username": 
username}).fetchall()
           return [role[0] for role in roles]
   
   SECURITY_MANAGER_CLASS = CustomSecurityManager
   ```
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
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