hue icon indicating copy to clipboard operation
hue copied to clipboard

Unable to logout when using keycloak with OIDC

Open yuanbw opened this issue 2 years ago • 0 comments

Is the issue already present in https://github.com/cloudera/hue/issues or discussed in the forum https://discourse.gethue.com? Yes. https://github.com/cloudera/hue/issues/2590

Describe the bug: OIDC authentication using Keycloak for example does not work again when Hue is built with Python3. User can make a successful login via the client (Keycloak), However, User is always logged in and cannot log out.

Terminal or console logs:

[01/Mar/2022 19:00:03 -0800] backend WARNING OpenID Connect tokens are not available, logout skipped! [01/Mar/2022 19:00:03 -0800] database INFO AXES: Successful logout by {username: "bowen", ip_address: "11.36.14.70", user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36 Edg/98.0.1108.51", path_info: "/accounts/logout"}.

After several hours of debugging it appears that the authenticate method defined in the OIDCBackend which extends OIDCAuthencationBackend in desktop/core/src/desktop/auth/backend.py does not .

#OIDC Backend - desktop/core/src/desktop/auth/backend.py
def logout(self, request, next_page):
    if request.session.get('_auth_user_backend', '') != 'desktop.auth.backend.OIDCBackend':
      return None

    session = request.session
    access_token = session.get('oidc_access_token', '')
    refresh_token = session.get('oidc_refresh_token', '')

    if access_token and refresh_token:
      oidc_logout_url = OIDC.LOGOUT_REDIRECT_URL.get()
      client_id = import_from_settings('OIDC_RP_CLIENT_ID')
      client_secret = import_from_settings('OIDC_RP_CLIENT_SECRET')
      oidc_verify_ssl = import_from_settings('OIDC_VERIFY_SSL')
      form = {
        'client_id': client_id,
        'client_secret': client_secret,
        'refresh_token': refresh_token,
      }
      headers = {
        'Authorization': 'Bearer {0}'.format(access_token),
        'Content-Type': 'application/x-www-form-urlencoded'
      }
      resp = requests.post(oidc_logout_url, data=form, headers=headers, verify=oidc_verify_ssl)
      if resp.status_code >= 200 and resp.status_code < 300:
        LOG.debug("OpenID Connect logout succeed!")
        delete_oidc_session_tokens(session)
        auth.logout(request)
        response = HttpResponseRedirect(next_page)
        response.delete_cookie(LOAD_BALANCER_COOKIE)
        return response
      else:
        LOG.error("OpenID Connect logout failed: %s" % resp.content)
    else:
      LOG.warning("OpenID Connect tokens are not available, logout skipped!")  <-------- THIS:   requires both access_token and refresh_token are valid. Otherwise, the oidc logout will skipped.
    return None
#OIDC Backend - desktop/core/src/desktop/auth/backend.py
 class OIDCBackend(OIDCAuthenticationBackend):
    def authenticate(self, *args, **kwargs):
    ...
    if verified_id:
      self.save_refresh_tokens(refresh_token)   <-------- THIS:   only save refresh token, which cause the logout method above failed.
      user = self.get_or_create_user(access_token, id_token, verified_id)
      user = rewrite_user(user)
      ensure_has_a_group(user)
      
      return user

How to fix:

Changing the OIDCBackend authenticate method to save both access_token and refresh_token fixed the issue.

#OIDC Backend - desktop/core/src/desktop/auth/backend.py

 class OIDCBackend(OIDCAuthenticationBackend):
    def authenticate(self, *args, **kwargs):
    ...
    if verified_id:
      self.save_access_tokens(access_token)  <--------added
      self.save_refresh_tokens(refresh_token)   
      user = self.get_or_create_user(access_token, id_token, verified_id)
      user = rewrite_user(user)
      ensure_has_a_group(user)
      
      return user
#OIDC Backend - desktop/core/src/desktop/auth/backend.py
    def save_access_tokens(self, access_token):   
      session = self.request.session

      if import_from_settings('OIDC_STORE_ACCESS_TOKEN', False):
        session['oidc_access_token'] = access_token
   

    def save_refresh_tokens(self, refresh_token):
      session = self.request.session

      if import_from_settings('OIDC_STORE_REFRESH_TOKEN', False):
        session['oidc_refresh_token'] = refresh_token

Steps to reproduce it? 1.Start hue (built with python3) 2.Set the auth backend to desktop.auth.backend.OIDCBackend 3.Try to login & logout

Hue version or source? (e.g. open source 4.5, CDH 5.16, CDP 1.0...). System info (e.g. OS, Browser...). Hue version: 4.10.0 Browser: Latest versions of Google Chrome or Firefox OS: macOS Big Sur version 11.5.2

yuanbw avatar Mar 02 '22 08:03 yuanbw