electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 88bb5309c4d74adb7ba1ac24e5bcb05ef34b2bf9
parent dc6dbe5bfba448dba739b33bff8041f9ccfa0d62
Author: ThomasV <thomasv@electrum.org>
Date:   Thu,  4 Jun 2020 07:51:14 +0200

Fix issue #6201:
 - Pass a proper callback to WalletDialog
   (we used to call load_wallet_by_name recursively)
 - Do not cache PasswordDialog instances

Diffstat:
Melectrum/gui/kivy/main_window.py | 93+++++++++++++++++++++++++------------------------------------------------------
Melectrum/gui/kivy/uix/dialogs/installwizard.py | 3+--
Melectrum/gui/kivy/uix/dialogs/password_dialog.py | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Melectrum/gui/kivy/uix/dialogs/wallets.py | 24+++++++++---------------
4 files changed, 125 insertions(+), 87 deletions(-)

diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py @@ -34,7 +34,7 @@ from kivy.clock import Clock from kivy.factory import Factory from kivy.metrics import inch from kivy.lang import Builder -from .uix.dialogs.password_dialog import PasswordDialog, PincodeDialog +from .uix.dialogs.password_dialog import OpenWalletDialog, ChangePasswordDialog, PincodeDialog ## lazy imports for factory so that widgets can be used in kv #Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard') @@ -379,8 +379,6 @@ class ElectrumWindow(App): # cached dialogs self._settings_dialog = None - self._pincode_dialog = None - self._password_dialog = None self._channels_dialog = None self._addresses_dialog = None self.fee_status = self.electrum_config.get_fee_status() @@ -635,43 +633,10 @@ class ElectrumWindow(App): return if self.wallet and self.wallet.storage.path == path: return - wallet = self.daemon.load_wallet(path, None) - if wallet: - if wallet.has_password(): - def on_success(x): - # save password in memory - self.password = x - self.load_wallet(wallet) - self.password_dialog( - basename = wallet.basename(), - check_password=wallet.check_password, - on_success=on_success, - on_failure=self.stop) - else: - self.load_wallet(wallet) else: def launch_wizard(): - storage = WalletStorage(path) - if not storage.file_exists(): - wizard = Factory.InstallWizard(self.electrum_config, self.plugins) - wizard.path = path - wizard.bind(on_wizard_complete=self.on_wizard_complete) - wizard.run('new') - else: - if storage.is_encrypted(): - if not storage.is_encrypted_with_user_pw(): - raise Exception("Kivy GUI does not support this type of encrypted wallet files.") - def on_password(pw): - self.password = pw - storage.decrypt(pw) - self._on_decrypted_storage(storage) - self.password_dialog( - basename = storage.basename(), - check_password=storage.check_password, - on_success=on_password, - on_failure=self.stop) - return - self._on_decrypted_storage(storage) + d = OpenWalletDialog(self, path, self.on_open_wallet) + d.open() if not ask_if_wizard: launch_wizard() else: @@ -685,6 +650,21 @@ class ElectrumWindow(App): d = Question(_('Do you want to launch the wizard again?'), handle_answer) d.open() + def on_open_wallet(self, pw, storage): + if not storage.file_exists(): + wizard = Factory.InstallWizard(self.electrum_config, self.plugins) + wizard.path = storage.path + wizard.bind(on_wizard_complete=self.on_wizard_complete) + wizard.run('new') + else: + try: + storage.decrypt(pw) + except StorageReadWriteError: + app.show_error(_("R/W error accessing path")) + return + self.password = pw + self._on_decrypted_storage(storage) + def on_stop(self): Logger.info('on_stop') self.stop_wallet() @@ -749,8 +729,8 @@ class ElectrumWindow(App): def wallets_dialog(self): from .uix.dialogs.wallets import WalletDialog - d = WalletDialog() - d.path = os.path.dirname(self.electrum_config.get_wallet_path()) + dirname = os.path.dirname(self.electrum_config.get_wallet_path()) + d = WalletDialog(dirname, self.load_wallet_by_name) d.open() def popup_dialog(self, name): @@ -969,7 +949,8 @@ class ElectrumWindow(App): def on_resume(self): now = time.time() if self.wallet and self.wallet.has_password() and now - self.pause_time > 5*60: - self.pincode_dialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop) + d = PincodeDialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop) + d.open() if self.nfcscanner: self.nfcscanner.nfc_enable() @@ -1150,11 +1131,12 @@ class ElectrumWindow(App): if self.electrum_config.get('pin_code'): msg += "\n" + _("Enter your PIN code to proceed") on_success = lambda pw: f(*args, self.password) - self.pincode_dialog( + d = PincodeDialog( message = msg, check_password=self.check_pin_code, on_success=on_success, on_failure=lambda: None) + d.open() else: d = Question( msg, @@ -1242,45 +1224,28 @@ class ElectrumWindow(App): if pin != self.electrum_config.get('pin_code'): raise InvalidPassword - def password_dialog(self, **kwargs): - if self._password_dialog is None: - self._password_dialog = PasswordDialog() - self._password_dialog.init(self, **kwargs) - self._password_dialog.open() - - def pincode_dialog(self, **kwargs): - if self._pincode_dialog is None: - self._pincode_dialog = PincodeDialog() - self._pincode_dialog.init(self, **kwargs) - self._pincode_dialog.open() - def change_password(self, cb): def on_success(old_password, new_password): self.wallet.update_password(old_password, new_password) self.password = new_password self.show_info(_("Your password was updated")) on_failure = lambda: self.show_error(_("Password not updated")) - self.password_dialog( - basename = self.wallet.basename(), - check_password = self.wallet.check_password, - on_success=on_success, on_failure=on_failure, - is_change=True, - has_password=self.wallet.has_password()) + d = ChangePasswordDialog(self, self.wallet, on_success, on_failure) + d.open() def change_pin_code(self, cb): - if self._pincode_dialog is None: - self._pincode_dialog = PincodeDialog() def on_success(old_password, new_password): self.electrum_config.set_key('pin_code', new_password) cb() self.show_info(_("PIN updated") if new_password else _('PIN disabled')) on_failure = lambda: self.show_error(_("PIN not updated")) - self._pincode_dialog.init( + + d = PincodeDialog( self, check_password=self.check_pin_code, on_success=on_success, on_failure=on_failure, is_change=True, has_password = self.has_pin_code()) - self._pincode_dialog.open() + d.open() def save_backup(self): if platform != 'android': diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py @@ -1168,9 +1168,8 @@ class InstallWizard(BaseWizard, Widget): def on_failure(): self.show_error(_('Password mismatch')) self.run('request_password', run_next) - popup = PasswordDialog() app = App.get_running_app() - popup.init( + popup = PasswordDialog( app, check_password=lambda x:True, on_success=on_success, diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py @@ -1,4 +1,5 @@ from typing import Callable, TYPE_CHECKING, Optional, Union +import os from kivy.app import App from kivy.factory import Factory @@ -8,8 +9,11 @@ from decimal import Decimal from kivy.clock import Clock from electrum.util import InvalidPassword +from electrum.wallet import WalletStorage from electrum.gui.kivy.i18n import _ +from .wallets import WalletDialog + if TYPE_CHECKING: from ...main_window import ElectrumWindow from electrum.wallet import Abstract_Wallet @@ -23,6 +27,7 @@ Builder.load_string(''' message: '' basename:'' is_change: False + require_password: True BoxLayout: size_hint: 1, 1 orientation: 'vertical' @@ -57,6 +62,8 @@ Builder.load_string(''' BoxLayout: orientation: 'horizontal' id: box_generic_password + disabled: not root.require_password + opacity: int(root.require_password) size_hint_y: 0.05 height: '40dp' TextInput: @@ -79,6 +86,20 @@ Builder.load_string(''' textinput_generic_password.password = False if textinput_generic_password.password else True Widget: size_hint: 1, 1 + BoxLayout: + orientation: 'horizontal' + size_hint: 1, 0.5 + Button: + text: 'Cancel' + size_hint: 0.5, None + height: '48dp' + on_release: popup.dismiss() + Button: + text: 'Next' + size_hint: 0.5, None + height: '48dp' + on_release: + popup.on_password(textinput_generic_password.text) <PincodeDialog@Popup> @@ -142,9 +163,9 @@ Builder.load_string(''' ''') -class AbstractPasswordDialog: +class AbstractPasswordDialog(Factory.Popup): - def init(self, app: 'ElectrumWindow', *, + def __init__(self, app: 'ElectrumWindow', *, check_password = None, on_success: Callable = None, on_failure: Callable = None, is_change: bool = False, @@ -152,6 +173,7 @@ class AbstractPasswordDialog: has_password: bool = False, message: str = '', basename:str=''): + Factory.Popup.__init__(self) self.app = app self.pw_check = check_password self.message = message @@ -234,17 +256,26 @@ class AbstractPasswordDialog: self.clear_password() -class PasswordDialog(AbstractPasswordDialog, Factory.Popup): +class PasswordDialog(AbstractPasswordDialog): enter_pw_message = _('Enter your password') enter_new_pw_message = _('Enter new password') confirm_new_pw_message = _('Confirm new password') wrong_password_message = _('Wrong password') allow_disable = False + def __init__(self, app, **kwargs): + AbstractPasswordDialog.__init__(self, app, **kwargs) + def clear_password(self): self.ids.textinput_generic_password.text = '' def on_password(self, pw: str): + # + if not self.require_password: + self.success = True + self.message = _('Please wait...') + self.dismiss() + return # if setting new generic password, enforce min length if self.level > 0: if len(pw) < 6: @@ -253,17 +284,18 @@ class PasswordDialog(AbstractPasswordDialog, Factory.Popup): # don't enforce minimum length on existing self.do_check(pw) - def select_file(self): - self.app.wallets_dialog() -class PincodeDialog(AbstractPasswordDialog, Factory.Popup): +class PincodeDialog(AbstractPasswordDialog): enter_pw_message = _('Enter your PIN') enter_new_pw_message = _('Enter new PIN') confirm_new_pw_message = _('Confirm new PIN') wrong_password_message = _('Wrong PIN') allow_disable = True + def __init__(self, app, **kwargs): + AbstractPasswordDialog.__init__(self, app, **kwargs) + def clear_password(self): self.ids.kb.password = '' @@ -271,3 +303,51 @@ class PincodeDialog(AbstractPasswordDialog, Factory.Popup): # PIN codes are exactly 6 chars if len(pw) >= 6: self.do_check(pw) + + +class ChangePasswordDialog(PasswordDialog): + + def __init__(self, app, wallet, on_success, on_failure): + PasswordDialog.__init__(self, app, + basename = wallet.basename(), + check_password = wallet.check_password, + on_success=on_success, + on_failure=on_failure, + is_change=True, + has_password=wallet.has_password()) + + +class OpenWalletDialog(PasswordDialog): + + def __init__(self, app, path, callback): + self.app = app + self.callback = callback + PasswordDialog.__init__(self, app, + on_success=lambda pw: self.callback(pw, self.storage), + on_failure=self.app.stop) + self.init_storage_from_path(path) + + def select_file(self): + dirname = os.path.dirname(self.app.electrum_config.get_wallet_path()) + d = WalletDialog(dirname, self.init_storage_from_path) + d.open() + + def init_storage_from_path(self, path): + self.storage = WalletStorage(path) + self.basename = self.storage.basename() + if not self.storage.file_exists(): + self.require_password = False + self.message = _('Press Next to create') + elif self.storage.is_encrypted(): + if not self.storage.is_encrypted_with_user_pw(): + raise Exception("Kivy GUI does not support this type of encrypted wallet files.") + self.require_password = True + self.pw_check = self.storage.check_password + self.message = self.enter_pw_message + else: + # it is a bit wasteful load the wallet here and load it again in main_window, + # but that is fine, because we are progressively enforcing storage encryption. + wallet = self.app.daemon.load_wallet(path, None) + self.require_password = wallet.has_password() + self.pw_check = wallet.check_password + self.message = self.enter_pw_message if self.require_password else _('Wallet not encrypted') diff --git a/electrum/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py @@ -12,7 +12,6 @@ from ...i18n import _ from .label_dialog import LabelDialog Builder.load_string(''' -#:import os os <WalletDialog@Popup>: title: _('Wallets') id: popup @@ -40,7 +39,7 @@ Builder.load_string(''' text: _('New') on_release: popup.dismiss() - root.new_wallet(app, wallet_selector.path) + root.new_wallet(wallet_selector.path) Button: id: open_button size_hint: 0.1, None @@ -49,26 +48,21 @@ Builder.load_string(''' disabled: not wallet_selector.selection on_release: popup.dismiss() - root.open_wallet(app) + root.callback(wallet_selector.selection[0]) ''') class WalletDialog(Factory.Popup): - def new_wallet(self, app, dirname): + def __init__(self, path, callback): + Factory.Popup.__init__(self) + self.path = path + self.callback = callback + + def new_wallet(self, dirname): def cb(filename): if not filename: return # FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible - try: - app.load_wallet_by_name(os.path.join(dirname, filename)) - except StorageReadWriteError: - app.show_error(_("R/W error accessing path")) + self.callback(os.path.join(dirname, filename)) d = LabelDialog(_('Enter wallet name'), '', cb) d.open() - - def open_wallet(self, app): - try: - app.load_wallet_by_name(self.ids.wallet_selector.selection[0]) - except StorageReadWriteError: - app.show_error(_("R/W error accessing path")) -