You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "ddhanabalan (via GitHub)" <gi...@apache.org> on 2023/09/28 10:13:09 UTC

[GitHub] [superset] ddhanabalan opened a new issue, #25443: Unable to complete url-redirection after OAuth authentication completion from OKTA

ddhanabalan opened a new issue, #25443:
URL: https://github.com/apache/superset/issues/25443

   #### How to reproduce the bug
   In the OKTA server, i have set the following configurations:
   
   1. Created a default OKTA server and added this application
   2. Set client-authentication type and created the clientid and clientsecret for this application.
   3. Set grant-type as AuthorizationCode
   4. Set Login-flow to "redirect to app to initiate login".
   5. Added redirect-uri as : https://<superset-app-domain>/oauth-authorized/okta
   
   I have configured the below code-snipped in superset_config.py.
   
   ```
   from flask import redirect, g, flash, request
   from flask_appbuilder.security.views import UserDBModelView,AuthDBView
   from superset.security import SupersetSecurityManager
   from flask_appbuilder.security.views import expose
   from flask_appbuilder.security.manager import BaseSecurityManager
   from flask_login import login_user, logout_user
   from flask_appbuilder.security.manager import AUTH_OAUTH
   from dotenv import load_dotenv
   from typing import Optional
   import os
   import logging
   import base64
   
   logger = logging.getLogger()
   load_dotenv()
   
   # Superset specific config
   ROW_LIMIT = 5000
   SUPERSET_WORKERS = 1
   SUPERSET_WEBSERVER_PORT = os.environ['PORT']
   MAPBOX_API_KEY = os.getenv('MAPBOX_API_KEY')
   SQLLAB_ASYNC_TIME_LIMIT_SEC = 300
   SQLLAB_TIMEOUT = 300
   SUPERSET_WEBSERVER_TIMEOUT = 300
   PUBLIC_ROLE_LIKE = 'Gamma'
   ENABLE_PROXY_FIX = True
   
   # Flask App Builder configuration# Your App secret key
   
   SECRET_KEY='Dj0aDlHmYJi/MFxJdw0d8XoNxhDPML8VF/VsTJ/ktb6x7NQNEACuwLha'
   
   # The SQLAlchemy connection string to your database backend
   SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')
   SQLALCHEMY_TRACK_MODIFICATIONS = True
   
   # Flask-WTF flag for CSRF.
   WTF_CSRF_ENABLED = CSRF_ENABLED = True
   
   # ====== Start Okta Login ===========
   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=True 
   
   OKTA_BASE_URL = os.getenv('OKTA_BASE_URL')
   OKTA_KEY = os.getenv('OKTA_KEY')
   OKTA_SECRET = os.getenv('OKTA_SECRET')
   
   sencode = (OKTA_KEY + ':' + OKTA_SECRET).encode("ascii")
   bencode = base64.b64encode(sencode)
   base64_string = bencode.decode("ascii")
   
   OAUTH_PROVIDERS = [{
       'name':'okta',
       'token_key': 'code', # 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/oauth-authorization-server',
           '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 CustomAuthDBView(AuthDBView):
       login_template = 'appbuilder/general/security/login_db.html'
   
       @expose('/login/', methods=['GET', 'POST'])
       def login(self):
           redirect_url = "https://<superset-app-domain>/superset/welcome"
           if request.args.get('username') is not None:
               user = self.appbuilder.sm.find_user(username=request.args.get('username'))
               login_user(user, remember=False)
               return redirect(redirect_url)
           elif g.user is not None and g.user.is_authenticated():
               return redirect(redirect_url)
           else:
               flash('Unable to auto login', 'warning')
               return super(CustomAuthDBView,self).login()
   
   class CustomSsoSecurityManager(SupersetSecurityManager):
       authdbview = CustomAuthDBView
       def oauth_user_info(self, provider, response=None):
           logger.log('oauth2 provider: {0}'.format(provider))
           logger.log('response: {0}'.format(response))
           if provider == "okta":
               me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
               data = me.json()
               return {
                   "username": data.get("sub", ""),
                   "first_name": "",
                   "last_name": "",
                   "email": data.get("email", ""),
                   "role_keys": data.get("groups", []),
               }
           
       def auth_user_oauth(self, userinfo):
           user = super(CustomSsoSecurityManager, self).auth_user_oauth(userinfo)
           logger.debug(' Update <User: %s> role to %s', user.username)
           self.update_user(user)  # update user roles
           return user
   
   CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
   # ====== End Okta Login ============
   
   ```
   After all these configurations, executing the below commands to configure and
   run superset in Heroku. 
   
   1. superset db upgrade
   2. superset fab create-admin
   3. superset init
   4. superset run
   
   ### 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
   
   ![Screenshot from 2023-09-28 15-36-59](https://github.com/apache/superset/assets/107998315/edab86bd-d715-4db5-b342-1ebd9ea77b3e)
   
   ### Environment
   
   - browser type and version: latest version of firefox/chrome/edge browser
   - superset version: `2.0.1`
   - python version: `3.8.16`
   


-- 
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: notifications-unsubscribe@superset.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] Unable to complete url-redirection, redirection getting stuck after OAuth authentication completion from OKTA [superset]

Posted by "ddhanabalan (via GitHub)" <gi...@apache.org>.
ddhanabalan closed issue #25443: Unable to complete url-redirection, redirection getting stuck after OAuth authentication completion from OKTA 
URL: https://github.com/apache/superset/issues/25443


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] Unable to complete url-redirection, redirection getting stuck after OAuth authentication completion from OKTA [superset]

Posted by "ddhanabalan (via GitHub)" <gi...@apache.org>.
ddhanabalan commented on issue #25443:
URL: https://github.com/apache/superset/issues/25443#issuecomment-1746159420

   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.


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] mdeshmu commented on issue #25443: Unable to complete url-redirection, redirection getting stuck after OAuth authentication completion from OKTA

Posted by "mdeshmu (via GitHub)" <gi...@apache.org>.
mdeshmu commented on issue #25443:
URL: https://github.com/apache/superset/issues/25443#issuecomment-1741941910

   Any hints in the logs?


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] Unable to complete url-redirection, redirection getting stuck after OAuth authentication completion from OKTA [superset]

Posted by "vrychkov-repay (via GitHub)" <gi...@apache.org>.
vrychkov-repay commented on issue #25443:
URL: https://github.com/apache/superset/issues/25443#issuecomment-2298591814

   @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


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org