From fd11bee274815d4407fd2de19fe0e2bec0bc5bf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 10:49:59 +0000 Subject: [PATCH] =?UTF-8?q?Ov=C4=9B=C5=99en=C3=AD=20p=C5=99ihla=C5=A1ovac?= =?UTF-8?q?=C3=ADch=20=C3=BAdaj=C5=AF=20p=C5=99ed=20ulo=C5=BEen=C3=ADm=20d?= =?UTF-8?q?o=20spr=C3=A1vce=20autentizace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Špatné heslo zadané v přihlašovacím dialogu se dosud uložilo do QGIS Authentication Manageru a chyba se ukázala až po zavření dialogu. - Dialog nyní před uložením zkusí přihlášení k API; při neplatných údajích se nic neuloží a uživatel může údaje rovnou opravit. - Pokud je server nedostupný (údaje nelze ověřit), uživatel si může zvolit, zda je uložit neověřené – změna hesla na webu u dříve uložených údajů tím není dotčena. - login_to_api nově rozlišuje důvod selhání (LAST_LOGIN_ERROR: 'auth' × 'network'), aby dialog uměl oba případy odlišit. - Ověřuje se i změna samotného e-mailu (proti uloženému heslu). --- amcr_viewer/amcr_dialog.py | 43 ++++++++++++++++++++++++++++++++++++++ amcr_viewer/amcr_tools.py | 17 +++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/amcr_viewer/amcr_dialog.py b/amcr_viewer/amcr_dialog.py index 2c3a154..f109f65 100644 --- a/amcr_viewer/amcr_dialog.py +++ b/amcr_viewer/amcr_dialog.py @@ -627,6 +627,39 @@ class LoginDialog(QDialog): return True + def _verify_credentials(self, username: str, password: str) -> bool: + """ + Verify the credentials against the API before saving them. + Returns True if they should be stored: either the login succeeded, + or the server was unreachable and the user chose to keep them + unverified. Wrong credentials are never stored. + """ + # Lazy import to avoid an import cycle + # (amcr_tools imports LoginDialog lazily as well) + from . import amcr_tools + + if amcr_tools.login_to_api(username, password): + return True + + if amcr_tools.LAST_LOGIN_ERROR == 'network': + answer = QMessageBox.question( + self, + "Server nedostupný", + "Přihlašovací údaje se nepodařilo ověřit – server AMČR " + "je nedostupný.\nChcete je přesto uložit (neověřené)?", + QMessageBox.StandardButton.Yes + | QMessageBox.StandardButton.No + ) + return answer == QMessageBox.StandardButton.Yes + + QMessageBox.warning( + self, + "Neplatné přihlašovací údaje", + "Přihlášení se nezdařilo – zkontrolujte e-mail a heslo.\n" + "Údaje nebyly uloženy." + ) + return False + # ------------------------------------------------------------------ # Button actions # ------------------------------------------------------------------ @@ -653,6 +686,11 @@ class LoginDialog(QDialog): if ok: if not self._ensure_master_password(): return + # Verify the new username against the stored password + if not self._verify_credentials( + username, cfg.config("password", "") + ): + return cfg.setConfig("username", username) auth_mgr.updateAuthenticationConfig(cfg) self.accept() @@ -662,6 +700,11 @@ class LoginDialog(QDialog): QMessageBox.warning(self, "Chybí údaje", "Vyplňte prosím heslo.") return + # Verify before prompting for the master password – wrong + # credentials must never reach the Authentication Manager + if not self._verify_credentials(username, password): + return + if not self._ensure_master_password(): return diff --git a/amcr_viewer/amcr_tools.py b/amcr_viewer/amcr_tools.py index e872cbf..e9a22ea 100644 --- a/amcr_viewer/amcr_tools.py +++ b/amcr_viewer/amcr_tools.py @@ -17,6 +17,10 @@ TRANSLATIONS = {} # None = not logged in (anonymous access) AMCR_SESSION: requests.Session | None = None +# Reason of the last failed login: 'auth' (wrong credentials), +# 'network' (server unreachable / invalid response) or None +LAST_LOGIN_ERROR: str | None = None + # Re-entrancy guard: the download runs in the main thread and pumps the # event loop via processEvents(), so the user could otherwise start # a second download while the first one is still running @@ -38,6 +42,9 @@ def login_to_api(username: str, password: str): """ login_url = "https://digiarchiv.aiscr.cz/api/user/login" + global LAST_LOGIN_ERROR + LAST_LOGIN_ERROR = None + _log(f"Přihlašuji uživatele: '{username}'") if not username or not password: @@ -45,6 +52,7 @@ def login_to_api(username: str, password: str): "CHYBA: username nebo heslo je prázdné.", Qgis.MessageLevel.Critical ) + LAST_LOGIN_ERROR = 'auth' return None session = requests.Session() @@ -72,6 +80,7 @@ def login_to_api(username: str, password: str): f"CHYBA přihlášení (API): {body['error']}", Qgis.MessageLevel.Critical ) + LAST_LOGIN_ERROR = 'auth' return None _log("Přihlášení proběhlo úspěšně.") @@ -80,18 +89,22 @@ def login_to_api(username: str, password: str): return session except requests.exceptions.HTTPError as e: - _log(f"CHYBA HTTP {e.response.status_code if e.response else '?'}: " - f"{e.response.text[:300] if e.response else 'žádná odpověď'}", + status = e.response.status_code if e.response is not None else None + _log(f"CHYBA HTTP {status if status else '?'}: " + f"{e.response.text[:300] if e.response is not None else 'žádná odpověď'}", Qgis.MessageLevel.Critical) + LAST_LOGIN_ERROR = 'auth' if status in (401, 403) else 'network' return None except requests.exceptions.RequestException as e: _log(f"CHYBA sítě: {e}", Qgis.MessageLevel.Critical) + LAST_LOGIN_ERROR = 'network' return None except ValueError: # Server returned non-JSON (e.g. an HTML error page behind a proxy) _log("CHYBA: server nevrátil platný JSON: " f"{response.text[:300]}", Qgis.MessageLevel.Critical) + LAST_LOGIN_ERROR = 'network' return None