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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user