Skip to content

Commit a9fb853

Browse files
fix: Bump FAB to 5.X (#33055)
Co-authored-by: Joe Li <[email protected]>
1 parent dea9068 commit a9fb853

File tree

27 files changed

+100
-327
lines changed

27 files changed

+100
-327
lines changed

UPDATING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ This file documents any backwards-incompatible changes in Superset and
2323
assists people when migrating to a new version.
2424

2525
## Next
26-
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
26+
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
27+
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
2728
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
2829
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
2930
- [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`:

docs/docs/configuration/configuring-superset.mdx

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
363363
]
364364
```
365365

366-
### Keycloak-Specific Configuration using Flask-OIDC
367-
368-
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
369-
370-
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
371-
372-
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
373-
374-
```python
375-
from flask_appbuilder.security.manager import AUTH_OID
376-
from superset.security import SupersetSecurityManager
377-
from flask_oidc import OpenIDConnect
378-
from flask_appbuilder.security.views import AuthOIDView
379-
from flask_login import login_user
380-
from urllib.parse import quote
381-
from flask_appbuilder.views import ModelView, SimpleFormView, expose
382-
from flask import (
383-
redirect,
384-
request
385-
)
386-
import logging
387-
388-
class OIDCSecurityManager(SupersetSecurityManager):
389-
390-
def __init__(self, appbuilder):
391-
super(OIDCSecurityManager, self).__init__(appbuilder)
392-
if self.auth_type == AUTH_OID:
393-
self.oid = OpenIDConnect(self.appbuilder.get_app)
394-
self.authoidview = AuthOIDCView
395-
396-
class AuthOIDCView(AuthOIDView):
397-
398-
@expose('/login/', methods=['GET', 'POST'])
399-
def login(self, flag=True):
400-
sm = self.appbuilder.sm
401-
oidc = sm.oid
402-
403-
@self.appbuilder.sm.oid.require_login
404-
def handle_login():
405-
user = sm.auth_user_oid(oidc.user_getfield('email'))
406-
407-
if user is None:
408-
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
409-
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
410-
info.get('email'), sm.find_role('Gamma'))
411-
412-
login_user(user, remember=False)
413-
return redirect(self.appbuilder.get_url_for_index)
414-
415-
return handle_login()
416-
417-
@expose('/logout/', methods=['GET', 'POST'])
418-
def logout(self):
419-
oidc = self.appbuilder.sm.oid
420-
421-
oidc.logout()
422-
super(AuthOIDCView, self).logout()
423-
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
424-
425-
return redirect(
426-
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
427-
```
428-
429-
Then add to your `superset_config.py` file:
430-
431-
```python
432-
from keycloak_security_manager import OIDCSecurityManager
433-
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
434-
import os
435-
436-
AUTH_TYPE = AUTH_OID
437-
SECRET_KEY: 'SomethingNotEntirelySecret'
438-
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
439-
OIDC_ID_TOKEN_COOKIE_SECURE = False
440-
OIDC_OPENID_REALM: '<myRealm>'
441-
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
442-
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
443-
444-
# Will allow user self registration, allowing to create Flask users from Authorized User
445-
AUTH_USER_REGISTRATION = True
446-
447-
# The default user self registration role
448-
AUTH_USER_REGISTRATION_ROLE = 'Public'
449-
```
450-
451-
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
452-
453-
```json
454-
{
455-
"<myOpenIDProvider>": {
456-
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
457-
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
458-
"client_id": "https://<myKeycloakDomain>",
459-
"client_secret": "<myClientSecret>",
460-
"redirect_uris": [
461-
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
462-
],
463-
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
464-
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
465-
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
466-
}
467-
}
468-
```
469-
470366
## LDAP Authentication
471367

472368
FAB supports authenticating user credentials against an LDAP server.

docs/versioned_docs/version-6.0.0/configuration/configuring-superset.mdx

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -363,110 +363,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
363363
]
364364
```
365365

366-
### Keycloak-Specific Configuration using Flask-OIDC
367-
368-
If you are using Keycloak as OpenID Connect 1.0 Provider, the above configuration based on [`Authlib`](https://authlib.org/) might not work. In this case using [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is a viable option.
369-
370-
Make sure the pip package [`Flask-OIDC`](https://pypi.org/project/flask-oidc/) is installed on the webserver. This was successfully tested using version 2.2.0. This package requires [`Flask-OpenID`](https://pypi.org/project/Flask-OpenID/) as a dependency.
371-
372-
The following code defines a new security manager. Add it to a new file named `keycloak_security_manager.py`, placed in the same directory as your `superset_config.py` file.
373-
374-
```python
375-
from flask_appbuilder.security.manager import AUTH_OID
376-
from superset.security import SupersetSecurityManager
377-
from flask_oidc import OpenIDConnect
378-
from flask_appbuilder.security.views import AuthOIDView
379-
from flask_login import login_user
380-
from urllib.parse import quote
381-
from flask_appbuilder.views import ModelView, SimpleFormView, expose
382-
from flask import (
383-
redirect,
384-
request
385-
)
386-
import logging
387-
388-
class OIDCSecurityManager(SupersetSecurityManager):
389-
390-
def __init__(self, appbuilder):
391-
super(OIDCSecurityManager, self).__init__(appbuilder)
392-
if self.auth_type == AUTH_OID:
393-
self.oid = OpenIDConnect(self.appbuilder.get_app)
394-
self.authoidview = AuthOIDCView
395-
396-
class AuthOIDCView(AuthOIDView):
397-
398-
@expose('/login/', methods=['GET', 'POST'])
399-
def login(self, flag=True):
400-
sm = self.appbuilder.sm
401-
oidc = sm.oid
402-
403-
@self.appbuilder.sm.oid.require_login
404-
def handle_login():
405-
user = sm.auth_user_oid(oidc.user_getfield('email'))
406-
407-
if user is None:
408-
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
409-
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'),
410-
info.get('email'), sm.find_role('Gamma'))
411-
412-
login_user(user, remember=False)
413-
return redirect(self.appbuilder.get_url_for_index)
414-
415-
return handle_login()
416-
417-
@expose('/logout/', methods=['GET', 'POST'])
418-
def logout(self):
419-
oidc = self.appbuilder.sm.oid
420-
421-
oidc.logout()
422-
super(AuthOIDCView, self).logout()
423-
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
424-
425-
return redirect(
426-
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
427-
```
428-
429-
Then add to your `superset_config.py` file:
430-
431-
```python
432-
from keycloak_security_manager import OIDCSecurityManager
433-
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH
434-
import os
435-
436-
AUTH_TYPE = AUTH_OID
437-
SECRET_KEY: 'SomethingNotEntirelySecret'
438-
OIDC_CLIENT_SECRETS = '/path/to/client_secret.json'
439-
OIDC_ID_TOKEN_COOKIE_SECURE = False
440-
OIDC_OPENID_REALM: '<myRealm>'
441-
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
442-
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
443-
444-
# Will allow user self registration, allowing to create Flask users from Authorized User
445-
AUTH_USER_REGISTRATION = True
446-
447-
# The default user self registration role
448-
AUTH_USER_REGISTRATION_ROLE = 'Public'
449-
```
450-
451-
Store your client-specific OpenID information in a file called `client_secret.json`. Create this file in the same directory as `superset_config.py`:
452-
453-
```json
454-
{
455-
"<myOpenIDProvider>": {
456-
"issuer": "https://<myKeycloakDomain>/realms/<myRealm>",
457-
"auth_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/auth",
458-
"client_id": "https://<myKeycloakDomain>",
459-
"client_secret": "<myClientSecret>",
460-
"redirect_uris": [
461-
"https://<SupersetWebserver>/oauth-authorized/<myOpenIDProvider>"
462-
],
463-
"userinfo_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/userinfo",
464-
"token_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token",
465-
"token_introspection_uri": "https://<myKeycloakDomain>/realms/<myRealm>/protocol/openid-connect/token/introspect"
466-
}
467-
}
468-
```
469-
470366
## LDAP Authentication
471367

472368
FAB supports authenticating user credentials against an LDAP server.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ dependencies = [
4848
"cryptography>=42.0.4, <45.0.0",
4949
"deprecation>=2.1.0, <2.2.0",
5050
"flask>=2.2.5, <3.0.0",
51-
"flask-appbuilder>=4.8.1, <5.0.0",
51+
"flask-appbuilder>=5.0.0,<6",
5252
"flask-caching>=2.1.0, <3",
5353
"flask-compress>=1.13, <2.0",
5454
"flask-talisman>=1.0.0, <2.0",

requirements/base.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,9 @@ flask==2.3.3
114114
# flask-session
115115
# flask-sqlalchemy
116116
# flask-wtf
117-
flask-appbuilder==4.8.1
118-
# via
119-
# apache-superset (pyproject.toml)
120-
# apache-superset-core
121-
flask-babel==2.0.0
117+
flask-appbuilder==5.0.0
118+
# via apache-superset (pyproject.toml)
119+
flask-babel==3.1.0
122120
# via flask-appbuilder
123121
flask-caching==2.3.1
124122
# via apache-superset (pyproject.toml)

requirements/development.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,11 @@ flask==2.3.3
208208
# flask-sqlalchemy
209209
# flask-testing
210210
# flask-wtf
211-
flask-appbuilder==4.8.1
211+
flask-appbuilder==5.0.0
212212
# via
213213
# -c requirements/base-constraint.txt
214214
# apache-superset
215-
# apache-superset-core
216-
flask-babel==2.0.0
215+
flask-babel==3.1.0
217216
# via
218217
# -c requirements/base-constraint.txt
219218
# flask-appbuilder

superset-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ classifiers = [
4242
"Topic :: Software Development :: Libraries :: Python Modules",
4343
]
4444
dependencies = [
45-
"flask-appbuilder>=4.5.3, <5.0.0",
45+
"flask-appbuilder>=5.0.0,<6",
4646
]
4747

4848
[project.urls]

superset/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ def _try_json_readsha(filepath: str, length: int) -> str | None:
350350
# AUTHENTICATION CONFIG
351351
# ----------------------------------------------------
352352
# The authentication type
353-
# AUTH_OID : Is for OpenID
354353
# AUTH_DB : Is for database (username/password)
355354
# AUTH_LDAP : Is for LDAP
356355
# AUTH_REMOTE_USER : Is for using REMOTE_USER from web server

superset/dashboards/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from typing import Any, Callable, cast
2323
from zipfile import is_zipfile, ZipFile
2424

25-
from flask import g, redirect, request, Response, send_file, url_for
25+
from flask import current_app, g, redirect, request, Response, send_file, url_for
2626
from flask_appbuilder import permission_name
2727
from flask_appbuilder.api import expose, protect, rison, safe
2828
from flask_appbuilder.models.sqla.interface import SQLAInterface
@@ -332,8 +332,8 @@ def __repr__(self) -> str:
332332
"""Deterministic string representation of the API instance for etag_cache."""
333333
# pylint: disable=consider-using-f-string
334334
return "Superset.dashboards.api.DashboardRestApi@v{}{}".format(
335-
self.appbuilder.app.config["VERSION_STRING"],
336-
self.appbuilder.app.config["VERSION_SHA"],
335+
current_app.config["VERSION_STRING"],
336+
current_app.config["VERSION_SHA"],
337337
)
338338

339339
@expose("/<id_or_slug>", methods=("GET",))

superset/extensions/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
import celery
2222
from flask import Flask
23-
from flask_appbuilder import AppBuilder, SQLA
23+
from flask_appbuilder import AppBuilder
24+
from flask_appbuilder.utils.legacy import get_sqla_class
2425
from flask_caching.backends.base import BaseCache
2526
from flask_migrate import Migrate
2627
from flask_talisman import Talisman
@@ -123,7 +124,7 @@ def init_app(self, app: Flask) -> None:
123124
cache_manager = CacheManager()
124125
celery_app = celery.Celery()
125126
csrf = CSRFProtect()
126-
db = SQLA() # pylint: disable=disallowed-name
127+
db = get_sqla_class()()
127128
_event_logger: dict[str, Any] = {}
128129
encrypted_field_factory = EncryptedFieldFactory()
129130
event_logger = LocalProxy(lambda: _event_logger.get("event_logger"))

0 commit comments

Comments
 (0)