Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OZ-671: Update the Superset config to use authlib for SSO as Flask OIDC using old dependencies #87

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
OZ-671: Update the Superset config to use authlib for SSO as Flask OI…
…DC using old dependencies
  • Loading branch information
enyachoke committed Sep 5, 2024
commit 57792eddd3f75d515bad89430cfc38e1e6cb273b
102 changes: 47 additions & 55 deletions distro/configs/superset/security.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,50 @@
from flask import redirect, request
from flask_appbuilder.security.manager import AUTH_OID
from math import log
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
import logging
logger = logging.getLogger(__name__)

class AuthOIDCView(AuthOIDView):
def add_role_if_missing(self, sm, user_id, role_name):
found_role = sm.find_role(role_name)
session = sm.get_session
user = session.query(sm.user_model).get(user_id)
if found_role and found_role not in user.roles:
user.roles += [found_role]
session.commit()

@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid


@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email','roles'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'), info.get('email'), sm.find_role('Gamma'))
role_info = oidc.user_getinfo(['roles'])
if role_info is not None:
for role in role_info['roles']:
self.add_role_if_missing(sm, user.id, role)
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)

return handle_login()

@expose('/logout/', methods=['GET', 'POST'])
def logout(self):

oidc = self.appbuilder.sm.oid

oidc.logout()
super(AuthOIDCView, self).logout()
from flask_appbuilder.security.views import AuthOAuthView
from flask_appbuilder.baseviews import expose
import time
from flask import (
redirect,
request
)

class CustomAuthOAuthView(AuthOAuthView):

@expose("/logout/")
def logout(self, provider="keycloak", register=None):
provider_obj = self.appbuilder.sm.oauth_remotes[provider]
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login

return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))

class OIDCSecurityManager(SupersetSecurityManager):
authoidview = AuthOIDCView
def __init__(self,appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
url = ("logout?client_id={}&post_logout_redirect_uri={}".format(
provider_obj.client_id,
redirect_url
))

ret = super().logout()
time.sleep(1)

return redirect("{}{}".format(provider_obj.api_base_url, url))


class CustomSecurityManager(SupersetSecurityManager):
# override the logout function
authoauthview = CustomAuthOAuthView

def oauth_user_info(self, provider, response=None):
logging.debug("Oauth2 provider: {0}.".format(provider))
if provider == 'keycloak':
# superset_roles: list[str] = ["Admin", "Alpha", "Gamma", "Public", "granter", "sql_lab"]
me = self.appbuilder.sm.oauth_remotes[provider].get('userinfo').json()
roles = ["public", ]
if "roles" in me:
role_prefix = "superset-"
roles = [r[len(role_prefix):].lower() for r in me.get("roles", []) if r.startswith(role_prefix)]

return {
"username": me.get("preferred_username", ""),
"first_name": me.get("given_name", ""),
"last_name": me.get("family_name", ""),
"email": me.get("email", ""),
"role_keys": roles,
}
return {}
62 changes: 26 additions & 36 deletions distro/configs/superset/superset_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from cachelib import RedisCache

from cachelib.file import FileSystemCache

logger = logging.getLogger()

def password_from_env(url):
Expand Down Expand Up @@ -70,34 +69,12 @@ class CeleryConfig(object):
'CACHE_REDIS_HOST': 'redis',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 1,
'CACHE_REDIS_URL': f"redis://{REDIS_HOST}:{REDIS_PORT}/1"
}

FILTER_STATE_CACHE_CONFIG = {
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 300,
'CACHE_KEY_PREFIX': 'superset_filter_',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 2,
'CACHE_REDIS_URL': f"redis://{REDIS_HOST}:{REDIS_PORT}/2"
}

EXPLORE_FORM_DATA_CACHE_CONFIG = {
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 300,
'CACHE_KEY_PREFIX': 'superset_form_date_',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 3,
'CACHE_REDIS_URL': f"redis://{REDIS_HOST}:{REDIS_PORT}/3"
'CACHE_REDIS_URL': 'redis://redis:6379/1'
}

CELERY_CONFIG = CeleryConfig
SQLLAB_CTAS_NO_LIMIT = True
PERMANENT_SESSION_LIFETIME = 86400
FEATURE_FLAGS = {
"DASHBOARD_RBAC": True,
'ENABLE_TEMPLATE_PROCESSING': True,
}

class ReverseProxied(object):

Expand All @@ -120,20 +97,33 @@ def __call__(self, environ, start_response):

ADDITIONAL_MIDDLEWARE = [ReverseProxied, ]
ENABLE_PROXY_FIX = True
PREVENT_UNSAFE_DB_CONNECTIONS = False

# Enable the security manager API.
FAB_ADD_SECURITY_API = True

if os.getenv("ENABLE_OAUTH") == "true":
from security import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID
KEYCLOAK_URL = os.getenv('KEYCLOAK_URL')
SUPERSET_CLIENT_SECRET = os.getenv('SUPERSET_CLIENT_SECRET')
SUPERSET_URL = os.getenv('SUPERSET_URL')
AUTH_TYPE = AUTH_OID
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
from flask_appbuilder.security.manager import AUTH_OAUTH
from security import CustomSecurityManager
AUTH_ROLES_SYNC_AT_LOGIN = True
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
OIDC_CLIENT_SECRETS = '/etc/superset/client_secret.json'
AUTH_USER_REGISTRATION_ROLE = "Gamma"
CUSTOM_SECURITY_MANAGER = CustomSecurityManager
LOGOUT_REDIRECT_URL = os.environ.get("SUPERSET_URL")
AUTH_TYPE = AUTH_OAUTH
OAUTH_PROVIDERS = [
{
'name': 'keycloak',
'token_key': 'access_token', # Name of the token in the response of access_token_url
'icon': 'fa-key', # Icon for the provider
'remote_app': {
'client_id': os.environ.get("SUPERSET_CLIENT_ID","superset"), # Client Id (Identify Superset application)
'client_secret': os.environ.get("SUPERSET_CLIENT_SECRET"), # Secret for this Client Id (Identify Superset application)
'api_base_url': os.environ.get("ISSUER_URL").rstrip('/') + "/protocol/openid-connect/",
'client_kwargs': {
'scope': 'openid profile email',
},
'logout_redirect_uri': os.environ.get("SUPERSET_URL"),
'server_metadata_url': os.environ.get("ISSUER_URL").rstrip('/') + '/.well-known/openid-configuration', # URL to get metadata from
}
}
]
Loading