diff --git a/google_auth_oauthlib/flow.py b/google_auth_oauthlib/flow.py index 7d8e521..f55a023 100644 --- a/google_auth_oauthlib/flow.py +++ b/google_auth_oauthlib/flow.py @@ -378,6 +378,7 @@ def run_local_server( open_browser=True, redirect_uri_trailing_slash=True, timeout_seconds=None, + token_audience=None, **kwargs ): """Run the flow using the server strategy. @@ -412,6 +413,9 @@ def run_local_server( 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 + token. Determines the endpoints with which the token can be + used. Optional. kwargs: Additional keyword arguments passed through to :meth:`authorization_url`. @@ -444,7 +448,9 @@ def run_local_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") - self.fetch_token(authorization_response=authorization_response) + self.fetch_token( + authorization_response=authorization_response, audience=token_audience + ) # This closes the socket local_server.server_close() diff --git a/tests/unit/test_flow.py b/tests/unit/test_flow.py index d9c9f8b..103bffd 100644 --- a/tests/unit/test_flow.py +++ b/tests/unit/test_flow.py @@ -242,6 +242,7 @@ def test_authorized_session(self, instance): class TestInstalledAppFlow(object): SCOPES = ["email", "profile"] REDIRECT_REQUEST_PATH = "/?code=code&state=state" + AUDIENCE = "dummy-audience" @pytest.fixture def instance(self): @@ -312,6 +313,46 @@ def test_run_local_server(self, webbrowser_mock, instance, mock_fetch_token, por client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], authorization_response=expected_auth_response, code_verifier=None, + audience=None, + ) + + @pytest.mark.webtest + @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) + def test_run_local_server_audience( + self, webbrowser_mock, instance, mock_fetch_token, port + ): + auth_redirect_url = urllib.parse.urljoin( + f"http://localhost:{port}", self.REDIRECT_REQUEST_PATH + ) + + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + future = pool.submit( + partial( + instance.run_local_server, port=port, token_audience=self.AUDIENCE + ) + ) + + while not future.done(): + try: + requests.get(auth_redirect_url) + except requests.ConnectionError: # pragma: NO COVER + pass + + credentials = future.result() + + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + assert credentials.id_token == mock.sentinel.id_token + assert webbrowser_mock.open.called + assert instance.redirect_uri == f"http://localhost:{port}/" + + expected_auth_response = auth_redirect_url.replace("http", "https") + mock_fetch_token.assert_called_with( + CLIENT_SECRETS_INFO["web"]["token_uri"], + client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], + authorization_response=expected_auth_response, + code_verifier=None, + audience=self.AUDIENCE, ) @pytest.mark.webtest @@ -353,6 +394,7 @@ def test_run_local_server_code_verifier( client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], authorization_response=expected_auth_response, code_verifier="amanaplanacanalpanama", + audience=None, ) @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True)