From c3872bc86d9ed7dd33ae5f9784b5925bcad7c603 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Henryk=20Pl=C3=B6tz?= Date: Wed, 29 Aug 2018 17:03:18 +0200 Subject: [PATCH] Pool Exceptions in one module Raise Exceptions during executions, generally prepare for integration with external library users Be over-cautious with PIN errors: On first sign of wrong PIN, block the PIN from usage (raise Exception when trying to use the PIN). Library user will need to create a new FinTS3PinTanClient instance. --- fints/client.py | 22 +++++++++++++++++----- fints/connection.py | 4 +--- fints/dialog.py | 31 ++++++++++++++++--------------- fints/exceptions.py | 21 +++++++++++++++++++++ fints/utils.py | 6 ++++++ 5 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 fints/exceptions.py diff --git a/fints/client.py b/fints/client.py index d34d9f9..a6e37af 100644 --- a/fints/client.py +++ b/fints/client.py @@ -38,6 +38,7 @@ from .segments.journal import HKPRO3, HKPRO4 from .types import SegmentSequence from .utils import MT535_Miniparser, Password, mt940_to_array, compress_datablob, decompress_datablob, SubclassesMixin from .parser import FinTS3Serializer +from .exceptions import * logger = logging.getLogger(__name__) @@ -108,10 +109,10 @@ class FinTS3Client: def _ensure_system_id(self): raise NotImplemented() - def _process_response(self, segment, response): + def _process_response(self, dialog, segment, response): pass - def process_response_message(self, message: FinTSInstituteMessage, internal_send=True): + def process_response_message(self, dialog, message: FinTSInstituteMessage, internal_send=True): bpa = message.find_segment_first(HIBPA3) if bpa: self.bpa = bpa @@ -137,7 +138,7 @@ class FinTS3Client: self._call_callbacks(None, response) - self._process_response(None, response) + self._process_response(dialog, None, response) for seg in message.find_segments(HIRMS2): for response in seg.responses: @@ -148,7 +149,7 @@ class FinTS3Client: self._call_callbacks(segment, response) - self._process_response(segment, response) + self._process_response(dialog, segment, response) def _send_with_possible_retry(self, dialog, command_seg, resume_func): response = dialog._send(command_seg) @@ -158,6 +159,7 @@ class FinTS3Client: if self._standing_dialog: raise Exception("Cannot double __enter__() {}".format(self)) self._standing_dialog = self._get_dialog() + self._standing_dialog.lazy_init = True # FIXME Inelegant self._standing_dialog.__enter__() def __exit__(self, exc_type, exc_value, traceback): @@ -931,7 +933,7 @@ class FinTS3PinTanClient(FinTS3Client): resume_func = getattr(self, challenge.resume_method) return resume_func(challenge.command_seg, response) - def _process_response(self, segment, response): + def _process_response(self, dialog, segment, response): if response.code == '3920': self.allowed_security_functions = list(response.parameters) if self.selected_security_function is None or not self.selected_security_function in self.allowed_security_functions: @@ -952,6 +954,16 @@ class FinTS3PinTanClient(FinTS3Client): # Fall back to onestep self.set_tan_mechanism('999') + if (not dialog.open and response.code.startswith('9')) or response.code in ('9340', '9910', '9930', '9931', '9942'): + # Assume all 9xxx errors in a not-yet-open dialog refer to the PIN or authentication + # During a dialog also listen for the following codes which may explicitly indicate an + # incorrect pin: 9340, 9910, 9930, 9931, 9942 + # Fail-safe block all further attempts with this PIN + if self.pin: + self.pin.block() + raise FinTSClientPINError("Error during dialog initialization, PIN wrong?") + + def get_tan_mechanisms(self): """ Get the available TAN mechanisms. diff --git a/fints/connection.py b/fints/connection.py index 20cba03..23349fa 100644 --- a/fints/connection.py +++ b/fints/connection.py @@ -7,12 +7,10 @@ from fints.parser import FinTS3Parser from fints.utils import Password from .message import FinTSInstituteMessage, FinTSMessage +from .exceptions import * logger = logging.getLogger(__name__) -class FinTSConnectionError(Exception): - pass - class FinTSHTTPSConnection: def __init__(self, url): diff --git a/fints/dialog.py b/fints/dialog.py index 6a020f1..163d74b 100644 --- a/fints/dialog.py +++ b/fints/dialog.py @@ -12,16 +12,14 @@ from .segments.auth import HKIDN2, HKVVB3 from .segments.dialog import HKEND1 from .segments.message import HNHBK3, HNHBS1 from .utils import compress_datablob, decompress_datablob +from .connection import FinTSConnectionError +from .exceptions import * logger = logging.getLogger(__name__) DIALOGUE_ID_UNASSIGNED = '0' DATA_BLOB_MAGIC = b'python-fints_DIALOG_DATABLOB' -class FinTSDialogError(Exception): - pass - - class FinTSDialog: def __init__(self, client=None, lazy_init=False, enc_mechanism=None, auth_mechanisms=[]): self.client = client @@ -51,7 +49,7 @@ class FinTSDialog: def init(self, *extra_segments): if self.paused: - raise Error("Cannot init() a paused dialog") + raise FinTSDialogStateError("Cannot init() a paused dialog") if self.need_init and not self.open: segments = [ @@ -77,15 +75,18 @@ class FinTSDialog: retval = self.send(*segments, internal_send=True) self.need_init = False return retval - except: + except Exception as e: self.open = False - raise + if isinstance(e, (FinTSConnectionError, FinTSClientError)): + raise + else: + raise FinTSDialogInitError("Couldn't establish dialog with bank, Authentication data wrong?") from e finally: self.lazy_init = False def end(self): if self.paused: - raise Error("Cannot end() on a paused dialog") + raise FinTSDialogStateError("Cannot end() on a paused dialog") if self.open: response = self.send(HKEND1(self.dialogue_id), internal_send=True) @@ -95,14 +96,14 @@ class FinTSDialog: internal_send = kwargs.pop('internal_send', False) if self.paused: - raise Error("Cannot send() on a paused dialog") + raise FinTSDialogStateError("Cannot send() on a paused dialog") if not self.open: if self.lazy_init and self.need_init: self.init() if not self.open: - raise Exception("Cannot send on dialog that is not open") + raise FinTSDialogStateError("Cannot send on dialog that is not open") message = self.new_customer_message() for s in segments: @@ -129,16 +130,16 @@ class FinTSDialog: if self.dialogue_id == DIALOGUE_ID_UNASSIGNED: seg = response.find_segment_first(HNHBK3) if not seg: - raise ValueError('Could not find dialogue_id') + raise FinTSDialogError('Could not find dialogue_id') self.dialogue_id = seg.dialogue_id - self.client.process_response_message(response, internal_send=internal_send) + self.client.process_response_message(self, response, internal_send=internal_send) return response def new_customer_message(self): if self.paused: - raise Error("Cannot call new_customer_message() on a paused dialog") + raise FinTSDialogStateError("Cannot call new_customer_message() on a paused dialog") message = FinTSCustomerMessage(self) message += HNHBK3(0, 300, self.dialogue_id, self.next_message_number[message.DIRECTION]) @@ -150,7 +151,7 @@ class FinTSDialog: def finish_message(self, message): if self.paused: - raise Error("Cannot call finish_message() on a paused dialog") + raise FinTSDialogStateError("Cannot call finish_message() on a paused dialog") # Create signature(s) in reverse order: from inner to outer for auth_mech in reversed(self.auth_mechanisms): @@ -166,7 +167,7 @@ class FinTSDialog: def pause(self): # FIXME Document, test if self.paused: - raise Error("Cannot pause a paused dialog") + raise FinTSDialogStateError("Cannot pause a paused dialog") external_dialog = self external_client = self.client diff --git a/fints/exceptions.py b/fints/exceptions.py new file mode 100644 index 0000000..3fb5e0a --- /dev/null +++ b/fints/exceptions.py @@ -0,0 +1,21 @@ +class FinTSError(Exception): + pass + +class FinTSClientError(FinTSError): + pass + +class FinTSClientPINError(FinTSClientError): + pass + +class FinTSDialogError(FinTSError): + pass + +class FinTSDialogStateError(FinTSDialogError): + pass + +class FinTSDialogInitError(FinTSDialogError): + pass + +class FinTSConnectionError(FinTSError): + pass + diff --git a/fints/utils.py b/fints/utils.py index f4afe23..4d6b35b 100644 --- a/fints/utils.py +++ b/fints/utils.py @@ -264,6 +264,7 @@ class Password(str): def __init__(self, value): self.value = value + self.blocked = False @classmethod @contextmanager @@ -274,7 +275,12 @@ class Password(str): finally: cls.protected = False + def block(self): + self.blocked = True + def __str__(self): + if self.blocked and not self.protected: + raise Exception("Refusing to use PIN after block") return '***' if self.protected else str(self.value) def __repr__(self): -- 2.39.5