Add @auth_protect decorator.
This guards function calls by storing the function, args and kwargs into an added attribute '__auth_fcall' on the object using the decorator, then emits a signal that can be handled by the UI. The UI can signal auth-success or auth-failure back to the object by calling either the authProceed() slot or the authCancel slot. The object utilizing this decorator MUST inherit/mixin the AuthMixin class, which provides the above two slots, and handling of state. The decorator also accepts a 'reject' parameter, containing the name of a parameterless function on the object, which is called when authentication has failed/is cancelled.
This commit is contained in:
58
electrum/gui/qml/auth.py
Normal file
58
electrum/gui/qml/auth.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from functools import wraps, partial
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
from electrum.logging import get_logger
|
||||||
|
|
||||||
|
def auth_protect(func=None, reject=None):
|
||||||
|
if func is None:
|
||||||
|
return partial(auth_protect, reject=reject)
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
self._logger.debug(str(self))
|
||||||
|
if hasattr(self, '__auth_fcall'):
|
||||||
|
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()
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class AuthMixin:
|
||||||
|
_auth_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
authRequired = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def authProceed(self):
|
||||||
|
self._auth_logger.debug('Proceeding with authed fn()')
|
||||||
|
try:
|
||||||
|
self._auth_logger.debug(str(getattr(self, '__auth_fcall')))
|
||||||
|
(func,args,kwargs,reject) = getattr(self, '__auth_fcall')
|
||||||
|
r = func(self, *args, **kwargs)
|
||||||
|
return r
|
||||||
|
except Exception as e:
|
||||||
|
self._auth_logger.error('Error executing wrapped fn(): %s' % repr(e))
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
delattr(self,'__auth_fcall')
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def authCancel(self):
|
||||||
|
self._auth_logger.debug('Cancelling authed fn()')
|
||||||
|
if not hasattr(self, '__auth_fcall'):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
(func,args,kwargs,reject) = getattr(self, '__auth_fcall')
|
||||||
|
if reject is not None:
|
||||||
|
if hasattr(self, reject):
|
||||||
|
getattr(self, reject)()
|
||||||
|
else:
|
||||||
|
self._auth_logger.error('Reject method \'%s\' not defined' % reject)
|
||||||
|
except Exception as e:
|
||||||
|
self._auth_logger.error('Error executing reject function \'%s\': %s' % (reject, repr(e)))
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
delattr(self, '__auth_fcall')
|
||||||
@@ -123,5 +123,8 @@ Dialog {
|
|||||||
s.state = 'failed'
|
s.state = 'failed'
|
||||||
errorText.text = reason
|
errorText.text = reason
|
||||||
}
|
}
|
||||||
|
function onPaymentAuthRejected() {
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,4 +221,18 @@ ApplicationWindow
|
|||||||
notificationPopup.show(message)
|
notificationPopup.show(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Daemon.currentWallet
|
||||||
|
function onAuthRequired() {
|
||||||
|
var dialog = app.messageDialog.createObject(app, {'text': 'Auth placeholder', 'yesno': true})
|
||||||
|
dialog.yesClicked.connect(function() {
|
||||||
|
Daemon.currentWallet.authProceed()
|
||||||
|
})
|
||||||
|
dialog.noClicked.connect(function() {
|
||||||
|
Daemon.currentWallet.authCancel()
|
||||||
|
})
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ from .qetransactionlistmodel import QETransactionListModel
|
|||||||
from .qeaddresslistmodel import QEAddressListModel
|
from .qeaddresslistmodel import QEAddressListModel
|
||||||
from .qechannellistmodel import QEChannelListModel
|
from .qechannellistmodel import QEChannelListModel
|
||||||
from .qetypes import QEAmount
|
from .qetypes import QEAmount
|
||||||
|
from .auth import AuthMixin, auth_protect
|
||||||
|
|
||||||
class QEWallet(QObject):
|
class QEWallet(AuthMixin, QObject):
|
||||||
__instances = []
|
__instances = []
|
||||||
|
|
||||||
# this factory method should be used to instantiate QEWallet
|
# this factory method should be used to instantiate QEWallet
|
||||||
@@ -90,7 +91,7 @@ class QEWallet(QObject):
|
|||||||
return self.wallet.is_up_to_date()
|
return self.wallet.is_up_to_date()
|
||||||
|
|
||||||
def on_network(self, event, *args):
|
def on_network(self, event, *args):
|
||||||
if event == 'new_transaction':
|
if event in ['new_transaction', 'payment_succeeded']:
|
||||||
# Handle in GUI thread (_network_signal -> on_network_qt)
|
# Handle in GUI thread (_network_signal -> on_network_qt)
|
||||||
self._network_signal.emit(event, args)
|
self._network_signal.emit(event, args)
|
||||||
else:
|
else:
|
||||||
@@ -356,6 +357,7 @@ class QEWallet(QObject):
|
|||||||
tx.set_rbf(use_rbf)
|
tx.set_rbf(use_rbf)
|
||||||
self.sign_and_broadcast(tx)
|
self.sign_and_broadcast(tx)
|
||||||
|
|
||||||
|
@auth_protect
|
||||||
def sign_and_broadcast(self, tx):
|
def sign_and_broadcast(self, tx):
|
||||||
def cb(result):
|
def cb(result):
|
||||||
self._logger.info('signing was succesful? %s' % str(result))
|
self._logger.info('signing was succesful? %s' % str(result))
|
||||||
@@ -379,13 +381,20 @@ class QEWallet(QObject):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
paymentAuthRejected = pyqtSignal()
|
||||||
|
def ln_auth_rejected(self):
|
||||||
|
self.paymentAuthRejected.emit()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
@auth_protect(reject='ln_auth_rejected')
|
||||||
def pay_lightning_invoice(self, invoice_key):
|
def pay_lightning_invoice(self, invoice_key):
|
||||||
self._logger.debug('about to pay LN')
|
self._logger.debug('about to pay LN')
|
||||||
invoice = self.wallet.get_invoice(invoice_key)
|
invoice = self.wallet.get_invoice(invoice_key)
|
||||||
assert(invoice)
|
assert(invoice)
|
||||||
assert(invoice.lightning_invoice)
|
assert(invoice.lightning_invoice)
|
||||||
|
|
||||||
amount_msat = invoice.get_amount_msat()
|
amount_msat = invoice.get_amount_msat()
|
||||||
|
|
||||||
def pay_thread():
|
def pay_thread():
|
||||||
try:
|
try:
|
||||||
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
|
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
|
||||||
@@ -393,8 +402,7 @@ class QEWallet(QObject):
|
|||||||
fut.result()
|
fut.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.userNotify(repr(e))
|
self.userNotify(repr(e))
|
||||||
#self.app.show_error(repr(e))
|
|
||||||
#self.save_invoice(invoice)
|
|
||||||
threading.Thread(target=pay_thread).start()
|
threading.Thread(target=pay_thread).start()
|
||||||
|
|
||||||
def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]:
|
def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user