⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions google_auth_oauthlib/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,9 @@ def run_local_server(
in the user's browser.
redirect_uri_trailing_slash (bool): whether or not to add trailing
slash when constructing the redirect_uri. Default value is True.
timeout_seconds (int): It will raise an error after the timeout timing
if there are no credentials response. The value is in seconds.
timeout_seconds (int): It will raise a WSGITimeout exception after the
timeout timing if there are no credentials response. The value is in
seconds.
When set to None there is no timeout.
Default value is None.
token_audience (str): Passed along with the request for an access
Expand All @@ -425,6 +426,10 @@ def run_local_server(
Returns:
google.oauth2.credentials.Credentials: The OAuth 2.0 credentials
for the user.

Raises:
WSGITimeout: If there is a timeout when waiting for the response from the
authorization server.
"""
wsgi_app = _RedirectWSGIApp(success_message)
# Fail fast if the address is occupied
Expand Down Expand Up @@ -452,6 +457,10 @@ def run_local_server(
local_server.timeout = timeout_seconds
local_server.handle_request()

if wsgi_app.last_request_uri is None:
# Timeout occurred
raise WSGITimeout("Timed out waiting for response from authorization server")

# Note: using https here because oauthlib is very picky that
# OAuth 2.0 should only occur over https.
authorization_response = wsgi_app.last_request_uri.replace("http", "https")
Expand Down Expand Up @@ -505,3 +514,7 @@ def __call__(self, environ, start_response):
start_response("200 OK", [("Content-type", "text/plain; charset=utf-8")])
self.last_request_uri = wsgiref.util.request_uri(environ)
return [self._success_message.encode("utf-8")]


class WSGITimeout(Exception):
"""Raised when the WSGI server times out waiting for a response."""
14 changes: 14 additions & 0 deletions tests/unit/test_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,17 @@ def test_local_server_socket_cleanup(
instance.run_local_server()

server_mock.server_close.assert_called_once()

@mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True)
@mock.patch("wsgiref.simple_server.make_server", autospec=True)
def test_run_local_server_timeout(
self, make_server_mock, webbrowser_mock, instance, mock_fetch_token
):
mock_server = mock.Mock()
make_server_mock.return_value = mock_server

# handle_request does nothing (simulating timeout), so last_request_uri remains None
mock_server.handle_request.return_value = None

with pytest.raises(flow.WSGITimeout):
instance.run_local_server(timeout_seconds=1)
Comment on lines +470 to +471
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This test correctly verifies that a WSGITimeout is raised. To make it more robust, you could also assert that webbrowser.open() was called, as this happens before the server waits for the request and times out. This would ensure that part of the flow is also behaving as expected in the timeout scenario, consistent with other tests for run_local_server.

Suggested change
with pytest.raises(flow.WSGITimeout):
instance.run_local_server(timeout_seconds=1)
with pytest.raises(flow.WSGITimeout):
instance.run_local_server(timeout_seconds=1)
webbrowser_mock.get.assert_called_with(None)
webbrowser_mock.get.return_value.open.assert_called_once()

Loading