Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Google] Make looking up google groups far less blocking #764

Merged
merged 13 commits into from
Oct 8, 2024

Conversation

jrdnbradford
Copy link
Contributor

@jrdnbradford jrdnbradford commented Sep 26, 2024

Closes #607 and should only be merged after #763 as it is stacked. There might be some conflicts that I will resolve.

The main change was removing the creation of the service and instead retrieving an oauth token and using an asynchronous aiohttp session to get the groups. If Google ever releases an asynchronous client that uses the service then we should switch to that because it's more readable.

requirements.txt Outdated Show resolved Hide resolved
Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use aiohttp, but there is also the OAuthenticator base class function below that makes use of tornado.HTTPRequest which can be done async. If using this could work, that would be preferred as we avoid introducing another way of making HTTP requests.

    async def httpfetch(
        self, url, label="fetching", parse_json=True, raise_error=True, **kwargs
    ):
        """Wrapper for creating and fetching http requests

        Includes http_request_kwargs in request kwargs
        logs error responses, parses successful JSON responses

        Args:
            url (str): url to fetch
            label (str): label describing what is happening,
                used in log message when the request fails.
            parse_json (bool): whether to parse the response as JSON
            raise_error (bool): whether to raise an exception on HTTP errors
            **kwargs: remaining keyword args
                passed to underlying `tornado.HTTPRequest`, overrides
                `http_request_kwargs`
        Returns:
            parsed JSON response if `parse_json=True`, else `tornado.HTTPResponse`
        """
        request_kwargs = self.http_request_kwargs.copy()
        request_kwargs.update(kwargs)
        req = HTTPRequest(url, **request_kwargs)
        return await self.fetch(
            req, label=label, parse_json=parse_json, raise_error=raise_error
        )

@jrdnbradford jrdnbradford marked this pull request as ready for review October 3, 2024 16:29
@jrdnbradford
Copy link
Contributor Author

Thanks for your guidance here on using httpfetch, @consideRatio.

@jrdnbradford jrdnbradford changed the title Async Google Group lookup for auth [Google] Add Async Google Group Lookup for Auth Oct 3, 2024
@consideRatio
Copy link
Member

Oh beautiful!!! Have you been able to test that this works as well? I think our test coverage won't be able to tell so well if it does

@jrdnbradford
Copy link
Contributor Author

I've tested this on the-littlest-jupyterhub 1.0.0 on the following, including with the new include_nested_groups config:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.1 LTS
Release:        24.04
Codename:       noble

If you like, I can probably test with several logins at the same time sometime soon. 🚀

@consideRatio
Copy link
Member

@jrdnbradford I pushed a commit removing a dependency I think wasn't used more, do you think that was correct? I really appreciate those complexity reductions, thanks for working this!!!

@jrdnbradford
Copy link
Contributor Author

@jrdnbradford I pushed a commit removing a dependency I think wasn't used more, do you think that was correct? I really appreciate those complexity reductions, thanks for working this!!!

I'm pretty sure you're right, now that we don't build the service that isn't needed. I'll test to make sure. I've also scheduled a time tomorrow to get several semi-simultaneous logins on a dev TLJH with the oauthenticator changes. I'll report back after.

oauthenticator/google.py Outdated Show resolved Hide resolved
Comment on lines 318 to 326
def _get_credentials(self, user_email_domain):
"""
Returns the stored credentials or fetches and stores new ones.

Checks if the credentials are valid before returning them. Refreshes
if necessary and stores the refreshed credentials.
"""
if not self._credentials or not self._is_token_valid():
self._credentials = self._setup_credentials(user_email_domain)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Credentials seems to be received for a user_email_domain, doesn't that makes them different between users? If so, isn't it causing issues to re-use them for any user?

I figure we would at least need to have a dictionary of credentials, with one for each user_email_domain, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If multiple people login at the same time _setup_credentials could be called multiple times in parallel. Is this likely to cause any problems?

Copy link
Contributor Author

@jrdnbradford jrdnbradford Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manics this is a good question. On the off-chance this does happen and there multiple tokens fetched, those tokens will still be valid for making the request.

@jrdnbradford
Copy link
Contributor Author

I have added support so that creds/tokens are saved on a domain-basis in a dictionary and are refreshed only if necessary. My testing with a single domain works, but I'll need some more time to test multiple Google workspace domains in a JupyterHub installation to ensure it works.

@consideRatio consideRatio changed the title [Google] Add Async Google Group Lookup for Auth [Google] Make looking up google groups less blocking Oct 4, 2024
Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is brilliant, really pleased about the refresh logic detail and the removal of now unused python dependency.

If this passes your end to end testing I figure we go for a merge!

@manics what do you think?

@consideRatio consideRatio changed the title [Google] Make looking up google groups less blocking [Google] Make looking up google groups far less blocking Oct 4, 2024
Copy link
Member

@manics manics left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've suggested adding a comment to help with future maintenance, e.g. if a change somewhere results in the race condition causing a failure. Otherwise I agree this is good to merge.

oauthenticator/google.py Outdated Show resolved Hide resolved
@jrdnbradford
Copy link
Contributor Author

jrdnbradford commented Oct 6, 2024

Awesome, will report back after I setup another Workspace domain for testing this.

@jrdnbradford
Copy link
Contributor Author

Turns out I don't have the access to another workspace domain to do this testing. @consideRatio and @manics, any contacts that use Google with multiple domains that could install the dev version for a test?

Comment on lines +406 to +409
# WARNING: There's a race condition here if multiple users login at the same time.
# This is currently ignored.
credentials = credentials or self._get_service_credentials(user_email_domain)
token = credentials[user_email_domain].token
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manics I've tried to understand this race condition, but I've failed to see it. Are you sure there is one?

My understanding made explicit: whenever an await is showing up, then and only then is Python possibly switching to execute something else.

@consideRatio
Copy link
Member

Turns out I don't have the access to another workspace domain to do this testing. @consideRatio and @manics, any contacts that use Google with multiple domains that could install the dev version for a test?

I'm not able to get this done, but I'm willing to go for a merge anyhow I think.

As @manics wrote:

Otherwise I agree this is good to merge.

I'll go for it!

Thank you a lot for thorough work on this @jrdnbradford !

@consideRatio consideRatio merged commit b9c8905 into jupyterhub:main Oct 8, 2024
11 checks passed
@jrdnbradford
Copy link
Contributor Author

If there are any issues please @ me and I'd be happy to fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The google authenticator's calls to check groups is done sync, can it be made async?
3 participants