From 7170f6592db3755386a9cd7d9b7c76c61ab9836a Mon Sep 17 00:00:00 2001 From: spla Date: Tue, 9 Aug 2022 21:55:05 +0200 Subject: [PATCH] New ejabberd API endpoint (check_account) and added api_request helper --- akkomabot.py | 1 + ejabberdapi.py | 155 ++++++++++++++++++++++++++++++++++++++++++++----- xmpp.py | 26 ++++++--- 3 files changed, 159 insertions(+), 23 deletions(-) diff --git a/akkomabot.py b/akkomabot.py index 3bc51c1..c60e247 100644 --- a/akkomabot.py +++ b/akkomabot.py @@ -255,6 +255,7 @@ class Akkomabot: self.node_users_str = self.__get_parameter("node_users_str", lang_file_path) self.uptime_str = self.__get_parameter("uptime_str", lang_file_path) self.processes_str = self.__get_parameter("processes_str", lang_file_path) + self.account_exists_str = self.__get_parameter("account_exists_str", lang_file_path) @staticmethod def __modify_file(self, file_name, pattern,value=""): diff --git a/ejabberdapi.py b/ejabberdapi.py index 2e72fc6..2288a3f 100644 --- a/ejabberdapi.py +++ b/ejabberdapi.py @@ -58,26 +58,50 @@ class Ejabberd: return password - def register(self, username, host, user_password): + def check_account(self, username, host): data = {'user':username, 'host':self.__local_vhost, - 'password':user_password, } - API_ENDPOINT = self.__api_base_url + '/api/register?' + endpoint = self.__api_base_url + '/api/check_account?' - response = requests.post(url = API_ENDPOINT, json = data, auth=(self.__admin_account, self.__admin_pass)) + response = self.__api_request(endpoint, data) - is_registered = response.ok + account_exists = True if response.json() == 0 else False - if is_registered: + return account_exists - response_text = response.json() + def register(self, username, host, user_password): + + account_exists = self.account_exists(username, host) + + if not account_exists: + + data = {'user':username, + 'host':self.__local_vhost, + 'password':user_password, + } + + endpoint = self.__api_base_url + '/api/register?' + + response = self.__api_request(endpoint, data) + + is_registered = response.ok + + if is_registered: + + response_text = response.json() + + else: + + response_text = f"{response.json()['status']}: {response.json()['message']}" else: - response_text = f"{response.json()['status']}: {response.json()['message']}" + is_registered = False + + response_text = f"el compte {username}@{host} ja existeix!" return (is_registered, response_text) @@ -97,9 +121,9 @@ class Ejabberd: 'host':self.__local_vhost, } - API_ENDPOINT = self.__api_base_url + '/api/unregister?' + endpoint = self.__api_base_url + '/api/unregister?' - response = requests.post(url = API_ENDPOINT, json = data, auth=(self.__admin_account, self.__admin_pass)) + response = self.__api_request(endpoint, data) is_unregistered = response.ok @@ -119,9 +143,9 @@ class Ejabberd: "name": name } - API_ENDPOINT = self.__api_base_url + '/api/stats?' + endpoint = self.__api_base_url + '/api/stats?' - response = requests.post(url = API_ENDPOINT, json = data, auth=(self.__admin_account, self.__admin_pass)) + response = self.__api_request(endpoint, data) result = response.json()['stat'] @@ -136,14 +160,73 @@ class Ejabberd: data = { } - API_ENDPOINT = self.__api_base_url + '/api/status?' + endpoint = self.__api_base_url + '/api/status?' - response = requests.post(url = API_ENDPOINT, json = data, auth=(self.__admin_account, self.__admin_pass)) + response = self.__api_request(endpoint, data) result = response.json() return result + def __api_request(self, endpoint, data): + + try: + + response = requests.post(url = endpoint, json = data, auth=(self._Ejabberd__admin_account, self._Ejabberd__admin_pass)) + + except Exception as e: + + raise EjabberdNetworkError(f"Could not complete request: {e}") + + if response is None: + + raise EjabberdIllegalArgumentError("Illegal request.") + + if not response.ok: + + try: + if isinstance(response, dict) and 'error' in response: + error_msg = response['error'] + elif isinstance(response, str): + error_msg = response + else: + error_msg = None + except ValueError: + error_msg = None + + if response.status_code == 404: + ex_type = EjabberdNotFoundError + if not error_msg: + error_msg = 'Endpoint not found.' + # this is for compatibility with older versions + # which raised EjabberdAPIError('Endpoint not found.') + # on any 404 + elif response.status_code == 401: + ex_type = EjabberdUnauthorizedError + elif response.status_code == 500: + ex_type = EjabberdInternalServerError + elif response.status_code == 502: + ex_type = EjabberdBadGatewayError + elif response.status_code == 503: + ex_type = EjabberdServiceUnavailableError + elif response.status_code == 504: + ex_type = EjabberdGatewayTimeoutError + elif response.status_code >= 500 and \ + response.status_code <= 511: + ex_type = EjabberdServerError + else: + ex_type = EjabberdAPIError + + raise ex_type( + 'Ejabberd API returned error', + response.status_code, + response.reason, + error_msg) + + else: + + return response + @staticmethod def __check_setup(self): @@ -197,3 +280,47 @@ class Ejabberd: if isinstance(json_object, dict): return AttribAccessDict(json_object) return json_object +## +# Exceptions +## +class EjabberdError(Exception): + """Base class for Mastodon.py exceptions""" + +class EjabberdIOError(IOError, EjabberdError): + """Base class for Mastodon.py I/O errors""" + +class EjabberdNetworkError(EjabberdIOError): + """Raised when network communication with the server fails""" + pass +class EjabberdAPIError(EjabberdError): + """Raised when the mastodon API generates a response that cannot be handled""" + pass +class EjabberdServerError(EjabberdAPIError): + """Raised if the Server is malconfigured and returns a 5xx error code""" + pass +class EjabberdInternalServerError(EjabberdServerError): + """Raised if the Server returns a 500 error""" + pass + +class EjabberdBadGatewayError(EjabberdServerError): + """Raised if the Server returns a 502 error""" + pass + +class EjabberdServiceUnavailableError(EjabberdServerError): + """Raised if the Server returns a 503 error""" + pass + +class EjabberdGatewayTimeoutError(EjabberdServerError): + """Raised if the Server returns a 504 error""" + pass +class EjabberdNotFoundError(EjabberdAPIError): + """Raised when the ejabberd API returns a 404 Not Found error""" + pass + +class EjabberdUnauthorizedError(EjabberdAPIError): + """Raised when the ejabberd API returns a 401 Unauthorized error + + This happens when an OAuth token is invalid or has been revoked, + or when trying to access an endpoint that can't be used without + authentication without providing credentials.""" + pass diff --git a/xmpp.py b/xmpp.py index d226cc3..d9a6040 100644 --- a/xmpp.py +++ b/xmpp.py @@ -13,9 +13,9 @@ if __name__ == '__main__': notifications = bot.akkoma.notifications() - except: + except bot.akkoma.AkkomaNetworkError as net_error: - pass + sys.exit(net_error) for notif in notifications: @@ -33,21 +33,29 @@ if __name__ == '__main__': if mention.question == bot.register_str: - password = ejabberd.generate_pass() + account_exists = ejabberd.check_account(mention.acct, bot.akkoma_hostname) - is_registered, text = ejabberd.register(mention.acct, bot.akkoma_hostname, password) + if not account_exists: - if is_registered: + password = ejabberd.generate_pass() - post = f"@{mention.acct} {bot.registerok_str}\n\n{bot.user_str} {mention.acct}@{bot.akkoma_hostname}\n" + is_registered, text = ejabberd.register(mention.acct, bot.akkoma_hostname, password) - post += f"{bot.password_str} {password}\n{bot.server_str} {bot.akkoma_hostname}" + if is_registered: - bot.akkoma.status_post(post, in_reply_to_id=mention.status_id, visibility='direct') + post = f"@{mention.acct} {bot.registerok_str}\n\n{bot.user_str} {mention.acct}@{bot.akkoma_hostname}\n" + + post += f"{bot.password_str} {password}\n{bot.server_str} {bot.akkoma_hostname}" + + bot.akkoma.status_post(post, in_reply_to_id=mention.status_id, visibility='direct') + + else: + + bot.akkoma.status_post(f'@{mention.acct}, {text}', in_reply_to_id=mention.status_id, visibility='direct') else: - bot.akkoma.status_post(f'@{mention.acct}, {text}', in_reply_to_id=mention.status_id, visibility='direct') + bot.akkoma.status_post(f'@{mention.acct}, {bot.account_exists_str}', in_reply_to_id=mention.status_id, visibility='direct') elif mention.question == bot.unregister_str: