oidcauthlib 3.0.4__tar.gz → 3.0.6__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 (133) hide show
  1. {oidcauthlib-3.0.4/oidcauthlib.egg-info → oidcauthlib-3.0.6}/PKG-INFO +1 -1
  2. oidcauthlib-3.0.6/VERSION +1 -0
  3. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/auth_manager.py +4 -0
  4. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/dcr/dcr_client.py +30 -2
  5. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/dcr/dcr_manager.py +63 -3
  6. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache.py +3 -0
  7. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/url_validator.py +33 -11
  8. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6/oidcauthlib.egg-info}/PKG-INFO +1 -1
  9. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/test_auth_manager.py +11 -2
  10. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/test_register_dynamic_provider.py +10 -1
  11. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/well_known_configuration/test_well_known_configuration_cache.py +12 -0
  12. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/utilities/test_url_validator.py +27 -0
  13. oidcauthlib-3.0.4/VERSION +0 -1
  14. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/LICENSE +0 -0
  15. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/MANIFEST.in +0 -0
  16. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/Makefile +0 -0
  17. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/README.md +0 -0
  18. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/__init__.py +0 -0
  19. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/__init__.py +0 -0
  20. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/auth_helper.py +0 -0
  21. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/cache/__init__.py +0 -0
  22. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/cache/oauth_cache.py +0 -0
  23. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/cache/oauth_memory_cache.py +0 -0
  24. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/cache/oauth_mongo_cache.py +0 -0
  25. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/config/__init__.py +0 -0
  26. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/config/auth_config.py +0 -0
  27. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/config/auth_config_reader.py +0 -0
  28. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/dcr/__init__.py +0 -0
  29. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/dcr/dcr_registration.py +0 -0
  30. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/exceptions/__init__.py +0 -0
  31. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/exceptions/authorization_bearer_token_expired_exception.py +0 -0
  32. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/exceptions/authorization_bearer_token_invalid_exception.py +0 -0
  33. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/exceptions/authorization_bearer_token_missing_exception.py +0 -0
  34. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/exceptions/authorization_needed_exception.py +0 -0
  35. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/fastapi_auth_manager.py +0 -0
  36. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/middleware/__init__.py +0 -0
  37. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/middleware/request_scope_middleware.py +0 -0
  38. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/middleware/token_reader_middleware.py +0 -0
  39. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/__init__.py +0 -0
  40. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/auth.py +0 -0
  41. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/base_db_model.py +0 -0
  42. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/cache_item.py +0 -0
  43. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/client_key_set.py +0 -0
  44. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/models/token.py +0 -0
  45. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/__init__.py +0 -0
  46. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/base_repository.py +0 -0
  47. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/memory/__init__.py +0 -0
  48. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/memory/memory_repository.py +0 -0
  49. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/mongo/__init__.py +0 -0
  50. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/mongo/mongo_repository.py +0 -0
  51. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/repository/repository_factory.py +0 -0
  52. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/routers/__init__.py +0 -0
  53. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/routers/auth_router.py +0 -0
  54. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/token_reader.py +0 -0
  55. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/__init__.py +0 -0
  56. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/auth_server_metadata.py +0 -0
  57. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/auth_server_metadata_discovery.py +0 -0
  58. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/well_known_configuration_cache_result.py +0 -0
  59. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/auth/well_known_configuration/well_known_configuration_manager.py +0 -0
  60. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/container/__init__.py +0 -0
  61. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/container/oidc_authlib_container_factory.py +0 -0
  62. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/open_telemetry/__init__.py +0 -0
  63. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/open_telemetry/attribute_names.py +0 -0
  64. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/open_telemetry/span_names.py +0 -0
  65. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/py.typed +0 -0
  66. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/__init__.py +0 -0
  67. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/cache_to_collection_mapper.py +0 -0
  68. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/memory_storage_factory.py +0 -0
  69. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/mongo_gridfs_db.py +0 -0
  70. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/mongo_gridfs_exception.py +0 -0
  71. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/mongo_storage_factory.py +0 -0
  72. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/storage_factory.py +0 -0
  73. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/storage/storage_factory_creator.py +0 -0
  74. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/__init__.py +0 -0
  75. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/cached.py +0 -0
  76. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/environment/__init__.py +0 -0
  77. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/environment/abstract_environment_variables.py +0 -0
  78. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/environment/oidc_environment_variables.py +0 -0
  79. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/logger/__init__.py +0 -0
  80. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/logger/log_levels.py +0 -0
  81. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/logger/logging_response.py +0 -0
  82. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/logger/logging_transport.py +0 -0
  83. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib/utilities/mongo_url_utils.py +0 -0
  84. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib.egg-info/SOURCES.txt +0 -0
  85. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib.egg-info/dependency_links.txt +0 -0
  86. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib.egg-info/not-zip-safe +0 -0
  87. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib.egg-info/requires.txt +0 -0
  88. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/oidcauthlib.egg-info/top_level.txt +0 -0
  89. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/setup.cfg +0 -0
  90. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/setup.py +0 -0
  91. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/__init__.py +0 -0
  92. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/__init__.py +0 -0
  93. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/cache/__init__.py +0 -0
  94. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/cache/test_oauth_memory_cache.py +0 -0
  95. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/config/__init__.py +0 -0
  96. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/config/test_auth_config.py +0 -0
  97. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/config/test_auth_config_reader.py +0 -0
  98. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/config/test_auth_config_reader_register.py +0 -0
  99. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/config/test_auth_config_reader_thread_safety.py +0 -0
  100. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/conftest.py +0 -0
  101. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/dcr/__init__.py +0 -0
  102. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/dcr/test_dcr_client.py +0 -0
  103. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/dcr/test_dcr_manager.py +0 -0
  104. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/middleware/__init__.py +0 -0
  105. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/middleware/test_request_scope_middleware.py +0 -0
  106. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/middleware/test_token_reader_middleware.py +0 -0
  107. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/models/__init__.py +0 -0
  108. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/models/test_auth.py +0 -0
  109. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/models/test_base_db_model.py +0 -0
  110. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/models/test_cache_item.py +0 -0
  111. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/models/test_token.py +0 -0
  112. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/__init__.py +0 -0
  113. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/memory/__init__.py +0 -0
  114. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/memory/test_memory_repository.py +0 -0
  115. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/mongo/__init__.py +0 -0
  116. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/mongo/test_mongo_repository.py +0 -0
  117. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/repository/mongo/test_mongo_repository_real.py +0 -0
  118. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/test_auth_helper.py +0 -0
  119. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/test_auth_helper2.py +0 -0
  120. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/well_known_configuration/__init__.py +0 -0
  121. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/well_known_configuration/test_auth_server_metadata_discovery.py +0 -0
  122. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/auth/well_known_configuration/test_well_known_configuration_manager.py +0 -0
  123. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/conftest.py +0 -0
  124. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/storage/__init__.py +0 -0
  125. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/storage/test_mongo_gridfs_db.py +0 -0
  126. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/storage/test_mongo_gridfs_race_setup.py +0 -0
  127. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/storage/test_mongo_store_factory.py +0 -0
  128. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/storage/test_storage_factory_security.py +0 -0
  129. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/test_mongo_url_utils.py +0 -0
  130. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/test_simple.py +0 -0
  131. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/utilities/__init__.py +0 -0
  132. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/utilities/test_cached.py +0 -0
  133. {oidcauthlib-3.0.4 → oidcauthlib-3.0.6}/tests/utilities/test_mongo_url_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oidcauthlib
3
- Version: 3.0.4
3
+ Version: 3.0.6
4
4
  Summary: oidcauthlib
5
5
  Home-page: https://github.com/icanbwell/oidc-auth-lib
6
6
  Author: Imran Qureshi
@@ -0,0 +1 @@
1
+ 3.0.6
@@ -33,6 +33,7 @@ from oidcauthlib.utilities.logger.log_levels import SRC_LOG_LEVELS
33
33
  from oidcauthlib.utilities.logger.logging_transport import (
34
34
  LoggingTransport,
35
35
  )
36
+ from oidcauthlib.utilities.url_validator import validate_url
36
37
 
37
38
  logger = logging.getLogger(__name__)
38
39
  logger.setLevel(SRC_LOG_LEVELS["AUTH"])
@@ -199,9 +200,12 @@ class AuthManager:
199
200
  }
200
201
 
201
202
  if auth_config.authorization_endpoint and auth_config.token_endpoint:
203
+ validate_url(auth_config.authorization_endpoint)
204
+ validate_url(auth_config.token_endpoint)
202
205
  register_kwargs["authorize_url"] = auth_config.authorization_endpoint
203
206
  register_kwargs["access_token_url"] = auth_config.token_endpoint
204
207
  elif auth_config.well_known_uri:
208
+ validate_url(auth_config.well_known_uri)
205
209
  register_kwargs["server_metadata_url"] = auth_config.well_known_uri
206
210
  else:
207
211
  raise ValueError(
@@ -46,7 +46,16 @@ class DcrClient:
46
46
  if contacts:
47
47
  payload["contacts"] = contacts
48
48
 
49
- logger.info("Performing DCR at '%s'", registration_url)
49
+ logger.info(
50
+ "DCR: Sending registration request to '%s' with payload keys: %s",
51
+ registration_url,
52
+ sorted(payload.keys()),
53
+ )
54
+ logger.debug(
55
+ "DCR: Full registration payload for '%s': %s",
56
+ registration_url,
57
+ payload,
58
+ )
50
59
 
51
60
  async with httpx.AsyncClient(timeout=self._timeout_seconds) as client:
52
61
  response = await client.post(
@@ -57,6 +66,12 @@ class DcrClient:
57
66
  try:
58
67
  response.raise_for_status()
59
68
  except httpx.HTTPStatusError as e:
69
+ logger.error(
70
+ "DCR: Registration failed at '%s' — HTTP %s. Response body: %s",
71
+ registration_url,
72
+ e.response.status_code,
73
+ e.response.text[:500],
74
+ )
60
75
  raise ValueError(
61
76
  f"DCR registration failed at '{registration_url}' "
62
77
  f"with status {e.response.status_code}"
@@ -65,14 +80,27 @@ class DcrClient:
65
80
 
66
81
  if "client_id" not in dcr_response:
67
82
  response_keys: list[str] = sorted(dcr_response.keys())
83
+ logger.error(
84
+ "DCR: Response from '%s' missing 'client_id'. Keys present: %s",
85
+ registration_url,
86
+ response_keys,
87
+ )
68
88
  raise ValueError(
69
89
  f"DCR response from '{registration_url}' missing 'client_id'. "
70
90
  f"Response keys: {response_keys}"
71
91
  )
72
92
 
73
93
  logger.info(
74
- "DCR successful at '%s': client_id=%s",
94
+ "DCR: Registration successful at '%s' — client_id=%s, "
95
+ "has_secret=%s, expires_at=%s",
75
96
  registration_url,
76
97
  dcr_response["client_id"],
98
+ "client_secret" in dcr_response,
99
+ dcr_response.get("client_secret_expires_at", "not_set"),
100
+ )
101
+ logger.debug(
102
+ "DCR: Full response from '%s': %s",
103
+ registration_url,
104
+ {k: v for k, v in dcr_response.items() if k != "client_secret"},
77
105
  )
78
106
  return dcr_response
@@ -52,9 +52,20 @@ class DcrManager:
52
52
  contacts: list[str] | None = None,
53
53
  ) -> DcrRegistration | None:
54
54
  if client_id:
55
+ logger.info(
56
+ "DCR: Skipping registration for '%s' — client_id already "
57
+ "provided (client_id=%s)",
58
+ auth_provider,
59
+ client_id,
60
+ )
55
61
  return None
56
62
 
57
63
  if not registration_url:
64
+ logger.error(
65
+ "DCR: No registration_url and no client_id for '%s' — "
66
+ "cannot resolve credentials",
67
+ auth_provider,
68
+ )
58
69
  raise ValueError(
59
70
  f"registration_url is required for DCR when client_id is not "
60
71
  f"provided (auth_provider='{auth_provider}')"
@@ -62,19 +73,42 @@ class DcrManager:
62
73
 
63
74
  validate_url(registration_url)
64
75
 
76
+ logger.info(
77
+ "DCR: Resolving credentials for '%s' via registration_url='%s'",
78
+ auth_provider,
79
+ registration_url,
80
+ )
81
+
65
82
  cached = await self._find_cached(
66
83
  auth_provider=auth_provider,
67
84
  registration_url=registration_url,
68
85
  )
69
86
 
70
87
  if cached and not self._is_expired(cached):
71
- logger.debug(
72
- "Using cached DCR credentials for '%s' (client_id=%s)",
88
+ logger.info(
89
+ "DCR: Using cached credentials for '%s' — client_id=%s, expires_at=%s",
73
90
  auth_provider,
74
91
  cached.client_id,
92
+ cached.client_secret_expires_at or "never",
75
93
  )
76
94
  return cached
77
95
 
96
+ if cached and self._is_expired(cached):
97
+ logger.info(
98
+ "DCR: Cached credentials for '%s' are expired "
99
+ "(client_id=%s, expired_at=%s) — re-registering",
100
+ auth_provider,
101
+ cached.client_id,
102
+ cached.client_secret_expires_at,
103
+ )
104
+ else:
105
+ logger.info(
106
+ "DCR: No cached credentials found for '%s' — performing "
107
+ "new registration at '%s'",
108
+ auth_provider,
109
+ registration_url,
110
+ )
111
+
78
112
  dcr_response = await self._dcr_client.register(
79
113
  registration_url=registration_url,
80
114
  redirect_uri=self._redirect_uri,
@@ -84,11 +118,17 @@ class DcrManager:
84
118
  contacts=contacts,
85
119
  )
86
120
 
87
- return await self._persist(
121
+ registration = await self._persist(
88
122
  auth_provider=auth_provider,
89
123
  registration_url=registration_url,
90
124
  dcr_response=dcr_response,
91
125
  )
126
+ logger.info(
127
+ "DCR: Persisted credentials for '%s' — client_id=%s",
128
+ auth_provider,
129
+ registration.client_id,
130
+ )
131
+ return registration
92
132
 
93
133
  async def _find_cached(
94
134
  self,
@@ -96,6 +136,13 @@ class DcrManager:
96
136
  auth_provider: str,
97
137
  registration_url: str,
98
138
  ) -> DcrRegistration | None:
139
+ logger.debug(
140
+ "DCR: Querying cache for auth_provider='%s', "
141
+ "registration_url='%s', collection='%s'",
142
+ auth_provider,
143
+ registration_url,
144
+ self._collection_name,
145
+ )
99
146
  results = await self._repository.find_many(
100
147
  collection_name=self._collection_name,
101
148
  model_class=DcrRegistration,
@@ -104,6 +151,19 @@ class DcrManager:
104
151
  "registration_url": registration_url,
105
152
  },
106
153
  )
154
+ if results:
155
+ logger.debug(
156
+ "DCR: Cache hit for '%s' — found %d result(s), client_id=%s",
157
+ auth_provider,
158
+ len(results),
159
+ results[0].client_id,
160
+ )
161
+ else:
162
+ logger.debug(
163
+ "DCR: Cache miss for '%s' at '%s'",
164
+ auth_provider,
165
+ registration_url,
166
+ )
107
167
  return results[0] if results else None
108
168
 
109
169
  @staticmethod
@@ -20,6 +20,7 @@ from oidcauthlib.utilities.environment.oidc_environment_variables import (
20
20
  )
21
21
  from oidcauthlib.utilities.logger.log_levels import SRC_LOG_LEVELS
22
22
  from oidcauthlib.open_telemetry.attribute_names import OidcOpenTelemetryAttributeNames
23
+ from oidcauthlib.utilities.url_validator import validate_url
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
  logger.setLevel(SRC_LOG_LEVELS["AUTH"])
@@ -249,6 +250,7 @@ class WellKnownConfigurationCache:
249
250
  span.set_attribute(
250
251
  OidcOpenTelemetryAttributeNames.WELL_KNOWN_URI, well_known_uri
251
252
  )
253
+ validate_url(well_known_uri)
252
254
  async with httpx.AsyncClient() as client:
253
255
  try:
254
256
  logger.info(
@@ -465,6 +467,7 @@ class WellKnownConfigurationCache:
465
467
  Security:
466
468
  - Keys are not logged; only counts are logged to avoid PII/token leakage.
467
469
  """
470
+ validate_url(jwks_uri)
468
471
  async with httpx.AsyncClient() as client:
469
472
  try:
470
473
  logger.info(f"Fetching JWKS from {jwks_uri}")
@@ -3,10 +3,15 @@
3
3
  Validates URLs before making HTTP requests to prevent Server-Side Request
4
4
  Forgery (SSRF) attacks. Enforces HTTPS-only, rejects private/reserved IP
5
5
  ranges, and resolves hostnames to verify the target is not internal.
6
+
7
+ Set the ``AUTH_ALLOW_HTTP_URLS`` environment variable to ``true`` to relax
8
+ HTTPS-only enforcement and private-IP blocking for local development
9
+ (e.g. Docker Compose with ``http://keycloak:8080``).
6
10
  """
7
11
 
8
12
  import ipaddress
9
13
  import logging
14
+ import os
10
15
  import socket
11
16
  from urllib.parse import urlparse
12
17
 
@@ -59,13 +64,23 @@ def _is_private_ip(addr: str) -> bool:
59
64
  return any(ip in network for network in _BLOCKED_NETWORKS)
60
65
 
61
66
 
67
+ def _env_allow_http() -> bool:
68
+ """Return True when AUTH_ALLOW_HTTP_URLS is set to a truthy value."""
69
+ return os.environ.get("AUTH_ALLOW_HTTP_URLS", "").lower() in (
70
+ "true",
71
+ "1",
72
+ "yes",
73
+ )
74
+
75
+
62
76
  def validate_url(url: str, *, allow_http: bool = False) -> str:
63
77
  """Validate a URL for safe outbound HTTP requests.
64
78
 
65
79
  Args:
66
80
  url: The URL to validate.
67
81
  allow_http: If True, permit ``http://`` in addition to ``https://``.
68
- Defaults to False (HTTPS only).
82
+ Defaults to False (HTTPS only). Also enabled globally by the
83
+ ``AUTH_ALLOW_HTTP_URLS`` environment variable.
69
84
 
70
85
  Returns:
71
86
  The validated URL (unchanged).
@@ -73,6 +88,8 @@ def validate_url(url: str, *, allow_http: bool = False) -> str:
73
88
  Raises:
74
89
  ValueError: If the URL fails any validation check.
75
90
  """
91
+ allow_http = allow_http or _env_allow_http()
92
+
76
93
  parsed = urlparse(url)
77
94
 
78
95
  # --- scheme ---
@@ -80,7 +97,8 @@ def validate_url(url: str, *, allow_http: bool = False) -> str:
80
97
  if parsed.scheme not in allowed_schemes:
81
98
  raise ValueError(
82
99
  f"URL scheme must be {' or '.join(sorted(allowed_schemes))}, "
83
- f"got '{parsed.scheme}' in '{url}'"
100
+ f"got '{parsed.scheme}' in '{url}'. "
101
+ f"Set AUTH_ALLOW_HTTP_URLS=true to allow http:// URLs."
84
102
  )
85
103
 
86
104
  # --- hostname ---
@@ -98,15 +116,19 @@ def validate_url(url: str, *, allow_http: bool = False) -> str:
98
116
  raise ValueError(f"URL must use a hostname, not a raw IP address: '{hostname}'")
99
117
 
100
118
  # --- resolve DNS and check all IPs ---
101
- try:
102
- addr_infos = socket.getaddrinfo(hostname, parsed.port or 443)
103
- except socket.gaierror as exc:
104
- raise ValueError(f"Cannot resolve hostname '{hostname}': {exc}") from exc
105
-
106
- for _family, _type, _proto, _canonname, sockaddr in addr_infos:
107
- ip_str = str(sockaddr[0])
108
- if _is_private_ip(ip_str):
109
- raise ValueError(f"URL '{url}' resolves to blocked IP {ip_str}")
119
+ # When allow_http is enabled (local development), skip the private-IP
120
+ # check because Docker-internal hostnames (e.g. keycloak) resolve to
121
+ # RFC 1918 addresses on the container network.
122
+ if not allow_http:
123
+ try:
124
+ addr_infos = socket.getaddrinfo(hostname, parsed.port or 443)
125
+ except socket.gaierror as exc:
126
+ raise ValueError(f"Cannot resolve hostname '{hostname}': {exc}") from exc
127
+
128
+ for _family, _type, _proto, _canonname, sockaddr in addr_infos:
129
+ ip_str = str(sockaddr[0])
130
+ if _is_private_ip(ip_str):
131
+ raise ValueError(f"URL '{url}' resolves to blocked IP {ip_str}")
110
132
 
111
133
  logger.debug("URL validated: %s", url)
112
134
  return url
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oidcauthlib
3
- Version: 3.0.4
3
+ Version: 3.0.6
4
4
  Summary: oidcauthlib
5
5
  Home-page: https://github.com/icanbwell/oidc-auth-lib
6
6
  Author: Imran Qureshi
@@ -4,8 +4,7 @@ import pytest
4
4
  import httpx
5
5
  import respx
6
6
  from unittest.mock import patch
7
-
8
- from typing import Any, Dict, List, Optional, override
7
+ from typing import Any, Dict, Generator, List, Optional, override
9
8
 
10
9
  from oidcauthlib.auth.auth_manager import AuthManager
11
10
  from oidcauthlib.auth.auth_helper import AuthHelper
@@ -188,6 +187,16 @@ def well_known_manager(
188
187
  )
189
188
 
190
189
 
190
+ _PATCH_VALIDATE = "oidcauthlib.auth.auth_manager.validate_url"
191
+
192
+
193
+ @pytest.fixture(autouse=True)
194
+ def _bypass_url_validation() -> Generator[None, None, None]:
195
+ """Skip DNS resolution in validate_url for unit tests with fake hostnames."""
196
+ with patch(_PATCH_VALIDATE, side_effect=lambda url, **kw: url):
197
+ yield
198
+
199
+
191
200
  # ---------------- Tests -----------------
192
201
  @pytest.mark.asyncio
193
202
  async def test_init_memory_cache(
@@ -1,5 +1,5 @@
1
1
  from typing import Any, AsyncGenerator
2
- from unittest.mock import AsyncMock, MagicMock
2
+ from unittest.mock import AsyncMock, MagicMock, patch
3
3
 
4
4
  import pytest
5
5
  from simple_container.container.interfaces import IContainer
@@ -10,6 +10,15 @@ from oidcauthlib.auth.dcr.dcr_registration import DcrRegistration
10
10
 
11
11
  pytestmark = pytest.mark.asyncio
12
12
 
13
+ _PATCH_VALIDATE = "oidcauthlib.auth.auth_manager.validate_url"
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def _bypass_url_validation() -> Any:
18
+ """Skip DNS resolution in validate_url for unit tests with fake hostnames."""
19
+ with patch(_PATCH_VALIDATE, side_effect=lambda url, **kw: url):
20
+ yield
21
+
13
22
 
14
23
  @pytest.fixture(scope="function")
15
24
  async def test_container() -> AsyncGenerator[None, None]:
@@ -1,4 +1,7 @@
1
1
  import asyncio
2
+ from typing import Any
3
+ from unittest.mock import patch
4
+
2
5
  import respx
3
6
  import httpx
4
7
  import pytest
@@ -16,6 +19,15 @@ from oidcauthlib.utilities.environment.oidc_environment_variables import (
16
19
  OidcEnvironmentVariables,
17
20
  )
18
21
 
22
+ _PATCH_VALIDATE = "oidcauthlib.auth.well_known_configuration.well_known_configuration_cache.validate_url"
23
+
24
+
25
+ @pytest.fixture(autouse=True)
26
+ def _bypass_url_validation() -> Any:
27
+ """Skip DNS resolution in validate_url for unit tests with fake hostnames."""
28
+ with patch(_PATCH_VALIDATE, side_effect=lambda url, **kw: url):
29
+ yield
30
+
19
31
 
20
32
  @pytest.mark.asyncio
21
33
  async def test_get_async_caches_on_first_call(test_container: IContainer) -> None:
@@ -126,6 +126,33 @@ class TestDNSRebindingProtection:
126
126
  validate_url("https://some-external-host.com/register")
127
127
 
128
128
 
129
+ class TestEnvVarAllowHttp:
130
+ """AUTH_ALLOW_HTTP_URLS env var globally enables allow_http."""
131
+
132
+ def test_env_var_allows_http(self) -> None:
133
+ with patch.dict("os.environ", {"AUTH_ALLOW_HTTP_URLS": "true"}):
134
+ assert (
135
+ validate_url(
136
+ "http://keycloak:8080/realms/test/.well-known/openid-configuration"
137
+ )
138
+ == "http://keycloak:8080/realms/test/.well-known/openid-configuration"
139
+ )
140
+
141
+ def test_env_var_skips_private_ip_check(self) -> None:
142
+ with patch.dict("os.environ", {"AUTH_ALLOW_HTTP_URLS": "true"}):
143
+ with patch(
144
+ "oidcauthlib.utilities.url_validator.socket.getaddrinfo"
145
+ ) as mock_gai:
146
+ mock_gai.return_value = [(2, 1, 6, "", ("172.18.0.5", 8080))]
147
+ validate_url("http://keycloak:8080/realms/test")
148
+ mock_gai.assert_not_called()
149
+
150
+ def test_env_var_false_keeps_https_only(self) -> None:
151
+ with patch.dict("os.environ", {"AUTH_ALLOW_HTTP_URLS": "false"}):
152
+ with pytest.raises(ValueError, match="AUTH_ALLOW_HTTP_URLS"):
153
+ validate_url("http://example.com/register")
154
+
155
+
129
156
  class TestPublicHostnamesAccepted:
130
157
  @pytest.mark.parametrize(
131
158
  "ip",
oidcauthlib-3.0.4/VERSION DELETED
@@ -1 +0,0 @@
1
- 3.0.4
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes