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' s.state = 'failed'
errorText.text = reason errorText.text = reason
} }
function onPaymentAuthRejected() {
dialog.close()
}
} }
} }

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

@ -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()
}
}
} }

16
electrum/gui/qml/qewallet.py

@ -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]:

Loading…
Cancel
Save