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:
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"))
-