Skip to content

Commit

Permalink
Fix wallet password dialogs
Browse files Browse the repository at this point in the history
 - fix #6201
 - password dialogs are no longer cached
  • Loading branch information
ecdsa committed Jun 4, 2020
1 parent e07d5d8 commit a8ab76c
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 85 deletions.
93 changes: 29 additions & 64 deletions electrum/gui/kivy/main_window.py
Expand Up @@ -34,7 +34,7 @@
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 PasswordDialog, PincodeDialog, OpenWalletDialog, ChangePasswordDialog

## lazy imports for factory so that widgets can be used in kv
#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
Expand Down Expand Up @@ -379,8 +379,6 @@ def __init__(self, **kwargs):

# 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()
Expand Down Expand Up @@ -635,43 +633,10 @@ def load_wallet_by_name(self, path, ask_if_wizard=False):
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:
Expand All @@ -685,6 +650,21 @@ def handle_answer(b: bool):
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 = 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()
Expand Down Expand Up @@ -749,8 +729,8 @@ def on_channels(self, evt, wallet):

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):
Expand Down Expand Up @@ -969,7 +949,8 @@ def on_pause(self):
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()

Expand Down Expand Up @@ -1150,11 +1131,12 @@ def protected(self, msg, f, args):
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,
Expand Down Expand Up @@ -1242,45 +1224,28 @@ def check_pin_code(self, pin):
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':
Expand Down
91 changes: 85 additions & 6 deletions 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
Expand All @@ -8,8 +9,11 @@
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
Expand All @@ -23,6 +27,7 @@
message: ''
basename:''
is_change: False
require_password: True
BoxLayout:
size_hint: 1, 1
orientation: 'vertical'
Expand Down Expand Up @@ -57,6 +62,8 @@
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:
Expand All @@ -79,6 +86,20 @@
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('')
<PincodeDialog@Popup>
Expand Down Expand Up @@ -142,16 +163,17 @@
''')


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,
is_password: bool = True, # whether this is for a generic password or for a numeric PIN
has_password: bool = False,
message: str = '',
basename:str=''):
Factory.Popup.__init__(self)
self.app = app
self.pw_check = check_password
self.message = message
Expand Down Expand Up @@ -234,17 +256,25 @@ def do_check(self, pw):
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.dismiss()
return
# if setting new generic password, enforce min length
if self.level > 0:
if len(pw) < 6:
Expand All @@ -253,21 +283,70 @@ def on_password(self, pw: str):
# 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 = ''

def on_password(self, pw: str):
# 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')

0 comments on commit a8ab76c

Please sign in to comment.