From 9b8b9f2819b0a5c314cd711faceb5465d8c199ce Mon Sep 17 00:00:00 2001 From: Graeme Meyer Date: Thu, 8 Aug 2024 10:59:43 +0100 Subject: [PATCH 1/3] Expire and renew the access token when using OAuth 2.0 --- okta/oauth.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/okta/oauth.py b/okta/oauth.py index 0ff18923..92a3065f 100644 --- a/okta/oauth.py +++ b/okta/oauth.py @@ -1,3 +1,4 @@ +import time from urllib.parse import urlencode, quote from okta.jwt import JWT from okta.http_client import HTTPClient @@ -37,6 +38,13 @@ async def get_access_token(self): str, Exception: Tuple of the access token, error that was raised (if any) """ + + # Check if access token has expired or will expire in the next 5 minutes + current_time = int(time.time()) + if self._access_token and hasattr(self, '_access_token_expiry_time'): + if current_time + 300 >= self._access_token_expiry_time: + self.clear_access_token() + # Return token if already generated if self._access_token: return (self._access_token, None) @@ -83,6 +91,9 @@ async def get_access_token(self): # Otherwise set token and return it self._access_token = parsed_response["access_token"] + + # Set token expiry time + self._access_token_expiry_time = int(time.time()) + parsed_response["expires_in"] return (self._access_token, None) def clear_access_token(self): @@ -92,3 +103,4 @@ def clear_access_token(self): self._access_token = None self._request_executor._cache.delete("OKTA_ACCESS_TOKEN") self._request_executor._default_headers.pop("Authorization", None) + self._access_token_expiry_time = None From 4ad1a428ca65ae9742096b597bbee643fc94da31 Mon Sep 17 00:00:00 2001 From: Graeme Meyer Date: Wed, 28 Aug 2024 10:58:37 +0100 Subject: [PATCH 2/3] Fix typo --- okta/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/okta/client.py b/okta/client.py index 065b32eb..dbea5385 100644 --- a/okta/client.py +++ b/okta/client.py @@ -116,7 +116,7 @@ def __init__(self, user_config: dict = {}): client_config_setter = ConfigSetter() client_config_setter._apply_config({'client': user_config}) self._config = client_config_setter.get_config() - # Prune configuration to remove unnecesary fields + # Prune configuration to remove unnecessary fields self._config = client_config_setter._prune_config(self._config) # Validate configuration ConfigValidator(self._config) From 1b386b96ba7b16e66c8a14c0e1831f6794756b76 Mon Sep 17 00:00:00 2001 From: Graeme Meyer Date: Wed, 28 Aug 2024 11:54:37 +0100 Subject: [PATCH 3/3] Getters, setters, validators and default value for oauthTokenRenewalOffset --- okta/client.py | 2 ++ okta/config/config_setter.py | 4 +++- okta/config/config_validator.py | 15 +++++++++++---- okta/oauth.py | 5 +++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/okta/client.py b/okta/client.py index dbea5385..b0fa318b 100644 --- a/okta/client.py +++ b/okta/client.py @@ -128,6 +128,7 @@ def __init__(self, user_config: dict = {}): self._client_id = None self._scopes = None self._private_key = None + self._oauth_token_renewal_offset = None # Determine which cache to use cache = NoOpCache() @@ -154,6 +155,7 @@ def __init__(self, user_config: dict = {}): self._client_id = self._config["client"]["clientId"] self._scopes = self._config["client"]["scopes"] self._private_key = self._config["client"]["privateKey"] + self._oauth_token_renewal_offset = self._config["client"]["oauthTokenRenewalOffset"] setup_logging(log_level=self._config["client"]["logging"]["logLevel"]) # Check if logging should be enabled diff --git a/okta/config/config_setter.py b/okta/config/config_setter.py index b7d8c5db..b2db40db 100644 --- a/okta/config/config_setter.py +++ b/okta/config/config_setter.py @@ -37,7 +37,8 @@ class ConfigSetter(): }, "rateLimit": { "maxRetries": '' - } + }, + "oauthTokenRenewalOffset": '' }, "testing": { "testingDisableHttpsCheck": '' @@ -116,6 +117,7 @@ def _apply_default_values(self): self._config["client"]["rateLimit"] = { "maxRetries": 2 } + self._config["client"]["oauthTokenRenewalOffset"] = 5 self._config["testing"]["testingDisableHttpsCheck"] = False diff --git a/okta/config/config_validator.py b/okta/config/config_validator.py index 9d72900c..2a0e000f 100644 --- a/okta/config/config_validator.py +++ b/okta/config/config_validator.py @@ -45,9 +45,8 @@ def validate_config(self): self._validate_token( client.get('token', "")) elif client.get('authorizationMode') == "PrivateKey": - client_fields = ['clientId', 'scopes', 'privateKey'] - client_fields_values = [self._config.get( - 'client').get(field, "") for field in client_fields] + client_fields = ['clientId', 'scopes', 'privateKey', 'oauthTokenRenewalOffset'] + client_fields_values = [client.get(field, "") for field in client_fields] errors += self._validate_client_fields(*client_fields_values) else: # Not a valid authorization mode errors += [ @@ -61,7 +60,7 @@ def validate_config(self): f"See {REPO_URL} for usage") def _validate_client_fields(self, client_id, client_scopes, - client_private_key): + client_private_key, oauth_token_renewal_offset): client_fields_errors = [] # check client id @@ -77,6 +76,14 @@ def _validate_client_fields(self, client_id, client_scopes, if not (client_scopes and client_private_key): client_fields_errors.append(ERROR_MESSAGE_SCOPES_PK_MISSING) + # Validate oauthTokenRenewalOffset + if not oauth_token_renewal_offset: + client_fields_errors.append("oauthTokenRenewalOffset must be provided") + if not isinstance(oauth_token_renewal_offset, int): + client_fields_errors.append("oauthTokenRenewalOffset must be a valid integer") + if isinstance(oauth_token_renewal_offset, int) and oauth_token_renewal_offset < 0: + client_fields_errors.append("oauthTokenRenewalOffset must be a non-negative integer") + return client_fields_errors def _validate_token(self, token: str): diff --git a/okta/oauth.py b/okta/oauth.py index 92a3065f..1e94c985 100644 --- a/okta/oauth.py +++ b/okta/oauth.py @@ -39,10 +39,11 @@ async def get_access_token(self): (if any) """ - # Check if access token has expired or will expire in the next 5 minutes + # Check if access token has expired or will expire soon current_time = int(time.time()) if self._access_token and hasattr(self, '_access_token_expiry_time'): - if current_time + 300 >= self._access_token_expiry_time: + renewal_offset = self._config["client"]["oauthTokenRenewalOffset"] * 60 # Convert minutes to seconds + if current_time + renewal_offset >= self._access_token_expiry_time: self.clear_access_token() # Return token if already generated