From: Raphael Michel Date: Sun, 29 Sep 2019 15:21:51 +0000 (+0200) Subject: Allow TANs during dialog initialization X-Git-Tag: v3.0.0~2^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e6bfb911fe323e931993d483a47c3741f7ac9bd6;p=thirdparty%2Fpython-fints.git Allow TANs during dialog initialization --- diff --git a/fints/client.py b/fints/client.py index fa91bee..0e2b023 100644 --- a/fints/client.py +++ b/fints/client.py @@ -187,6 +187,7 @@ class FinTS3Client: self.product_version = product_version self.response_callbacks = [] self.mode = mode + self.init_tan_response = None self._standing_dialog = None if from_data: @@ -252,7 +253,11 @@ class FinTS3Client: def __exit__(self, exc_type, exc_value, traceback): if self._standing_dialog: - self._standing_dialog.__exit__(exc_type, exc_value, traceback) + if exc_type is not None and issubclass(exc_type, FinTSSCARequiredError): + # In case of SCARequiredError, the dialog has already been closed by the bank + self._standing_dialog.open = False + else: + self._standing_dialog.__exit__(exc_type, exc_value, traceback) else: raise Exception("Cannot double __exit__() {}".format(self)) @@ -817,6 +822,9 @@ class FinTS3Client: return retval + def _continue_dialog_initialization(self, command_seg, response): + return response + def sepa_debit(self, account: SEPAAccount, pain_message: str, multiple=False, cor1=False, control_sum=None, currency='EUR', book_as_single=False, pain_descriptor='urn:iso:std:iso:20022:tech:xsd:pain.008.003.01'): @@ -1078,6 +1086,7 @@ class FinTS3PinTanClient(FinTS3Client): self.allowed_security_functions = [] self.selected_security_function = None self.selected_tan_medium = None + self._bootstrap_mode = True super().__init__(bank_identifier=bank_identifier, user_id=user_id, customer_id=customer_id, *args, **kwargs) def _new_dialog(self, lazy_init=False): @@ -1102,6 +1111,11 @@ class FinTS3PinTanClient(FinTS3Client): auth_mechanisms=auth, ) + def fetch_tan_mechanisms(self): + self.set_tan_mechanism('999') + with self._new_dialog(): + return self.get_current_tan_mechanism() + def _ensure_system_id(self): if self.system_id != SYSTEM_ID_UNASSIGNED or self.user_id == CUSTOMER_ID_ANONYMOUS: return @@ -1153,7 +1167,10 @@ class FinTS3PinTanClient(FinTS3Client): if tan_process in ('1', '3', '4') and getattr(tan_mechanism, 'supported_media_number', None) is not None and \ tan_mechanism.supported_media_number > 1 and \ tan_mechanism.description_required == DescriptionRequired.MUST: - seg.tan_medium_name = self.selected_tan_medium.tan_medium_name + if self.selected_tan_medium: + seg.tan_medium_name = self.selected_tan_medium.tan_medium_name + else: + seg.tan_medium_name = 'DUMMY' if tan_process == '4' and tan_mechanism.VERSION >= 6: seg.segment_type = orig_seg.header.type @@ -1246,7 +1263,7 @@ class FinTS3PinTanClient(FinTS3Client): raise FinTSClientError("Error during dialog initialization, could not fetch BPD. Please check that you " "passed the correct bank identifier to the HBCI URL of the correct bank.") - if (not dialog.open and response.code.startswith('9')) or response.code in ('9340', '9910', '9930', '9931', '9942'): + if ((not dialog.open and response.code.startswith('9')) or response.code in ('9340', '9910', '9930', '9931', '9942')) and not self._bootstrap_mode: # 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 @@ -1264,7 +1281,11 @@ class FinTS3PinTanClient(FinTS3Client): raise FinTSClientTemporaryAuthError("Account is temporarily locked.") if response.code == '9075': - raise FinTSSCARequiredError("This operation requires strong customer authentication.") + if self._bootstrap_mode: + if self._standing_dialog: + self._standing_dialog.open = False + else: + raise FinTSSCARequiredError("This operation requires strong customer authentication.") def get_tan_mechanisms(self): """ @@ -1310,8 +1331,13 @@ class FinTS3PinTanClient(FinTS3Client): tan_media_type = media_type, tan_media_class = str(media_class), ) + tan_seg = self._get_tan_segment(seg, '4') - response = dialog.send(seg) + try: + self._bootstrap_mode = True + response = dialog.send(seg, tan_seg) + finally: + self._bootstrap_mode = False for resp in response.response_segments(seg, 'HITAB'): return resp.tan_usage_option, list(resp.tan_media_list) diff --git a/fints/dialog.py b/fints/dialog.py index dd0efb9..e115d69 100644 --- a/fints/dialog.py +++ b/fints/dialog.py @@ -48,7 +48,7 @@ class FinTSDialog: if self.paused: raise FinTSDialogStateError("Cannot init() a paused dialog") - from fints.client import FinTSClientMode + from fints.client import FinTSClientMode, NeedTANResponse if self.client.mode == FinTSClientMode.OFFLINE: raise FinTSDialogOfflineError("Cannot open a dialog with mode=FinTSClientMode.OFFLINE. " "This is a control flow error, no online functionality " @@ -70,14 +70,29 @@ class FinTSDialog: self.client.product_version ), ] + if self.client.mode == FinTSClientMode.INTERACTIVE and self.client.get_tan_mechanisms(): - segments.append(self.client._get_tan_segment(segments[0], '4')) + tan_seg = self.client._get_tan_segment(segments[0], '4') + segments.append(tan_seg) + else: + tan_seg = None + for s in extra_segments: segments.append(s) try: self.open = True retval = self.send(*segments, internal_send=True) + + if tan_seg: + for resp in retval.responses(tan_seg): + if resp.code == '0030': + self.client.init_tan_response = NeedTANResponse( + None, + retval.find_segment_first('HITAN'), + '_continue_dialog_initialization', + self.client.is_challenge_structured() + ) self.need_init = False return retval except Exception as e: