]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Allow TANs during dialog initialization
authorRaphael Michel <mail@raphaelmichel.de>
Sun, 29 Sep 2019 15:21:51 +0000 (17:21 +0200)
committerRaphael Michel <mail@raphaelmichel.de>
Sun, 29 Sep 2019 15:21:51 +0000 (17:21 +0200)
fints/client.py
fints/dialog.py

index fa91beed1a6a14ab6eeb8958ff0e61af2d244013..0e2b023b65ce78af6651a567d120930ee7c2a5e6 100644 (file)
@@ -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)
index dd0efb9aa7f1aa6d080b1a246d84d43fd46faaef..e115d69a2e8df237912b7cc4bb0bf9dd432602ab 100644 (file)
@@ -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: