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]