diff --git a/releases/unreleased/oauth2-token-refreshing.yml b/releases/unreleased/oauth2-token-refreshing.yml new file mode 100644 index 0000000..8c91ae0 --- /dev/null +++ b/releases/unreleased/oauth2-token-refreshing.yml @@ -0,0 +1,7 @@ +--- +title: OAuth2 token refreshing +category: added +author: Santiago DueƱas +issue: null +notes: > + OAuth2 token will be refreshed when they expired. diff --git a/sortinghat/core/importer/backends/eclipse.py b/sortinghat/core/importer/backends/eclipse.py index 2ff7f1f..49130d2 100644 --- a/sortinghat/core/importer/backends/eclipse.py +++ b/sortinghat/core/importer/backends/eclipse.py @@ -206,9 +206,14 @@ class EclipseFoundationAPIClient: ECLIPSE_API_URL = "https://api.eclipse.org" ECLIPSE_ACCOUNTS_URL = "https://accounts.eclipse.org" OAUTH_TOKEN_ENDPOINT = "https://accounts.eclipse.org/oauth2/token" + ECLIPSE_SCOPE = "eclipsefdn_view_all_profiles" + + MAX_RETRIES = 3 def __init__(self): self.token = None + self.user_id = None + self.password = None def login(self, user_id, password): """Login on the Eclipse platform. @@ -217,10 +222,12 @@ def login(self, user_id, password): "eclipsefdn_view_all_profiles" that will allow us to fetch all the info about profiles/identities. """ + self.user_id = user_id + self.password = password self.token = self._authenticate( - user_id, - password, - "eclipsefdn_view_all_profiles", + self.user_id, + self.password, + self.ECLIPSE_SCOPE, ) def logout(self): @@ -281,8 +288,7 @@ def _fetch(self, url, params=None): """Generic query to Eclipse usr API.""" try: - response = requests.get(url, params=params, auth=self.token) - response.raise_for_status() + data = self._fetch_retry(url, params) except requests.exceptions.HTTPError as error: # Ignore 5xx errors if 500 <= error.response.status_code < 600: @@ -296,7 +302,41 @@ def _fetch(self, url, params=None): else: raise error - return response.json() + return data + + def _fetch_retry(self, url, params=None): + """Fetch URL retrying in case of 403 or 500 errors. + + When getting a 403 error, the method will try to authenticate + again in case the OAuth2 token has expired. + """ + retries = 0 + max_retries = self.MAX_RETRIES + + while retries < max_retries: + response = requests.get(url, params=params, auth=self.token) + + if response.status_code == 200: + return response.json() + elif response.status_code == 403: + # Refresh token if needed and try again + if self.token.expires_at <= datetime_utcnow(): + self.token = self._authenticate( + self.user_id, + self.password, + self.ECLIPSE_SCOPE, + ) + retries += 1 + elif 500 <= response.status_code < 600: + # Errors could have been related to server overloading + retries += 1 + else: + response.raise_for_status() + + response = requests.get(url, params=params, auth=self.token) + response.raise_for_status() + + return response def _authenticate(self, client_id, client_secret, scope): """Authenticate using OAuth2. diff --git a/tests/test_eclipse.py b/tests/test_eclipse.py index 007ecdf..2355dc8 100644 --- a/tests/test_eclipse.py +++ b/tests/test_eclipse.py @@ -67,7 +67,8 @@ def request_callback(request, uri, headers): # John Smith profile pages httpretty.register_uri(httpretty.GET, ECLIPSE_API_URL + "/account/profile/jsmith", - body=read_file('data/eclipse_jsmith.json')) + body=read_file('data/eclipse_jsmith.json') if not raise_5xx_errors else "", + status=200 if not raise_5xx_errors else 500) httpretty.register_uri(httpretty.GET, ECLIPSE_API_URL + "/account/profile/jsmith/employment-history", body=read_file('data/eclipse_jsmith_employment.json')) @@ -75,7 +76,8 @@ def request_callback(request, uri, headers): # John Doe profile pages httpretty.register_uri(httpretty.GET, ECLIPSE_API_URL + "/account/profile/jdoe", - body=read_file('data/eclipse_jdoe.json')) + body=read_file('data/eclipse_jdoe.json') if not raise_5xx_errors else "", + status=200 if not raise_5xx_errors else 500) httpretty.register_uri(httpretty.GET, ECLIPSE_API_URL + "/account/profile/jdoe/employment-history", body=read_file('data/eclipse_jdoe_employment.json')) @@ -86,22 +88,8 @@ def request_callback(request, uri, headers): body=read_file('data/eclipse_jrae.json')) httpretty.register_uri(httpretty.GET, ECLIPSE_API_URL + "/account/profile/jrae/employment-history", - body=read_file('data/eclipse_jrae_employment.json')) - - # Overwrite jsmith and jdoe profile replies to raise 5xx errors - if raise_5xx_errors: - httpretty.register_uri(httpretty.GET, - ECLIPSE_API_URL + "/account/profile/jsmith", - body="", - status=500) - httpretty.register_uri(httpretty.GET, - ECLIPSE_API_URL + "/account/profile/jdoe", - body="", - status=504) - httpretty.register_uri(httpretty.GET, - ECLIPSE_API_URL + "/account/profile/jrae/employment-history", - body="", - status=504) + body=read_file('data/eclipse_jrae_employment.json') if not raise_5xx_errors else "", + status=200 if not raise_5xx_errors else 500) return requests, bodies