From 9401fb5cd4645998dbb01f1703a02d70e2bf5a5b Mon Sep 17 00:00:00 2001 From: Aaron Schaer Date: Wed, 1 May 2024 16:16:21 -0500 Subject: [PATCH 1/3] add AuthorizerFactory, AccessTokenAuthorizerFactory, RefreshTokenAuthorizerFactory, and ClientCredentialsAuthorizerFactory --- ...430_164920_aaschaer_authorizer_factory.rst | 7 + .../experimental/globus_app/__init__.py | 10 + .../globus_app/authorizer_factory.py | 198 +++++++++++++++ .../experimental/globus_app/errors.py | 4 + .../globus_app/test_authorizer_factory.py | 233 ++++++++++++++++++ 5 files changed, 452 insertions(+) create mode 100644 changelog.d/20240430_164920_aaschaer_authorizer_factory.rst create mode 100644 src/globus_sdk/experimental/globus_app/authorizer_factory.py create mode 100644 tests/unit/experimental/globus_app/test_authorizer_factory.py diff --git a/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst b/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst new file mode 100644 index 000000000..91c63bbb3 --- /dev/null +++ b/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst @@ -0,0 +1,7 @@ +Added +~~~~~ + +- Added ``AuthorizerFactory``, an interface for getting a ``GlobusAuthorizer`` + from a ``ValidatingTokenStorage`` to experimental along with + ``AccessTokenAuthorizerFactory``, ``RefreshTokenAuthorizerFactory``, and + ``ClientCredentialsAuthorizerFactory`` that implement it (:pr:`972`) diff --git a/src/globus_sdk/experimental/globus_app/__init__.py b/src/globus_sdk/experimental/globus_app/__init__.py index e8444415d..c7eb79a62 100644 --- a/src/globus_sdk/experimental/globus_app/__init__.py +++ b/src/globus_sdk/experimental/globus_app/__init__.py @@ -1,5 +1,15 @@ from ._validating_token_storage import ValidatingTokenStorage +from .authorizer_factory import ( + AccessTokenAuthorizerFactory, + AuthorizerFactory, + ClientCredentialsAuthorizerFactory, + RefreshTokenAuthorizerFactory, +) __all__ = [ "ValidatingTokenStorage", + "AuthorizerFactory", + "AccessTokenAuthorizerFactory", + "RefreshTokenAuthorizerFactory", + "ClientCredentialsAuthorizerFactory", ] diff --git a/src/globus_sdk/experimental/globus_app/authorizer_factory.py b/src/globus_sdk/experimental/globus_app/authorizer_factory.py new file mode 100644 index 000000000..093366bdb --- /dev/null +++ b/src/globus_sdk/experimental/globus_app/authorizer_factory.py @@ -0,0 +1,198 @@ +from __future__ import annotations + +import abc + +from globus_sdk import AuthLoginClient, ConfidentialAppAuthClient +from globus_sdk.authorizers import ( + AccessTokenAuthorizer, + ClientCredentialsAuthorizer, + GlobusAuthorizer, + RefreshTokenAuthorizer, +) +from globus_sdk.experimental.tokenstorage import TokenData +from globus_sdk.services.auth import OAuthTokenResponse + +from ._validating_token_storage import ValidatingTokenStorage +from .errors import MissingTokensError + + +class AuthorizerFactory(metaclass=abc.ABCMeta): + """ + An ``AuthorizerFactory`` is an interface for getting some class of + ``GlobusAuthorizer`` from a ``ValidatingTokenStorage`` that meets the + authorization requirements used to initialize the ``ValidatingTokenStorage``. + + An ``AuthorizerFactory`` keeps a cache of authorizer objects that are + re-used until its ``store_token_response`` method is called. + """ + + def __init__(self, token_storage: ValidatingTokenStorage): + """ + :param token_storage: The ``ValidatingTokenStorage`` used + for defining and validating the set of authorization requirements that + constructed authorizers will meet and accessing underlying token storage + """ + self.token_storage = token_storage + self._authorizer_cache: dict[str, GlobusAuthorizer] = {} + + def _get_token_data_or_error(self, resource_server: str) -> TokenData: + token_data = self.token_storage.get_token_data(resource_server) + if token_data is None: + raise MissingTokensError(f"No token data for {resource_server}") + + return token_data + + def store_token_response(self, token_res: OAuthTokenResponse) -> None: + """ + Store a token response in the underlying ``ValidatingTokenStorage`` + and clear the authorizer cache. + + :param token_res: An ``OAuthTokenResponse`` containing token data to be stored + in the underlying ``ValidatingTokenStorage``. + """ + self.token_storage.store_token_response(token_res) + self._authorizer_cache = {} + + def get_authorizer(self, resource_server: str) -> GlobusAuthorizer: + """ + Either retrieve a cached authorizer for the given resource server or construct + a new one if none is cached. + + Raises ``MissingTokensError`` if the underlying ``TokenStorage`` does not + have the needed tokens to create the authorizer. + + :param resource_server: The resource server the authorizer will produce + authentication for + """ + if resource_server in self._authorizer_cache: + return self._authorizer_cache[resource_server] + + new_authorizer = self._make_authorizer(resource_server) + self._authorizer_cache[resource_server] = new_authorizer + return new_authorizer + + @abc.abstractmethod + def _make_authorizer(self, resource_server: str) -> GlobusAuthorizer: + """ + Construct the ``GlobusAuthorizer`` class specific to this ``AuthorizerFactory`` + + :param resource_server: The resource server the authorizer will produce + authentication for + """ + + +class AccessTokenAuthorizerFactory(AuthorizerFactory): + """ + An ``AuthorizerFactory`` that constructs ``AccessTokenAuthorizer``. + """ + + def _make_authorizer(self, resource_server: str) -> AccessTokenAuthorizer: + """ + Construct an ``AccessTokenAuthorizer`` for the given resource server. + + Raises ``MissingTokensError`` if the underlying ``TokenStorage`` does not + have token data for the given resource server. + + :param resource_server: The resource server the authorizer will produce + authentication for + """ + token_data = self._get_token_data_or_error(resource_server) + return AccessTokenAuthorizer(token_data.access_token) + + +class RefreshTokenAuthorizerFactory(AuthorizerFactory): + """ + An ``AuthorizerFactory`` that constructs ``RefreshTokenAuthorizer``. + """ + + def __init__( + self, + token_storage: ValidatingTokenStorage, + auth_login_client: AuthLoginClient, + ): + """ + :param token_storage: The ``ValidatingTokenStorage`` used + for defining and validating the set of authorization requirements that + constructed authorizers will meet and accessing underlying token storage + :auth_login_client: The ``AuthLoginCLient` used for refreshing tokens with + Globus Auth + """ + self.auth_login_client = auth_login_client + super().__init__(token_storage) + + def _make_authorizer(self, resource_server: str) -> RefreshTokenAuthorizer: + """ + Construct a ``RefreshTokenAuthorizer`` for the given resource server. + + Raises ``MissingTokensError`` if the underlying ``TokenStorage`` does not + have a refresh token for the given resource server. + + :param resource_server: The resource server the authorizer will produce + authentication for + """ + token_data = self._get_token_data_or_error(resource_server) + if token_data.refresh_token is None: + raise MissingTokensError(f"No refresh_token for {resource_server}") + + return RefreshTokenAuthorizer( + refresh_token=token_data.refresh_token, + auth_client=self.auth_login_client, + access_token=token_data.access_token, + expires_at=token_data.expires_at_seconds, + on_refresh=self.token_storage.store_token_response, + ) + + +class ClientCredentialsAuthorizerFactory(AuthorizerFactory): + """ + An ``AuthorizerFactory`` that constructs ``ClientCredentialsAuthorizer``. + """ + + def __init__( + self, + token_storage: ValidatingTokenStorage, + confidential_client: ConfidentialAppAuthClient, + ): + """ + :param token_storage: The ``ValidatingTokenStorage`` used + for defining and validating the set of authorization requirements that + constructed authorizers will meet and accessing underlying token storage + :param confidential_client: The ``ConfidentialAppAuthClient`` that will + get client credentials tokens from Globus Auth to act as itself + """ + self.confidential_client = confidential_client + super().__init__(token_storage) + + def _make_authorizer( + self, + resource_server: str, + ) -> ClientCredentialsAuthorizer: + """ + Construct a ``ClientCredentialsAuthorizer`` for the given resource server. + + Does not require that tokens exist in the token storage but will use them if + present. + + :param resource_server: The resource server the authorizer will produce + authentication for. The ``ValidatingTokenStorage`` used to create the + ``ClientCredentialsAuthorizerFactory`` must have scope requirements defined + for this resource server. + """ + token_data = self.token_storage.get_token_data(resource_server) + access_token = token_data.access_token if token_data else None + expires_at = token_data.expires_at_seconds if token_data else None + + scopes = self.token_storage.scope_requirements.get(resource_server) + if scopes is None: + raise ValueError( + "ValidatingTokenStorage has no scope_requirements for " + f"resource_server {resource_server}" + ) + + return ClientCredentialsAuthorizer( + confidential_client=self.confidential_client, + scopes=scopes, + access_token=access_token, + expires_at=expires_at, + on_refresh=self.token_storage.store_token_response, + ) diff --git a/src/globus_sdk/experimental/globus_app/errors.py b/src/globus_sdk/experimental/globus_app/errors.py index f450404f8..3b120344a 100644 --- a/src/globus_sdk/experimental/globus_app/errors.py +++ b/src/globus_sdk/experimental/globus_app/errors.py @@ -14,6 +14,10 @@ class TokenValidationError(Exception): pass +class MissingTokensError(Exception): + pass + + class IdentityMismatchError(TokenValidationError): def __init__(self, message: str, stored_id: UUIDLike, new_id: UUIDLike): super().__init__(message) diff --git a/tests/unit/experimental/globus_app/test_authorizer_factory.py b/tests/unit/experimental/globus_app/test_authorizer_factory.py new file mode 100644 index 000000000..a47a67234 --- /dev/null +++ b/tests/unit/experimental/globus_app/test_authorizer_factory.py @@ -0,0 +1,233 @@ +import time +from unittest import mock + +import pytest + +from globus_sdk.experimental.globus_app import ( + AccessTokenAuthorizerFactory, + ClientCredentialsAuthorizerFactory, + RefreshTokenAuthorizerFactory, +) +from globus_sdk.experimental.globus_app.errors import MissingTokensError +from globus_sdk.experimental.tokenstorage import TokenData + + +def make_mock_token_response(token_number=1): + ret = mock.Mock() + ret.by_resource_server = { + "rs1": { + "resource_server": "rs1", + "scope": "rs1:all", + "access_token": f"rs1_access_token_{token_number}", + "refresh_token": f"rs1_refresh_token_{token_number}", + "expires_at_seconds": int(time.time()) + 3600, + "token_type": "Bearer", + } + } + return ret + + +class MockValidatingTokenStorage: + def __init__(self): + self.token_data = {} + self.scope_requirements = {"rs1": "rs1:all"} + + def get_token_data(self, resource_server): + dict_data = self.token_data.get(resource_server) + if dict_data: + return TokenData.from_dict(dict_data) + else: + return None + + def store_token_response(self, mock_token_response): + self.token_data = mock_token_response.by_resource_server + + +def test_access_token_authorizer_factory(): + initial_response = make_mock_token_response() + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + factory = AccessTokenAuthorizerFactory(token_storage=mock_token_storage) + + # cache is initially empty + assert factory._authorizer_cache == {} + + # calling get_authorizer once creates a new authorizer from underlying storage + authorizer = factory.get_authorizer("rs1") + assert authorizer.get_authorization_header() == "Bearer rs1_access_token_1" + + # calling get_authorizer again gets the same cached authorizer + authorizer2 = factory.get_authorizer("rs1") + assert authorizer is authorizer2 + + # manually calling store clears cache + new_data = make_mock_token_response(token_number=2) + factory.store_token_response(new_data) + assert factory._authorizer_cache == {} + authorizer = factory.get_authorizer("rs1") + assert authorizer.get_authorization_header() == "Bearer rs1_access_token_2" + + +def test_access_token_authorizer_factory_no_tokens(): + initial_response = make_mock_token_response() + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + factory = AccessTokenAuthorizerFactory(token_storage=mock_token_storage) + + with pytest.raises(MissingTokensError) as exc: + factory.get_authorizer("rs2") + assert str(exc.value) == "No token data for rs2" + + +def test_refresh_token_authorizer_factory(): + initial_response = make_mock_token_response() + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + + refresh_data = make_mock_token_response(token_number=2) + mock_auth_login_client = mock.Mock() + mock_refresh = mock.Mock() + mock_refresh.return_value = refresh_data + mock_auth_login_client.oauth2_refresh_token = mock_refresh + + factory = RefreshTokenAuthorizerFactory( + token_storage=mock_token_storage, + auth_login_client=mock_auth_login_client, + ) + + # calling get authorizer creates a new authorizer with existing token data + authorizer1 = factory.get_authorizer("rs1") + assert authorizer1.get_authorization_header() == "Bearer rs1_access_token_1" + assert mock_auth_login_client.oauth2_refresh_token.call_count == 0 + + # standard refresh doesn't change the authorizer + authorizer1._get_new_access_token() + authorizer2 = factory.get_authorizer("rs1") + assert authorizer2 is authorizer1 + assert authorizer2.get_authorization_header() == "Bearer rs1_access_token_2" + assert mock_auth_login_client.oauth2_refresh_token.call_count == 1 + + # manually calling store clears cache + factory.store_token_response(initial_response) + authorizer3 = factory.get_authorizer("rs1") + assert authorizer3 is not authorizer1 + assert authorizer3.get_authorization_header() == "Bearer rs1_access_token_1" + + +def test_refresh_token_authorizer_factory_expired_access_token(): + initial_response = make_mock_token_response() + initial_response.by_resource_server["rs1"]["expires_at_seconds"] = int( + time.time() - 3600 + ) + + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + + refresh_data = make_mock_token_response(token_number=2) + mock_auth_login_client = mock.Mock() + mock_refresh = mock.Mock() + mock_refresh.return_value = refresh_data + mock_auth_login_client.oauth2_refresh_token = mock_refresh + + factory = RefreshTokenAuthorizerFactory( + token_storage=mock_token_storage, + auth_login_client=mock_auth_login_client, + ) + + # calling get_authorizer automatically causes a refresh call to get an access token + authorizer = factory.get_authorizer("rs1") + assert authorizer.get_authorization_header() == "Bearer rs1_access_token_2" + assert mock_refresh.call_count == 1 + + +def test_refresh_token_authorizer_factory_no_refresh_token(): + initial_response = make_mock_token_response() + initial_response.by_resource_server["rs1"]["refresh_token"] = None + + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + + factory = RefreshTokenAuthorizerFactory( + token_storage=mock_token_storage, + auth_login_client=mock.Mock(), + ) + + with pytest.raises(MissingTokensError) as exc: + factory.get_authorizer("rs1") + assert str(exc.value) == "No refresh_token for rs1" + + +def test_client_credentials_authorizer_factory(): + initial_response = make_mock_token_response() + mock_token_storage = MockValidatingTokenStorage() + mock_token_storage.store_token_response(initial_response) + + client_token_data = make_mock_token_response(token_number=2) + mock_confidential_client = mock.Mock() + mock_client_credentials_tokens = mock.Mock() + mock_client_credentials_tokens.return_value = client_token_data + mock_confidential_client.oauth2_client_credentials_tokens = ( + mock_client_credentials_tokens + ) + + factory = ClientCredentialsAuthorizerFactory( + token_storage=mock_token_storage, + confidential_client=mock_confidential_client, + ) + + # calling get_authorizer once creates a new authorizer using existing tokens + authorizer1 = factory.get_authorizer("rs1") + assert authorizer1.get_authorization_header() == "Bearer rs1_access_token_1" + assert mock_confidential_client.oauth2_client_credentials_tokens.call_count == 0 + + # renewing with existing tokens doesn't change the authorizer + authorizer1._get_new_access_token() + authorizer2 = factory.get_authorizer("rs1") + assert authorizer2 is authorizer1 + assert authorizer2.get_authorization_header() == "Bearer rs1_access_token_2" + assert mock_confidential_client.oauth2_client_credentials_tokens.call_count == 1 + + # manually calling store clears cache + factory.store_token_response(initial_response) + authorizer3 = factory.get_authorizer("rs1") + assert authorizer3 is not authorizer1 + assert authorizer3.get_authorization_header() == "Bearer rs1_access_token_1" + + +def test_client_credentials_authorizer_factory_no_tokens(): + mock_token_storage = MockValidatingTokenStorage() + + client_token_data = make_mock_token_response() + mock_confidential_client = mock.Mock() + mock_client_credentials_tokens = mock.Mock() + mock_client_credentials_tokens.return_value = client_token_data + mock_confidential_client.oauth2_client_credentials_tokens = ( + mock_client_credentials_tokens + ) + + factory = ClientCredentialsAuthorizerFactory( + token_storage=mock_token_storage, + confidential_client=mock_confidential_client, + ) + + # calling get_authorizer once creates a new authorizer automatically making + # a client credentials call to get an access token that is then stored + authorizer = factory.get_authorizer("rs1") + assert authorizer.get_authorization_header() == "Bearer rs1_access_token_1" + assert mock_client_credentials_tokens.call_count == 1 + + +def test_client_credentials_authorizer_factory_no_scopes(): + mock_token_storage = MockValidatingTokenStorage() + mock_confidential_client = mock.Mock() + factory = ClientCredentialsAuthorizerFactory( + token_storage=mock_token_storage, + confidential_client=mock_confidential_client, + ) + + with pytest.raises(ValueError) as exc: + factory.get_authorizer("rs2") + assert ( + str(exc.value) + == "ValidatingTokenStorage has no scope_requirements for resource_server rs2" + ) From 284ec9378086bc3e2548279b58a9be11ad17df0f Mon Sep 17 00:00:00 2001 From: aaschaer Date: Mon, 20 May 2024 15:43:36 -0500 Subject: [PATCH 2/3] Fix changelog PR number Co-authored-by: Stephen Rosen --- changelog.d/20240430_164920_aaschaer_authorizer_factory.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst b/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst index 91c63bbb3..7cb9a4ac7 100644 --- a/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst +++ b/changelog.d/20240430_164920_aaschaer_authorizer_factory.rst @@ -4,4 +4,4 @@ Added - Added ``AuthorizerFactory``, an interface for getting a ``GlobusAuthorizer`` from a ``ValidatingTokenStorage`` to experimental along with ``AccessTokenAuthorizerFactory``, ``RefreshTokenAuthorizerFactory``, and - ``ClientCredentialsAuthorizerFactory`` that implement it (:pr:`972`) + ``ClientCredentialsAuthorizerFactory`` that implement it (:pr:`985`) From 80d0b3f9558565d4f96b3bd1f571dc63bc382376 Mon Sep 17 00:00:00 2001 From: Aaron Schaer Date: Mon, 20 May 2024 16:15:41 -0500 Subject: [PATCH 3/3] refactors from review --- .../globus_app/authorizer_factory.py | 29 ++++++++++++++----- .../globus_app/test_authorizer_factory.py | 12 ++++---- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/globus_sdk/experimental/globus_app/authorizer_factory.py b/src/globus_sdk/experimental/globus_app/authorizer_factory.py index 093366bdb..535739bf4 100644 --- a/src/globus_sdk/experimental/globus_app/authorizer_factory.py +++ b/src/globus_sdk/experimental/globus_app/authorizer_factory.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import typing as t from globus_sdk import AuthLoginClient, ConfidentialAppAuthClient from globus_sdk.authorizers import ( @@ -15,8 +16,13 @@ from ._validating_token_storage import ValidatingTokenStorage from .errors import MissingTokensError +GA = t.TypeVar("GA", bound=GlobusAuthorizer) -class AuthorizerFactory(metaclass=abc.ABCMeta): + +class AuthorizerFactory( + t.Generic[GA], + metaclass=abc.ABCMeta, +): """ An ``AuthorizerFactory`` is an interface for getting some class of ``GlobusAuthorizer`` from a ``ValidatingTokenStorage`` that meets the @@ -33,7 +39,7 @@ def __init__(self, token_storage: ValidatingTokenStorage): constructed authorizers will meet and accessing underlying token storage """ self.token_storage = token_storage - self._authorizer_cache: dict[str, GlobusAuthorizer] = {} + self._authorizer_cache: dict[str, GA] = {} def _get_token_data_or_error(self, resource_server: str) -> TokenData: token_data = self.token_storage.get_token_data(resource_server) @@ -42,18 +48,23 @@ def _get_token_data_or_error(self, resource_server: str) -> TokenData: return token_data - def store_token_response(self, token_res: OAuthTokenResponse) -> None: + def store_token_response_and_clear_cache( + self, token_res: OAuthTokenResponse + ) -> None: """ Store a token response in the underlying ``ValidatingTokenStorage`` and clear the authorizer cache. + This should not be called when a ``RenewingAuthorizer`` created by this factory + gets new tokens for itself as there is no need to clear the cache. + :param token_res: An ``OAuthTokenResponse`` containing token data to be stored in the underlying ``ValidatingTokenStorage``. """ self.token_storage.store_token_response(token_res) self._authorizer_cache = {} - def get_authorizer(self, resource_server: str) -> GlobusAuthorizer: + def get_authorizer(self, resource_server: str) -> GA: """ Either retrieve a cached authorizer for the given resource server or construct a new one if none is cached. @@ -72,7 +83,7 @@ def get_authorizer(self, resource_server: str) -> GlobusAuthorizer: return new_authorizer @abc.abstractmethod - def _make_authorizer(self, resource_server: str) -> GlobusAuthorizer: + def _make_authorizer(self, resource_server: str) -> GA: """ Construct the ``GlobusAuthorizer`` class specific to this ``AuthorizerFactory`` @@ -81,7 +92,7 @@ def _make_authorizer(self, resource_server: str) -> GlobusAuthorizer: """ -class AccessTokenAuthorizerFactory(AuthorizerFactory): +class AccessTokenAuthorizerFactory(AuthorizerFactory[AccessTokenAuthorizer]): """ An ``AuthorizerFactory`` that constructs ``AccessTokenAuthorizer``. """ @@ -100,7 +111,7 @@ def _make_authorizer(self, resource_server: str) -> AccessTokenAuthorizer: return AccessTokenAuthorizer(token_data.access_token) -class RefreshTokenAuthorizerFactory(AuthorizerFactory): +class RefreshTokenAuthorizerFactory(AuthorizerFactory[RefreshTokenAuthorizer]): """ An ``AuthorizerFactory`` that constructs ``RefreshTokenAuthorizer``. """ @@ -143,7 +154,9 @@ def _make_authorizer(self, resource_server: str) -> RefreshTokenAuthorizer: ) -class ClientCredentialsAuthorizerFactory(AuthorizerFactory): +class ClientCredentialsAuthorizerFactory( + AuthorizerFactory[ClientCredentialsAuthorizer] +): """ An ``AuthorizerFactory`` that constructs ``ClientCredentialsAuthorizer``. """ diff --git a/tests/unit/experimental/globus_app/test_authorizer_factory.py b/tests/unit/experimental/globus_app/test_authorizer_factory.py index a47a67234..b50ebf0cc 100644 --- a/tests/unit/experimental/globus_app/test_authorizer_factory.py +++ b/tests/unit/experimental/globus_app/test_authorizer_factory.py @@ -60,9 +60,9 @@ def test_access_token_authorizer_factory(): authorizer2 = factory.get_authorizer("rs1") assert authorizer is authorizer2 - # manually calling store clears cache + # calling store_token_response_and_clear_cache then get gets a new authorizer new_data = make_mock_token_response(token_number=2) - factory.store_token_response(new_data) + factory.store_token_response_and_clear_cache(new_data) assert factory._authorizer_cache == {} authorizer = factory.get_authorizer("rs1") assert authorizer.get_authorization_header() == "Bearer rs1_access_token_2" @@ -107,8 +107,8 @@ def test_refresh_token_authorizer_factory(): assert authorizer2.get_authorization_header() == "Bearer rs1_access_token_2" assert mock_auth_login_client.oauth2_refresh_token.call_count == 1 - # manually calling store clears cache - factory.store_token_response(initial_response) + # calling store_token_response_and_clear_cache then get gets a new authorizer + factory.store_token_response_and_clear_cache(initial_response) authorizer3 = factory.get_authorizer("rs1") assert authorizer3 is not authorizer1 assert authorizer3.get_authorization_header() == "Bearer rs1_access_token_1" @@ -187,8 +187,8 @@ def test_client_credentials_authorizer_factory(): assert authorizer2.get_authorization_header() == "Bearer rs1_access_token_2" assert mock_confidential_client.oauth2_client_credentials_tokens.call_count == 1 - # manually calling store clears cache - factory.store_token_response(initial_response) + # calling store_token_response_and_clear_cache then get gets a new authorizer + factory.store_token_response_and_clear_cache(initial_response) authorizer3 = factory.get_authorizer("rs1") assert authorizer3 is not authorizer1 assert authorizer3.get_authorization_header() == "Bearer rs1_access_token_1"