Browse Source

implement auth by PIN and allow auth override to wallet password

by passing method='wallet' to auth_protect
patch-4
Sander van Grieken 3 years ago
parent
commit
2a13212ded
  1. 8
      electrum/gui/qml/auth.py
  2. 90
      electrum/gui/qml/components/Pin.qml
  3. 50
      electrum/gui/qml/components/Preferences.qml
  4. 79
      electrum/gui/qml/components/main.qml
  5. 19
      electrum/gui/qml/qeconfig.py

8
electrum/gui/qml/auth.py

@ -4,9 +4,9 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot
from electrum.logging import get_logger
def auth_protect(func=None, reject=None):
def auth_protect(func=None, reject=None, method='pin'):
if func is None:
return partial(auth_protect, reject=reject)
return partial(auth_protect, reject=reject, method=method)
@wraps(func)
def wrapper(self, *args, **kwargs):
@ -15,14 +15,14 @@ def auth_protect(func=None, reject=None):
self._logger.debug('object already has a pending authed function call')
raise Exception('object already has a pending authed function call')
setattr(self, '__auth_fcall', (func,args,kwargs,reject))
getattr(self, 'authRequired').emit()
getattr(self, 'authRequired').emit(method)
return wrapper
class AuthMixin:
_auth_logger = get_logger(__name__)
authRequired = pyqtSignal()
authRequired = pyqtSignal([str],arguments=['method'])
@pyqtSlot()
def authProceed(self):

90
electrum/gui/qml/components/Pin.qml

@ -0,0 +1,90 @@
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0
import org.electrum 1.0
import "controls"
Dialog {
id: root
width: parent.width * 2/3
height: parent.height * 1/3
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: "#aa000000"
}
focus: true
standardButtons: Dialog.Cancel
property string mode // [check, enter, change]
property string pincode // old one passed in when change, new one passed out
property int _phase: mode == 'enter' ? 1 : 0 // 0 = existing pin, 1 = new pin, 2 = re-enter new pin
property string _pin
function submit() {
if (_phase == 0) {
if (pin.text == pincode) {
pin.text = ''
if (mode == 'check')
accepted()
else
_phase = 1
return
}
}
if (_phase == 1) {
_pin = pin.text
pin.text = ''
_phase = 2
return
}
if (_phase == 2) {
if (_pin == pin.text) {
pincode = pin.text
accepted()
}
return
}
}
ColumnLayout {
width: parent.width
height: parent.height
Label {
text: [qsTr('Enter PIN'), qsTr('Enter New PIN'), qsTr('Re-enter New PIN')][_phase]
font.pixelSize: constants.fontSizeXXLarge
Layout.alignment: Qt.AlignHCenter
}
TextField {
id: pin
Layout.preferredWidth: root.width *2/3
Layout.alignment: Qt.AlignHCenter
font.pixelSize: constants.fontSizeXXLarge
maximumLength: 6
inputMethodHints: Qt.ImhDigitsOnly
echoMode: TextInput.Password
focus: true
onTextChanged: {
if (text.length == 6) {
submit()
}
}
}
Item { Layout.fillHeight: true; Layout.preferredWidth: 1 }
}
}

50
electrum/gui/qml/components/Preferences.qml

@ -6,6 +6,8 @@ import QtQuick.Controls.Material 2.0
import org.electrum 1.0
Pane {
id: preferences
property string title: qsTr("Preferences")
ColumnLayout {
@ -116,6 +118,49 @@ Pane {
}
}
Label {
text: qsTr('PIN')
}
RowLayout {
Label {
text: Config.pinCode == '' ? qsTr('Off'): qsTr('On')
color: Material.accentColor
Layout.rightMargin: constants.paddingMedium
}
Button {
text: qsTr('Enable')
visible: Config.pinCode == ''
onClicked: {
var dialog = pinSetup.createObject(preferences, {mode: 'enter'})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.open()
}
}
Button {
text: qsTr('Change')
visible: Config.pinCode != ''
onClicked: {
var dialog = pinSetup.createObject(preferences, {mode: 'change', pincode: Config.pinCode})
dialog.accepted.connect(function() {
Config.pinCode = dialog.pincode
dialog.close()
})
dialog.open()
}
}
Button {
text: qsTr('Remove')
visible: Config.pinCode != ''
onClicked: {
Config.pinCode = ''
}
}
}
Label {
text: qsTr('Lightning Routing')
}
@ -136,6 +181,11 @@ Pane {
}
Component {
id: pinSetup
Pin {}
}
Component.onCompleted: {
baseUnit.currentIndex = ['BTC','mBTC','bits','sat'].indexOf(Config.baseUnit)
thousands.checked = Config.thousandsSeparator

79
electrum/gui/qml/components/main.qml

@ -179,6 +179,14 @@ ApplicationWindow
}
}
property alias pinDialog: _pinDialog
Component {
id: _pinDialog
Pin {
onClosed: destroy()
}
}
NotificationPopup {
id: notificationPopup
}
@ -221,7 +229,7 @@ ApplicationWindow
interval: 5000
repeat: false
}
Connections {
target: Daemon
function onWalletRequiresPassword() {
@ -233,6 +241,9 @@ ApplicationWindow
var dialog = app.messageDialog.createObject(app, {'text': error})
dialog.open()
}
function onAuthRequired(method) {
handleAuthRequired(Daemon, method)
}
}
Connections {
@ -244,45 +255,61 @@ ApplicationWindow
Connections {
target: Daemon.currentWallet
function onAuthRequired() {
function onAuthRequired(method) {
handleAuthRequired(Daemon.currentWallet, method)
}
// TODO: add to notification queue instead of barging through
function onPaymentSucceeded(key) {
notificationPopup.show(qsTr('Payment Succeeded'))
}
function onPaymentFailed(key, reason) {
notificationPopup.show(qsTr('Payment Failed') + ': ' + reason)
}
}
Connections {
target: Config
function onAuthRequired(method) {
handleAuthRequired(Config, method)
}
}
function handleAuthRequired(qtobject, method) {
console.log('AUTHENTICATING USING METHOD ' + method)
if (method == 'wallet') {
if (Daemon.currentWallet.verify_password('')) {
// wallet has no password
Daemon.currentWallet.authProceed()
qtobject.authProceed()
} else {
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
dialog.accepted.connect(function() {
if (Daemon.currentWallet.verify_password(dialog.password)) {
Daemon.currentWallet.authProceed()
qtobject.authProceed()
} else {
Daemon.currentWallet.authCancel()
qtobject.authCancel()
}
})
dialog.rejected.connect(function() {
Daemon.currentWallet.authCancel()
qtobject.authCancel()
})
dialog.open()
}
} else if (method == 'pin') {
if (Config.pinCode == '') {
// no PIN configured
qtobject.authProceed()
} else {
var dialog = app.pinDialog.createObject(app, {mode: 'check', pincode: Config.pinCode})
dialog.accepted.connect(function() {
qtobject.authProceed()
dialog.close()
})
dialog.rejected.connect(function() {
qtobject.authCancel()
})
dialog.open()
}
}
// TODO: add to notification queue instead of barging through
function onPaymentSucceeded(key) {
notificationPopup.show(qsTr('Payment Succeeded'))
}
function onPaymentFailed(key, reason) {
notificationPopup.show(qsTr('Payment Failed') + ': ' + reason)
}
}
Connections {
target: Daemon
function onAuthRequired() {
var dialog = app.messageDialog.createObject(app, {'text': 'Auth placeholder', 'yesno': true})
dialog.yesClicked.connect(function() {
Daemon.authProceed()
})
dialog.noClicked.connect(function() {
Daemon.authCancel()
})
dialog.open()
}
}
}

19
electrum/gui/qml/qeconfig.py

@ -6,8 +6,9 @@ from electrum.logging import get_logger
from electrum.util import DECIMAL_POINT_DEFAULT, format_satoshis
from .qetypes import QEAmount
from .auth import AuthMixin, auth_protect
class QEConfig(QObject):
class QEConfig(AuthMixin, QObject):
def __init__(self, config, parent=None):
super().__init__(parent)
self.config = config
@ -80,6 +81,22 @@ class QEConfig(QObject):
self.config.set_key('confirmed_only', not checked, True)
self.spendUnconfirmedChanged.emit()
pinCodeChanged = pyqtSignal()
@pyqtProperty(str, notify=pinCodeChanged)
def pinCode(self):
return self.config.get('pin_code', '')
@pinCode.setter
def pinCode(self, pin_code):
if pin_code == '':
self.pinCodeRemoveAuth()
self.config.set_key('pin_code', pin_code, True)
self.pinCodeChanged.emit()
@auth_protect(method='wallet')
def pinCodeRemoveAuth(self):
pass # no-op
useGossipChanged = pyqtSignal()
@pyqtProperty(bool, notify=useGossipChanged)
def useGossip(self):

Loading…
Cancel
Save