implement auth by PIN and allow auth override to wallet password
by passing method='wallet' to auth_protect
This commit is contained in:
@@ -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
Normal file
90
electrum/gui/qml/components/Pin.qml
Normal file
@@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,24 +255,8 @@ ApplicationWindow
|
||||
|
||||
Connections {
|
||||
target: Daemon.currentWallet
|
||||
function onAuthRequired() {
|
||||
if (Daemon.currentWallet.verify_password('')) {
|
||||
// wallet has no password
|
||||
Daemon.currentWallet.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()
|
||||
} else {
|
||||
Daemon.currentWallet.authCancel()
|
||||
}
|
||||
})
|
||||
dialog.rejected.connect(function() {
|
||||
Daemon.currentWallet.authCancel()
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
function onAuthRequired(method) {
|
||||
handleAuthRequired(Daemon.currentWallet, method)
|
||||
}
|
||||
// TODO: add to notification queue instead of barging through
|
||||
function onPaymentSucceeded(key) {
|
||||
@@ -273,16 +268,48 @@ ApplicationWindow
|
||||
}
|
||||
|
||||
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()
|
||||
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
|
||||
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)) {
|
||||
qtobject.authProceed()
|
||||
} else {
|
||||
qtobject.authCancel()
|
||||
}
|
||||
})
|
||||
dialog.rejected.connect(function() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user