From: rhn Date: Sun, 5 Jun 2022 17:40:15 +0000 (+0200) Subject: Comdirect support (#142) X-Git-Tag: v4.0.0~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=73921795da9a2cd21b38ca1998a1bfa4f3eec721;p=thirdparty%2Fpython-fints.git Comdirect support (#142) Co-authored-by: Morre Co-authored-by: Raphael Michel --- diff --git a/.travis.yml b/.travis.yml index cbd252f..4979cb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index f7fad70..62c6f4a 100644 --- 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 ------------------- diff --git a/docs/tans.rst b/docs/tans.rst index 52d55d3..ff9138f 100644 --- a/docs/tans.rst +++ b/docs/tans.rst @@ -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 --------------- diff --git a/docs/tested.rst b/docs/tested.rst index 3a53e78..4b695d2 100644 --- a/docs/tested.rst +++ b/docs/tested.rst @@ -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" diff --git a/fints/utils.py b/fints/utils.py index 66ee1e4..20108a1 100644 --- a/fints/utils.py +++ b/fints/utils.py @@ -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 index 0000000..01fe2bd --- /dev/null +++ b/tests/test_utils.py @@ -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'