From f8f1ae58ac4eb427fc03bd61795651085b2c138b Mon Sep 17 00:00:00 2001 From: Raphael Krupinski Date: Thu, 7 Mar 2024 14:50:57 +0100 Subject: [PATCH 1/2] Breaking: Use httpx generator-based auth-flow protocol in place of internal Clients. --- httpx_auth/_authentication.py | 31 ++++++-- httpx_auth/_oauth2/authorization_code.py | 63 +++++++--------- httpx_auth/_oauth2/authorization_code_pkce.py | 60 +++++++--------- httpx_auth/_oauth2/client_credentials.py | 26 ++----- httpx_auth/_oauth2/common.py | 16 +++-- httpx_auth/_oauth2/implicit.py | 8 ++- httpx_auth/_oauth2/resource_owner_password.py | 71 +++++++++---------- httpx_auth/_oauth2/tokens.py | 21 ++++-- httpx_auth/testing.py | 1 + .../token_cache/test_json_token_file_cache.py | 36 ++++++---- ...st_oauth2_authorization_code_okta_async.py | 4 +- ...est_oauth2_authorization_code_okta_sync.py | 4 +- .../test_oauth2_authorization_code_async.py | 12 ++-- .../test_oauth2_authorization_code_sync.py | 12 ++-- ...auth2_authorization_code_wakatime_async.py | 4 +- ...oauth2_authorization_code_wakatime_sync.py | 4 +- ...uth2_authorization_code_pkce_okta_async.py | 4 +- ...auth2_authorization_code_pkce_okta_sync.py | 4 +- ...st_oauth2_authorization_code_pkce_async.py | 12 ++-- ...est_oauth2_authorization_code_pkce_sync.py | 12 ++-- ...est_oauth2_client_credential_okta_async.py | 4 +- ...test_oauth2_client_credential_okta_sync.py | 4 +- .../test_oauth2_client_credential_async.py | 8 +-- .../test_oauth2_client_credential_sync.py | 8 +-- ...uth2_resource_owner_password_okta_async.py | 12 ++-- ...auth2_resource_owner_password_okta_sync.py | 12 ++-- ...st_oauth2_resource_owner_password_async.py | 12 ++-- ...est_oauth2_resource_owner_password_sync.py | 12 ++-- 28 files changed, 247 insertions(+), 230 deletions(-) diff --git a/httpx_auth/_authentication.py b/httpx_auth/_authentication.py index f251bff..766fb2f 100644 --- a/httpx_auth/_authentication.py +++ b/httpx_auth/_authentication.py @@ -1,6 +1,8 @@ +import typing from typing import Generator import httpx +from httpx import Request, Response class _MultiAuth(httpx.Auth): @@ -9,11 +11,32 @@ class _MultiAuth(httpx.Auth): def __init__(self, *authentication_modes): self.authentication_modes = authentication_modes - def auth_flow( - self, request: httpx.Request - ) -> Generator[httpx.Request, httpx.Response, None]: + def sync_auth_flow( + self, request: Request + ) -> typing.Generator[Request, Response, None]: + for authentication_mode in self.authentication_modes: + # auth_flow may yield one or more requests, the last of which is the user request with added auth headers + flow = authentication_mode.sync_auth_flow(request) + req = next(flow) + while True: + if req is request: + break + resp = yield req + req = flow.send(resp) + yield request + + async def async_auth_flow( + self, request: Request + ) -> typing.AsyncGenerator[Request, Response]: for authentication_mode in self.authentication_modes: - next(authentication_mode.auth_flow(request)) + # auth_flow may yield one or more requests, the last of which is the user request with added auth headers + flow = authentication_mode.async_auth_flow(request) + req = await anext(flow) + while True: + if req is request: + break + resp = yield req + req = await flow.asend(resp) yield request def __add__(self, other) -> "_MultiAuth": diff --git a/httpx_auth/_oauth2/authorization_code.py b/httpx_auth/_oauth2/authorization_code.py index 334a44c..76099bb 100644 --- a/httpx_auth/_oauth2/authorization_code.py +++ b/httpx_auth/_oauth2/authorization_code.py @@ -1,3 +1,4 @@ +from collections.abc import Generator from hashlib import sha512 from typing import Iterable, Union @@ -52,7 +53,7 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): :param code_field_name: Field name containing the code. code by default. :param username: Username in case basic authentication should be used to retrieve token. :param password: User password in case basic authentication should be used to retrieve token. - :param client: httpx.Client instance that will be used to request the token. + :param headers: Additional headers to set when requesting or refreshing token. Use it to provide a custom proxying rule for instance. :param kwargs: all additional authorization parameters that should be put as query parameter in the authorization URL and as body parameters in the token URL. @@ -80,7 +81,7 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): username = kwargs.pop("username", None) password = kwargs.pop("password", None) self.auth = (username, password) if username and password else None - self.client = kwargs.pop("client", None) + self.token_headers = kwargs.pop("headers", {}) # As described in https://tools.ietf.org/html/rfc6749#section-4.1.2 code_field_name = kwargs.pop("code_field_name", "code") @@ -136,7 +137,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): self.refresh_token, ) - def request_new_token(self) -> tuple: + def request_new_token( + self, + ) -> Generator[ + httpx.Request, httpx.Response, Union[tuple[str, str], tuple[str, str, int]] + ]: # Request code state, code = authentication_responses_server.request_new_grant( self.code_grant_details @@ -145,17 +150,10 @@ def request_new_token(self) -> tuple: # As described in https://tools.ietf.org/html/rfc6749#section-4.1.3 self.token_data["code"] = code - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-4.1.4 - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, self.token_data, self.token_field_name, client - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + # As described in https://tools.ietf.org/html/rfc6749#section-4.1.4 + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, self.token_data, self.token_field_name, self.token_headers + ) # Handle both Access and Bearer tokens return ( (self.state, token, expires_in, refresh_token) @@ -163,28 +161,19 @@ def request_new_token(self) -> tuple: else (self.state, token) ) - def refresh_token(self, refresh_token: str) -> tuple: - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-6 - self.refresh_data["refresh_token"] = refresh_token - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, - self.refresh_data, - self.token_field_name, - client, - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + def refresh_token( + self, refresh_token: str + ) -> Generator[httpx.Request, httpx.Response, tuple[str, str, int, str]]: + # As described in https://tools.ietf.org/html/rfc6749#section-6 + self.refresh_data["refresh_token"] = refresh_token + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, + self.refresh_data, + self.token_field_name, + self.token_headers, + ) return self.state, token, expires_in, refresh_token - def _configure_client(self, client: httpx.Client): - client.auth = self.auth - client.timeout = self.timeout - class OktaAuthorizationCode(OAuth2AuthorizationCode): """ @@ -220,8 +209,7 @@ def __init__(self, instance: str, client_id: str, **kwargs): :param header_value: Format used to send the token value. "{token}" must be present as it will be replaced by the actual token. Token will be sent as "Bearer {token}" by default. - :param client: httpx.Client instance that will be used to request the token. - Use it to provide a custom proxying rule for instance. + :param headers: Additional headers to set when requesting or refreshing token. :param kwargs: all additional authorization parameters that should be put as query parameter in the authorization URL. Usual parameters are: @@ -276,8 +264,7 @@ def __init__( :param header_value: Format used to send the token value. "{token}" must be present as it will be replaced by the actual token. Token will be sent as "Bearer {token}" by default. - :param client: httpx.Client instance that will be used to request the token. - Use it to provide a custom proxying rule for instance. + :param headers: Additional headers to set when requesting or refreshing token. :param kwargs: all additional authorization parameters that should be put as query parameter in the authorization URL. """ diff --git a/httpx_auth/_oauth2/authorization_code_pkce.py b/httpx_auth/_oauth2/authorization_code_pkce.py index 314f736..c656581 100644 --- a/httpx_auth/_oauth2/authorization_code_pkce.py +++ b/httpx_auth/_oauth2/authorization_code_pkce.py @@ -1,6 +1,8 @@ import base64 import os +from collections.abc import Generator from hashlib import sha256, sha512 +from typing import Union import httpx @@ -50,7 +52,7 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. :param code_field_name: Field name containing the code. code by default. - :param client: httpx.Client instance that will be used to request the token. + :param headers: Additional headers to set when requesting or refreshing token. Use it to provide a custom proxying rule for instance. :param kwargs: all additional authorization parameters that should be put as query parameter in the authorization URL and as body parameters in the token URL. @@ -69,7 +71,7 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): BrowserAuth.__init__(self, kwargs) - self.client = kwargs.pop("client", None) + self.token_headers = kwargs.pop("headers", {}) header_name = kwargs.pop("header_name", None) or "Authorization" header_value = kwargs.pop("header_value", None) or "Bearer {token}" @@ -140,7 +142,11 @@ def __init__(self, authorization_url: str, token_url: str, **kwargs): self, state, early_expiry, header_name, header_value, self.refresh_token ) - def request_new_token(self) -> tuple: + def request_new_token( + self, + ) -> Generator[ + httpx.Request, httpx.Response, Union[tuple[str, str, int, str], tuple[str, str]] + ]: # Request code state, code = authentication_responses_server.request_new_grant( self.code_grant_details @@ -149,17 +155,10 @@ def request_new_token(self) -> tuple: # As described in https://tools.ietf.org/html/rfc6749#section-4.1.3 self.token_data["code"] = code - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-4.1.4 - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, self.token_data, self.token_field_name, client - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + # As described in https://tools.ietf.org/html/rfc6749#section-4.1.4 + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, self.token_data, self.token_field_name, self.token_headers + ) # Handle both Access and Bearer tokens return ( (self.state, token, expires_in, refresh_token) @@ -167,27 +166,19 @@ def request_new_token(self) -> tuple: else (self.state, token) ) - def refresh_token(self, refresh_token: str) -> tuple: - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-6 - self.refresh_data["refresh_token"] = refresh_token - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, - self.refresh_data, - self.token_field_name, - client, - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + def refresh_token( + self, refresh_token: str + ) -> Generator[httpx.Request, httpx.Response, tuple[str, str, int, str]]: + # As described in https://tools.ietf.org/html/rfc6749#section-6 + self.refresh_data["refresh_token"] = refresh_token + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, + self.refresh_data, + self.token_field_name, + self.token_headers, + ) return self.state, token, expires_in, refresh_token - def _configure_client(self, client: httpx.Client): - client.timeout = self.timeout - @staticmethod def generate_code_verifier() -> bytes: """ @@ -256,8 +247,7 @@ def __init__(self, instance: str, client_id: str, **kwargs): :param header_value: Format used to send the token value. "{token}" must be present as it will be replaced by the actual token. Token will be sent as "Bearer {token}" by default. - :param client: httpx.Client instance that will be used to request the token. - Use it to provide a custom proxying rule for instance. + :param headers: Additional headers to set when requesting or refreshing token. :param kwargs: all additional authorization parameters that should be put as query parameter in the authorization URL and as body parameters in the token URL. Usual parameters are: diff --git a/httpx_auth/_oauth2/client_credentials.py b/httpx_auth/_oauth2/client_credentials.py index 487b5fd..c95cc71 100644 --- a/httpx_auth/_oauth2/client_credentials.py +++ b/httpx_auth/_oauth2/client_credentials.py @@ -35,8 +35,7 @@ def __init__(self, token_url: str, client_id: str, client_secret: str, **kwargs) :param early_expiry: Number of seconds before actual token expiry where token will be considered as expired. Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. - :param client: httpx.Client instance that will be used to request the token. - Use it to provide a custom proxying rule for instance. + :param headers: Additional headers to set when requesting or refreshing token. :param kwargs: all additional authorization parameters that should be put as query parameter in the token URL. """ self.token_url = token_url @@ -58,7 +57,7 @@ def __init__(self, token_url: str, client_id: str, client_secret: str, **kwargs) # Time is expressed in seconds self.timeout = int(kwargs.pop("timeout", None) or 60) - self.client = kwargs.pop("client", None) + self.token_headers = kwargs.pop("headers", {}) # As described in https://tools.ietf.org/html/rfc6749#section-4.4.2 self.data = {"grant_type": "client_credentials"} @@ -78,24 +77,13 @@ def __init__(self, token_url: str, client_id: str, client_secret: str, **kwargs) ) def request_new_token(self) -> tuple: - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-4.4.3 - token, expires_in, _ = request_new_grant_with_post( - self.token_url, self.data, self.token_field_name, client - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + # As described in https://tools.ietf.org/html/rfc6749#section-4.4.3 + token, expires_in, _ = yield from request_new_grant_with_post( + self.token_url, self.data, self.token_field_name, self.token_headers + ) # Handle both Access and Bearer tokens return (self.state, token, expires_in) if expires_in else (self.state, token) - def _configure_client(self, client: httpx.Client): - client.auth = (self.client_id, self.client_secret) - client.timeout = self.timeout - class OktaClientCredentials(OAuth2ClientCredentials): """ @@ -131,7 +119,7 @@ def __init__( :param early_expiry: Number of seconds before actual token expiry where token will be considered as expired. Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. - :param client: httpx.Client instance that will be used to request the token. + :param headers: Additional headers to set when requesting or refreshing token. Use it to provide a custom proxying rule for instance. :param kwargs: all additional authorization parameters that should be put as query parameter in the token URL. """ diff --git a/httpx_auth/_oauth2/common.py b/httpx_auth/_oauth2/common.py index 7670c1f..de2437c 100644 --- a/httpx_auth/_oauth2/common.py +++ b/httpx_auth/_oauth2/common.py @@ -1,4 +1,5 @@ import abc +from collections.abc import Mapping from typing import Callable, Generator, Optional, Union from urllib.parse import parse_qs, urlsplit, urlunsplit, urlencode @@ -69,9 +70,9 @@ def _content_from_response(response: httpx.Response) -> dict: def request_new_grant_with_post( - url: str, data, grant_name: str, client: httpx.Client -) -> (str, int, str): - response = client.post(url, data=data) + url: str, data, grant_name: str, headers: Mapping[str, str] +) -> Generator[httpx.Request, httpx.Response, tuple[str, int, str]]: + response = yield httpx.Request("post", url, data=data, headers=headers) if response.is_error: # As described in https://tools.ietf.org/html/rfc6749#section-5.2 @@ -106,11 +107,12 @@ def __init__( self.header_name = header_name self.header_value = header_value self.refresh_token = refresh_token + self.requires_response_body = True def auth_flow( self, request: httpx.Request ) -> Generator[httpx.Request, httpx.Response, None]: - token = OAuth2.token_cache.get_token( + token = yield from OAuth2.token_cache.get_token( self.state, early_expiry=self.early_expiry, on_missing_token=self.request_new_token, @@ -120,7 +122,11 @@ def auth_flow( yield request @abc.abstractmethod - def request_new_token(self) -> Union[tuple[str, str], tuple[str, str, int]]: + def request_new_token( + self, + ) -> Generator[ + httpx.Request, httpx.Response, Union[tuple[str, str], tuple[str, str, int]] + ]: pass # pragma: no cover def _update_user_request(self, request: httpx.Request, token: str) -> None: diff --git a/httpx_auth/_oauth2/implicit.py b/httpx_auth/_oauth2/implicit.py index 3ebc986..cac07eb 100644 --- a/httpx_auth/_oauth2/implicit.py +++ b/httpx_auth/_oauth2/implicit.py @@ -1,4 +1,5 @@ import uuid +from collections.abc import Generator from hashlib import sha512 import httpx @@ -109,7 +110,12 @@ def __init__(self, authorization_url: str, **kwargs): header_value, ) - def request_new_token(self) -> tuple[str, str]: + def request_new_token( + self, + ) -> Generator[httpx.Request, httpx.Response, tuple[str, str]]: + # make this function an empty generator + yield from () + return authentication_responses_server.request_new_grant(self.grant_details) diff --git a/httpx_auth/_oauth2/resource_owner_password.py b/httpx_auth/_oauth2/resource_owner_password.py index 2be25bf..33d331c 100644 --- a/httpx_auth/_oauth2/resource_owner_password.py +++ b/httpx_auth/_oauth2/resource_owner_password.py @@ -1,4 +1,6 @@ +from collections.abc import Generator from hashlib import sha512 +from typing import Union import httpx from httpx_auth._authentication import SupportMultiAuth @@ -37,7 +39,7 @@ def __init__(self, token_url: str, username: str, password: str, **kwargs): :param early_expiry: Number of seconds before actual token expiry where token will be considered as expired. Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. - :param client: httpx.Client instance that will be used to request the token. + :param headers: Additional headers to set when requesting or refreshing token. Use it to provide a custom proxying rule for instance. :param kwargs: all additional authorization parameters that should be put as body parameters in the token URL. """ @@ -60,8 +62,12 @@ def __init__(self, token_url: str, username: str, password: str, **kwargs): # Time is expressed in seconds self.timeout = int(kwargs.pop("timeout", None) or 60) - self.client = kwargs.pop("client", None) - self.client_auth = kwargs.pop("client_auth", None) + self.token_headers = kwargs.pop("headers", {}) + client_auth = kwargs.pop("client_auth", None) + if client_auth: + self.token_headers["Authorization"] = httpx.BasicAuth._build_auth_header( + None, *client_auth + ) # As described in https://tools.ietf.org/html/rfc6749#section-4.3.2 self.data = { @@ -92,18 +98,18 @@ def __init__(self, token_url: str, username: str, password: str, **kwargs): self.refresh_token, ) - def request_new_token(self) -> tuple: - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-4.3.3 - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, self.data, self.token_field_name, client - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + def request_new_token( + self, + ) -> Generator[ + httpx.Request, httpx.Response, Union[tuple[str, str], tuple[str, str, int, str]] + ]: + # As described in https://tools.ietf.org/html/rfc6749#section-4.3.3 + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, + self.data, + self.token_field_name, + self.token_headers, + ) # Handle both Access and Bearer tokens return ( (self.state, token, expires_in, refresh_token) @@ -111,29 +117,19 @@ def request_new_token(self) -> tuple: else (self.state, token) ) - def refresh_token(self, refresh_token: str) -> tuple: - client = self.client or httpx.Client() - self._configure_client(client) - try: - # As described in https://tools.ietf.org/html/rfc6749#section-6 - self.refresh_data["refresh_token"] = refresh_token - token, expires_in, refresh_token = request_new_grant_with_post( - self.token_url, - self.refresh_data, - self.token_field_name, - client, - ) - finally: - # Close client only if it was created by this module - if self.client is None: - client.close() + def refresh_token( + self, refresh_token: str + ) -> Generator[httpx.Request, httpx.Response, tuple[str, str, int, str]]: + # As described in https://tools.ietf.org/html/rfc6749#section-6 + self.refresh_data["refresh_token"] = refresh_token + token, expires_in, refresh_token = yield from request_new_grant_with_post( + self.token_url, + self.refresh_data, + self.token_field_name, + self.token_headers, + ) return self.state, token, expires_in, refresh_token - def _configure_client(self, client: httpx.Client): - if self.client_auth: - client.auth = self.client_auth - client.timeout = self.timeout - class OktaResourceOwnerPasswordCredentials(OAuth2ResourceOwnerPasswordCredentials): """ @@ -170,8 +166,7 @@ def __init__( :param early_expiry: Number of seconds before actual token expiry where token will be considered as expired. Default to 30 seconds to ensure token will not expire between the time of retrieval and the time the request reaches the actual server. Set it to 0 to deactivate this feature and use the same token until actual expiry. - :param client: httpx.Client instance that will be used to request the token. - Use it to provide a custom proxying rule for instance. + :param headers: Additional headers to set when requesting or refreshing token. :param kwargs: all additional authorization parameters that should be put as body parameters in the token URL. """ if not instance: diff --git a/httpx_auth/_oauth2/tokens.py b/httpx_auth/_oauth2/tokens.py index 0e8085a..9248685 100644 --- a/httpx_auth/_oauth2/tokens.py +++ b/httpx_auth/_oauth2/tokens.py @@ -4,9 +4,12 @@ import datetime import threading import logging +from collections.abc import Callable, Generator from pathlib import Path from typing import Union, Optional +import httpx + from httpx_auth._errors import ( InvalidToken, TokenExpiryNotProvided, @@ -113,8 +116,13 @@ def get_token( *, early_expiry: float = 30.0, on_missing_token=None, - on_expired_token=None, - ) -> str: + on_expired_token: Optional[ + Callable[ + [str], + Generator[httpx.Request, httpx.Response, tuple[str, str, int, str]], + ] + ] = None, + ) -> Generator[httpx.Request, httpx.Response, str]: """ Return the bearer token. :param key: key identifier of the token @@ -123,7 +131,8 @@ def get_token( even if still valid when fetched. This is the number of seconds to subtract to the actual token expiry. Token will be considered as expired 30 seconds before real expiry by default. - :param on_missing_token: function to call when token is expired or missing (returning token and expiry tuple) + :param on_missing_token: generator function to call when token is expired or missing + (yielding requests, accepting responses and returning token and expiry tuple) :param on_expired_token: function to call to refresh the token when it is expired :return: the token :raise AuthenticationFailed: in case token cannot be retrieved. @@ -151,8 +160,8 @@ def get_token( if refresh_token is not None and on_expired_token is not None: try: with self._forbid_concurrent_missing_token_function_call: - state, token, expires_in, refresh_token = on_expired_token( - refresh_token + state, token, expires_in, refresh_token = ( + yield from on_expired_token(refresh_token) ) self._add_access_token(state, token, expires_in, refresh_token) logger.debug(f"Refreshed token with key {key}.") @@ -169,7 +178,7 @@ def get_token( logger.debug("Token cannot be found in cache.") if on_missing_token is not None: with self._forbid_concurrent_missing_token_function_call: - new_token = on_missing_token() + new_token = yield from on_missing_token() if len(new_token) == 2: # Bearer token state, token = new_token self._add_bearer_token(state, token) diff --git a/httpx_auth/testing.py b/httpx_auth/testing.py index c7fb000..8342a08 100644 --- a/httpx_auth/testing.py +++ b/httpx_auth/testing.py @@ -310,6 +310,7 @@ def token_mock() -> str: def token_cache_mock(monkeypatch, token_mock: str): class TokenCacheMock: def get_token(self, *args, **kwargs) -> str: + yield from () return token_mock monkeypatch.setattr(httpx_auth.OAuth2, "token_cache", TokenCacheMock()) diff --git a/tests/features/token_cache/test_json_token_file_cache.py b/tests/features/token_cache/test_json_token_file_cache.py index 2073abc..ceff6c3 100644 --- a/tests/features/token_cache/test_json_token_file_cache.py +++ b/tests/features/token_cache/test_json_token_file_cache.py @@ -16,6 +16,13 @@ def token_cache(tmp_path): _token_cache.clear() +def generator_return_val(generator): + try: + next(generator) + except StopIteration as si: + return si.value + + def test_add_bearer_tokens(token_cache): expiry_in_1_hour = datetime.datetime.now( datetime.timezone.utc @@ -30,12 +37,12 @@ def test_add_bearer_tokens(token_cache): token_cache._add_bearer_token("key2", token2) # Assert that tokens can be retrieved properly even after other token were inserted - assert token_cache.get_token("key1") == token1 - assert token_cache.get_token("key2") == token2 + assert generator_return_val(token_cache.get_token("key1")) == token1 + assert generator_return_val(token_cache.get_token("key2")) == token2 # Assert that tokens are not removed from the cache on retrieval - assert token_cache.get_token("key1") == token1 - assert token_cache.get_token("key2") == token2 + assert generator_return_val(token_cache.get_token("key1")) == token1 + assert generator_return_val(token_cache.get_token("key2")) == token2 def test_save_bearer_tokens(token_cache, tmp_path): @@ -52,8 +59,8 @@ def test_save_bearer_tokens(token_cache, tmp_path): token_cache._add_bearer_token("key2", token2) same_cache = httpx_auth.JsonTokenFileCache(tmp_path / "my_tokens.cache") - assert same_cache.get_token("key1") == token1 - assert same_cache.get_token("key2") == token2 + assert generator_return_val(same_cache.get_token("key1")) == token1 + assert generator_return_val(same_cache.get_token("key2")) == token2 def test_save_bearer_token_exception_handling( @@ -76,7 +83,7 @@ def failing_dump(*args): same_cache = httpx_auth.JsonTokenFileCache(tmp_path / "my_tokens.cache") with pytest.raises(httpx_auth.AuthenticationFailed) as exception_info: - same_cache.get_token("key1") + generator_return_val(same_cache.get_token("key1")) assert str(exception_info.value) == "User was not authenticated." assert caplog.messages == [ @@ -92,7 +99,7 @@ def failing_dump(*args): def test_missing_token_on_empty_cache(token_cache, caplog): caplog.set_level(logging.DEBUG) with pytest.raises(httpx_auth.AuthenticationFailed): - token_cache.get_token("key1") + generator_return_val(token_cache.get_token("key1")) assert caplog.messages == [ 'Retrieving token with "key1" key.', "No token loaded. Token cache does not exists.", @@ -110,7 +117,7 @@ def test_missing_token_on_non_empty_cache(token_cache, caplog): caplog.set_level(logging.DEBUG) with pytest.raises(httpx_auth.AuthenticationFailed): - token_cache.get_token("key1") + generator_return_val(token_cache.get_token("key1")) assert caplog.messages == [ 'Retrieving token with "key1" key.', "Token cannot be found in cache.", @@ -123,8 +130,13 @@ def test_missing_token_function(token_cache): datetime.timezone.utc ) + datetime.timedelta(hours=1) token = jwt.encode({"exp": expiry_in_1_hour}, "secret") - retrieved_token = token_cache.get_token( - "key1", on_missing_token=lambda: ("key1", token) + + def on_missing_token(): + yield from () + return "key1", token + + retrieved_token = generator_return_val( + token_cache.get_token("key1", on_missing_token=on_missing_token) ) assert retrieved_token == token @@ -142,7 +154,7 @@ def test_token_without_refresh_token(token_cache): token_cache._save_tokens() # try to retrieve it - retrieved_token = token_cache.get_token("key1") + retrieved_token = generator_return_val(token_cache.get_token("key1")) assert token == retrieved_token diff --git a/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_async.py b/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_async.py index 7b5c8a0..7244822 100644 --- a/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_async.py +++ b/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_async.py @@ -11,11 +11,11 @@ async def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaAuthorizationCode( "testserver.okta-emea.com", "54239d18-c68c-4c47-8bdd-ce71ea1d50cd", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://testserver.okta-emea.com/oauth2/default/v1/authorize?client_id=54239d18-c68c-4c47-8bdd-ce71ea1d50cd&scope=openid&response_type=code&state=5264d11c8b268ccf911ce564ca42fd75cea68c4a3c1ec3ac1ab20243891ab7cd5250ad4c2d002017c6e8ac2ba34954293baa5e0e4fd00bb9ffd4a39c45f1960b&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_sync.py b/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_sync.py index bf0379b..b5b63e3 100644 --- a/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_sync.py +++ b/tests/oauth2/authorization_code/okta/test_oauth2_authorization_code_okta_sync.py @@ -10,11 +10,11 @@ def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaAuthorizationCode( "testserver.okta-emea.com", "54239d18-c68c-4c47-8bdd-ce71ea1d50cd", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://testserver.okta-emea.com/oauth2/default/v1/authorize?client_id=54239d18-c68c-4c47-8bdd-ce71ea1d50cd&scope=openid&response_type=code&state=5264d11c8b268ccf911ce564ca42fd75cea68c4a3c1ec3ac1ab20243891ab7cd5250ad4c2d002017c6e8ac2ba34954293baa5e0e4fd00bb9ffd4a39c45f1960b&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code/test_oauth2_authorization_code_async.py b/tests/oauth2/authorization_code/test_oauth2_authorization_code_async.py index 0a37cd0..a4a503a 100644 --- a/tests/oauth2/authorization_code/test_oauth2_authorization_code_async.py +++ b/tests/oauth2/authorization_code/test_oauth2_authorization_code_async.py @@ -13,9 +13,9 @@ async def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", @@ -157,9 +157,9 @@ async def test_with_invalid_request_error_uses_custom_failure( async def test_oauth2_authorization_code_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", @@ -206,9 +206,9 @@ async def test_oauth2_authorization_code_flow_is_able_to_reuse_client( async def test_oauth2_authorization_code_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code/test_oauth2_authorization_code_sync.py b/tests/oauth2/authorization_code/test_oauth2_authorization_code_sync.py index 564a9f0..d604ff3 100644 --- a/tests/oauth2/authorization_code/test_oauth2_authorization_code_sync.py +++ b/tests/oauth2/authorization_code/test_oauth2_authorization_code_sync.py @@ -12,9 +12,9 @@ def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", @@ -152,9 +152,9 @@ def test_with_invalid_request_error_uses_custom_failure( def test_oauth2_authorization_code_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", @@ -200,9 +200,9 @@ def test_oauth2_authorization_code_flow_is_able_to_reuse_client( def test_oauth2_authorization_code_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCode( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_async.py b/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_async.py index 622ea21..66e9b19 100644 --- a/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_async.py +++ b/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_async.py @@ -11,12 +11,12 @@ async def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.WakaTimeAuthorizationCode( "jPJQV0op6Pu3b66MWDi8b1wD", "waka_sec_0c4MBGeR9LN74LzV5uelF9SgeQ32CqfeWpIuieneBbsL57dAAlqqJWDiVDJOlsSx61pVwHMKlsb3uMvU", scope="email", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://wakatime.com/oauth/authorize?client_id=jPJQV0op6Pu3b66MWDi8b1wD&client_secret=waka_sec_0c4MBGeR9LN74LzV5uelF9SgeQ32CqfeWpIuieneBbsL57dAAlqqJWDiVDJOlsSx61pVwHMKlsb3uMvU&scope=email&response_type=code&state=5d0adb208bdbecaf5cfb6de0bf4ba0aea52986f3fc5ea7bc30c4b2db449c17e5c9d15f9a3926476cdaf1c72e9f73c7cfdc624dde0187c38d8c6b04532770df2a&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_sync.py b/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_sync.py index 2107df2..111419f 100644 --- a/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_sync.py +++ b/tests/oauth2/authorization_code/wakatime/test_oauth2_authorization_code_wakatime_sync.py @@ -10,12 +10,12 @@ def test_oauth2_authorization_code_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.WakaTimeAuthorizationCode( "jPJQV0op6Pu3b66MWDi8b1wD", "waka_sec_0c4MBGeR9LN74LzV5uelF9SgeQ32CqfeWpIuieneBbsL57dAAlqqJWDiVDJOlsSx61pVwHMKlsb3uMvU", scope="email", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://wakatime.com/oauth/authorize?client_id=jPJQV0op6Pu3b66MWDi8b1wD&client_secret=waka_sec_0c4MBGeR9LN74LzV5uelF9SgeQ32CqfeWpIuieneBbsL57dAAlqqJWDiVDJOlsSx61pVwHMKlsb3uMvU&scope=email&response_type=code&state=5d0adb208bdbecaf5cfb6de0bf4ba0aea52986f3fc5ea7bc30c4b2db449c17e5c9d15f9a3926476cdaf1c72e9f73c7cfdc624dde0187c38d8c6b04532770df2a&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F", diff --git a/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_async.py b/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_async.py index dc088f1..5ecbe15 100644 --- a/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_async.py +++ b/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_async.py @@ -11,11 +11,11 @@ async def test_oauth2_pkce_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaAuthorizationCodePKCE( "testserver.okta-emea.com", "54239d18-c68c-4c47-8bdd-ce71ea1d50cd", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://testserver.okta-emea.com/oauth2/default/v1/authorize?client_id=54239d18-c68c-4c47-8bdd-ce71ea1d50cd&scope=openid&response_type=code&state=5264d11c8b268ccf911ce564ca42fd75cea68c4a3c1ec3ac1ab20243891ab7cd5250ad4c2d002017c6e8ac2ba34954293baa5e0e4fd00bb9ffd4a39c45f1960b&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", diff --git a/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_sync.py b/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_sync.py index bc83077..4424439 100644 --- a/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_sync.py +++ b/tests/oauth2/authorization_code_pkce/okta/test_oauth2_authorization_code_pkce_okta_sync.py @@ -10,11 +10,11 @@ def test_oauth2_pkce_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaAuthorizationCodePKCE( "testserver.okta-emea.com", "54239d18-c68c-4c47-8bdd-ce71ea1d50cd", - client=client, + headers=headers, ) tab = browser_mock.add_response( opened_url="https://testserver.okta-emea.com/oauth2/default/v1/authorize?client_id=54239d18-c68c-4c47-8bdd-ce71ea1d50cd&scope=openid&response_type=code&state=5264d11c8b268ccf911ce564ca42fd75cea68c4a3c1ec3ac1ab20243891ab7cd5250ad4c2d002017c6e8ac2ba34954293baa5e0e4fd00bb9ffd4a39c45f1960b&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", diff --git a/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_async.py b/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_async.py index 37bf72c..26f6d48 100644 --- a/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_async.py +++ b/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_async.py @@ -13,9 +13,9 @@ async def test_oauth2_pkce_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", @@ -155,9 +155,9 @@ async def test_oauth2_pkce_flow_uses_custom_failure( async def test_oauth2_pkce_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", @@ -203,9 +203,9 @@ async def test_oauth2_pkce_flow_is_able_to_reuse_client( async def test_oauth2_pkce_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", diff --git a/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_sync.py b/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_sync.py index ceec24b..f2e2be4 100644 --- a/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_sync.py +++ b/tests/oauth2/authorization_code_pkce/test_oauth2_authorization_code_pkce_sync.py @@ -12,9 +12,9 @@ def test_oauth2_pkce_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", @@ -150,9 +150,9 @@ def test_oauth2_pkce_flow_uses_custom_failure( def test_oauth2_pkce_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", @@ -197,9 +197,9 @@ def test_oauth2_pkce_flow_is_able_to_reuse_client( def test_oauth2_pkce_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2AuthorizationCodePKCE( - "https://provide_code", "https://provide_access_token", client=client + "https://provide_code", "https://provide_access_token", headers=headers ) tab = browser_mock.add_response( opened_url="https://provide_code?response_type=code&state=ce9c755b41b5e3c5b64c70598715d5de271023a53f39a67a70215d265d11d2bfb6ef6e9c701701e998e69cbdbf2cee29fd51d2a950aa05f59a20cf4a646099d5&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", diff --git a/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_async.py b/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_async.py index d3db2c6..6548083 100644 --- a/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_async.py +++ b/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_async.py @@ -12,13 +12,13 @@ async def test_okta_client_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaClientCredentials( "test_okta", client_id="test_user", client_secret="test_pwd", scope="dummy", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_sync.py b/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_sync.py index fee800c..0764941 100644 --- a/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_sync.py +++ b/tests/oauth2/client_credential/okta/test_oauth2_client_credential_okta_sync.py @@ -9,13 +9,13 @@ def test_okta_client_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaClientCredentials( "test_okta", client_id="test_user", client_secret="test_pwd", scope="dummy", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/client_credential/test_oauth2_client_credential_async.py b/tests/oauth2/client_credential/test_oauth2_client_credential_async.py index 2969193..be8937d 100644 --- a/tests/oauth2/client_credential/test_oauth2_client_credential_async.py +++ b/tests/oauth2/client_credential/test_oauth2_client_credential_async.py @@ -14,12 +14,12 @@ async def test_oauth2_client_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ClientCredentials( "https://provide_access_token", client_id="test_user", client_secret="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -51,12 +51,12 @@ async def test_oauth2_client_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ClientCredentials( "https://provide_access_token", client_id="test_user", client_secret="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/client_credential/test_oauth2_client_credential_sync.py b/tests/oauth2/client_credential/test_oauth2_client_credential_sync.py index 9aa28de..ebbfb94 100644 --- a/tests/oauth2/client_credential/test_oauth2_client_credential_sync.py +++ b/tests/oauth2/client_credential/test_oauth2_client_credential_sync.py @@ -12,12 +12,12 @@ def test_oauth2_client_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ClientCredentials( "https://provide_access_token", client_id="test_user", client_secret="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -47,12 +47,12 @@ def test_oauth2_client_credentials_flow_uses_provided_client( def test_oauth2_client_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ClientCredentials( "https://provide_access_token", client_id="test_user", client_secret="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_async.py b/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_async.py index 1351fd6..d55d2c9 100644 --- a/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_async.py +++ b/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_async.py @@ -14,14 +14,14 @@ async def test_oauth2_password_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -56,14 +56,14 @@ async def test_oauth2_password_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -102,14 +102,14 @@ async def test_oauth2_password_credentials_flow_is_able_to_reuse_client_with_tok token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_sync.py b/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_sync.py index dc5ddf4..9b17749 100644 --- a/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_sync.py +++ b/tests/oauth2/resource_owner_password/okta/test_oauth2_resource_owner_password_okta_sync.py @@ -12,14 +12,14 @@ def test_oauth2_password_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -52,14 +52,14 @@ def test_oauth2_password_credentials_flow_uses_provided_client( def test_oauth2_password_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -96,14 +96,14 @@ def test_oauth2_password_credentials_flow_is_able_to_reuse_client( def test_oauth2_password_credentials_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OktaResourceOwnerPasswordCredentials( "testserver.okta-emea.com", username="test_user", password="test_pwd", client_id="test_user2", client_secret="test_pwd2", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_async.py b/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_async.py index 6c1fedc..4969496 100644 --- a/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_async.py +++ b/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_async.py @@ -14,12 +14,12 @@ async def test_oauth2_password_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -51,12 +51,12 @@ async def test_oauth2_password_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -92,12 +92,12 @@ async def test_oauth2_password_credentials_flow_is_able_to_reuse_client_with_tok token_cache, httpx_mock: HTTPXMock ): # TODO Add support for AsyncClient - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", diff --git a/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_sync.py b/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_sync.py index aafc22e..94cfd26 100644 --- a/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_sync.py +++ b/tests/oauth2/resource_owner_password/test_oauth2_resource_owner_password_sync.py @@ -12,12 +12,12 @@ def test_oauth2_password_credentials_flow_uses_provided_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -47,12 +47,12 @@ def test_oauth2_password_credentials_flow_uses_provided_client( def test_oauth2_password_credentials_flow_is_able_to_reuse_client( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", @@ -86,12 +86,12 @@ def test_oauth2_password_credentials_flow_is_able_to_reuse_client( def test_oauth2_password_credentials_flow_is_able_to_reuse_client_with_token_refresh( token_cache, httpx_mock: HTTPXMock ): - client = httpx.Client(headers={"x-test": "Test value"}) + headers = {"x-test": "Test value"} auth = httpx_auth.OAuth2ResourceOwnerPasswordCredentials( "https://provide_access_token", username="test_user", password="test_pwd", - client=client, + headers=headers, ) httpx_mock.add_response( method="POST", From b49b4dc28e31b9d83e3e61d1ec5002cfb2b14763 Mon Sep 17 00:00:00 2001 From: Raphael Krupinski Date: Thu, 7 Mar 2024 15:12:03 +0100 Subject: [PATCH 2/2] Fix anext use for python 3.9 --- httpx_auth/_authentication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/httpx_auth/_authentication.py b/httpx_auth/_authentication.py index 766fb2f..9fc24da 100644 --- a/httpx_auth/_authentication.py +++ b/httpx_auth/_authentication.py @@ -31,7 +31,8 @@ async def async_auth_flow( for authentication_mode in self.authentication_modes: # auth_flow may yield one or more requests, the last of which is the user request with added auth headers flow = authentication_mode.async_auth_flow(request) - req = await anext(flow) + + req = await flow.__anext__() while True: if req is request: break