electrum

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

password_dialog.py (11097B)


      1 #!/usr/bin/env python
      2 #
      3 # Electrum - lightweight Bitcoin client
      4 # Copyright (C) 2013 ecdsa@github
      5 #
      6 # Permission is hereby granted, free of charge, to any person
      7 # obtaining a copy of this software and associated documentation files
      8 # (the "Software"), to deal in the Software without restriction,
      9 # including without limitation the rights to use, copy, modify, merge,
     10 # publish, distribute, sublicense, and/or sell copies of the Software,
     11 # and to permit persons to whom the Software is furnished to do so,
     12 # subject to the following conditions:
     13 #
     14 # The above copyright notice and this permission notice shall be
     15 # included in all copies or substantial portions of the Software.
     16 #
     17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
     21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
     22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     24 # SOFTWARE.
     25 
     26 import re
     27 import math
     28 from functools import partial
     29 
     30 from PyQt5.QtCore import Qt
     31 from PyQt5.QtGui import QPixmap
     32 from PyQt5.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox
     33 
     34 from electrum.i18n import _
     35 from electrum.plugin import run_hook
     36 
     37 from .util import (icon_path, WindowModalDialog, OkButton, CancelButton, Buttons,
     38                    PasswordLineEdit)
     39 
     40 
     41 def check_password_strength(password):
     42 
     43     '''
     44     Check the strength of the password entered by the user and return back the same
     45     :param password: password entered by user in New Password
     46     :return: password strength Weak or Medium or Strong
     47     '''
     48     password = password
     49     n = math.log(len(set(password)))
     50     num = re.search("[0-9]", password) is not None and re.match("^[0-9]*$", password) is None
     51     caps = password != password.upper() and password != password.lower()
     52     extra = re.match("^[a-zA-Z0-9]*$", password) is None
     53     score = len(password)*( n + caps + num + extra)/20
     54     password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"}
     55     return password_strength[min(3, int(score))]
     56 
     57 
     58 PW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)
     59 
     60 
     61 class PasswordLayout(object):
     62 
     63     titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
     64 
     65     def __init__(self, msg, kind, OK_button, wallet=None, force_disable_encrypt_cb=False):
     66         self.wallet = wallet
     67 
     68         self.pw = PasswordLineEdit()
     69         self.new_pw = PasswordLineEdit()
     70         self.conf_pw = PasswordLineEdit()
     71         self.kind = kind
     72         self.OK_button = OK_button
     73 
     74         vbox = QVBoxLayout()
     75         label = QLabel(msg + "\n")
     76         label.setWordWrap(True)
     77 
     78         grid = QGridLayout()
     79         grid.setSpacing(8)
     80         grid.setColumnMinimumWidth(0, 150)
     81         grid.setColumnMinimumWidth(1, 100)
     82         grid.setColumnStretch(1,1)
     83 
     84         if kind == PW_PASSPHRASE:
     85             vbox.addWidget(label)
     86             msgs = [_('Passphrase:'), _('Confirm Passphrase:')]
     87         else:
     88             logo_grid = QGridLayout()
     89             logo_grid.setSpacing(8)
     90             logo_grid.setColumnMinimumWidth(0, 70)
     91             logo_grid.setColumnStretch(1,1)
     92 
     93             logo = QLabel()
     94             logo.setAlignment(Qt.AlignCenter)
     95 
     96             logo_grid.addWidget(logo,  0, 0)
     97             logo_grid.addWidget(label, 0, 1, 1, 2)
     98             vbox.addLayout(logo_grid)
     99 
    100             m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')
    101             msgs = [m1, _('Confirm Password:')]
    102             if wallet and wallet.has_password():
    103                 grid.addWidget(QLabel(_('Current Password:')), 0, 0)
    104                 grid.addWidget(self.pw, 0, 1)
    105                 lockfile = "lock.png"
    106             else:
    107                 lockfile = "unlock.png"
    108             logo.setPixmap(QPixmap(icon_path(lockfile))
    109                            .scaledToWidth(36, mode=Qt.SmoothTransformation))
    110 
    111         grid.addWidget(QLabel(msgs[0]), 1, 0)
    112         grid.addWidget(self.new_pw, 1, 1)
    113 
    114         grid.addWidget(QLabel(msgs[1]), 2, 0)
    115         grid.addWidget(self.conf_pw, 2, 1)
    116         vbox.addLayout(grid)
    117 
    118         # Password Strength Label
    119         if kind != PW_PASSPHRASE:
    120             self.pw_strength = QLabel()
    121             grid.addWidget(self.pw_strength, 3, 0, 1, 2)
    122             self.new_pw.textChanged.connect(self.pw_changed)
    123 
    124         self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
    125         self.encrypt_cb.setEnabled(False)
    126         grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)
    127         self.encrypt_cb.setVisible(kind != PW_PASSPHRASE)
    128 
    129         def enable_OK():
    130             ok = self.new_pw.text() == self.conf_pw.text()
    131             OK_button.setEnabled(ok)
    132             self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text())
    133                                        and not force_disable_encrypt_cb)
    134         self.new_pw.textChanged.connect(enable_OK)
    135         self.conf_pw.textChanged.connect(enable_OK)
    136 
    137         self.vbox = vbox
    138 
    139     def title(self):
    140         return self.titles[self.kind]
    141 
    142     def layout(self):
    143         return self.vbox
    144 
    145     def pw_changed(self):
    146         password = self.new_pw.text()
    147         if password:
    148             colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green",
    149                       "Very Strong":"Green"}
    150             strength = check_password_strength(password)
    151             label = (_("Password Strength") + ": " + "<font color="
    152                      + colors[strength] + ">" + strength + "</font>")
    153         else:
    154             label = ""
    155         self.pw_strength.setText(label)
    156 
    157     def old_password(self):
    158         if self.kind == PW_CHANGE:
    159             return self.pw.text() or None
    160         return None
    161 
    162     def new_password(self):
    163         pw = self.new_pw.text()
    164         # Empty passphrases are fine and returned empty.
    165         if pw == "" and self.kind != PW_PASSPHRASE:
    166             pw = None
    167         return pw
    168 
    169     def clear_password_fields(self):
    170         for field in [self.pw, self.new_pw, self.conf_pw]:
    171             field.clear()
    172 
    173 
    174 class PasswordLayoutForHW(object):
    175 
    176     def __init__(self, msg, wallet=None):
    177         self.wallet = wallet
    178 
    179         vbox = QVBoxLayout()
    180         label = QLabel(msg + "\n")
    181         label.setWordWrap(True)
    182 
    183         grid = QGridLayout()
    184         grid.setSpacing(8)
    185         grid.setColumnMinimumWidth(0, 150)
    186         grid.setColumnMinimumWidth(1, 100)
    187         grid.setColumnStretch(1,1)
    188 
    189         logo_grid = QGridLayout()
    190         logo_grid.setSpacing(8)
    191         logo_grid.setColumnMinimumWidth(0, 70)
    192         logo_grid.setColumnStretch(1,1)
    193 
    194         logo = QLabel()
    195         logo.setAlignment(Qt.AlignCenter)
    196 
    197         logo_grid.addWidget(logo,  0, 0)
    198         logo_grid.addWidget(label, 0, 1, 1, 2)
    199         vbox.addLayout(logo_grid)
    200 
    201         if wallet and wallet.has_storage_encryption():
    202             lockfile = "lock.png"
    203         else:
    204             lockfile = "unlock.png"
    205         logo.setPixmap(QPixmap(icon_path(lockfile))
    206                        .scaledToWidth(36, mode=Qt.SmoothTransformation))
    207 
    208         vbox.addLayout(grid)
    209 
    210         self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
    211         grid.addWidget(self.encrypt_cb, 1, 0, 1, 2)
    212 
    213         self.vbox = vbox
    214 
    215     def title(self):
    216         return _("Toggle Encryption")
    217 
    218     def layout(self):
    219         return self.vbox
    220 
    221 
    222 class ChangePasswordDialogBase(WindowModalDialog):
    223 
    224     def __init__(self, parent, wallet):
    225         WindowModalDialog.__init__(self, parent)
    226         is_encrypted = wallet.has_storage_encryption()
    227         OK_button = OkButton(self)
    228 
    229         self.create_password_layout(wallet, is_encrypted, OK_button)
    230 
    231         self.setWindowTitle(self.playout.title())
    232         vbox = QVBoxLayout(self)
    233         vbox.addLayout(self.playout.layout())
    234         vbox.addStretch(1)
    235         vbox.addLayout(Buttons(CancelButton(self), OK_button))
    236         self.playout.encrypt_cb.setChecked(is_encrypted)
    237 
    238     def create_password_layout(self, wallet, is_encrypted, OK_button):
    239         raise NotImplementedError()
    240 
    241 
    242 class ChangePasswordDialogForSW(ChangePasswordDialogBase):
    243 
    244     def __init__(self, parent, wallet):
    245         ChangePasswordDialogBase.__init__(self, parent, wallet)
    246         if not wallet.has_password():
    247             self.playout.encrypt_cb.setChecked(True)
    248 
    249     def create_password_layout(self, wallet, is_encrypted, OK_button):
    250         if not wallet.has_password():
    251             msg = _('Your wallet is not protected.')
    252             msg += ' ' + _('Use this dialog to add a password to your wallet.')
    253         else:
    254             if not is_encrypted:
    255                 msg = _('Your bitcoins are password protected. However, your wallet file is not encrypted.')
    256             else:
    257                 msg = _('Your wallet is password protected and encrypted.')
    258             msg += ' ' + _('Use this dialog to change your password.')
    259         self.playout = PasswordLayout(msg=msg,
    260                                       kind=PW_CHANGE,
    261                                       OK_button=OK_button,
    262                                       wallet=wallet,
    263                                       force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
    264 
    265     def run(self):
    266         try:
    267             if not self.exec_():
    268                 return False, None, None, None
    269             return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
    270         finally:
    271             self.playout.clear_password_fields()
    272 
    273 
    274 class ChangePasswordDialogForHW(ChangePasswordDialogBase):
    275 
    276     def __init__(self, parent, wallet):
    277         ChangePasswordDialogBase.__init__(self, parent, wallet)
    278 
    279     def create_password_layout(self, wallet, is_encrypted, OK_button):
    280         if not is_encrypted:
    281             msg = _('Your wallet file is NOT encrypted.')
    282         else:
    283             msg = _('Your wallet file is encrypted.')
    284         msg += '\n' + _('Note: If you enable this setting, you will need your hardware device to open your wallet.')
    285         msg += '\n' + _('Use this dialog to toggle encryption.')
    286         self.playout = PasswordLayoutForHW(msg)
    287 
    288     def run(self):
    289         if not self.exec_():
    290             return False, None
    291         return True, self.playout.encrypt_cb.isChecked()
    292 
    293 
    294 class PasswordDialog(WindowModalDialog):
    295 
    296     def __init__(self, parent=None, msg=None):
    297         msg = msg or _('Please enter your password')
    298         WindowModalDialog.__init__(self, parent, _("Enter Password"))
    299         self.pw = pw = PasswordLineEdit()
    300         vbox = QVBoxLayout()
    301         vbox.addWidget(QLabel(msg))
    302         grid = QGridLayout()
    303         grid.setSpacing(8)
    304         grid.addWidget(QLabel(_('Password')), 1, 0)
    305         grid.addWidget(pw, 1, 1)
    306         vbox.addLayout(grid)
    307         vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
    308         self.setLayout(vbox)
    309         run_hook('password_dialog', pw, grid, 1)
    310 
    311     def run(self):
    312         try:
    313             if not self.exec_():
    314                 return
    315             return self.pw.text()
    316         finally:
    317             self.pw.clear()