Skip to content

Commit 9dc38eb

Browse files
authored
Merge pull request #1136 from sirosen/id-token-decoder
Implement JWTDecoder and IDTokenDecoder, use them to reimplement decoding and improve the behavior and customizability of GlobusApp
2 parents c933d19 + b7f00b9 commit 9dc38eb

File tree

15 files changed

+641
-66
lines changed

15 files changed

+641
-66
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Added
2+
~~~~~
3+
4+
- Introduce ``globus_sdk.IDTokenDecoder``, which implements ``id_token``
5+
decoding. (:pr:`NUMBER`)
6+
7+
- For integration with ``GlobusApp``, a new builder protocol is defined,
8+
``IDTokenDecoderProvider``. This defines instantiation within the context
9+
of an app.
10+
11+
- When ``OAuthTokenResponse.decode_id_token`` is called, it now internally
12+
instantiates an ``IDTokenDecoder`` and uses it to perform the decode.
13+
14+
- ``IDTokenDecoder`` objects cache OpenID configuration data and JWKs
15+
after looking them up. If a decoder is used multiple times, it will reuse
16+
the cached data.
17+
18+
- Token storage constructs can now contain an ``IDTokenDecoder`` in their
19+
``id_token_decoder`` attribute. The decoder is used preferentially when
20+
trying to read the ``sub`` field from an ``id_token`` to store.
21+
22+
- ``GlobusAppConfig`` can now contain ``id_token_decoder``, an
23+
``IDTokenDecoder`` or ``IDTokenDecoderProvider``.
24+
The default is ``IDTokenDecoder``.
25+
26+
- ``GlobusApp`` initialization will now use the config's
27+
``id_token_decoder`` and attach the ``IDTokenDecoder`` to the
28+
token storage which is used.

docs/authorization/globus_app/config.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ Providers
3636
:members:
3737
:special-members: __call__
3838
:member-order: bysource
39+
40+
.. autoclass:: IDTokenDecoderProvider()
41+
:members: for_globus_app
42+
:member-order: bysource

docs/services/auth.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ batching, and other functionality.
5252
:exclude-members: __dict__,__weakref__
5353
:show-inheritance:
5454

55+
.. autoclass:: IDTokenDecoder
56+
:show-inheritance:
57+
5558
.. autoclass:: DependentScopeSpec
5659

5760
Auth Responses

src/globus_sdk/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ from .services.auth import (
3535
GetConsentsResponse,
3636
GetIdentitiesResponse,
3737
IdentityMap,
38+
IDTokenDecoder,
3839
NativeAppAuthClient,
3940
OAuthAuthorizationCodeResponse,
4041
OAuthClientCredentialsResponse,
@@ -173,6 +174,7 @@ __all__ = (
173174
"OAuthDependentTokenResponse",
174175
"OAuthRefreshTokenResponse",
175176
"OAuthTokenResponse",
177+
"IDTokenDecoder",
176178
"ComputeAPIError",
177179
"ComputeClient",
178180
"ComputeClientV2",

src/globus_sdk/globus_app/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .client_app import ClientApp
33
from .config import GlobusAppConfig
44
from .protocols import (
5+
IDTokenDecoderProvider,
56
LoginFlowManagerProvider,
67
TokenStorageProvider,
78
TokenValidationErrorHandler,
@@ -14,6 +15,7 @@
1415
"ClientApp",
1516
"GlobusAppConfig",
1617
# Protocols
18+
"IDTokenDecoderProvider",
1719
"TokenValidationErrorHandler",
1820
"TokenStorageProvider",
1921
"LoginFlowManagerProvider",

src/globus_sdk/globus_app/app.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
import copy
66
import typing as t
77

8-
from globus_sdk import AuthClient, AuthLoginClient, GlobusSDKUsageError, Scope
8+
from globus_sdk import (
9+
AuthClient,
10+
AuthLoginClient,
11+
GlobusSDKUsageError,
12+
IDTokenDecoder,
13+
Scope,
14+
)
915
from globus_sdk._types import ScopeCollectionType, UUIDLike
1016
from globus_sdk.authorizers import GlobusAuthorizer
1117
from globus_sdk.gare import GlobusAuthorizationParameters
@@ -97,6 +103,11 @@ def __init__(
97103
scope_requirements=self._scope_requirements,
98104
)
99105

106+
# setup an ID Token Decoder based on config; build one if it was not provided
107+
self._id_token_decoder = self._initialize_id_token_decoder(
108+
app_name=self.app_name, config=self.config, login_client=self._login_client
109+
)
110+
100111
# initialize our authorizer factory
101112
self._initialize_authorizer_factory()
102113
self._authorizer_factory_initialized = True
@@ -239,6 +250,34 @@ def _resolve_token_storage(
239250
f"TokenStorage, TokenStorageProvider, or a supported string value."
240251
)
241252

253+
def _initialize_id_token_decoder(
254+
self, *, app_name: str, config: GlobusAppConfig, login_client: AuthLoginClient
255+
) -> IDTokenDecoder:
256+
"""
257+
Create an IDTokenDecoder or use the one provided via config, and set it on
258+
the token storage adapters.
259+
260+
It is only set on inner storage if the decoder was not already set, so a
261+
non-null value won't be overwritten.
262+
263+
This must run near the end of app initialization, when the `_token_storage`
264+
(inner) and `token_storage` (validating storage, outer) storages have both
265+
been initialized.
266+
"""
267+
if isinstance(self.config.id_token_decoder, IDTokenDecoder):
268+
id_token_decoder: IDTokenDecoder = self.config.id_token_decoder
269+
else:
270+
id_token_decoder = self.config.id_token_decoder.for_globus_app(
271+
app_name=app_name,
272+
config=config,
273+
login_client=login_client,
274+
)
275+
if self._token_storage.id_token_decoder is None:
276+
self._token_storage.id_token_decoder = id_token_decoder
277+
self.token_storage.id_token_decoder = id_token_decoder
278+
279+
return id_token_decoder
280+
242281
@abc.abstractmethod
243282
def _initialize_authorizer_factory(self) -> None:
244283
"""

src/globus_sdk/globus_app/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dataclasses
44
import typing as t
55

6+
import globus_sdk
67
from globus_sdk.config import get_environment_name
78
from globus_sdk.login_flows import (
89
CommandLineLoginFlowManager,
@@ -19,6 +20,7 @@
1920
from globus_sdk.tokenstorage.v2.validating_token_storage import IdentityMismatchError
2021

2122
from .protocols import (
23+
IDTokenDecoderProvider,
2224
LoginFlowManagerProvider,
2325
TokenStorageProvider,
2426
TokenValidationErrorHandler,
@@ -101,6 +103,11 @@ class GlobusAppConfig:
101103
Default: ``resolve_by_login_flow`` (runs a login flow, storing the resulting
102104
tokens).
103105
106+
:ivar ``IDTokenDecoder`` | ``IDTokenDecoderProvider`` id_token_decoder:
107+
An ID token decoder or a provider which produces a decoder. The decoder is used
108+
when decoding ``id_token`` JWTs from Globus Auth.
109+
Defaults to ``IDTokenDecoder``.
110+
104111
:ivar str environment: The Globus environment of services to interact with. This is
105112
mostly used for testing purposes. This may additionally be set with the
106113
environment variable `GLOBUS_SDK_ENVIRONMENT`. Default: ``"production"``.
@@ -115,6 +122,9 @@ class GlobusAppConfig:
115122
token_validation_error_handler: TokenValidationErrorHandler | None = (
116123
resolve_by_login_flow
117124
)
125+
id_token_decoder: globus_sdk.IDTokenDecoder | IDTokenDecoderProvider = (
126+
globus_sdk.IDTokenDecoder
127+
)
118128
environment: str = dataclasses.field(default_factory=get_environment_name)
119129

120130

src/globus_sdk/globus_app/protocols.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@
22

33
import typing as t
44

5-
from globus_sdk import AuthLoginClient
6-
from globus_sdk._types import UUIDLike
7-
from globus_sdk.login_flows import LoginFlowManager
8-
from globus_sdk.tokenstorage import TokenStorage
9-
105
if t.TYPE_CHECKING:
11-
from globus_sdk.tokenstorage import TokenValidationError
6+
from globus_sdk import AuthLoginClient, IDTokenDecoder
7+
from globus_sdk._types import UUIDLike
8+
from globus_sdk.login_flows import LoginFlowManager
9+
from globus_sdk.tokenstorage import TokenStorage, TokenValidationError
1210

1311
from .app import GlobusApp
1412
from .config import GlobusAppConfig
1513

1614

1715
@t.runtime_checkable
1816
class TokenStorageProvider(t.Protocol):
19-
"""
20-
A protocol for a factory which can create ``TokenStorages``.
17+
r"""
18+
A protocol for a factory which can create ``TokenStorage``\s.
2119
2220
SDK-provided :ref:`token_storages` support this protocol.
2321
"""
@@ -43,8 +41,8 @@ def for_globus_app(
4341

4442
@t.runtime_checkable
4543
class LoginFlowManagerProvider(t.Protocol):
46-
"""
47-
A protocol for a factory which can create ``LoginFlowManagers``.
44+
r"""
45+
A protocol for a factory which can create ``LoginFlowManager``\s.
4846
4947
SDK-provided :ref:`login_flow_managers` support this protocol.
5048
"""
@@ -62,6 +60,32 @@ def for_globus_app(
6260
"""
6361

6462

63+
@t.runtime_checkable
64+
class IDTokenDecoderProvider(t.Protocol):
65+
r"""
66+
A protocol for a factory which can create ``IDTokenDecoder``\s.
67+
68+
The SDK-provided ``IDTokenDecoder`` class supports this protocol.
69+
"""
70+
71+
@classmethod
72+
def for_globus_app(
73+
cls,
74+
*,
75+
app_name: str,
76+
config: GlobusAppConfig,
77+
login_client: AuthLoginClient,
78+
) -> IDTokenDecoder:
79+
"""
80+
Create an ``IDTokenDecoder`` for use in a GlobusApp.
81+
82+
:param app_name: The name supplied to the GlobusApp.
83+
:param config: The configuration supplied to the GlobusApp.
84+
:param login_client: A login client to use for instantiating an
85+
``IDTokenDecoder``.
86+
"""
87+
88+
6589
class TokenValidationErrorHandler(t.Protocol):
6690
"""
6791
A handler invoked when a :class:`TokenValidationError` is raised during a

src/globus_sdk/services/auth/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
GlobusAuthorizationCodeFlowManager,
1111
GlobusNativeAppFlowManager,
1212
)
13+
from .id_token_decoder import IDTokenDecoder
1314
from .identity_map import IdentityMap
1415
from .response import (
1516
GetConsentsResponse,
@@ -32,6 +33,7 @@
3233
# high-level helpers
3334
"DependentScopeSpec",
3435
"IdentityMap",
36+
"IDTokenDecoder",
3537
# flow managers
3638
"GlobusNativeAppFlowManager",
3739
"GlobusAuthorizationCodeFlowManager",

0 commit comments

Comments
 (0)