Browse Source

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.
patch-4
Sander van Grieken 3 years ago
parent
commit
7cb8c347b5
  1. 58
      electrum/gui/qml/auth.py
  2. 3
      electrum/gui/qml/components/LightningPaymentProgressDialog.qml
  3. 14
      electrum/gui/qml/components/main.qml
  4. 16
      electrum/gui/qml/qewallet.py

58
electrum/gui/qml/auth.py

@ -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')

3
electrum/gui/qml/components/LightningPaymentProgressDialog.qml

@ -123,5 +123,8 @@ Dialog {
s.state = 'failed'
errorText.text = reason
}
function onPaymentAuthRejected() {
dialog.close()
}
}
}

14
electrum/gui/qml/components/main.qml

@ -221,4 +221,18 @@ ApplicationWindow
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()
}
}
}

16
electrum/gui/qml/qewallet.py

@ -21,8 +21,9 @@ from .qetransactionlistmodel import QETransactionListModel
from .qeaddresslistmodel import QEAddressListModel
from .qechannellistmodel import QEChannelListModel
from .qetypes import QEAmount
from .auth import AuthMixin, auth_protect
class QEWallet(QObject):
class QEWallet(AuthMixin, QObject):
__instances = []
# this factory method should be used to instantiate QEWallet
@ -90,7 +91,7 @@ class QEWallet(QObject):
return self.wallet.is_up_to_date()
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)
self._network_signal.emit(event, args)
else:
@ -356,6 +357,7 @@ class QEWallet(QObject):
tx.set_rbf(use_rbf)
self.sign_and_broadcast(tx)
@auth_protect
def sign_and_broadcast(self, tx):
def cb(result):
self._logger.info('signing was succesful? %s' % str(result))
@ -379,13 +381,20 @@ class QEWallet(QObject):
return
paymentAuthRejected = pyqtSignal()
def ln_auth_rejected(self):
self.paymentAuthRejected.emit()
@pyqtSlot(str)
@auth_protect(reject='ln_auth_rejected')
def pay_lightning_invoice(self, invoice_key):
self._logger.debug('about to pay LN')
invoice = self.wallet.get_invoice(invoice_key)
assert(invoice)
assert(invoice.lightning_invoice)
amount_msat = invoice.get_amount_msat()
def pay_thread():
try:
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
@ -393,8 +402,7 @@ class QEWallet(QObject):
fut.result()
except Exception as e:
self.userNotify(repr(e))
#self.app.show_error(repr(e))
#self.save_invoice(invoice)
threading.Thread(target=pay_thread).start()
def create_bitcoin_request(self, amount: int, message: str, expiration: int, ignore_gap: bool) -> Optional[str]:

Loading…
Cancel
Save