]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Comdirect support (#142)
authorrhn <gihu.rhn@porcupinefactory.org>
Sun, 5 Jun 2022 17:40:15 +0000 (19:40 +0200)
committerGitHub <noreply@github.com>
Sun, 5 Jun 2022 17:40:15 +0000 (19:40 +0200)
Co-authored-by: Morre <morre@mor.re>
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
.travis.yml
README.md
docs/tans.rst
docs/tested.rst
fints/utils.py
tests/test_utils.py [new file with mode: 0644]

index cbd252fce33803e37d0399c86ae1c4da54e203d7..4979cb9dac0922a6b1b30ad00d39f990a42683cd 100644 (file)
@@ -1,9 +1,10 @@
 language: python
 sudo: false
 python:
-    - "3.4"
-    - "3.5"
     - "3.6"
+    - "3.7"
+    - "3.8"
+    - "3.9"
 install:
     - pip install -U pip wheel coverage codecov
     - pip install -r requirements.txt pytest pytest-mock
index f7fad706d938b03ecd40f890291ea526d657b0fb..62c6f4a6cfb1446181955e92c70c7b105e87baab 100644 (file)
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Limitations
   * Fetching balances
   * Fetching holdings
   * SEPA transfers and debits (only with required TAN and with specific TAN methods)
-* Supports Python 3.4+
+* Supports Python 3.6+
 
 Credits and License
 -------------------
index 52d55d3ca5393cefaed9fc32a32c7d99e9d012f2..ff9138f8e868b9304f2b7264ae55a326e4641e27 100644 (file)
@@ -79,6 +79,23 @@ with the TAN:
     except KeyboardInterrupt:
         pass
 
+photoTAN
+--------
+
+If you want to use photoTAN, we provide a helper function to decode the challenge. Pass the ``challenge_hhd_uc`` value to this method:
+
+.. autofunction:: fints.hhd.utils.decode_phototan_image
+
+This returns a dictionary with a ``mime_type`` and an ``image`` field. The ``image`` field contains the binary data
+of the image itself and can e.g. be written to a file
+
+.. code-block:: python
+    from fints.utils import decode_phototan_image
+
+    data = decode_phototan_image(challenge_hhduc)
+    writer = open("tan.png", "wb")
+    writer.write(data["image"])
+    writer.close()
 
 Sending the TAN
 ---------------
index 3a53e7828b64873c463c45b3e3074018133a69d9..4b695d26c493478d498119d73e7630d9a743433d 100644 (file)
@@ -10,11 +10,13 @@ Bank                                     Transactions Holdings Transfer Debits
 Postbank                                 Yes
 BBBank eG                                Yes                   Yes
 Sparkasse Heidelberg                     Yes
+comdirect                                Yes
 ======================================== ============ ======== ======== ======
 
 Tested security functions
 -------------------------
 
+* ``902`` "photoTAN"
 * ``921`` "pushTAN"
 * ``930`` "mobile TAN"
 * ``942`` "mobile TAN"
index 66ee1e4309558f2c9f4700cb5fbe800eaad3a5c8..20108a1da86d3a452f1988b7abcdb8002b50072a 100644 (file)
@@ -329,3 +329,33 @@ def minimal_interactive_cli_bootstrap(client):
                           p=mm))
             choice = input("Choice: ").strip()
             client.set_tan_medium(m[1][int(choice)])
+
+
+def decode_phototan_image(data):
+    """
+    This decodes photoTAN data sent as challenge_hhduc into its mime type and the actual image data.
+
+    :returns: a dictionary with two values, 'mime_type' and 'image'
+    :rtype: dict
+
+    The markup of the data is taken from https://github.com/hbci4j/hbci4java/blob/c8eabe6809e8d0271f944ea28a59ed6b736af56e/src/main/java/org/kapott/hbci/manager/MatrixCode.java#L61-L97
+    The encoding is taken from https://github.com/hbci4j/hbci4java/blob/c8eabe6809e8d0271f944ea28a59ed6b736af56e/src/main/java/org/kapott/hbci/comm/Comm.java#L46
+    """
+    # Mime type length is the first two bytes of data
+    mime_type_length = int.from_bytes(data[:2], byteorder='big')
+
+    # The mime type follows from byte three to (mime_type_length - 1)
+    mime_type = data[2:2 + mime_type_length].decode("iso-8859-1")
+
+    # The image length is coded in the next two bytes
+    image_length_start = 2 + mime_type_length
+    image_length = int.from_bytes(data[image_length_start:2 + image_length_start], byteorder='big')
+
+    # The actual image data is everything that follows.
+    # To be compatible with possible future extensions, we still slice the data
+    image = data[2 + image_length_start: 2 + image_length_start + image_length]
+
+    return {
+        "mime_type": mime_type,
+        "image": image
+    }
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644 (file)
index 0000000..01fe2bd
--- /dev/null
@@ -0,0 +1,27 @@
+import pytest
+from fints.utils import decode_phototan_image
+
+
+# HITAN3:
+#  'challenge' contains a HHD 1.3 code embedded in the normal text payload
+#  field.
+#  Example: 'CHLGUC  00312908881344731012345678900515,00CHLGTEXT0292Sie haben eine ...'
+#    The code in NeedTANResponse._parse_tan_challenge extracts
+#     '2908881344731012345678900515,00'
+#    from this, as a version 1.3 code. parse() should accept it
+#    (start code: '88134473', IBAN: '1234567890', amount: '15,00')
+
+# HITAN6:
+# 'challenge' contains 4 fields:
+#   2 bytes: mime type length
+#   x bytes (see above): mime type
+#   2 bytes: data length
+#   y bytes: image data
+
+CHALLENGE = b'\x00\timage/png\x0e\x82\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc8\x00\x00\x00\xc8\x08\x06\x00\x00\x00\xadX\xae\x9e\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02MIDATx\x9c\xed\xdd\xb1\r\xc40\x0c\x04A\xea\xe1\xfe[\xf6w\xb0\x89\x023\x98\xa9@ \xb0Pxgf\xdeY\xec}W?o\xce9_?!\xb9\xdf\x9d\xdf\xd7\x0f\x80\xcd\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x1e;\xdaw\xdc\xef\xce\xf6\xfb\xf9A \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x9c\x99Y=T\xbd}G\xdb\x0e\xf9\x9d\xed\xf7\xf3\x83@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@x\xech\xdfq\xbf;\xdb\xef\xe7\x07\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81pff\xf5P\xf5\xf6\x1dm;\xe4w\xb6\xdf\xcf\x0f\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02\xe1\x0fp-(\x89A#\xd8\xc6\x00\x00\x00\x00IEND\xaeB`\x82'
+
+def test_decode_phototan_image():
+    data = decode_phototan_image(CHALLENGE)
+
+    assert data["mime_type"] == "image/png"
+    assert data["image"] == b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc8\x00\x00\x00\xc8\x08\x06\x00\x00\x00\xadX\xae\x9e\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02MIDATx\x9c\xed\xdd\xb1\r\xc40\x0c\x04A\xea\xe1\xfe[\xf6w\xb0\x89\x023\x98\xa9@ \xb0Pxgf\xdeY\xec}W?o\xce9_?!\xb9\xdf\x9d\xdf\xd7\x0f\x80\xcd\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x1e;\xdaw\xdc\xef\xce\xf6\xfb\xf9A \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x9c\x99Y=T\xbd}G\xdb\x0e\xf9\x9d\xed\xf7\xf3\x83@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@x\xech\xdfq\xbf;\xdb\xef\xe7\x07\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81pff\xf5P\xf5\xf6\x1dm;\xe4w\xb6\xdf\xcf\x0f\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02\xe1\x0fp-(\x89A#\xd8\xc6\x00\x00\x00\x00IEND\xaeB`\x82'