@@ -342,28 +342,32 @@ def _authenticate_oidc(
342342 * ,
343343 provider_id : str ,
344344 store_refresh_token : bool = False ,
345- fallback_refresh_token_to_store : Optional [str ] = None ,
345+ auto_renew_from_refresh_token : bool = False ,
346+ fallback_refresh_token : Optional [str ] = None ,
346347 oidc_auth_renewer : Optional [OidcAuthenticator ] = None ,
347348 ) -> Connection :
348349 """
349350 Authenticate through OIDC and set up bearer token (based on OIDC access_token) for further requests.
350351 """
351- tokens = authenticator .get_tokens (request_refresh_token = store_refresh_token )
352+ request_refresh_token = store_refresh_token or (not oidc_auth_renewer and auto_renew_from_refresh_token )
353+ tokens = authenticator .get_tokens (request_refresh_token = request_refresh_token )
352354 _log .info ("Obtained tokens: {t}" .format (t = [k for k , v in tokens ._asdict ().items () if v ]))
355+
356+ refresh_token = tokens .refresh_token or fallback_refresh_token
353357 if store_refresh_token :
354- refresh_token = tokens .refresh_token or fallback_refresh_token_to_store
355358 if refresh_token :
356359 self ._get_refresh_token_store ().set_refresh_token (
357360 issuer = authenticator .provider_info .issuer ,
358361 client_id = authenticator .client_id ,
359362 refresh_token = refresh_token
360363 )
361- if not oidc_auth_renewer :
362- oidc_auth_renewer = OidcRefreshTokenAuthenticator (
363- client_info = authenticator .client_info , refresh_token = refresh_token
364- )
365364 else :
366365 _log .warning ("No OIDC refresh token to store." )
366+ if not oidc_auth_renewer and auto_renew_from_refresh_token and refresh_token :
367+ oidc_auth_renewer = OidcRefreshTokenAuthenticator (
368+ client_info = authenticator .client_info , refresh_token = refresh_token
369+ )
370+
367371 token = tokens .access_token
368372 self .auth = OidcBearerAuth (provider_id = provider_id , access_token = token )
369373 self ._oidc_auth_renewer = oidc_auth_renewer
@@ -452,7 +456,12 @@ def authenticate_oidc_resource_owner_password_credentials(
452456 authenticator = OidcResourceOwnerPasswordAuthenticator (
453457 client_info = client_info , username = username , password = password
454458 )
455- return self ._authenticate_oidc (authenticator , provider_id = provider_id , store_refresh_token = store_refresh_token )
459+ return self ._authenticate_oidc (
460+ authenticator ,
461+ provider_id = provider_id ,
462+ store_refresh_token = store_refresh_token ,
463+ oidc_auth_renewer = authenticator ,
464+ )
456465
457466 def authenticate_oidc_refresh_token (
458467 self ,
@@ -493,7 +502,7 @@ def authenticate_oidc_refresh_token(
493502 authenticator ,
494503 provider_id = provider_id ,
495504 store_refresh_token = store_refresh_token ,
496- fallback_refresh_token_to_store = refresh_token ,
505+ fallback_refresh_token = refresh_token ,
497506 oidc_auth_renewer = authenticator ,
498507 )
499508
@@ -534,7 +543,13 @@ def authenticate_oidc_device(
534543 authenticator = OidcDeviceAuthenticator (
535544 client_info = client_info , use_pkce = use_pkce , max_poll_time = max_poll_time , ** kwargs
536545 )
537- return self ._authenticate_oidc (authenticator , provider_id = provider_id , store_refresh_token = store_refresh_token )
546+ return self ._authenticate_oidc (
547+ authenticator ,
548+ provider_id = provider_id ,
549+ store_refresh_token = store_refresh_token ,
550+ # TODO: expose `auto_renew_from_refresh_token` directly as option instead of reusing `store_refresh_token` arg?
551+ auto_renew_from_refresh_token = store_refresh_token ,
552+ )
538553
539554 def authenticate_oidc (
540555 self ,
@@ -604,7 +619,8 @@ def authenticate_oidc(
604619 authenticator ,
605620 provider_id = provider_id ,
606621 store_refresh_token = store_refresh_token ,
607- fallback_refresh_token_to_store = refresh_token ,
622+ fallback_refresh_token = refresh_token ,
623+ oidc_auth_renewer = authenticator ,
608624 )
609625 # TODO: pluggable/jupyter-aware display function?
610626 print ("Authenticated using refresh token." )
@@ -622,6 +638,8 @@ def authenticate_oidc(
622638 authenticator ,
623639 provider_id = provider_id ,
624640 store_refresh_token = store_refresh_token ,
641+ # TODO: expose `auto_renew_from_refresh_token` directly as option instead of reusing `store_refresh_token` arg?
642+ auto_renew_from_refresh_token = store_refresh_token ,
625643 )
626644 print ("Authenticated using device code flow." )
627645 return con
@@ -665,6 +683,28 @@ def authenticate_bearer_token(self, bearer_token: str) -> Connection:
665683 self ._oidc_auth_renewer = None
666684 return self
667685
686+ def try_access_token_refresh (self , * , reason : Optional [str ] = None ) -> bool :
687+ """
688+ Try to get a fresh access token if possible.
689+ Returns whether a new access token was obtained.
690+ """
691+ reason = f" Reason: { reason } " if reason else ""
692+ if isinstance (self .auth , OidcBearerAuth ) and self ._oidc_auth_renewer :
693+ try :
694+ self ._authenticate_oidc (
695+ authenticator = self ._oidc_auth_renewer ,
696+ provider_id = self ._oidc_auth_renewer .provider_info .id ,
697+ store_refresh_token = False ,
698+ oidc_auth_renewer = self ._oidc_auth_renewer ,
699+ )
700+ _log .info (f"Obtained new access token (grant { self ._oidc_auth_renewer .grant_type !r} ).{ reason } " )
701+ return True
702+ except OpenEoClientException as auth_exc :
703+ _log .error (
704+ f"Failed to obtain new access token (grant { self ._oidc_auth_renewer .grant_type !r} ): { auth_exc !r} .{ reason } "
705+ )
706+ return False
707+
668708 def request (
669709 self ,
670710 method : str ,
@@ -690,24 +730,11 @@ def _request():
690730 api_exc .http_status_code in {HTTP_401_UNAUTHORIZED , HTTP_403_FORBIDDEN }
691731 and api_exc .code == "TokenInvalid"
692732 ):
693- # Auth token expired: can we refresh?
694- if isinstance (self .auth , OidcBearerAuth ) and self ._oidc_auth_renewer :
695- msg = f"OIDC access token expired ({ api_exc .http_status_code } { api_exc .code } )."
696- try :
697- self ._authenticate_oidc (
698- authenticator = self ._oidc_auth_renewer ,
699- provider_id = self ._oidc_auth_renewer .provider_info .id ,
700- store_refresh_token = False ,
701- oidc_auth_renewer = self ._oidc_auth_renewer ,
702- )
703- _log .info (f"{ msg } Obtained new access token (grant { self ._oidc_auth_renewer .grant_type !r} )." )
704- except OpenEoClientException as auth_exc :
705- _log .error (
706- f"{ msg } Failed to obtain new access token (grant { self ._oidc_auth_renewer .grant_type !r} ): { auth_exc !r} ."
707- )
708- else :
709- # Retry request.
710- return _request ()
733+ # Retry if we can refresh the access token
734+ if self .try_access_token_refresh (
735+ reason = f"OIDC access token expired ({ api_exc .http_status_code } { api_exc .code } )."
736+ ):
737+ return _request ()
711738 raise
712739
713740 def describe_account (self ) -> dict :
@@ -821,12 +848,12 @@ def list_services(self) -> list:
821848 :return: data_dict: Dict All available services
822849 """
823850 # TODO return parsed service objects
824- services = self .get (' /services' , expected_status = 200 ).json ()[ "services" ]
825- federation_missing = federation_extension .get_federation_missing (data = services , resource_name = "services" )
851+ response = self .get (" /services" , expected_status = 200 ).json ()
852+ federation_missing = federation_extension .get_federation_missing (data = response , resource_name = "services" )
826853 federation = self .capabilities ().ext_federation_backend_details ()
827854 return VisualList (
828855 "data-table" ,
829- data = services ,
856+ data = response [ " services" ] ,
830857 parameters = {"columns" : "services" , "missing" : federation_missing , "federation" : federation },
831858 )
832859
0 commit comments