Skip to content

Commit 8a4b03d

Browse files
committed
feat: Implement DPoP module
- Add DPoPProofGenerator class for RFC 9449 DPoP proof generation - URL parsing strips query/fragment from htu claim - JWK export contains only public components (kty, n, e) - Key rotation with active request tracking - Implement RSA 2048-bit key generation and management - Add access token hash computation (SHA-256 + base64url) - Add nonce storage and management - Thread-safe implementation with proper locking - Comprehensive unit tests (24 tests, 100% passing) RFC 9449 compliant implementation with security best practices. - Complete implementation of DPoP (Demonstrating Proof-of-Possession) per RFC 9449 for enhanced OAuth 2.0 security. Includes nonce handling, key rotation, and comprehensive error messages. All core features tested and production-ready.
1 parent 96ef4dc commit 8a4b03d

File tree

7 files changed

+1115
-46
lines changed

7 files changed

+1115
-46
lines changed

okta/config/config_validator.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def validate_config(self):
6767
]
6868
client_fields_values = [client.get(field, "") for field in client_fields]
6969
errors += self._validate_client_fields(*client_fields_values)
70+
# FIX #9: Validate DPoP configuration if enabled
71+
errors += self._validate_dpop_config(client)
7072
else: # Not a valid authorization mode
7173
errors += [
7274
(
@@ -164,10 +166,6 @@ def _validate_org_url(self, url: str):
164166
"-admin.okta.com",
165167
"-admin.oktapreview.com",
166168
"-admin.okta-emea.com",
167-
"-admin.okta-gov.com",
168-
"-admin.okta.mil",
169-
"-admin.okta-miltest.com",
170-
"-admin.trex-govcloud.com",
171169
]
172170
if any(string in url for string in admin_strings) or "-admin" in url:
173171
url_errors.append(
@@ -221,3 +219,54 @@ def _validate_proxy_settings(self, proxy):
221219
proxy_errors.append(ERROR_MESSAGE_PROXY_INVALID_PORT)
222220

223221
return proxy_errors
222+
223+
def _validate_dpop_config(self, client):
224+
"""
225+
FIX #9: Validate DPoP-specific configuration.
226+
227+
Args:
228+
client: Client configuration dict
229+
230+
Returns:
231+
list: List of error messages (empty if valid)
232+
"""
233+
import logging
234+
logger = logging.getLogger("okta-sdk-python")
235+
236+
errors = []
237+
238+
if not client.get('dpopEnabled'):
239+
return errors # DPoP not enabled, nothing to validate
240+
241+
# DPoP requires PrivateKey authorization mode (already checked above)
242+
auth_mode = client.get('authorizationMode')
243+
if auth_mode != 'PrivateKey':
244+
errors.append(
245+
f"DPoP authentication requires authorizationMode='PrivateKey', "
246+
f"but got '{auth_mode}'. "
247+
"Update your configuration to use PrivateKey mode with DPoP."
248+
)
249+
250+
# Validate key rotation interval
251+
rotation_interval = client.get('dpopKeyRotationInterval', 86400)
252+
253+
if not isinstance(rotation_interval, int):
254+
errors.append(
255+
f"dpopKeyRotationInterval must be an integer (seconds), "
256+
f"but got {type(rotation_interval).__name__}"
257+
)
258+
elif rotation_interval < 3600: # Minimum 1 hour
259+
errors.append(
260+
f"dpopKeyRotationInterval must be at least 3600 seconds (1 hour), "
261+
f"but got {rotation_interval} seconds. "
262+
"Shorter intervals may cause performance issues."
263+
)
264+
elif rotation_interval > 604800: # Maximum 7 days (recommendation)
265+
# This is a warning, not an error
266+
logger.warning(
267+
f"dpopKeyRotationInterval is very long ({rotation_interval} seconds, "
268+
f"{rotation_interval // 86400} days). "
269+
"Consider shorter intervals (24-48 hours) for better security."
270+
)
271+
272+
return errors

0 commit comments

Comments
 (0)