Closed ddhanabalan closed 1 year ago
Any hints in the logs?
The CustomSsoSecurityManager was not overridden. Replaced the OKTA code with this:
# ====== Start Okta Login ===========
ENABLE_PROXY_FIX = True
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True # allow self-registration (login creates a user)
AUTH_USER_REGISTRATION_ROLE = 'Public'
AUTH_ROLE_ADMIN = 'Public'
AUTH_ROLE_PUBLIC = 'Public'
SESSION_COOKIE_SAMESITE='None'
SESSION_COOKIE_HTTPONLY=False
SESSION_COOKIE_SECURE = True
OKTA_BASE_URL = get_env_variable('OKTA_BASE_URL')
OKTA_KEY = get_env_variable('OKTA_KEY')
OKTA_SECRET = get_env_variable('OKTA_SECRET')
sencode = (OKTA_KEY + ':' + OKTA_SECRET).encode("ascii")
bencode = base64.b64encode(sencode)
base64_string = bencode.decode("ascii")
OAUTH_PROVIDERS = [{
'name':'okta',
'token_key': 'access_token', # Name of the token in the response of access_token_url
'icon':'fa-circle-o', # Icon for the provider
'remote_app': {
'client_id': OKTA_KEY, # Client Id (Identify Superset application)
'client_secret': OKTA_SECRET, # Secret for this Client Id (Identify Superset application)
'client_kwargs': {
'scope': 'read profile email openid groups'
},
'access_token_method': 'POST', # HTTP Method to call access_token_url
'api_base_url': OKTA_BASE_URL+'/oauth2/default/v1/',
'access_token_url': OKTA_BASE_URL + '/oauth2/default/v1/token',
'authorize_url': OKTA_BASE_URL + '/oauth2/default/v1/authorize',
'server_metadata_url': f'{OKTA_BASE_URL}/.well-known/openid-configuration',
'jwks_uri': OKTA_BASE_URL + '/oauth2/default/v1/keys',
'access_token_headers':{ # Additional headers for calls to access_token_url
'Authorization': 'Basic ' + base64_string
}
}
}]
class CustomAuthOAuthView(AuthOAuthView):
@expose("/login/")
@expose("/login/<provider>")
@expose("/login/<provider>/<register>")
def login(self, provider: Optional[str] = None) -> WerkzeugResponse:
logger.debug("Provider: {0}".format(provider))
if g.user is not None and g.user.is_authenticated:
logger.debug("Already authenticated {0}".format(g.user))
return redirect(self.appbuilder.get_url_for_index)
if provider is None:
if len(self.appbuilder.sm.oauth_providers) > 1:
return self.render_template(
self.login_template,
providers=self.appbuilder.sm.oauth_providers,
title=self.title,
appbuilder=self.appbuilder,
)
else:
provider = self.appbuilder.sm.oauth_providers[0]["name"]
logger.debug("Going to call authorize for: {0}".format(provider))
state = jwt.encode(
request.args.to_dict(flat=False),
self.appbuilder.app.config["SECRET_KEY"],
algorithm="HS256",
)
try:
if provider == "okta":
return self.appbuilder.sm.oauth_remotes[provider].authorize_redirect(
redirect_uri=url_for(
".oauth_authorized",
provider=provider,
_external=True,
state=state,
)
)
else:
return self.appbuilder.sm.oauth_remotes[provider].authorize_redirect(
redirect_uri=url_for(
".oauth_authorized", provider=provider, _external=True
),
state=state.decode("ascii") if isinstance(state, bytes) else state,
)
except Exception as e:
logger.error("Error on OAuth authorize: {0}".format(e))
flash(as_unicode(self.invalid_login_message), "warning")
return redirect(self.appbuilder.get_url_for_index)
@expose("/oauth-authorized/okta")
def oauth_authorized(self, provider):
logger.debug("Authorized init")
resp = self.appbuilder.sm.oauth_remotes[provider].authorized_response()
if resp is None:
flash(u"You denied the request to sign in.", "warning")
return redirect("login")
logger.debug("OAUTH Authorized resp: {0}".format(resp))
# Retrieves specific user info from the provider
try:
self.appbuilder.sm.set_oauth_session(provider, resp)
userinfo = self.appbuilder.sm.oauth_user_info(provider, resp)
except Exception as e:
logger.error("Error returning OAuth user info: {0}".format(e))
user = None
else:
logger.debug("User info retrieved from {0}: {1}".format(provider, userinfo))
# User email is not whitelisted
if provider in self.appbuilder.sm.oauth_whitelists:
whitelist = self.appbuilder.sm.oauth_whitelists[provider]
allow = False
for e in whitelist:
if re.search(e, userinfo["email"]):
allow = True
break
if not allow:
flash(u"You are not authorized.", "warning")
return redirect("login")
else:
logger.debug("No whitelist for OAuth provider")
user = self.appbuilder.sm.auth_user_oauth(userinfo)
if user is None:
flash(as_unicode(self.invalid_login_message), "warning")
return redirect("login")
else:
login_user(user)
try:
state = jwt.decode(
request.args["code"],
self.appbuilder.app.config["SECRET_KEY"],
algorithms=["HS256"],
)
except jwt.InvalidTokenError:
raise Exception("State signature is not valid!")
try:
next_url = state["next"][0] or self.appbuilder.get_url_for_index
print("next_url: " + next_url)
if (len(next_url) <= 1):
next_url = "/superset/welcome/"
except (KeyError, IndexError):
next_url = self.appbuilder.get_url_for_index
print("next_url: " + next_url)
return redirect(next_url)
class CustomSsoSecurityManager(SupersetSecurityManager):
authoidview = CustomAuthOAuthView
def __init__(self, appbuilder):
super(CustomSsoSecurityManager, self).__init__(appbuilder)
def oauth_user_info(self, provider, response=None):
logger.debug('oauth2 provider: {0}'.format(provider))
logger.debug('response: {0}'.format(response))
if provider == "okta":
me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
return {
"username": me.data.get("email", ""),
"first_name": "",
"last_name": "",
"email": me.data.get("email", ""),
"role_keys": me.data.get("groups", []),
}
def auth_user_oauth(self, userinfo):
user = super(CustomSsoSecurityManager, self).auth_user_oauth(userinfo)
if user == None:
logger.debug(' Update <User: %s> role to %s', userinfo.username)
self.update_user(userinfo) # update user roles
return user
SECURITY_MANAGER_CLASS = CustomSsoSecurityManager
# ====== End Okta Login ============
Guess its an older version of Flask-AppBuilder.
@ddhanabalan, this may be related to a different way security manager is customized in Superset - https://github.com/apache/superset/blob/4.0.1/superset/initialization/__init__.py#L524-L539 - not like in classic FAB. Try CUSTOM_SECURITY_MANAGER
How to reproduce the bug
In the OKTA server, i have set the following configurations:
I have configured the below code-snipped in superset_config.py.
After all these configurations, executing the below commands to configure and run superset in Heroku.
Expected results
When clicked login, user is directed to OKTA sign-in page and user is able to complete the authentication/authorization process and user is redirected to superset welcome screen.
Actual results
The redirect is getting stuck in the middle. After authorization is complete, the app-redirect-url is getting stuck at https://superset-app-domain/login/okta?next=
as there is no url parameter passed.
Screenshots
Environment
2.0.1
3.8.16