oauthenticator: Bitbucket auth broken

Bug description

I think this might be a bug in Bitbucket rather than OAuthenticator, but I’m not sure, and it may be specific to my setup. The requests to get user information and workspace permissions get a 401 response, handled as a 500 in Jupyterhub when using BitbucketOAuthenticator.

This appears to be a problem with the way the Authorization header is being sent/handled, because including the token in the querystring appears to work. Setting OAUTH2_USERDATA_REQUEST_TYPE=url in the environment makes it work for token_to_user but _fetch_user_teams still breaks

Expected behaviour

Setting Authorization header should successfully authenticate with bitbucket cloud

Actual behaviour

401 status returned

How to reproduce

  1. Setup OAuthentiator as the setup below
  2. Try to login with Bitbucket

Your personal set up

jupyterhub 4.0.2 oauthenticator 16.0.5

c.JupyterHub.authenticator_class = "bitbucket"
c.OAuthenticator.oauth_callback_url = "http://localhost/hub/oauth_callback"
c.OAuthenticator.client_id = "CLIENT_ID"
c.OAuthenticator.client_secret = "SECRET"
c.OAuthenticator.allowed_users = ["user1", "user2"]
c.BitbucketOAuthenticator.allowed_teams = {"teamname"}
c.SubBitbucketAuthenticator.scope = ["email", "account", "repository", "workspace"]

Workaround

Currently I have setup OAUTH2_USERDATA_REQUEST_TYPE=url in the environment to authenticate on the user API, and am subclassing the _fetch_user_teams method as below to restrict the workspace.

I feel it might be better solved by putting userdata_token_method into httpfetch if appropriate and in subclasses setting self.userdata_token_method = url in the bitbucket class.

class SubBitbucketOAuthenticator(oauthenticator.BitbucketOAuthenticator):
    async def _fetch_user_teams(self, access_token, token_type):
        """
        Get user's team memberships via bitbucket's API.
        """
        try:
            app_log.debug("Trying usual bitbucket OAuth method...")
            return await super()._fetch_user_teams(access_token, token_type)
        except HTTPClientError as e:
            if e.code != 401:
                raise
        app_log.debug("Trying to authenticate with with access_token in the url")
        headers = self.build_userdata_request_headers(access_token, token_type)
        del headers["Authorization"]

        next_page = url_concat(
            "https://api.bitbucket.org/2.0/workspaces", {"access_token": access_token}
        )

        user_teams = set()
        while next_page:
            resp_json = await self.httpfetch(next_page, method="GET", headers=headers)
            next_page = resp_json.get('next', None)
            user_teams |= {entry["name"] for entry in resp_json["values"]}
        return user_teams

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 19 (13 by maintainers)

Most upvoted comments

I will have a look at the details later on. The difference I see is that in 15.1.0, the headers hardcodes Bearer in "Authorization": "Bearer {}".format(access_token), whereas in 16.0 it is "Authorization": f"{token_type} {access_token}" so my bet is that bitbucket gives a different token_type.

Another thing to think about is what I said about the different flow between 15.1 and 16.0, that the team membership is not checked if the user is in the list of allowed users. If it is deliberate, I think that should be documented more specifically that if set a user may be an allowed user OR a member of a team/workspace.

Okay so we have two bugs to fix:

  1. security bug, require uniquness of name specified in allowed_teams config
  2. functional bug, make requests to /2.0/user and /2.0/workspaces function

With regards to the securit bug, the fix is to switch to slug instead of name.

With regards to the functional bug, I’m not sure, but I don’t want us to pass the access token in a query parameter. Also, this isn’t recommended by bitbucket themselves who sais they prefer the header option.

Since they suggest they should support passing info in the header, and since 15.1.0 also passes it via the header, I don’t understand what has broken in 16.0.x compared to 15.1.0.

If you can figure out whats wrong and patch it while still using a header in the GET request, that would be great and a PR I’d see myself merging with a calm mind.

Yes, to confirm, I successfully logged in with 15.1.0 when I set the team name to display name, so 15.1.0 is working correctly.

However, the name does not appear to be unique which suggests you should not use it in the authenticator. On my personal account, I was able to create a workspace with the same name as my work company workspace. I’m not sure if it matters or not, because the access token should be tied to the account which has the particular team and they would presumably not want to create multiple workspaces with the same display name.

However, the slug is indeed part of the URL and I was not allowed to create a workspace with the same name, so I think that it would be reasonable to use that instead. I think using the slug is more intuitive anyway, but that might just be my preference.