From cb709df80c8a3105c040f8f34972335072790dc3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 16 May 2025 14:43:09 +0200 Subject: [PATCH 1/6] login type and checks --- mergin/client.py | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 192f7e31..c1194a07 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -52,6 +52,16 @@ class ServerType(Enum): SAAS = auto() # Server is SaaS +class LoginType(Enum): + """Types of login supported by Mergin Maps.""" + + PASSWORD = "password" # classic login with username and password + SSO = "sso" # login with SSO token + + def __str__(self) -> str: + return self.value + + def decode_token_data(token): token_prefix = "Bearer ." if not token.startswith(token_prefix): @@ -80,7 +90,16 @@ class MerginClient: Currently, only HTTP proxies are supported. """ - def __init__(self, url=None, auth_token=None, login=None, password=None, plugin_version=None, proxy_config=None): + def __init__( + self, + url=None, + auth_token=None, + login=None, + password=None, + plugin_version=None, + proxy_config=None, + login_type: LoginType = LoginType.PASSWORD, + ): self.url = url if url is not None else MerginClient.default_url() self._auth_params = None self._auth_session = None @@ -88,6 +107,7 @@ def __init__(self, url=None, auth_token=None, login=None, password=None, plugin_ self._server_type = None self._server_version = None self.client_version = "Python-client/" + __version__ + self._login_type = login_type if plugin_version is not None: # this could be e.g. "Plugin/2020.1 QGIS/3.14" self.client_version += " " + plugin_version self.setup_logging() @@ -134,15 +154,20 @@ def __init__(self, url=None, auth_token=None, login=None, password=None, plugin_ self.opener = urllib.request.build_opener(*handlers, https_handler) urllib.request.install_opener(self.opener) - if login and not password: - raise ClientError("Unable to log in: no password provided for '{}'".format(login)) - if password and not login: - raise ClientError("Unable to log in: password provided but no username/email") + if self._login_type == LoginType.PASSWORD: + if login and not password: + raise ClientError("Unable to log in: no password provided for '{}'".format(login)) + if password and not login: + raise ClientError("Unable to log in: password provided but no username/email") + + if login and password: + self._auth_params = {"login": login, "password": password} + if not self._auth_session: + self.login(login, password) - if login and password: - self._auth_params = {"login": login, "password": password} + elif self._login_type == LoginType.SSO: if not self._auth_session: - self.login(login, password) + raise ClientError("Unable to log in: no auth token provided for SSO login") def setup_logging(self): """Setup Mergin Maps client logging.""" From 45f821fd233fa64074318082aa68b5afd4aa2f9a Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 16 May 2025 14:44:52 +0200 Subject: [PATCH 2/6] raise auth token error --- mergin/client.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index c1194a07..65bb3a7f 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -45,6 +45,10 @@ class TokenError(Exception): pass +class AuthTokenExpiredError(Exception): + pass + + class ServerType(Enum): OLD = auto() # Server is old and does not support workspaces CE = auto() # Server is Community Edition @@ -214,12 +218,18 @@ def wrapper(self, *args): # Refresh auth token if it expired or will expire very soon delta = self._auth_session["expire"] - datetime.now(timezone.utc) if delta.total_seconds() < 5: - self.log.info("Token has expired - refreshing...") - self.login(self._auth_params["login"], self._auth_params["password"]) + if self._login_type == LoginType.PASSWORD: + self.log.info("Token has expired - refreshing...") + self.login(self._auth_params["login"], self._auth_params["password"]) + elif self._login_type == LoginType.SSO: + raise AuthTokenExpiredError("Token has expired - please re-login") else: # Create a new authorization token - self.log.info(f"No token - login user: {self._auth_params['login']}") - self.login(self._auth_params["login"], self._auth_params["password"]) + if self._login_type == LoginType.PASSWORD: + self.log.info(f"No token - login user: {self._auth_params['login']}") + self.login(self._auth_params["login"], self._auth_params["password"]) + elif self._login_type == LoginType.SSO: + raise AuthTokenExpiredError("Token has expired - please re-login") return f(self, *args) return wrapper From 417bf851a9c8fc11257a321d5a80444ea29d7ec2 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 25 Jun 2025 11:09:37 +0200 Subject: [PATCH 3/6] remove enum --- mergin/client.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 65bb3a7f..f51b4ec9 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -56,16 +56,6 @@ class ServerType(Enum): SAAS = auto() # Server is SaaS -class LoginType(Enum): - """Types of login supported by Mergin Maps.""" - - PASSWORD = "password" # classic login with username and password - SSO = "sso" # login with SSO token - - def __str__(self) -> str: - return self.value - - def decode_token_data(token): token_prefix = "Bearer ." if not token.startswith(token_prefix): From 064eb79fd06dbf19f66e03cd0a37a90de10d1de7 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 25 Jun 2025 11:10:02 +0200 Subject: [PATCH 4/6] remove --- mergin/client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index f51b4ec9..d70752a3 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -92,7 +92,6 @@ def __init__( password=None, plugin_version=None, proxy_config=None, - login_type: LoginType = LoginType.PASSWORD, ): self.url = url if url is not None else MerginClient.default_url() self._auth_params = None @@ -101,7 +100,6 @@ def __init__( self._server_type = None self._server_version = None self.client_version = "Python-client/" + __version__ - self._login_type = login_type if plugin_version is not None: # this could be e.g. "Plugin/2020.1 QGIS/3.14" self.client_version += " " + plugin_version self.setup_logging() From 79be60699d0abea6bc6229f50c99cd647cb694bc Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 25 Jun 2025 11:10:49 +0200 Subject: [PATCH 5/6] update --- mergin/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index d70752a3..12ae3780 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -146,7 +146,7 @@ def __init__( self.opener = urllib.request.build_opener(*handlers, https_handler) urllib.request.install_opener(self.opener) - if self._login_type == LoginType.PASSWORD: + if login or password: if login and not password: raise ClientError("Unable to log in: no password provided for '{}'".format(login)) if password and not login: @@ -157,9 +157,9 @@ def __init__( if not self._auth_session: self.login(login, password) - elif self._login_type == LoginType.SSO: + else: if not self._auth_session: - raise ClientError("Unable to log in: no auth token provided for SSO login") + raise ClientError("Unable to log in: no auth token provided for login") def setup_logging(self): """Setup Mergin Maps client logging.""" From d3b40a22258b77e963701bcbe08bfa54d13711c5 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 25 Jun 2025 11:11:29 +0200 Subject: [PATCH 6/6] raise error --- mergin/client.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mergin/client.py b/mergin/client.py index 12ae3780..ab74d5c5 100644 --- a/mergin/client.py +++ b/mergin/client.py @@ -206,18 +206,20 @@ def wrapper(self, *args): # Refresh auth token if it expired or will expire very soon delta = self._auth_session["expire"] - datetime.now(timezone.utc) if delta.total_seconds() < 5: - if self._login_type == LoginType.PASSWORD: + self.log.info("Token has expired - refreshing...") + if self._auth_params.get("login", None) and self._auth_params.get("password", None): self.log.info("Token has expired - refreshing...") self.login(self._auth_params["login"], self._auth_params["password"]) - elif self._login_type == LoginType.SSO: + else: raise AuthTokenExpiredError("Token has expired - please re-login") else: # Create a new authorization token - if self._login_type == LoginType.PASSWORD: - self.log.info(f"No token - login user: {self._auth_params['login']}") + self.log.info(f"No token - login user: {self._auth_params['login']}") + if self._auth_params.get("login", None) and self._auth_params.get("password", None): self.login(self._auth_params["login"], self._auth_params["password"]) - elif self._login_type == LoginType.SSO: - raise AuthTokenExpiredError("Token has expired - please re-login") + else: + raise ClientError("Missing login or password") + return f(self, *args) return wrapper