From 345a1e96ecca65eb58b5b8c072a6edde50703109 Mon Sep 17 00:00:00 2001 From: Amina Tadjer <141734868+atadj@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:01:06 +0100 Subject: [PATCH 1/4] feat: delete sharedkey if invalid and attempt other auth method --- src/sumo/wrapper/_auth_provider.py | 13 ++++++-- src/sumo/wrapper/sumo_client.py | 48 ++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/sumo/wrapper/_auth_provider.py b/src/sumo/wrapper/_auth_provider.py index e28aa19..1f83f1f 100644 --- a/src/sumo/wrapper/_auth_provider.py +++ b/src/sumo/wrapper/_auth_provider.py @@ -99,6 +99,9 @@ def has_case_token(self, case_uuid): get_token_path(self._resource_id, ".sharedkey", case_uuid) ) + def delete_token(self): + return False + pass @@ -400,9 +403,10 @@ class AuthProviderSumoToken(AuthProvider): def __init__(self, resource_id, case_uuid=None): super().__init__(resource_id) protect_token_cache(resource_id, ".sharedkey", case_uuid) - token_path = get_token_path(resource_id, ".sharedkey", case_uuid) - with open(token_path, "r") as f: + self.token_path = get_token_path(resource_id, ".sharedkey", case_uuid) + with open(self.token_path, "r") as f: self._token = f.readline().strip() + return def get_token(self): @@ -411,6 +415,11 @@ def get_token(self): def get_authorization(self): return {"X-SUMO-Token": self._token} + def delete_token(self): + if os.path.exists(self.token_path): + os.unlink(self.token_path) + return True + @tn.retry( retry=tn.retry_if_exception(_maybe_nfs_exception), diff --git a/src/sumo/wrapper/sumo_client.py b/src/sumo/wrapper/sumo_client.py index 4f93981..d04233a 100644 --- a/src/sumo/wrapper/sumo_client.py +++ b/src/sumo/wrapper/sumo_client.py @@ -100,19 +100,6 @@ def __init__( pass pass - cleanup_shared_keys() - - self.auth = get_auth_provider( - client_id=APP_REGISTRATION[env]["CLIENT_ID"], - authority=f"{AUTHORITY_HOST_URI}/{TENANT_ID}", - resource_id=APP_REGISTRATION[env]["RESOURCE_ID"], - interactive=interactive, - refresh_token=refresh_token, - access_token=access_token, - devicecode=devicecode, - case_uuid=case_uuid, - ) - if env == "prod": self.base_url = "https://api.sumo.equinor.com/api/v1" elif env == "localhost": @@ -121,6 +108,41 @@ def __init__( self.base_url = ( f"https://main-sumo-core-{env}.c3.radix.equinor.com/api/v1" ) + cleanup_shared_keys() + + def _get_auth_provider(): + return get_auth_provider( + client_id=APP_REGISTRATION[env]["CLIENT_ID"], + authority=f"{AUTHORITY_HOST_URI}/{TENANT_ID}", + resource_id=APP_REGISTRATION[env]["RESOURCE_ID"], + interactive=interactive, + refresh_token=refresh_token, + access_token=access_token, + devicecode=devicecode, + case_uuid=case_uuid, + ) + + def _try_setup_auth_provider(): + self.auth = _get_auth_provider() + response = httpx.get( + url=self.base_url + "/userpermissions", + headers=self.auth.get_authorization(), + ) + if response.is_success: + return True, False + + elif response.status_code == 401: + return False, self.auth.delete_token() + + else: + raise httpx.HTTPStatusError + + ok, retry = _try_setup_auth_provider() + if retry: + ok, retry = _try_setup_auth_provider() + if retry: + ok, retry = _try_setup_auth_provider() + return def __enter__(self): From f7ea0e79b009cbecf2949056b2844be93b9fefe6 Mon Sep 17 00:00:00 2001 From: Amina Tadjer <141734868+atadj@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:28:58 +0100 Subject: [PATCH 2/4] feat/delete shared key if not valid --- src/sumo/wrapper/_decorators.py | 17 +++++++++++++---- src/sumo/wrapper/sumo_client.py | 10 ++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/sumo/wrapper/_decorators.py b/src/sumo/wrapper/_decorators.py index d53d700..3cb61b2 100644 --- a/src/sumo/wrapper/_decorators.py +++ b/src/sumo/wrapper/_decorators.py @@ -1,14 +1,19 @@ # For sphinx: from functools import wraps +from sumo.wrapper._auth_provider import AuthProviderSumoToken def raise_for_status(func): @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(self, *args, **kwargs): # FIXME: in newer versions of httpx, raise_for_status() is chainable, # so we could simply write # return func(*args, **kwargs).raise_for_status() - response = func(*args, **kwargs) + response = func(self, *args, **kwargs) + if response.status_code == 401 and isinstance( + self.auth, AuthProviderSumoToken + ): + self._handle_invalid_shared_key() response.raise_for_status() return response @@ -17,11 +22,15 @@ def wrapper(*args, **kwargs): def raise_for_status_async(func): @wraps(func) - async def wrapper(*args, **kwargs): + async def wrapper(self, *args, **kwargs): # FIXME: in newer versions of httpx, raise_for_status() is chainable, # so we could simply write # return func(*args, **kwargs).raise_for_status() - response = await func(*args, **kwargs) + response = await func(self, *args, **kwargs) + if response.status_code == 401 and isinstance( + self.auth, AuthProviderSumoToken + ): + self._handle_invalid_shared_key() response.raise_for_status() return response diff --git a/src/sumo/wrapper/sumo_client.py b/src/sumo/wrapper/sumo_client.py index 2a85339..e9cd116 100644 --- a/src/sumo/wrapper/sumo_client.py +++ b/src/sumo/wrapper/sumo_client.py @@ -208,6 +208,16 @@ def blob_client(self) -> BlobClient: self._retry_strategy, ) + def _handle_invalid_shared_key(self): + """Handle the invalid shared key by deleting it.""" + self.auth.delete_token() + logger.error( + "Invalid shared key detected and deleted, run again to reset automatically" + ) + print( + "Invalid shared key detected and deleted, run again to reset automatically" + ) + @raise_for_status def get(self, path: str, params: Optional[Dict] = None) -> httpx.Response: """Performs a GET-request to the Sumo API. From f868bf240269a7f3e8e5f0a0571596a935cf6ae0 Mon Sep 17 00:00:00 2001 From: Amina Tadjer <141734868+atadj@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:18:11 +0100 Subject: [PATCH 3/4] refactor/optimize imports --- src/sumo/wrapper/_decorators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sumo/wrapper/_decorators.py b/src/sumo/wrapper/_decorators.py index 3cb61b2..de5f7c8 100644 --- a/src/sumo/wrapper/_decorators.py +++ b/src/sumo/wrapper/_decorators.py @@ -1,5 +1,6 @@ # For sphinx: from functools import wraps + from sumo.wrapper._auth_provider import AuthProviderSumoToken From f2f55beb873e537e78a10a31c23ea232d73612a3 Mon Sep 17 00:00:00 2001 From: Amina Tadjer <141734868+atadj@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:23:54 +0100 Subject: [PATCH 4/4] feat: remove verification of instance in the decorator and use delete_token response instead --- src/sumo/wrapper/_decorators.py | 10 ++-------- src/sumo/wrapper/sumo_client.py | 11 ++++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/sumo/wrapper/_decorators.py b/src/sumo/wrapper/_decorators.py index de5f7c8..628fad1 100644 --- a/src/sumo/wrapper/_decorators.py +++ b/src/sumo/wrapper/_decorators.py @@ -1,8 +1,6 @@ # For sphinx: from functools import wraps -from sumo.wrapper._auth_provider import AuthProviderSumoToken - def raise_for_status(func): @wraps(func) @@ -11,9 +9,7 @@ def wrapper(self, *args, **kwargs): # so we could simply write # return func(*args, **kwargs).raise_for_status() response = func(self, *args, **kwargs) - if response.status_code == 401 and isinstance( - self.auth, AuthProviderSumoToken - ): + if response.status_code == 401: self._handle_invalid_shared_key() response.raise_for_status() return response @@ -28,9 +24,7 @@ async def wrapper(self, *args, **kwargs): # so we could simply write # return func(*args, **kwargs).raise_for_status() response = await func(self, *args, **kwargs) - if response.status_code == 401 and isinstance( - self.auth, AuthProviderSumoToken - ): + if response.status_code == 401: self._handle_invalid_shared_key() response.raise_for_status() return response diff --git a/src/sumo/wrapper/sumo_client.py b/src/sumo/wrapper/sumo_client.py index e9cd116..c8780a7 100644 --- a/src/sumo/wrapper/sumo_client.py +++ b/src/sumo/wrapper/sumo_client.py @@ -210,13 +210,10 @@ def blob_client(self) -> BlobClient: def _handle_invalid_shared_key(self): """Handle the invalid shared key by deleting it.""" - self.auth.delete_token() - logger.error( - "Invalid shared key detected and deleted, run again to reset automatically" - ) - print( - "Invalid shared key detected and deleted, run again to reset automatically" - ) + if self.auth.delete_token(): + print( + "Invalid shared key detected and deleted, run again to reset automatically" + ) @raise_for_status def get(self, path: str, params: Optional[Dict] = None) -> httpx.Response: