superset icon indicating copy to clipboard operation
superset copied to clipboard

CustomSecurityManager - Can't ignore CSRF on route

Open jharmon96 opened this issue 2 years ago • 4 comments

A clear and concise description of what the bug is.

How to reproduce the bug

Backchannel logout sent from Keycloak OIDC server is failing. Login/Logout via the UI both work fine. The intention is to implement a Single Sign Out solution, where a user logging out of another client application will cause them to logout of Superset. At the moment, when a user signs out of another application, Keycloak is configured to send a POST request to Superset with the goal being that the request logs the user out.

I have added the following to my superset_config.py:

    from flask import redirect, request
    from flask_appbuilder.security.manager import AUTH_OID
    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
    class AuthOIDCView(AuthOIDView):

      @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'])
            user = sm.add_user(info.get('preferred_username'),   
                  info.get('given_name'), info.get('family_name'),    
                  info.get('email'), sm.find_role('Gamma'))
          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()        
        redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
        # return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?post_logout_redirect_uri=' +  quote(redirect_url, safe=''))
        return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout')

      @expose('/backchannel-logout/', methods=['GET', 'POST'])
      def backchannel_logout(self):
        oidc = self.appbuilder.sm.oid
        oidc.logout()
        super(AuthOIDCView, self).logout()        
        redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
        return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout')

    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)

   # Trying different variations here to get this to work
    WTF_CSRF_EXEMPT_LIST.append("backchannel_logout")
    WTF_CSRF_EXEMPT_LIST.append("backchannel-logout")
    WTF_CSRF_EXEMPT_LIST.append("superset.backchannel_ogout")
    WTF_CSRF_EXEMPT_LIST.append("superset.backchannel-logout")

    AUTH_TYPE = AUTH_OID
    OIDC_CLIENT_SECRETS='client_secret.json'
    OIDC_ID_TOKEN_COOKIE_SECURE = False
    OIDC_REQUIRE_VERIFIED_EMAIL = False
    OIDC_CLOCK_SKEW = 560
    OIDC_VALID_ISSUERS = 'https://auth.{{ .Values.global.domain }}/realms/{{ .Values.global.customerName }}'
    AUTH_USER_REGISTRATION = True
    AUTH_USER_REGISTRATION_ROLE = 'Gamma'
    CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
    OIDC_INTROSPECTION_AUTH_METHOD = 'client_secret_post'
    OIDC_TOKEN_TYPE_HINT = 'access_token'

Expected results

I expect that the POST request made against /backchannel-logout/ would not require a CSRF token.

Actual results

I get the following message in my Superset server logs:

2022-06-09 22:39:19,480:INFO:flask_wtf.csrf:The CSRF token is missing.
400 Bad Request: The CSRF token is missing.
2022-06-09 22:39:19,480:WARNING:superset.views.base:400 Bad Request: The CSRF token is missing.
10.244.0.133 - - [09/Jun/2022:22:39:19 +0000] "POST /backchannel-logout/ HTTP/1.1" 302 220 "-" "Apache-HttpClient/4.5.13 (Java/11.0.15)"

Screenshots

N/A

Environment

POST to /backchannel-logout/ is being sent from a Keycloak server. Latest Superset version Python 3.8

Additional context

Apologies if I am doing something dumb here, like simply getting the format of my WTF_CSRF_EXEMPT_LIST record wrong. I've been trying to get this to work for hours : /

Also, I realize that the code under def backchannel_logout(self): may not work, but right now I'm just trying to get past this CSRF issue.

jharmon96 avatar Jun 09 '22 22:06 jharmon96

Wondering if @john-bodley might have any insights here, or someone appropriate to redirect this to.

rusackas avatar Jun 10 '22 14:06 rusackas

Thanks! Happy to provide more info if needed.

jharmon96 avatar Jun 10 '22 23:06 jharmon96

@jharmon96 - did you ever get the backchannel logout working? I am facing similar issues right now. Also, if you want to test your endpoint, you can disable CSRF with WTF_CSRF_ENABLED = False. Not recommended for prod though.

ghost avatar Jul 29 '22 12:07 ghost

No I haven't, been pushed down on the priority list for the time being. And thanks for the tip! I'm planning to circle back to this in the next couple weeks, I'll give that a try.

jharmon96 avatar Aug 03 '22 21:08 jharmon96

@jharmon96 Did you ever resolve this issue? We have run into similar challenges on latest superset and Keycloak 11.x integration. Let me know if you figured a workaround. cheers!

mpreddy77 avatar Jan 17 '23 02:01 mpreddy77

Closing this as stale since it's been silent for so long, and we're trying to steer toward a more actionable Issues backlog. If people are still encountering this in current versions (currently 3.x) please re-open this issue, open a new Issue with updated context, or raise a PR to address the problem. Thanks!

rusackas avatar Feb 15 '24 18:02 rusackas