diff --git a/google_auth_oauthlib/flow.py b/google_auth_oauthlib/flow.py index e564ca4..e12da98 100644 --- a/google_auth_oauthlib/flow.py +++ b/google_auth_oauthlib/flow.py @@ -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 @@ -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 @@ -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") @@ -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.""" diff --git a/tests/unit/test_flow.py b/tests/unit/test_flow.py index 65ced12..5a70ec4 100644 --- a/tests/unit/test_flow.py +++ b/tests/unit/test_flow.py @@ -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)