From cb89f598e4cfc68a17e4de5f86d66de0c1a414e8 Mon Sep 17 00:00:00 2001
From: lovetox <philipp@hoerist.com>
Date: Tue, 5 Jul 2022 21:36:57 +0200
Subject: [PATCH] refactor: Remove side effects of password module

Fixes: #11032
---
 gajim/common/application.py  |  3 ++
 gajim/common/passwords.py    | 58 ++++++++++++++++++++++++++----------
 gajim/gtk/accounts.py        |  2 +-
 gajim/gtk/features.py        |  8 ++---
 gajim/gtk/password_dialog.py | 12 ++++----
 5 files changed, 56 insertions(+), 27 deletions(-)

diff --git a/gajim/common/application.py b/gajim/common/application.py
index b10c7e8d82..2a58f80f0e 100644
--- a/gajim/common/application.py
+++ b/gajim/common/application.py
@@ -36,6 +36,7 @@
 from gajim.common import ged
 from gajim.common import configpaths
 from gajim.common import logging_helpers
+from gajim.common import passwords
 from gajim.common.dbus import logind
 from gajim.common.events import AccountDisonnected
 from gajim.common.events import AllowGajimUpdateCheck
@@ -61,6 +62,8 @@ def _init_core(self) -> None:
         app.detect_dependencies()
         configpaths.create_paths()
 
+        passwords.init()
+
         app.settings = Settings()
         app.settings.init()
 
diff --git a/gajim/common/passwords.py b/gajim/common/passwords.py
index 5e8e1ba975..6dad6c5428 100644
--- a/gajim/common/passwords.py
+++ b/gajim/common/passwords.py
@@ -23,6 +23,7 @@
 from __future__ import annotations
 
 from typing import Optional
+from typing import cast
 
 import logging
 
@@ -31,21 +32,34 @@
 from gajim.common import app
 
 __all__ = [
+    'init',
+    'is_keyring_available',
     'get_password',
     'save_password',
     'delete_password'
 ]
 
-log = logging.getLogger('gajim.password')
+log = logging.getLogger('gajim.c.passwords')
 
-backends = keyring.backend.get_all_keyring()
-for backend in backends:
-    log.info('Found keyring backend: %s', backend)
 
-keyring_backend = keyring.get_keyring()
-log.info('Select %s backend', keyring_backend)
+class Interface:
+    def __init__(self):
+        self.backend = cast(keyring.backend.KeyringBackend, None)
+        self._is_keyring_available = False
 
-KEYRING_AVAILABLE = keyring.core.recommended(keyring_backend)
+    @property
+    def is_keyring_available(self):
+        return self._is_keyring_available
+
+    def init(self) -> None:
+        backends = keyring.backend.get_all_keyring()
+        for backend in backends:
+            log.info('Found keyring backend: %s', backend)
+
+        self.backend = keyring.get_keyring()
+        log.info('Select %s backend', self.backend)
+
+        self._is_keyring_available = keyring.core.recommended(self.backend)
 
 
 class SecretPasswordStorage:
@@ -55,13 +69,13 @@ class SecretPasswordStorage:
 
     @staticmethod
     def save_password(account_name: str, password: str) -> bool:
-        if not KEYRING_AVAILABLE:
+        if not is_keyring_available():
             log.warning('No recommended keyring backend available.'
                         'Passwords cannot be stored.')
             return True
         try:
             log.info('Save password to keyring')
-            keyring_backend.set_password('gajim', account_name, password)
+            _interface.backend.set_password('gajim', account_name, password)
             return True
         except Exception:
             log.exception('Save password failed')
@@ -69,25 +83,28 @@ def save_password(account_name: str, password: str) -> bool:
 
     @staticmethod
     def get_password(account_name: str) -> Optional[str]:
-        log.info('Request password from keyring')
-        if not KEYRING_AVAILABLE:
+        if not is_keyring_available():
             return
+
+        log.info('Request password from keyring')
+
         try:
             # For security reasons remove clear-text password
             ConfigPasswordStorage.delete_password(account_name)
-            return keyring_backend.get_password('gajim', account_name)
+            return _interface.backend.get_password('gajim', account_name)
         except Exception:
             log.exception('Request password failed')
             return
 
     @staticmethod
     def delete_password(account_name: str) -> None:
-        log.info('Remove password from keyring')
-        if not KEYRING_AVAILABLE:
+        if not is_keyring_available():
             return
 
+        log.info('Remove password from keyring')
+
         try:
-            return keyring_backend.delete_password('gajim', account_name)
+            return _interface.backend.delete_password('gajim', account_name)
         except keyring.errors.PasswordDeleteError as error:
             log.warning('Removing password failed: %s', error)
         except Exception:
@@ -113,6 +130,14 @@ def delete_password(account_name: str) -> None:
         app.settings.set_account_setting(account_name, 'password', '')
 
 
+def init() -> None:
+    _interface.init()
+
+
+def is_keyring_available() -> bool:
+    return _interface.is_keyring_available
+
+
 def get_password(account_name: str) -> Optional[str]:
     if app.settings.get('use_keyring'):
         return SecretPasswordStorage.get_password(account_name)
@@ -138,3 +163,6 @@ def delete_password(account_name: str) -> None:
     if app.settings.get('use_keyring'):
         return SecretPasswordStorage.delete_password(account_name)
     return ConfigPasswordStorage.delete_password(account_name)
+
+
+_interface = Interface()
diff --git a/gajim/gtk/accounts.py b/gajim/gtk/accounts.py
index 4e179fd2b0..bd68ff1896 100644
--- a/gajim/gtk/accounts.py
+++ b/gajim/gtk/accounts.py
@@ -1084,7 +1084,7 @@ def __init__(self, account: str, parent: Gtk.Window) -> None:
                     SettingType.ACCOUNT_CONFIG,
                     'savepass',
                     enabled_func=(lambda: not app.settings.get('use_keyring') or
-                                  passwords.KEYRING_AVAILABLE)),
+                                  passwords.is_keyring_available())),
 
             Setting(SettingKind.CHANGEPASSWORD,
                     _('Change Password'),
diff --git a/gajim/gtk/features.py b/gajim/gtk/features.py
index de8be1c6a1..e2fea2d2a4 100644
--- a/gajim/gtk/features.py
+++ b/gajim/gtk/features.py
@@ -29,6 +29,7 @@
 from gi.repository import Gdk
 
 from gajim.common import app
+from gajim.common import passwords
 from gajim.common.i18n import _
 
 
@@ -125,7 +126,7 @@ def _get_features(self) -> list[Feature]:
                     _('No additional requirements'),
                     notification_sounds_enabled),
             Feature(_('Secure Password Storage'),
-                    self._some_keyring_available(),
+                    passwords.is_keyring_available(),
                     _('Enables Gajim to store Passwords securely instead of '
                       'storing them in plaintext'),
                     _('Requires: gnome-keyring or kwallet'),
@@ -148,11 +149,6 @@ def _get_features(self) -> list[Feature]:
                     None)
         ]
 
-    @staticmethod
-    def _some_keyring_available() -> bool:
-        from gajim.common import passwords
-        return passwords.KEYRING_AVAILABLE
-
     @staticmethod
     def _idle_available() -> bool:
         from gajim.common import idle
diff --git a/gajim/gtk/password_dialog.py b/gajim/gtk/password_dialog.py
index 190a3dcaf7..a0a4dca2a8 100644
--- a/gajim/gtk/password_dialog.py
+++ b/gajim/gtk/password_dialog.py
@@ -18,10 +18,9 @@
 from gi.repository import Gdk
 
 from gajim.common import app
+from gajim.common import passwords
 from gajim.common.events import PasswordRequired
 from gajim.common.i18n import _
-from gajim.common.passwords import save_password
-from gajim.common.passwords import KEYRING_AVAILABLE
 
 from .builder import get_builder
 
@@ -69,9 +68,12 @@ def _process_event(self) -> None:
                 'jid': own_jid,
                 'account': account_name})
         self._ui.save_pass_checkbutton.show()
+
+        is_keyring_available = passwords.is_keyring_available()
         self._ui.save_pass_checkbutton.set_sensitive(
-            not app.settings.get('use_keyring') or KEYRING_AVAILABLE)
-        if not KEYRING_AVAILABLE:
+            not app.settings.get('use_keyring') or
+            is_keyring_available)
+        if not is_keyring_available:
             self._ui.keyring_hint.show()
 
     def _on_ok(self, _button: Gtk.Button) -> None:
@@ -81,7 +83,7 @@ def _on_ok(self, _button: Gtk.Button) -> None:
             self.account,
             'savepass',
             self._ui.save_pass_checkbutton.get_active())
-        save_password(self.account, password)
+        passwords.save_password(self.account, password)
         self._event.on_password(password)
         self.destroy()
 
-- 
GitLab