From b0885f7b84245fa22ccb1b91c00d03675c1cfafc Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Wed, 27 Jun 2018 17:06:17 +0200 Subject: [PATCH 1/8] Removing old import --- re2oapi/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index 5adb1bc..6348c8e 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -7,7 +7,6 @@ import json import requests from requests.exceptions import HTTPError -from . import endpoints from . import exceptions # Number of seconds before expiration where renewing the token is done From 61aba4812b58ba1a331d40ae6c213ae1b7d71558 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Wed, 27 Jun 2018 19:07:00 +0200 Subject: [PATCH 2/8] fix list function to follow pages --- re2oapi/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index 6348c8e..2aa15e4 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -480,7 +480,7 @@ class Re2oAPIClient: results = response['results'] # Get all next pages and append the results - while response['next'] is not None and \ + while results['next'] is not None and \ (max_results is None or len(results) < max_results): response = self.get(response['next']) results += response['results'] From 6565b92f3bfc13d02b95888ae021f5bd6f7ef317 Mon Sep 17 00:00:00 2001 From: Charlie Jacomme Date: Wed, 27 Jun 2018 19:10:21 +0200 Subject: [PATCH 3/8] Revert "fix list function to follow pages" Stupid Charlie, stupid This reverts commit 61aba4812b58ba1a331d40ae6c213ae1b7d71558. --- re2oapi/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index 2aa15e4..6348c8e 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -480,7 +480,7 @@ class Re2oAPIClient: results = response['results'] # Get all next pages and append the results - while results['next'] is not None and \ + while response['next'] is not None and \ (max_results is None or len(results) < max_results): response = self.get(response['next']) results += response['results'] From b4906d8b2547d53c329dc2aebb2f7f4e3242a9e3 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 14 Nov 2018 17:08:25 +0100 Subject: [PATCH 4/8] Api for sending mail --- re2oapi/__init__.py | 4 ++-- re2oapi/client.py | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/re2oapi/__init__.py b/re2oapi/__init__.py index 5939662..a6ea559 100644 --- a/re2oapi/__init__.py +++ b/re2oapi/__init__.py @@ -1,4 +1,4 @@ -from .client import Re2oAPIClient +from .client import ApiSendMail, Re2oAPIClient from . import exceptions -__all__ = ['Re2oAPIClient', 'exceptions'] +__all__ = ['Re2oAPIClient', 'ApiSendMail', 'exceptions'] diff --git a/re2oapi/client.py b/re2oapi/client.py index 5adb1bc..b654331 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -5,9 +5,11 @@ from pathlib import Path import stat import json import requests +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText from requests.exceptions import HTTPError -from . import endpoints from . import exceptions # Number of seconds before expiration where renewing the token is done @@ -556,3 +558,24 @@ class Re2oAPIClient: self.log.debug("Viewing object under '{}' successful" .format(endpoint)) return ret + + +class ApiSendMail: + """Basic api for sending mails""" + def __init__(self, server, port, starttls=False): + """Give here the server, the port and tls or not""" + self.connection = smtplib.SMTP(server, port) + if starttls: + self.connection.starttls() + + def send_mail(self, email_from, email_to, subject, body, mode='html'): + """Sending mail from from, to, subject and body""" + self.msg = MIMEMultipart() + self.msg['From'] = email_from + self.msg['To'] = email_to + self.msg['Subject'] = subject + self.msg.attach(MIMEText(body, mode)) + self.connection.sendmail(email_from, email_to, self.msg.as_string()) + + def close(self): + self.connection.quit() From 0dd459e3ec11e8a5b80212fb524540a7161e2e0d Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 14 Nov 2018 17:13:51 +0100 Subject: [PATCH 5/8] unindent error --- re2oapi/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index b654331..208a715 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -577,5 +577,5 @@ class ApiSendMail: self.msg.attach(MIMEText(body, mode)) self.connection.sendmail(email_from, email_to, self.msg.as_string()) - def close(self): - self.connection.quit() + def close(self): + self.connection.quit() From b12df74fe73f351986ff51c8122089644218f8fe Mon Sep 17 00:00:00 2001 From: Hugo Levy-Falk Date: Tue, 12 Mar 2019 22:05:06 +0100 Subject: [PATCH 6/8] Fix indentation --- re2oapi/client.py | 121 +++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index b654331..2fb15d2 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -35,23 +35,23 @@ class Re2oAPIClient: username: The username to use. password: The password to use. token_file: An optional path to the file where re2o tokens are - stored. Used both for retrieving the token and saving it, so - the file must be accessible for reading and writing. The - default value is `None`, which indicated to use - `$HOME/{DEFAULT_TOKEN_FILENAME}`. + stored. Used both for retrieving the token and saving it, so + the file must be accessible for reading and writing. The + default value is `None`, which indicated to use + `$HOME/{DEFAULT_TOKEN_FILENAME}`. use_tls: A boolean to indicate whether the client should us TLS - (recommended for production). The default is `True`. + (recommended for production). The default is `True`. log_level: Control the logging level to use. The default is - `logging.CRITICAL+10`. So nothing is logged. + `logging.CRITICAL+10`. So nothing is logged. Raises: requests.exceptions.ConnectionError: Unable to resolve the - provided hostname. + provided hostname. requests.exceptions.HTTPError: The server used does not have a - valid Re2o API. + valid Re2o API. re2oapi.exceptions.InvalidCredentials: The credentials provided - are not valid according to the Re2o server. - """.format(DEFAULT_TOKEN_FILENAME=DEFAULT_TOKEN_FILENAME) + are not valid according to the Re2o server. + """.format(DEFAULT_TOKEN_FILENAME=DEFAULT_TOKEN_FILENAME) # Enable logging self.log = logging.getLogger(__name__) @@ -63,7 +63,7 @@ class Re2oAPIClient: ) handler.setFormatter(formatter) self.log.addHandler(handler) - self.log.setLevel(log_level) + self.log.setLevel(log_level) self.log.info("Starting new Re2o API client.") self.log.debug("hostname = " + str(hostname)) @@ -89,8 +89,8 @@ class Re2oAPIClient: Returns: True is the token expiration time is within less than - {TIME_FOR_RENEW} seconds. - """.format(TIME_FOR_RENEW=TIME_FOR_RENEW) + {TIME_FOR_RENEW} seconds. + """.format(TIME_FOR_RENEW=TIME_FOR_RENEW) return self.token['expiration'] < \ datetime.datetime.now(datetime.timezone.utc) + \ @@ -133,7 +133,7 @@ class Re2oAPIClient: else: self.log.debug("Token successfully retrieved from token " "file '{}'.".format(self.token_file)) - return ret + return ret def _save_token_to_file(self): self.log.debug("Saving token to token file '{}'." @@ -152,20 +152,20 @@ class Re2oAPIClient: # Insert the new token in the data (replace old token if it exists) if self.hostname not in data.keys(): data[self.hostname] = {} - data[self.hostname][self._username] = { - 'token': self.token['token'], - 'expiration': self.token['expiration'].isoformat() - } + data[self.hostname][self._username] = { + 'token': self.token['token'], + 'expiration': self.token['expiration'].isoformat() + } # Rewrite the token file and ensure only the user can read it # Fails silently if the file cannot be written. try: with self.token_file.open('w') as f: json.dump(data, f) - self.token_file.chmod(stat.S_IWRITE | stat.S_IREAD) + self.token_file.chmod(stat.S_IWRITE | stat.S_IREAD) except Exception: self.log.error("Token file '{}' could not be written. Passing." - .format(self.token_file)) + .format(self.token_file)) else: self.log.debug("Token sucessfully writen in token file '{}'." .format(self.token_file)) @@ -210,8 +210,8 @@ class Re2oAPIClient: Raises: re2oapi.exceptions.InvalidCredentials: The token needs to be - renewed but the given credentials are not valid. - """ + renewed but the given credentials are not valid. + """ if self.need_renew_token: # Renew the token only if needed self._force_renew_token() @@ -222,6 +222,7 @@ class Re2oAPIClient: # Update headers to force the 'Authorization' field with the right token self.log.debug("Forcing authentication token.") + self.log.debug("Token =" + str(self.get_token())) headers.update({ 'Authorization': 'Token {}'.format(self.get_token()) }) @@ -248,7 +249,7 @@ class Re2oAPIClient: 'Authorization': 'Token {}'.format(self.get_token()) }) self.log.info("Re-performing the request {} {}" - .format(method.upper(), url)) + .format(method.upper(), url)) response = getattr(requests, method)( url, headers=headers, params=params, *args, **kwargs ) @@ -281,10 +282,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('delete', *args, **kwargs) def get(self, *args, **kwargs): @@ -304,10 +305,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('get', *args, **kwargs) def head(self, *args, **kwargs): @@ -327,10 +328,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('get', *args, **kwargs) def option(self, *args, **kwargs): @@ -350,10 +351,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('get', *args, **kwargs) def patch(self, *args, **kwargs): @@ -373,10 +374,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('patch', *args, **kwargs) def post(self, *args, **kwargs): @@ -396,10 +397,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('post', *args, **kwargs) def put(self, *args, **kwargs): @@ -419,10 +420,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ return self._request('put', *args, **kwargs) def get_url_for(self, endpoint): @@ -431,15 +432,15 @@ class Re2oAPIClient: Args: endpoint: The path of the endpoint. **kwargs: A dictionnary with the parameter to use to build the - URL (using .format() syntax) + URL (using .format() syntax) Returns: The full URL to use. Raises: re2oapi.exception.NameNotExists: The provided name does not - correspond to any endpoint. - """ + correspond to any endpoint. + """ return '{proto}://{host}/{namespace}/{endpoint}'.format( proto=('https' if self.use_tls else 'http'), host=self.hostname, @@ -461,12 +462,12 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ self.log.info("Starting listing objects under '{}'" - .format(endpoint)) + .format(endpoint)) self.log.debug("max_results = "+str(max_results)) # For optimization, list all results in one page unless the user @@ -484,7 +485,7 @@ class Re2oAPIClient: # Get all next pages and append the results while response['next'] is not None and \ - (max_results is None or len(results) < max_results): + (max_results is None or len(results) < max_results): response = self.get(response['next']) results += response['results'] @@ -507,12 +508,12 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ self.log.info("Starting counting objects under '{}'" - .format(endpoint)) + .format(endpoint)) # For optimization, ask for only 1 result (so the server will take # less time to process the request) unless the user is forcing the @@ -544,10 +545,10 @@ class Re2oAPIClient: Raises: requests.exceptions.RequestException: An error occured while - performing the request. + performing the request. exceptions.PermissionDenied: The user does not have the right - to perform this request. - """ + to perform this request. + """ self.log.info("Starting viewing an object under '{}'" .format(endpoint)) ret = self.get( @@ -577,5 +578,5 @@ class ApiSendMail: self.msg.attach(MIMEText(body, mode)) self.connection.sendmail(email_from, email_to, self.msg.as_string()) - def close(self): + def close(self): self.connection.quit() From 1f8366055d2029c9b18b3191aedd0700d7e5ae30 Mon Sep 17 00:00:00 2001 From: Hugo Levy-Falk Date: Sat, 28 Sep 2019 13:32:04 +0200 Subject: [PATCH 7/8] Better handle of redirections. --- re2oapi/client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index 2fb15d2..7a8f3d1 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -235,10 +235,19 @@ class Re2oAPIClient: # Perform the request self.log.info("Performing request {} {}".format(method.upper(), url)) response = getattr(requests, method)( - url, headers=headers, params=params, *args, **kwargs + url, headers=headers, params=params, + allow_redirects=False, *args, **kwargs ) self.log.debug("Response code: "+str(response.status_code)) + if response.is_redirect: + self.log.debug("Redirection detected.") + response = getattr(requests, method)( + response.headers['Location'], headers=headers, params=params, + allow_redirects=False, *args, **kwargs + ) + self.log.debug("Response code after redirection: "+str(response.status_code)) + if response.status_code == requests.codes.unauthorized: # Force re-login to the server (case of a wrong token but valid # credentials) and then retry the request without catching errors. @@ -262,7 +271,7 @@ class Re2oAPIClient: response.raise_for_status() ret = response.json() - self.log.debug("Request {} {} successful.".format(method, url)) + self.log.debug("Request {} {} successful.".format(method, response.url)) return ret def delete(self, *args, **kwargs): From 6d930e37ec211c4efd818dffc8b6141b02239873 Mon Sep 17 00:00:00 2001 From: Corentin Canebier Date: Tue, 12 Jan 2021 21:37:59 +0100 Subject: [PATCH 8/8] Better 403 error message --- re2oapi/client.py | 2 +- re2oapi/exceptions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/re2oapi/client.py b/re2oapi/client.py index ce57c8e..b67c1d8 100644 --- a/re2oapi/client.py +++ b/re2oapi/client.py @@ -265,7 +265,7 @@ class Re2oAPIClient: self.log.debug("Response code: "+str(response.status_code)) if response.status_code == requests.codes.forbidden: - e = exceptions.PermissionDenied(method, url, self._username) + e = exceptions.PermissionDenied(method, url, self._username, response.reason) self.log.debug(e) raise e response.raise_for_status() diff --git a/re2oapi/exceptions.py b/re2oapi/exceptions.py index 7522485..3b7732d 100644 --- a/re2oapi/exceptions.py +++ b/re2oapi/exceptions.py @@ -12,7 +12,7 @@ class InvalidCredentials(APIClientGenericError): class PermissionDenied(APIClientGenericError): - template = "The {} request to '{}' was denied for {}." + template = "The {} request to '{}' was denied for {} (reason: {})." class TokenFileNotFound(APIClientGenericError):