oidcauthlib 3.0.23__tar.gz → 3.0.25__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {oidcauthlib-3.0.23/oidcauthlib.egg-info → oidcauthlib-3.0.25}/PKG-INFO +3 -3
  2. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/README.md +2 -2
  3. oidcauthlib-3.0.25/VERSION +1 -0
  4. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/userinfo/oidc_userinfo_manager.py +44 -11
  5. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25/oidcauthlib.egg-info}/PKG-INFO +3 -3
  6. oidcauthlib-3.0.23/VERSION +0 -1
  7. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/LICENSE +0 -0
  8. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/MANIFEST.in +0 -0
  9. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/__init__.py +0 -0
  10. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/__init__.py +0 -0
  11. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/auth_helper.py +0 -0
  12. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/auth_manager.py +0 -0
  13. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/cache/__init__.py +0 -0
  14. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/cache/oauth_cache.py +0 -0
  15. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/cache/oauth_memory_cache.py +0 -0
  16. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/cache/oauth_mongo_cache.py +0 -0
  17. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/config/__init__.py +0 -0
  18. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/config/auth_config.py +0 -0
  19. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/config/auth_config_reader.py +0 -0
  20. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/dcr/__init__.py +0 -0
  21. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/dcr/dcr_client.py +0 -0
  22. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/dcr/dcr_manager.py +0 -0
  23. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/dcr/dcr_registration.py +0 -0
  24. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/exceptions/__init__.py +0 -0
  25. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/exceptions/authorization_bearer_token_expired_exception.py +0 -0
  26. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/exceptions/authorization_bearer_token_invalid_exception.py +0 -0
  27. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/exceptions/authorization_bearer_token_missing_exception.py +0 -0
  28. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/exceptions/authorization_needed_exception.py +0 -0
  29. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/fastapi_auth_manager.py +0 -0
  30. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/middleware/__init__.py +0 -0
  31. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/middleware/request_scope_middleware.py +0 -0
  32. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/middleware/token_reader_middleware.py +0 -0
  33. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/__init__.py +0 -0
  34. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/auth.py +0 -0
  35. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/base_db_model.py +0 -0
  36. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/cache_item.py +0 -0
  37. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/client_key_set.py +0 -0
  38. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/models/token.py +0 -0
  39. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/__init__.py +0 -0
  40. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/base_repository.py +0 -0
  41. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/memory/__init__.py +0 -0
  42. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/memory/memory_repository.py +0 -0
  43. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/mongo/__init__.py +0 -0
  44. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/mongo/mongo_repository.py +0 -0
  45. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/repository/repository_factory.py +0 -0
  46. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/routers/__init__.py +0 -0
  47. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/routers/auth_router.py +0 -0
  48. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/token_reader.py +0 -0
  49. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/userinfo/__init__.py +0 -0
  50. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/__init__.py +0 -0
  51. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/auth_server_metadata.py +0 -0
  52. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/auth_server_metadata_discovery.py +0 -0
  53. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache.py +0 -0
  54. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache_result.py +0 -0
  55. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/auth/well_known_configuration/well_known_configuration_manager.py +0 -0
  56. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/cache_gate/__init__.py +0 -0
  57. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/cache_gate/advisory_lock.py +0 -0
  58. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/cache_gate/version_gate.py +0 -0
  59. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/container/__init__.py +0 -0
  60. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/container/oidc_authlib_container_factory.py +0 -0
  61. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/__init__.py +0 -0
  62. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/attribute_names.py +0 -0
  63. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/configurator.py +0 -0
  64. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/filtering_sampler.py +0 -0
  65. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/filtering_span_exporter.py +0 -0
  66. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/filtering_span_processor.py +0 -0
  67. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/otel_setup.py +0 -0
  68. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/open_telemetry/span_names.py +0 -0
  69. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/py.typed +0 -0
  70. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/__init__.py +0 -0
  71. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/cache_to_collection_mapper.py +0 -0
  72. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/memory_storage_factory.py +0 -0
  73. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/mongo_gridfs_db.py +0 -0
  74. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/mongo_gridfs_exception.py +0 -0
  75. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/mongo_storage_factory.py +0 -0
  76. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/storage_factory.py +0 -0
  77. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/storage/storage_factory_creator.py +0 -0
  78. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/__init__.py +0 -0
  79. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/cached.py +0 -0
  80. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/environment/__init__.py +0 -0
  81. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/environment/abstract_environment_variables.py +0 -0
  82. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/environment/oidc_environment_variables.py +0 -0
  83. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/logger/__init__.py +0 -0
  84. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/logger/log_levels.py +0 -0
  85. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/logger/logging_response.py +0 -0
  86. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/logger/logging_transport.py +0 -0
  87. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/mongo_url_utils.py +0 -0
  88. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib/utilities/url_validator.py +0 -0
  89. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib.egg-info/SOURCES.txt +0 -0
  90. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib.egg-info/dependency_links.txt +0 -0
  91. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib.egg-info/requires.txt +0 -0
  92. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/oidcauthlib.egg-info/top_level.txt +0 -0
  93. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/pyproject.toml +0 -0
  94. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/setup.cfg +0 -0
  95. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/tests/test_mongo_url_utils.py +0 -0
  96. {oidcauthlib-3.0.23 → oidcauthlib-3.0.25}/tests/test_simple.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oidcauthlib
3
- Version: 3.0.23
3
+ Version: 3.0.25
4
4
  Summary: oidcauthlib
5
5
  Author-email: Imran Qureshi <imran.qureshi@bwell.com>
6
6
  License: Apache-2.0
@@ -58,8 +58,8 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
58
58
  - **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
59
59
  host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
60
60
  its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
61
- - **Subject check:** the userinfo response's `sub` must equal the token's `sub` (OIDC Core 5.3.2); otherwise
62
- the lookup raises `ValueError` and nothing is cached.
61
+ - **Subject check:** for a token that has a `sub`, the userinfo response must carry the same `sub`
62
+ (OIDC Core 5.3.2); a missing or different one raises `ValueError` and nothing is cached.
63
63
  - **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
64
64
  (so values containing `:` cannot collide, and two providers never share an entry); only the
65
65
  userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
@@ -30,8 +30,8 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
30
30
  - **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
31
31
  host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
32
32
  its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
33
- - **Subject check:** the userinfo response's `sub` must equal the token's `sub` (OIDC Core 5.3.2); otherwise
34
- the lookup raises `ValueError` and nothing is cached.
33
+ - **Subject check:** for a token that has a `sub`, the userinfo response must carry the same `sub`
34
+ (OIDC Core 5.3.2); a missing or different one raises `ValueError` and nothing is cached.
35
35
  - **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
36
36
  (so values containing `:` cannot collide, and two providers never share an entry); only the
37
37
  userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
@@ -0,0 +1 @@
1
+ 3.0.25
@@ -23,6 +23,9 @@ logger.setLevel(SRC_LOG_LEVELS["AUTH"])
23
23
 
24
24
  _DEFAULT_PORTS = {"http": 80, "https": 443}
25
25
 
26
+ # Okta org-server access tokens carry the login in ``sub`` and the stable user id in this claim.
27
+ _OKTA_USER_ID_CLAIM = "uid"
28
+
26
29
 
27
30
  class OidcUserInfoCacheEntry(BaseModel):
28
31
  """Document stored for one cached scope list."""
@@ -43,8 +46,8 @@ class OidcUserInfoManager:
43
46
  same scheme, host and port whose path segments start with the issuer's). Only a token with no ``iss`` at
44
47
  all is matched by its client id. A token matching no provider is an error: the bearer token is never
45
48
  sent to an unrelated provider.
46
- - The userinfo response's ``sub`` must equal the token's ``sub`` (OIDC Core 5.3.2), else the lookup fails
47
- and nothing is cached.
49
+ - For a token that has a ``sub``, the userinfo response must carry the same ``sub`` (OIDC Core 5.3.2); a
50
+ missing or different one fails the lookup and nothing is cached.
48
51
  - The userinfo endpoint is discovered through ``WellKnownConfigurationManager``.
49
52
  - Scopes are derived the way fhir-server-ui's ``jwtParser`` does: the ``scope`` claim (plus ``scp``,
50
53
  which Okta uses), the claims named in ``scope_claims``, and each claim named in ``group_claims``
@@ -179,17 +182,45 @@ class OidcUserInfoManager:
179
182
  """Short digest of ``cache_key``: the key embeds the user's ``sub``, which must not reach logs."""
180
183
  return hashlib.sha256(cache_key.encode()).hexdigest()[:12]
181
184
 
185
+ @staticmethod
186
+ def _user_id(*, access_token: Token) -> str | None:
187
+ """The token's stable user id claim, when it has a ``sub`` to alias and the claim is a non-empty string."""
188
+ user_id = (access_token.claims or {}).get(_OKTA_USER_ID_CLAIM)
189
+ if access_token.subject and isinstance(user_id, str) and user_id:
190
+ return user_id
191
+ return None
192
+
193
+ @staticmethod
194
+ def _token_subjects(*, access_token: Token) -> set[str]:
195
+ """Identifiers a userinfo ``sub`` may legitimately equal for this token; empty for a token without ``sub``.
196
+
197
+ Okta org-server access tokens carry the login in ``sub`` and the user id in ``uid``, while the userinfo
198
+ response's ``sub`` is that user id; so the token's ``uid`` counts as the same subject as its ``sub``.
199
+ The alias is not issuer-specific and relies on the caller having verified the token (the userinfo
200
+ request is made with that same token, so the response is always for the token's own user).
201
+ """
202
+ subject = access_token.subject
203
+ if not subject:
204
+ return set()
205
+ if not isinstance(subject, str):
206
+ # A malformed ``sub`` can never be shown to equal the response's; fail closed like any mismatch.
207
+ raise ValueError("User info response subject is missing or differs from the token's subject")
208
+ user_id = OidcUserInfoManager._user_id(access_token=access_token)
209
+ return {subject} | ({user_id} if user_id else set())
210
+
182
211
  async def get_user_info_async(self, *, access_token: Token) -> dict[str, Any]:
183
212
  """Fetch the userinfo response for ``access_token``.
184
213
 
185
214
  ``access_token`` must already have been verified by the caller; its claims are trusted.
186
215
 
187
- Per OIDC Core 5.3.2 the response's ``sub`` must equal the token's ``sub``; a response that carries a
188
- different ``sub`` describes another user and is rejected.
216
+ Per OIDC Core 5.3.2 the response must carry a ``sub`` equal to the token's ``sub`` (or its ``uid``, see
217
+ ``_token_subjects``); a response without one, or with a different one, cannot be shown to describe the
218
+ token's user and is rejected. A token that has no ``sub`` has nothing to compare (its ``uid`` is not
219
+ consulted), so its response is accepted (and never cached).
189
220
 
190
221
  Raises:
191
- ValueError: the token, auth config, discovery document or response is unusable, or the response
192
- is for a different subject.
222
+ ValueError: the token, auth config, discovery document or response is unusable, or the response's
223
+ subject is missing or differs from the token's.
193
224
  httpx.HTTPError: the userinfo request failed.
194
225
  """
195
226
  if not access_token.token:
@@ -221,10 +252,11 @@ class OidcUserInfoManager:
221
252
  user_info = response.json()
222
253
  if not isinstance(user_info, dict):
223
254
  raise ValueError("User info response is not a valid JSON object")
224
- response_subject = user_info.get("sub")
225
- if response_subject is not None and access_token.subject and response_subject != access_token.subject:
226
- # Neither subject is logged: they identify users.
227
- raise ValueError("User info response is for a different subject than the token")
255
+ token_subjects = self._token_subjects(access_token=access_token)
256
+ if token_subjects and user_info.get("sub") not in token_subjects:
257
+ # Missing or different: ``sub`` is required in the response, and one that cannot be shown
258
+ # to be the token's subject must not be turned into scopes. Neither subject is logged.
259
+ raise ValueError("User info response subject is missing or differs from the token's subject")
228
260
  return user_info
229
261
 
230
262
  async def get_scopes_from_user_info_async(self, *, access_token: Token) -> list[str]:
@@ -242,7 +274,8 @@ class OidcUserInfoManager:
242
274
  cache_key = self.build_cache_key(
243
275
  issuer=access_token.issuer or (access_token.claims or {}).get("iss"),
244
276
  client_id=access_token.client_id,
245
- subject=access_token.subject,
277
+ # The stable user id when the token has one: a login can be reassigned to another user.
278
+ subject=self._user_id(access_token=access_token) or access_token.subject,
246
279
  )
247
280
 
248
281
  user_info_scopes: list[str] | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oidcauthlib
3
- Version: 3.0.23
3
+ Version: 3.0.25
4
4
  Summary: oidcauthlib
5
5
  Author-email: Imran Qureshi <imran.qureshi@bwell.com>
6
6
  License: Apache-2.0
@@ -58,8 +58,8 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
58
58
  - **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
59
59
  host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
60
60
  its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
61
- - **Subject check:** the userinfo response's `sub` must equal the token's `sub` (OIDC Core 5.3.2); otherwise
62
- the lookup raises `ValueError` and nothing is cached.
61
+ - **Subject check:** for a token that has a `sub`, the userinfo response must carry the same `sub`
62
+ (OIDC Core 5.3.2); a missing or different one raises `ValueError` and nothing is cached.
63
63
  - **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
64
64
  (so values containing `:` cannot collide, and two providers never share an entry); only the
65
65
  userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
@@ -1 +0,0 @@
1
- 3.0.23
File without changes
File without changes
File without changes