apache / superset

Apache Superset is a Data Visualization and Data Exploration Platform
https://superset.apache.org/
Apache License 2.0
62.14k stars 13.64k forks source link

Endpoint /api/me/roles returns 401 "Not authorized" despite valid access token #25740

Open larshelge opened 11 months ago

larshelge commented 11 months ago

I am running Superset 3.0.0 with Docker Compose. Trying to access the following API endpoint:

/api/me/roles

I have created an access token from /api/v1/security/login for a user with the Admin, Public and Gamma roles. I make a request for the mentioned API endpoint with the appropriate header.

Authorization: Bearer {access-token}
Accept: application/json

Various endpoints return 200 and a valid JSON payload, such as /api/v1/dashboard and /api/v1/chart. However, the /api/me/roles endpoint returns 401 "Not authorized". This blocks the embedded dashboard feature. Could this be a bug, or am I doing something wrong? I sense there is something special with this endpoint and authentication as it relates to the currently authenticated user.

The /api/me/roles endpoint returns the roles when I log in through the UI and load the endpoint in a web browser.

Expected results

I expect the roles to be returned from the API with 200 OK status.

Actual results

The endpoint returns 401 "Not authorized" despite the access token seemingly being valid.

Environment

(please complete the following information):

Flags

WTF_CSRF_ENABLED = False
TALISMAN_ENABLED = False
ENABLE_CORS = True
cbuffevant commented 11 months ago

Still happening in 3.0.1

siddhartha8916 commented 9 months ago

You can try a fix around by hitting a get request to http://localhost:8088/login endpoint and sending the csrf_token obtained along with username and password as formData and then get the access token to request http://localhost:8088/api/v1/me/roles api

Issue addressed here #25876

bryanjknight commented 6 months ago

Still happening in 3.1.1, also blocking usage of embedded dashboards. Unfortunately the workaround above won't work because the embed SDK won't do that for me

bryanjknight commented 6 months ago

Digging into this more, it seems /api/v1/security/login does not set a session cookie; hence, the /api/v1/me and underlying calls fail b/c there's no session cookie

EuphoriaCelestial commented 6 months ago

Digging into this more, it seems /api/v1/security/login does not set a session cookie; hence, the /api/v1/me and underlying calls fail b/c there's no session cookie

I am facing the same issue Normally, how can I get the session cookie? if /api/v1/security/login doesn't return it, is there any other way to login and get cookie? how did superset work if there is no session cookie?

siddhartha8916 commented 6 months ago

Still happening in 3.1.1, also blocking usage of embedded dashboards. Unfortunately the workaround above won't work because the embed SDK won't do that for me

So superset only returns the dashboard available to that user according to the role defined

bryanjknight commented 6 months ago

Digging into this more, it seems /api/v1/security/login does not set a session cookie; hence, the /api/v1/me and underlying calls fail b/c there's no session cookie

I am facing the same issue Normally, how can I get the session cookie? if /api/v1/security/login doesn't return it, is there any other way to login and get cookie? how did superset work if there is no session cookie?

I had to do a hack (emphasis on hack: this is not production ready code, please review it b/c if done wrong it creates a security hole) to basically take a JWT, verify it, find the corresponding user, then login that user again. The result is a cookie getting set on response:

class CustomOAuthView(AuthOAuthView):

    @expose('/custom/session-by-jwt', methods=['GET'])
    def session_by_jwt(self, provider= None):
        # get the jwt in the request
        current_jwt = request.headers.get('Authorization').split(' ')[1]

        # decode the jwt to get the claims
        import jwt
        try:
            jwt_options = {
                'verify_signature': False, # TODO: get the public key from the jwks_uri to verify the signature
                'verify_exp': True,
                'verify_nbf': False,
                'verify_iat': True,
                'verify_aud': False
            }
            jwt_decoded = jwt.decode(jwt=str.encode(current_jwt), algorithms=['HS256'], options=jwt_options)

            # get the user from the jwt
            user = self.appbuilder.sm.find_user(email=jwt_decoded['sub'])

            # create a session cookie for the user
            login_user(user)
        except Exception as e:
            print(f"Exception: {e}")
            return "Invalid JWT"
        return "session_by_jwt"  

class CustomSsoSecurityManager(SupersetSecurityManager):

    authoauthview = CustomOAuthView

    def __init__(self, appbuilder):
        super(CustomSsoSecurityManager, self).__init__(appbuilder)
EuphoriaCelestial commented 6 months ago

Digging into this more, it seems /api/v1/security/login does not set a session cookie; hence, the /api/v1/me and underlying calls fail b/c there's no session cookie

I found out that if we set those flag in superset config: SESSION_COOKIE_HTTPONLY = False SESSION_COOKIE_SECURE = False

the /api/v1/security/login endpoint will return a cookie like this: image

is this the right cookie? if so, how to use it? I tried to add it in postman but it doesn't work image

image

bryanjknight commented 6 months ago

Ah, good find! Yeah, that's looks close, the one I have starts with a .. Digging into the superset_config.py in the main repo, I see this:

#
# Flask session cookie options
#
# See https://flask.palletsprojects.com/en/1.1.x/security/#set-cookie-options
# for details
#
SESSION_COOKIE_HTTPONLY = True  # Prevent cookie from being read by frontend JS?
SESSION_COOKIE_SECURE = False  # Prevent cookie from being transmitted over non-tls?
SESSION_COOKIE_SAMESITE: Literal["None", "Lax", "Strict"] | None = "Lax"
# Whether to use server side sessions from flask-session or Flask secure cookies
SESSION_SERVER_SIDE = False
# Example config using Redis as the backend for server side sessions
# from flask_session import RedisSessionInterface
#
# SESSION_SERVER_SIDE = True
# SESSION_USE_SIGNER = True
# SESSION_TYPE = "redis"
# SESSION_REDIS = Redis(host="localhost", port=6379, db=0)
#
# Other possible config options and backends:
# # https://flask-session.readthedocs.io/en/latest/config.html

Perhaps switch to server side sessions might help?

EuphoriaCelestial commented 6 months ago

Perhaps switch to server side sessions might help?

I tried, but still not working Any other idea?

YannGer commented 6 months ago

Hi all,

same problem here. Do I have to rollback on version 2 or do you think this problem may be fixed in a rather short time ?

Thx !!

mortenesbensen commented 4 months ago

Hi,

I'm also seeing this on Superset 3.1.0 when trying to embed dashboards using the embedded sdk. Any updates?

Thanks in advance

Turns out guest token I was using wasn't formatted correct in which case the Embedded SDK successfully creates the dashboard just to fail on the subsequent call to /roles

jay-growflow commented 3 months ago

@mortenesbensen when you say it "wasn't formatted correctly" what was the issue? I think we're running into this same issue now.