1
0

password unification refactor: move methods from wallet to daemon

Note in particular that check_password_for_directory was not safe to use while the daemon had wallets loaded,
as the same file would have two corresponding Wallet() instances in memory. This was specifically handled in
the kivy GUI, on the caller side, by stopping-before and reloading-after the wallets; but it was dirty to
have the caller handle this.
This commit is contained in:
SomberNight
2022-07-06 18:33:08 +02:00
parent c71f00cc8e
commit c463f5e23d
4 changed files with 97 additions and 86 deletions

View File

@@ -478,6 +478,7 @@ class Daemon(Logger):
self.gui_object = None
# path -> wallet; make sure path is standardized.
self._wallets = {} # type: Dict[str, Abstract_Wallet]
self._wallet_lock = threading.RLock()
daemon_jobs = []
# Setup commands server
self.commands_server = None
@@ -526,12 +527,35 @@ class Daemon(Logger):
# not see the exception (especially if the GUI did not start yet).
self._stopping_soon_or_errored.set()
def with_wallet_lock(func):
def func_wrapper(self: 'Daemon', *args, **kwargs):
with self._wallet_lock:
return func(self, *args, **kwargs)
return func_wrapper
@with_wallet_lock
def load_wallet(self, path, password, *, manual_upgrades=True) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
# wizard will be launched if we return
if path in self._wallets:
wallet = self._wallets[path]
return wallet
wallet = self._load_wallet(path, password, manual_upgrades=manual_upgrades, config=self.config)
if wallet is None:
return
wallet.start_network(self.network)
self._wallets[path] = wallet
return wallet
@staticmethod
def _load_wallet(
path,
password,
*,
manual_upgrades: bool = True,
config: SimpleConfig,
) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
storage = WalletStorage(path)
if not storage.file_exists():
return
@@ -547,11 +571,10 @@ class Daemon(Logger):
return
if db.get_action():
return
wallet = Wallet(db, storage, config=self.config)
wallet.start_network(self.network)
self._wallets[path] = wallet
wallet = Wallet(db, storage, config=config)
return wallet
@with_wallet_lock
def add_wallet(self, wallet: Abstract_Wallet) -> None:
path = wallet.storage.path
path = standardize_path(path)
@@ -561,6 +584,7 @@ class Daemon(Logger):
path = standardize_path(path)
return self._wallets.get(path)
@with_wallet_lock
def get_wallets(self) -> Dict[str, Abstract_Wallet]:
return dict(self._wallets) # copy
@@ -577,6 +601,7 @@ class Daemon(Logger):
fut = asyncio.run_coroutine_threadsafe(self._stop_wallet(path), self.asyncio_loop)
return fut.result()
@with_wallet_lock
async def _stop_wallet(self, path: str) -> bool:
"""Returns True iff a wallet was found."""
path = standardize_path(path)
@@ -642,3 +667,66 @@ class Daemon(Logger):
finally:
# app will exit now
asyncio.run_coroutine_threadsafe(self.stop(), self.asyncio_loop).result()
@with_wallet_lock
def _check_password_for_directory(self, *, old_password, new_password=None, wallet_dir: str) -> Tuple[bool, bool]:
"""Checks password against all wallets (in dir), returns whether they can be unified and whether they are already.
If new_password is not None, update all wallet passwords to new_password.
"""
assert os.path.exists(wallet_dir), f"path {wallet_dir!r} does not exist"
failed = []
is_unified = True
for filename in os.listdir(wallet_dir):
path = os.path.join(wallet_dir, filename)
path = standardize_path(path)
if not os.path.isfile(path):
continue
wallet = self.get_wallet(path)
if wallet is None:
try:
wallet = self._load_wallet(path, old_password, manual_upgrades=False, config=self.config)
except util.InvalidPassword:
pass
except Exception:
self.logger.exception(f'failed to load wallet at {path!r}:')
pass
if wallet is None:
failed.append(path)
continue
if not wallet.storage.is_encrypted():
is_unified = False
try:
wallet.check_password(old_password)
except Exception:
failed.append(path)
continue
if new_password:
self.logger.info(f'updating password for wallet: {path!r}')
wallet.update_password(old_password, new_password, encrypt_storage=True)
can_be_unified = failed == []
is_unified = can_be_unified and is_unified
return can_be_unified, is_unified
@with_wallet_lock
def update_password_for_directory(
self,
*,
old_password,
new_password,
wallet_dir: Optional[str] = None,
) -> bool:
"""returns whether password is unified"""
if new_password is None:
# we opened a non-encrypted wallet
return False
if wallet_dir is None:
wallet_dir = os.path.dirname(self.config.get_wallet_path())
can_be_unified, is_unified = self._check_password_for_directory(
old_password=old_password, new_password=None, wallet_dir=wallet_dir)
if not can_be_unified:
return False
if is_unified and old_password == new_password:
return True
self._check_password_for_directory(
old_password=old_password, new_password=new_password, wallet_dir=wallet_dir)
return True