From f691a3ff78c03a3e70bf7b439862378d00d571c8 Mon Sep 17 00:00:00 2001 From: MelvinFrederiks Date: Thu, 10 Sep 2026 09:24:55 +0200 Subject: [PATCH 1/2] Add possibility to authenticate with an API token instead of user+pw --- hashtopolis/hashtopolis.py | 65 +++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/hashtopolis/hashtopolis.py b/hashtopolis/hashtopolis.py index ff1a14f..5bb9c88 100644 --- a/hashtopolis/hashtopolis.py +++ b/hashtopolis/hashtopolis.py @@ -57,19 +57,38 @@ def __init__(self): self._cfg = confidence.load_name('hashtopolis-test', load_order=load_order, format=confidence.YAML()) self._hashtopolis_uri = self._cfg['hashtopolis_uri'] self._api_endpoint = self._hashtopolis_uri + '/api/v2' - self.username = self._cfg['username'] - self.password = self._cfg['password'] + if 'token' in self._cfg: + self.token = self._cfg['token'] + self.username = None + self.password = None + elif all(key in self._cfg for key in ('username', 'password')): + self.username = self._cfg['username'] + self.password = self._cfg['password'] + self.token = None + else: + logger.error('Either an API token or a username and password need to be present in the configuration file!') @classmethod def with_credentials(cls, uri, username, password): - """Create a config with explicit credentials instead of reading from a config file.""" + """Create a config with explicit user/pass instead of reading from a config file.""" config = cls.__new__(cls) config._hashtopolis_uri = uri config._api_endpoint = uri + '/api/v2' config.username = username config.password = password + config.token = None return config + @classmethod + def with_token(cls, uri, token): + """Create a config with explicit API token instead of reading from a config file.""" + config = cls.__new__(cls) + config._hashtopolis_uri = uri + config._api_endpoint = uri + '/api/v2' + config.username = None + config.password = None + config.token = token + return config class HashtopolisResponseError(HashtopolisError): pass @@ -126,24 +145,34 @@ def authenticate(self, auth=None): that differ from the config. This authentication is not cached. """ if auth is not None: - logger.info("Start authentication with provided credentials") - auth_uri = self._api_endpoint + '/auth/token' - r = requests.post(auth_uri, auth=auth) - self.validate_status_code(r, [201], "Authentication failed") - r_json = self.resp_to_json(r) - self._token = r_json['token'] - self._token_expires = r_json['token'] - else: - if self._api_endpoint not in HashtopolisConnector.token: - logger.info("Start authentication") + if isinstance(auth, str): + # Authenticate with token + logger.info("Start authentication with provided API token") + self._token = auth + # Ideally we would want some sort of validation here, to check if the token is correct. + else: + # Assume authentication object passed is ('user', 'password') + logger.info("Start authentication with provided username and password") auth_uri = self._api_endpoint + '/auth/token' - r = requests.post(auth_uri, auth=(self.config.username, self.config.password)) + r = requests.post(auth_uri, auth=auth) self.validate_status_code(r, [201], "Authentication failed") r_json = self.resp_to_json(r) - HashtopolisConnector.token[self._api_endpoint] = r_json['token'] - HashtopolisConnector.token_expires[self._api_endpoint] = r_json['token'] - self._token = HashtopolisConnector.token[self._api_endpoint] - self._token_expires = HashtopolisConnector.token_expires[self._api_endpoint] + self._token = r_json['token'] + self._token_expires = r_json['token'] + else: + if self.config.token is not None: + self._token = self.config.token + else: + if self._api_endpoint not in HashtopolisConnector.token: + logger.info("Start authentication") + auth_uri = self._api_endpoint + '/auth/token' + r = requests.post(auth_uri, auth=(self.config.username, self.config.password)) + self.validate_status_code(r, [201], "Authentication failed") + r_json = self.resp_to_json(r) + HashtopolisConnector.token[self._api_endpoint] = r_json['token'] + HashtopolisConnector.token_expires[self._api_endpoint] = r_json['token'] + self._token = HashtopolisConnector.token[self._api_endpoint] + self._token_expires = HashtopolisConnector.token_expires[self._api_endpoint] self._headers = { 'Authorization': 'Bearer ' + self._token From bc35097bc3e029bb437901f0001270ad03007ae9 Mon Sep 17 00:00:00 2001 From: MelvinFrederiks Date: Thu, 10 Sep 2026 09:52:12 +0200 Subject: [PATCH 2/2] Register token as belonging to the configured endpoint --- hashtopolis/hashtopolis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hashtopolis/hashtopolis.py b/hashtopolis/hashtopolis.py index 5bb9c88..ef48b5a 100644 --- a/hashtopolis/hashtopolis.py +++ b/hashtopolis/hashtopolis.py @@ -162,6 +162,7 @@ def authenticate(self, auth=None): else: if self.config.token is not None: self._token = self.config.token + HashtopolisConnector.token[self._api_endpoint] = self._token else: if self._api_endpoint not in HashtopolisConnector.token: logger.info("Start authentication")