oidcauthlib 3.0.22__tar.gz → 3.0.24__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.
- {oidcauthlib-3.0.22/oidcauthlib.egg-info → oidcauthlib-3.0.24}/PKG-INFO +11 -8
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/README.md +10 -7
- oidcauthlib-3.0.24/VERSION +1 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/userinfo/oidc_userinfo_manager.py +81 -22
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24/oidcauthlib.egg-info}/PKG-INFO +11 -8
- oidcauthlib-3.0.22/VERSION +0 -1
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/LICENSE +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/MANIFEST.in +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/auth_helper.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/auth_manager.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/cache/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/cache/oauth_cache.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/cache/oauth_memory_cache.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/cache/oauth_mongo_cache.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/config/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/config/auth_config.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/config/auth_config_reader.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/dcr/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/dcr/dcr_client.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/dcr/dcr_manager.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/dcr/dcr_registration.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/exceptions/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/exceptions/authorization_bearer_token_expired_exception.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/exceptions/authorization_bearer_token_invalid_exception.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/exceptions/authorization_bearer_token_missing_exception.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/exceptions/authorization_needed_exception.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/fastapi_auth_manager.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/middleware/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/middleware/request_scope_middleware.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/middleware/token_reader_middleware.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/auth.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/base_db_model.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/cache_item.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/client_key_set.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/models/token.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/base_repository.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/memory/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/memory/memory_repository.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/mongo/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/mongo/mongo_repository.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/repository_factory.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/routers/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/routers/auth_router.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/token_reader.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/userinfo/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/auth_server_metadata.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/auth_server_metadata_discovery.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache_result.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/well_known_configuration_manager.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/cache_gate/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/cache_gate/advisory_lock.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/cache_gate/version_gate.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/container/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/container/oidc_authlib_container_factory.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/attribute_names.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/configurator.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/filtering_sampler.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/filtering_span_exporter.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/filtering_span_processor.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/otel_setup.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/span_names.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/py.typed +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/cache_to_collection_mapper.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/memory_storage_factory.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/mongo_gridfs_db.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/mongo_gridfs_exception.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/mongo_storage_factory.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/storage_factory.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/storage/storage_factory_creator.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/cached.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/environment/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/environment/abstract_environment_variables.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/environment/oidc_environment_variables.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/logger/__init__.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/logger/log_levels.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/logger/logging_response.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/logger/logging_transport.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/mongo_url_utils.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/utilities/url_validator.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib.egg-info/SOURCES.txt +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib.egg-info/dependency_links.txt +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib.egg-info/requires.txt +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib.egg-info/top_level.txt +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/pyproject.toml +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/setup.cfg +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/tests/test_mongo_url_utils.py +0 -0
- {oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/tests/test_simple.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oidcauthlib
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.24
|
|
4
4
|
Summary: oidcauthlib
|
|
5
5
|
Author-email: Imran Qureshi <imran.qureshi@bwell.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -55,14 +55,17 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
|
|
|
55
55
|
`scope_claims`, then `group_claims` (each value taken whole). Commas separate like spaces, results are
|
|
56
56
|
de-duplicated, and `scope_remove_prefixes` are stripped (first match wins). Nothing beyond `scope`/`scp`
|
|
57
57
|
is read unless configured.
|
|
58
|
-
- **Provider** is matched to the token's `iss
|
|
59
|
-
|
|
60
|
-
never sent to another provider.
|
|
58
|
+
- **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
|
|
59
|
+
host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
|
|
60
|
+
its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
|
|
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.
|
|
61
63
|
- **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
|
|
62
64
|
(so values containing `:` cannot collide, and two providers never share an entry); only the
|
|
63
65
|
userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
|
|
64
|
-
`sub
|
|
66
|
+
`sub` or without an issuer, or an empty scope list, is not cached. Changes to a user's entitlements are visible after at most
|
|
65
67
|
`cache_ttl_seconds`.
|
|
66
|
-
- **Failures:**
|
|
67
|
-
entry
|
|
68
|
-
or
|
|
68
|
+
- **Failures:** the cache never fails a lookup. A write failure is logged and swallowed; a read failure or a
|
|
69
|
+
malformed entry is logged and treated as a miss, and the refetched scopes overwrite the bad entry. A
|
|
70
|
+
userinfo HTTP error, a missing provider or a mismatched subject is raised, so each service decides whether
|
|
71
|
+
to fail closed or degrade.
|
|
@@ -27,14 +27,17 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
|
|
|
27
27
|
`scope_claims`, then `group_claims` (each value taken whole). Commas separate like spaces, results are
|
|
28
28
|
de-duplicated, and `scope_remove_prefixes` are stripped (first match wins). Nothing beyond `scope`/`scp`
|
|
29
29
|
is read unless configured.
|
|
30
|
-
- **Provider** is matched to the token's `iss
|
|
31
|
-
|
|
32
|
-
never sent to another provider.
|
|
30
|
+
- **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
|
|
31
|
+
host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
|
|
32
|
+
its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
|
|
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.
|
|
33
35
|
- **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
|
|
34
36
|
(so values containing `:` cannot collide, and two providers never share an entry); only the
|
|
35
37
|
userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
|
|
36
|
-
`sub
|
|
38
|
+
`sub` or without an issuer, or an empty scope list, is not cached. Changes to a user's entitlements are visible after at most
|
|
37
39
|
`cache_ttl_seconds`.
|
|
38
|
-
- **Failures:**
|
|
39
|
-
entry
|
|
40
|
-
or
|
|
40
|
+
- **Failures:** the cache never fails a lookup. A write failure is logged and swallowed; a read failure or a
|
|
41
|
+
malformed entry is logged and treated as a miss, and the refetched scopes overwrite the bad entry. A
|
|
42
|
+
userinfo HTTP error, a missing provider or a mismatched subject is raised, so each service decides whether
|
|
43
|
+
to fail closed or degrade.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.0.24
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/userinfo/oidc_userinfo_manager.py
RENAMED
|
@@ -2,7 +2,7 @@ import hashlib
|
|
|
2
2
|
import logging
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from typing import Any
|
|
5
|
-
from urllib.parse import quote
|
|
5
|
+
from urllib.parse import SplitResult, quote, urlsplit
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
from key_value.aio.protocols.key_value import AsyncKeyValueProtocol
|
|
@@ -21,6 +21,8 @@ from oidcauthlib.utilities.logger.log_levels import SRC_LOG_LEVELS
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
logger.setLevel(SRC_LOG_LEVELS["AUTH"])
|
|
23
23
|
|
|
24
|
+
_DEFAULT_PORTS = {"http": 80, "https": 443}
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
class OidcUserInfoCacheEntry(BaseModel):
|
|
26
28
|
"""Document stored for one cached scope list."""
|
|
@@ -37,9 +39,12 @@ class OidcUserInfoManager:
|
|
|
37
39
|
JWT; the scopes that authorize a caller appear only on the userinfo response.
|
|
38
40
|
|
|
39
41
|
Behavior:
|
|
40
|
-
- The provider is matched to the token's ``iss`` claim (exact ``issuer``,
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
- The provider is matched to the token's ``iss`` claim (exact ``issuer``, else a ``well_known_uri`` on the
|
|
43
|
+
same scheme, host and port whose path segments start with the issuer's). Only a token with no ``iss`` at
|
|
44
|
+
all is matched by its client id. A token matching no provider is an error: the bearer token is never
|
|
45
|
+
sent to an unrelated provider.
|
|
46
|
+
- For a token that has a ``sub``, the userinfo response must carry the same ``sub`` (OIDC Core 5.3.2); a
|
|
47
|
+
missing or different one fails the lookup and nothing is cached.
|
|
43
48
|
- The userinfo endpoint is discovered through ``WellKnownConfigurationManager``.
|
|
44
49
|
- Scopes are derived the way fhir-server-ui's ``jwtParser`` does: the ``scope`` claim (plus ``scp``,
|
|
45
50
|
which Okta uses), the claims named in ``scope_claims``, and each claim named in ``group_claims``
|
|
@@ -52,16 +57,19 @@ class OidcUserInfoManager:
|
|
|
52
57
|
``oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v<CACHE_VERSION>`` (each segment percent-encoded). The token's own scopes are always
|
|
53
58
|
re-derived from the presented token, so a narrower token never inherits a broader cached set.
|
|
54
59
|
Bumping ``CACHE_VERSION`` obsoletes every existing entry.
|
|
55
|
-
- A token without ``sub`` is never cached: the key would be shared by every user of
|
|
60
|
+
- A token without ``sub`` or without an issuer is never cached: the key would be shared by every user of
|
|
61
|
+
the client, or by every provider that issues tokens without ``iss``.
|
|
56
62
|
- An empty scope list is never cached: it is also what an account with no scopes looks like, and
|
|
57
63
|
caching it would pin that result for the whole TTL.
|
|
58
|
-
-
|
|
64
|
+
- The cache is an optimization and never fails a lookup: a write failure is logged and swallowed, and a
|
|
65
|
+
read failure or an entry of the wrong shape is logged and treated as a miss (the refetched scopes
|
|
66
|
+
overwrite a bad entry).
|
|
59
67
|
|
|
60
68
|
The store and collection are supplied by the caller, so each service keeps its own database. A cached
|
|
61
69
|
scope list outlives a change to the user's entitlements by up to ``cache_ttl_seconds``.
|
|
62
70
|
|
|
63
|
-
Failures of the lookup itself (no matching provider, HTTP error,
|
|
64
|
-
|
|
71
|
+
Failures of the lookup itself (no matching provider, HTTP error, mismatched subject) are raised so the
|
|
72
|
+
caller decides whether to fail closed or degrade.
|
|
65
73
|
"""
|
|
66
74
|
|
|
67
75
|
CACHE_VERSION = 1
|
|
@@ -108,18 +116,24 @@ class OidcUserInfoManager:
|
|
|
108
116
|
def resolve_auth_config(self, *, access_token: Token) -> AuthConfig:
|
|
109
117
|
"""The configuration of the provider that issued ``access_token``.
|
|
110
118
|
|
|
119
|
+
The token's issuer decides: an exact ``issuer`` match, else a ``well_known_uri`` on the same scheme, host
|
|
120
|
+
and port whose path segments start with the issuer's. The client id is only used for a token that has
|
|
121
|
+
no issuer at all; a token whose issuer matches no provider is never routed by its client id, which
|
|
122
|
+
would send it to whichever provider owns that client id.
|
|
123
|
+
|
|
111
124
|
Raises:
|
|
112
|
-
ValueError: no configured provider matches the token
|
|
125
|
+
ValueError: no configured provider matches the token.
|
|
113
126
|
"""
|
|
114
127
|
issuer = access_token.issuer or (access_token.claims or {}).get("iss")
|
|
115
|
-
configs = self._auth_config_reader.get_auth_configs_for_all_auth_providers()
|
|
116
128
|
if issuer:
|
|
129
|
+
configs = self._auth_config_reader.get_auth_configs_for_all_auth_providers()
|
|
117
130
|
for config in configs:
|
|
118
131
|
if config.issuer and config.issuer == issuer:
|
|
119
132
|
return config
|
|
120
133
|
for config in configs:
|
|
121
134
|
if config.well_known_uri and self._is_under_issuer(uri=config.well_known_uri, issuer=issuer):
|
|
122
135
|
return config
|
|
136
|
+
raise ValueError(f"No auth provider matches token issuer {issuer!r}")
|
|
123
137
|
client_id = access_token.client_id
|
|
124
138
|
if client_id:
|
|
125
139
|
auth_provider = self._auth_config_reader.get_provider_for_client_id(client_id=client_id)
|
|
@@ -127,17 +141,38 @@ class OidcUserInfoManager:
|
|
|
127
141
|
auth_config = self._auth_config_reader.get_config_for_auth_provider(auth_provider=auth_provider)
|
|
128
142
|
if auth_config:
|
|
129
143
|
return auth_config
|
|
130
|
-
raise ValueError(f"No auth provider matches token
|
|
144
|
+
raise ValueError(f"No auth provider matches token client id {client_id!r}")
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _effective_port(*, parts: SplitResult) -> int | None:
|
|
148
|
+
if parts.port is not None:
|
|
149
|
+
return parts.port
|
|
150
|
+
return _DEFAULT_PORTS.get(parts.scheme.lower())
|
|
131
151
|
|
|
132
152
|
@staticmethod
|
|
133
153
|
def _is_under_issuer(*, uri: str, issuer: str) -> bool:
|
|
134
|
-
"""True when ``uri`` is the issuer
|
|
154
|
+
"""True when ``uri`` is on the issuer's origin and its path is the issuer's path or beneath it.
|
|
135
155
|
|
|
136
|
-
|
|
137
|
-
``https://a.example.com
|
|
156
|
+
Scheme, host (case-insensitive), port (default ports normalized) and path *segments* are compared.
|
|
157
|
+
A string prefix test would match ``https://a.example.com.au/...`` for ``https://a.example.com``,
|
|
158
|
+
``/realms/ab`` for ``/realms/a``, and every https URL for the degenerate issuer ``https://``.
|
|
138
159
|
"""
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
try:
|
|
161
|
+
issuer_parts = urlsplit(issuer)
|
|
162
|
+
uri_parts = urlsplit(uri)
|
|
163
|
+
issuer_port = OidcUserInfoManager._effective_port(parts=issuer_parts)
|
|
164
|
+
uri_port = OidcUserInfoManager._effective_port(parts=uri_parts)
|
|
165
|
+
except ValueError:
|
|
166
|
+
return False
|
|
167
|
+
if not issuer_parts.scheme or not issuer_parts.hostname or not uri_parts.hostname:
|
|
168
|
+
return False
|
|
169
|
+
if uri_parts.scheme.lower() != issuer_parts.scheme.lower():
|
|
170
|
+
return False
|
|
171
|
+
if uri_parts.hostname.lower() != issuer_parts.hostname.lower() or uri_port != issuer_port:
|
|
172
|
+
return False
|
|
173
|
+
issuer_segments = [segment for segment in issuer_parts.path.split("/") if segment]
|
|
174
|
+
uri_segments = [segment for segment in uri_parts.path.split("/") if segment]
|
|
175
|
+
return uri_segments[: len(issuer_segments)] == issuer_segments
|
|
141
176
|
|
|
142
177
|
@staticmethod
|
|
143
178
|
def _loggable(*, cache_key: str) -> str:
|
|
@@ -149,8 +184,13 @@ class OidcUserInfoManager:
|
|
|
149
184
|
|
|
150
185
|
``access_token`` must already have been verified by the caller; its claims are trusted.
|
|
151
186
|
|
|
187
|
+
Per OIDC Core 5.3.2 the response must carry a ``sub`` equal to the token's ``sub``; a response without
|
|
188
|
+
one, or with a different one, cannot be shown to describe the token's user and is rejected. A token
|
|
189
|
+
that has no ``sub`` has nothing to compare, so its response is accepted (and never cached).
|
|
190
|
+
|
|
152
191
|
Raises:
|
|
153
|
-
ValueError: the token, auth config, discovery document or response is unusable
|
|
192
|
+
ValueError: the token, auth config, discovery document or response is unusable, or the response's
|
|
193
|
+
subject is missing or differs from the token's.
|
|
154
194
|
httpx.HTTPError: the userinfo request failed.
|
|
155
195
|
"""
|
|
156
196
|
if not access_token.token:
|
|
@@ -182,6 +222,10 @@ class OidcUserInfoManager:
|
|
|
182
222
|
user_info = response.json()
|
|
183
223
|
if not isinstance(user_info, dict):
|
|
184
224
|
raise ValueError("User info response is not a valid JSON object")
|
|
225
|
+
if access_token.subject and user_info.get("sub") != access_token.subject:
|
|
226
|
+
# Missing or different: ``sub`` is required in the response, and one that cannot be shown
|
|
227
|
+
# to be the token's subject must not be turned into scopes. Neither subject is logged.
|
|
228
|
+
raise ValueError("User info response subject is missing or differs from the token's subject")
|
|
185
229
|
return user_info
|
|
186
230
|
|
|
187
231
|
async def get_scopes_from_user_info_async(self, *, access_token: Token) -> list[str]:
|
|
@@ -190,7 +234,7 @@ class OidcUserInfoManager:
|
|
|
190
234
|
The userinfo scopes come from the cache when present, else from the endpoint (then cached).
|
|
191
235
|
|
|
192
236
|
Raises:
|
|
193
|
-
ValueError:
|
|
237
|
+
ValueError: no provider matches the token, or the userinfo response is for another subject.
|
|
194
238
|
httpx.HTTPError: the userinfo request failed.
|
|
195
239
|
"""
|
|
196
240
|
tracer = trace.get_tracer(__name__)
|
|
@@ -264,18 +308,33 @@ class OidcUserInfoManager:
|
|
|
264
308
|
return f"{':'.join([cls._KEY_PREFIX, *segments])}|v{cls.CACHE_VERSION}"
|
|
265
309
|
|
|
266
310
|
async def _read_cached_scopes(self, *, cache_key: str) -> list[str] | None:
|
|
267
|
-
|
|
311
|
+
"""Cached userinfo scopes, or None for a miss.
|
|
312
|
+
|
|
313
|
+
A store failure or an entry of the wrong shape is logged and treated as a miss: the cache is an
|
|
314
|
+
optimization, so one bad entry (or a store outage) must not fail every request for that key until the
|
|
315
|
+
TTL expires. The refetched scopes then overwrite the bad entry.
|
|
316
|
+
"""
|
|
268
317
|
key_id = self._loggable(cache_key=cache_key)
|
|
318
|
+
try:
|
|
319
|
+
raw = await self._store.get(cache_key, collection=self._cache_collection)
|
|
320
|
+
except Exception:
|
|
321
|
+
logger.exception(f"Failed to read OIDC userinfo scopes from the cache for key: {key_id}")
|
|
322
|
+
return None
|
|
269
323
|
if raw is None:
|
|
270
324
|
logger.debug(f"OIDC userinfo scopes cache miss for key: {key_id}")
|
|
271
325
|
return None
|
|
272
326
|
try:
|
|
273
327
|
entry = OidcUserInfoCacheEntry.model_validate(raw)
|
|
274
|
-
except ValueError
|
|
275
|
-
|
|
328
|
+
except ValueError:
|
|
329
|
+
logger.warning(f"OIDC userinfo scopes cache entry has invalid shape for key: {key_id}; refetching")
|
|
330
|
+
return None
|
|
276
331
|
scopes = entry.value.get("scopes")
|
|
277
332
|
if not isinstance(scopes, list) or not all(isinstance(s, str) for s in scopes):
|
|
278
|
-
|
|
333
|
+
logger.warning(
|
|
334
|
+
f"OIDC userinfo scopes cache entry has invalid scopes ({type(scopes).__name__}) for key: {key_id}; "
|
|
335
|
+
"refetching"
|
|
336
|
+
)
|
|
337
|
+
return None
|
|
279
338
|
return scopes
|
|
280
339
|
|
|
281
340
|
async def _write_cached_scopes(self, *, cache_key: str, scopes: list[str]) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oidcauthlib
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.24
|
|
4
4
|
Summary: oidcauthlib
|
|
5
5
|
Author-email: Imran Qureshi <imran.qureshi@bwell.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -55,14 +55,17 @@ scopes = await manager.get_scopes_from_user_info_async(access_token=token)
|
|
|
55
55
|
`scope_claims`, then `group_claims` (each value taken whole). Commas separate like spaces, results are
|
|
56
56
|
de-duplicated, and `scope_remove_prefixes` are stripped (first match wins). Nothing beyond `scope`/`scp`
|
|
57
57
|
is read unless configured.
|
|
58
|
-
- **Provider** is matched to the token's `iss
|
|
59
|
-
|
|
60
|
-
never sent to another provider.
|
|
58
|
+
- **Provider** is matched to the token's `iss`: an exact `issuer`, else a `well_known_uri` on the same scheme,
|
|
59
|
+
host and port whose path segments start with the issuer's. Only a token with no `iss` at all is matched by
|
|
60
|
+
its client id. A token matching no provider raises `ValueError`; it is never sent to another provider.
|
|
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.
|
|
61
63
|
- **Cache** key is `oidc-userinfo-scopes:<issuer>:<client_id>:<sub>|v1` with each segment percent-encoded
|
|
62
64
|
(so values containing `:` cannot collide, and two providers never share an entry); only the
|
|
63
65
|
userinfo-derived scopes are stored, never the userinfo response. The token's own scopes are re-derived on every call. A token without
|
|
64
|
-
`sub
|
|
66
|
+
`sub` or without an issuer, or an empty scope list, is not cached. Changes to a user's entitlements are visible after at most
|
|
65
67
|
`cache_ttl_seconds`.
|
|
66
|
-
- **Failures:**
|
|
67
|
-
entry
|
|
68
|
-
or
|
|
68
|
+
- **Failures:** the cache never fails a lookup. A write failure is logged and swallowed; a read failure or a
|
|
69
|
+
malformed entry is logged and treated as a miss, and the refetched scopes overwrite the bad entry. A
|
|
70
|
+
userinfo HTTP error, a missing provider or a mismatched subject is raised, so each service decides whether
|
|
71
|
+
to fail closed or degrade.
|
oidcauthlib-3.0.22/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.0.22
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/middleware/request_scope_middleware.py
RENAMED
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/middleware/token_reader_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/memory/memory_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/repository/mongo/mongo_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/auth/well_known_configuration/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/container/oidc_authlib_container_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/filtering_span_exporter.py
RENAMED
|
File without changes
|
{oidcauthlib-3.0.22 → oidcauthlib-3.0.24}/oidcauthlib/open_telemetry/filtering_span_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|