Browse Source

qt: 2fa implement OTP check

master
Sander van Grieken 2 years ago
parent
commit
fd28c66670
  1. 88
      electrum/plugins/trustedcoin/qt.py
  2. 13
      electrum/plugins/trustedcoin/qt_common.py

88
electrum/plugins/trustedcoin/qt.py

@ -25,26 +25,22 @@
from functools import partial from functools import partial
import threading import threading
import sys
import os import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap, QMovie, QColor
from PyQt5.QtCore import QObject, pyqtSignal, QTimer from PyQt5.QtCore import QObject, pyqtSignal, QSize, Qt
from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout,
QRadioButton, QCheckBox, QLineEdit, QPushButton, QWidget) QRadioButton, QCheckBox, QLineEdit, QPushButton, QWidget)
from electrum.i18n import _ from electrum.i18n import _
from electrum import keystore
from electrum.bip32 import xpub_type
from electrum.plugin import hook from electrum.plugin import hook
from electrum.util import is_valid_email from electrum.util import is_valid_email
from electrum.logging import Logger, get_logger from electrum.logging import Logger, get_logger
from electrum.base_wizard import GoBack, UserCancelled from electrum.base_wizard import GoBack, UserCancelled
from .qt_common import QSignalObject
from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton, from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton,
CancelButton, Buttons, icon_path, WWLabel, CloseButton, ChoicesLayout) CancelButton, Buttons, icon_path, WWLabel, CloseButton, ChoicesLayout, ColorScheme)
from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.gui.qt.qrcodewidget import QRCodeWidget
from electrum.gui.qt.amountedit import AmountEdit from electrum.gui.qt.amountedit import AmountEdit
from electrum.gui.qt.main_window import StatusBarButton from electrum.gui.qt.main_window import StatusBarButton
@ -52,10 +48,8 @@ from electrum.gui.qt.installwizard import InstallWizard
from electrum.gui.qt.wizard.wallet import WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt from electrum.gui.qt.wizard.wallet import WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt
from electrum.gui.qt.wizard.wizard import WizardComponent from electrum.gui.qt.wizard.wizard import WizardComponent
# from .trustedcoin import TrustedCoinPlugin, server, DISCLAIMER, make_xpub, get_signing_xpub, get_user_id from .qt_common import QSignalObject
from .trustedcoin import (TrustedCoinPlugin, server, ErrorConnectingServer, from .trustedcoin import TrustedCoinPlugin, server, DISCLAIMER
DISCLAIMER, get_user_id, get_signing_xpub,
TrustedCoinException, make_xpub)
if TYPE_CHECKING: if TYPE_CHECKING:
@ -505,6 +499,7 @@ class WCShowConfirmOTP(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Authenticator secret')) WizardComponent.__init__(self, parent, wizard, title=_('Authenticator secret'))
self.plugin = wizard.plugins.get_plugin('trustedcoin') self.plugin = wizard.plugins.get_plugin('trustedcoin')
self._otp_verified = False
self.new_otp = QWidget() self.new_otp = QWidget()
new_otp_layout = QVBoxLayout() new_otp_layout = QVBoxLayout()
@ -527,6 +522,18 @@ class WCShowConfirmOTP(WizardComponent):
self.authlabelnew = WWLabel(_('Then, enter your Google Authenticator code:')) self.authlabelnew = WWLabel(_('Then, enter your Google Authenticator code:'))
self.authlabelexist = WWLabel(_('Google Authenticator code:')) self.authlabelexist = WWLabel(_('Google Authenticator code:'))
self.spinner = QMovie(icon_path('spinner.gif'))
self.spinner.setScaledSize(QSize(24, 24))
self.spinner.setBackgroundColor(QColor('black'))
self.spinner_l = QLabel()
self.spinner_l.setMargin(5)
self.spinner_l.setVisible(False)
self.spinner_l.setMovie(self.spinner)
self.otp_status_l = QLabel()
self.otp_status_l.setAlignment(Qt.AlignHCenter)
self.otp_status_l.setVisible(False)
self.resetlabel = WWLabel(_('If you have lost your OTP secret, click the button below to request a new secret from the server.')) self.resetlabel = WWLabel(_('If you have lost your OTP secret, click the button below to request a new secret from the server.'))
self.button = QPushButton('Request OTP secret') self.button = QPushButton('Request OTP secret')
self.button.clicked.connect(self.on_request_otp) self.button.clicked.connect(self.on_request_otp)
@ -534,15 +541,18 @@ class WCShowConfirmOTP(WizardComponent):
hbox = QHBoxLayout() hbox = QHBoxLayout()
hbox.addWidget(self.authlabelnew) hbox.addWidget(self.authlabelnew)
hbox.addWidget(self.authlabelexist) hbox.addWidget(self.authlabelexist)
pw = AmountEdit(None, is_int = True) hbox.addStretch(1)
pw.setFocus(True) hbox.addWidget(self.spinner_l)
pw.setMaximumWidth(150) self.otp_e = AmountEdit(None, is_int=True)
hbox.addWidget(pw) self.otp_e.setFocus(True)
# hbox.addStretch(1) self.otp_e.setMaximumWidth(150)
self.otp_e.textEdited.connect(self.on_otp_edited)
hbox.addWidget(self.otp_e)
self.layout().addWidget(self.new_otp) self.layout().addWidget(self.new_otp)
self.layout().addWidget(self.exist_otp) self.layout().addWidget(self.exist_otp)
self.layout().addLayout(hbox) self.layout().addLayout(hbox)
self.layout().addWidget(self.otp_status_l)
self.layout().addWidget(self.resetlabel) self.layout().addWidget(self.resetlabel)
self.layout().addWidget(self.button) self.layout().addWidget(self.button)
self.layout().addStretch(1) self.layout().addStretch(1)
@ -550,6 +560,10 @@ class WCShowConfirmOTP(WizardComponent):
def on_ready(self): def on_ready(self):
self.plugin.so.busyChanged.connect(self.on_busy_changed) self.plugin.so.busyChanged.connect(self.on_busy_changed)
self.plugin.so.remoteKeyError.connect(self.on_remote_key_error) self.plugin.so.remoteKeyError.connect(self.on_remote_key_error)
self.plugin.so.otpSuccess.connect(self.on_otp_success)
self.plugin.so.otpError.connect(self.on_otp_error)
self.plugin.so.remoteKeyError.connect(self.on_remote_key_error)
self.plugin.so.createKeystore(self.wizard_data['2fa_email']) self.plugin.so.createKeystore(self.wizard_data['2fa_email'])
def update(self): def update(self):
@ -558,8 +572,8 @@ class WCShowConfirmOTP(WizardComponent):
self.exist_otp.setVisible(not is_new) self.exist_otp.setVisible(not is_new)
self.authlabelnew.setVisible(is_new) self.authlabelnew.setVisible(is_new)
self.authlabelexist.setVisible(not is_new) self.authlabelexist.setVisible(not is_new)
self.resetlabel.setVisible(not is_new) self.resetlabel.setVisible(not is_new and not self._otp_verified)
self.button.setVisible(not is_new) self.button.setVisible(not is_new and not self._otp_verified)
if self.plugin.so.otpSecret: if self.plugin.so.otpSecret:
self.secretlabel.setText(self.plugin.so.otpSecret) self.secretlabel.setText(self.plugin.so.otpSecret)
@ -568,18 +582,50 @@ class WCShowConfirmOTP(WizardComponent):
self.qr.setData(uri) self.qr.setData(uri)
def on_busy_changed(self): def on_busy_changed(self):
self.busy = self.plugin.so.busy if not self.plugin.so._verifyingOtp:
if not self.busy: self.busy = self.plugin.so.busy
self.update() if not self.busy:
self.update()
def on_remote_key_error(self, text): def on_remote_key_error(self, text):
self._logger.error(text) self._logger.error(text)
self.error = text self.error = text
def on_request_otp(self): def on_request_otp(self):
self.otp_status_l.setVisible(False)
self.plugin.so.resetOtpSecret() self.plugin.so.resetOtpSecret()
self.update() self.update()
def on_otp_success(self):
self._otp_verified = True
self.otp_status_l.setText('Valid!')
self.otp_status_l.setVisible(True)
self.otp_status_l.setStyleSheet(ColorScheme.GREEN.as_stylesheet(False))
self.setEnabled(True)
self.spinner_l.setVisible(False)
self.spinner.stop()
self.valid = True
def on_otp_error(self, message):
self.otp_status_l.setText(message)
self.otp_status_l.setVisible(True)
self.otp_status_l.setStyleSheet(ColorScheme.RED.as_stylesheet(False))
self.setEnabled(True)
self.spinner_l.setVisible(False)
self.spinner.stop()
def on_otp_edited(self):
self.otp_status_l.setVisible(False)
text = self.otp_e.text()
if len(text) == 6:
# verify otp
self.plugin.so.checkOtp(self.plugin.so.shortId, int(text))
self.setEnabled(False)
self.spinner_l.setVisible(True)
self.spinner.start()
self.otp_e.setText('')
def apply(self): def apply(self):
pass pass

13
electrum/plugins/trustedcoin/qt_common.py

@ -13,7 +13,6 @@ from electrum.gui.qt_common.plugins import PluginQObject
class QSignalObject(PluginQObject): class QSignalObject(PluginQObject):
canSignWithoutServerChanged = pyqtSignal() canSignWithoutServerChanged = pyqtSignal()
_canSignWithoutServer = False
termsAndConditionsRetrieved = pyqtSignal([str], arguments=['message']) termsAndConditionsRetrieved = pyqtSignal([str], arguments=['message'])
termsAndConditionsError = pyqtSignal([str], arguments=['message']) termsAndConditionsError = pyqtSignal([str], arguments=['message'])
otpError = pyqtSignal([str], arguments=['message']) otpError = pyqtSignal([str], arguments=['message'])
@ -21,13 +20,9 @@ class QSignalObject(PluginQObject):
disclaimerChanged = pyqtSignal() disclaimerChanged = pyqtSignal()
keystoreChanged = pyqtSignal() keystoreChanged = pyqtSignal()
otpSecretChanged = pyqtSignal() otpSecretChanged = pyqtSignal()
_otpSecret = ''
shortIdChanged = pyqtSignal() shortIdChanged = pyqtSignal()
_shortId = ''
billingModelChanged = pyqtSignal() billingModelChanged = pyqtSignal()
_billingModel = []
_remoteKeyState = ''
remoteKeyStateChanged = pyqtSignal() remoteKeyStateChanged = pyqtSignal()
remoteKeyError = pyqtSignal([str], arguments=['message']) remoteKeyError = pyqtSignal([str], arguments=['message'])
@ -36,6 +31,12 @@ class QSignalObject(PluginQObject):
def __init__(self, plugin, wizard, parent): def __init__(self, plugin, wizard, parent):
super().__init__(plugin, parent) super().__init__(plugin, parent)
self.wizard = wizard self.wizard = wizard
self._canSignWithoutServer = False
self._otpSecret = ''
self._shortId = ''
self._billingModel = []
self._remoteKeyState = ''
self._verifyingOtp = False
@pyqtProperty(str, notify=disclaimerChanged) @pyqtProperty(str, notify=disclaimerChanged)
def disclaimer(self): def disclaimer(self):
@ -241,7 +242,9 @@ class QSignalObject(PluginQObject):
finally: finally:
self._busy = False self._busy = False
self.busyChanged.emit() self.busyChanged.emit()
self._verifyingOtp = False
self._verifyingOtp = True
self._busy = True self._busy = True
self.busyChanged.emit() self.busyChanged.emit()
t = threading.Thread(target=check_otp_task, daemon=True) t = threading.Thread(target=check_otp_task, daemon=True)

Loading…
Cancel
Save