From 08351362ad1c0edb5fb2db50a1fccbeb8196355b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enol=20Fern=C3=A1ndez?= Date: Tue, 22 Oct 2024 11:53:53 +0100 Subject: [PATCH] Add service accounts (#140) * Add service accounts And do not try to refresh identities that cannot be refreshed * Do not let refresh go if no refresh token --- egi_notebooks_hub/egiauthenticator.py | 38 ++++++++++++++++----------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/egi_notebooks_hub/egiauthenticator.py b/egi_notebooks_hub/egiauthenticator.py index eb56f55..43c4b91 100644 --- a/egi_notebooks_hub/egiauthenticator.py +++ b/egi_notebooks_hub/egiauthenticator.py @@ -8,7 +8,6 @@ import json import os import re -import time from urllib.parse import urlencode import jwt @@ -245,6 +244,17 @@ def _validate_scope(self, proposal): """, ) + # Service accounts may not have "sub", so this is an alternative + # claim for those accounts + servicename_claim = Unicode( + "client_id", + config=True, + help=""" + Claim name to use for getting the name for services where the `username_claim` + is not available. See also `allow_anonymous`. + """, + ) + allow_anonymous = Bool( True, config=True, @@ -275,6 +285,10 @@ def user_info_to_username(self, user_info): username = self.username_claim(user_info) else: username = user_info.get(self.username_claim, None) + if not username: + # try with the service name claim + username = user_info.get(self.servicename_claim, None) + # last attempt, go anonymous if not username: if not self.allow_anonymous: message = ( @@ -381,11 +395,10 @@ async def authenticate(self, handler, data=None): async def refresh_user(self, user, handler=None): auth_state = await user.get_auth_state() if not auth_state: - self.log.debug("No auth state, assuming user is not managed with Check-in") + self.log.debug("No auth state, assuming user is valid") return True access_token = auth_state.get("access_token", None) - refresh_token = auth_state.get("refresh_token", None) if not access_token: self.log.debug( @@ -415,16 +428,9 @@ async def refresh_user(self, user, handler=None): except jwt.exceptions.InvalidTokenError as e: self.log.debug(f"Invalid access token, will try to refresh: {e}") - now = time.time() - refresh_info = auth_state.get("refresh_info", {}) - # if the token is still valid, avoid refreshing - time_left = refresh_info.get("expiry_time", 0) - now - if time_left > self.auth_refresh_age: - self.log.debug("Credentials still valid, time left: %f", time_left) - return True - + refresh_token = auth_state.get("refresh_token", None) if not refresh_token: - self.log.debug("No refresh token, cannot refresh user") + self.log.warn(f"No refresh token, not allowing {user} without re-login") return False # performing the refresh token call @@ -460,9 +466,11 @@ async def refresh_user(self, user, handler=None): # clear here the existing auth state so it's no longer valid await user.save_auth_state(None) return False - refresh_info = json.loads(resp.body.decode("utf8", "replace")) - refresh_info["expiry_time"] = now + refresh_info["expires_in"] - auth_state["refresh_info"] = refresh_info + resp_body = resp.body.decode("utf8", "replace") + if not resp_body: + self.log.warning(f"Empty reply from refresh call for user {user}: {body}") + return False + refresh_info = json.loads(resp_body) auth_state["access_token"] = refresh_info["access_token"] if "refresh_token" in refresh_info: auth_state["refresh_token"] = refresh_info["refresh_token"]