raphaelm / python-fints

Pure-python FinTS (formerly known as HBCI) implementation
https://pypi.python.org/pypi/fints
GNU Lesser General Public License v3.0
329 stars 81 forks source link

Postbank: get_tan_media returns None #101

Closed rhn closed 3 years ago

rhn commented 4 years ago

Get_tan_media returns None, after requesting strong auth.

*Bank I tested this with Name of the bank: Postbank FinTS URL: 'https://hbci.postbank.de/banking/hbci.do'

Expected behavior Getting TAN media should be possible before and as part of having to authenticate.

Code required to reproduce

f = FinTS3PinTanClient(*client_args)
minimal_interactive_cli_bootstrap(f)

Log output / error message

You should register your program with the ZKA and pass your own product_id as a parameter.
Multiple tan mechanisms available. Which one do you prefer?
0 Function 912: chipTAN optisch HHD1.4
1 Function 913: chipTAN manuell HHD1.4
Choice: 0
We need the name of the TAN medium, let's fetch them from the bank
Dialog response: 9075 - Dialog abgebrochen - starke Authentifizierung erforderlich.
Dialog response: 9050 - Teilweise fehlerhaft.
Traceback (most recent call last):
  File "/home/foo/main.py", line 178, in <module>
    pb_pdfs = list(postbank.make_statement(postbank_config))
  File "/home/foo/postbank.py", line 28, in make_statement
    minimal_interactive_cli_bootstrap(f)
  File "/home/foo/fints/utils.py", line 323, in minimal_interactive_cli_bootstrap
    if len(m[1]) == 1:
TypeError: 'NoneType' object is not subscriptable

Additional context Tested 2020-01-01. Previously used 2019-12-01, that worked OK.

I'm happy to provide detailed logs from the test example privately if there's such a need.

raphaelm commented 4 years ago

This is interesting, unfortunately my Postbank access does not use Chiptan and I can therefore not really reproduce this. Can you try providing full logs to mail-fints@raphaelmichel.de? No promises I'll find time to look at it though.

Tested 2020-01-01. Previously used 2019-12-01, that worked OK.

Not with the same version probably, right?

rhn commented 4 years ago

It was actually the same revision:

python-fints-2.0 d70d6f41d2d88d2bf54490631d982f62ddc58c8c
python-sepaxml 2f319e23e5004cabd3c144a46df2aafe562579f8
mt940 038f7a78e08ad9ac8a43a114e61c75c0a53d91b

Afterwards I checked with all 3 at master, with the same result.

I'll try to get the logs, thanks for the answer.

raphaelm commented 4 years ago

Yeah, running 3.0 would be important to make the logs useful ;)

rhn commented 4 years ago

I've logged in to the bank website to see if something needed to get unlocked (like the "account access" TAN was not asked again after I entered it via fints for the first time in July), but no change occurred.

rhn commented 4 years ago

Okay, got it! The bank didn't like the name of the TAN medium in HKTAN, and didn't want to divulge TAN media names itself. What helped:

f.selected_tan_medium = 'cT:the-name-I-gave'

minimal_interactive_cli_bootstrap(f)

Afterwards it got at least to the flicker code, which I didn't test..

raphaelm commented 4 years ago

Okay, that's interesting. The bug is still valid, get_tan_media should work, but at least you found a workaround

rhn commented 4 years ago

I tried to open a dialog without HKTAN, in order to give it HKTAB, and so changed dialog.init to contain:

            if (self.client.mode == FinTSClientMode.INTERACTIVE
                    and self.client.selected_tan_medium # new condition
                    and self.client.get_tan_mechanisms()):

but that gives me 9075 Dialog abgebrochen - starke Authentifizierung erforderlich.

What finally worked was starting a new dialog with HKSYN, HKTAB together:

        with self._get_dialog(lazy_init=True) as dialog:
            seg = HKTAB4(
                tan_media_type=TANMediaType2.ALL,
                tan_media_class=str(TANMediaClass4.ALL),
            )

            response = dialog.init(
                HKSYN3(SynchronizationMode.NEW_SYSTEM_ID),
                seg,
            )

This didn't fail and I got correct TAN names. I stopped testing at that point, since this is a nasty hack.

hekrause commented 4 years ago

Okay, got it! The bank didn't like the name of the TAN medium in HKTAN, and didn't want to divulge TAN media names itself. What helped:

f.selected_tan_medium = 'cT:the-name-I-gave'

minimal_interactive_cli_bootstrap(f)

Afterwards it got at least to the flicker code, which I didn't test..

I can confirm that this trick works but i am not sure what to enter there. I use the BestSign-Method and would enter for selected_tan_medium what is called "Bezeichner" n the Online Banking settings which goes something like "Samsung 12345678" but there is also an "Seal One-ID" which goes like "AA11-BB22-CC33"(changed of course).

from fints.utils import minimal_interactive_cli_bootstrap
f.selected_tan_medium = 'Test'
minimal_interactive_cli_bootstrap(f)
print(f.init_tan_response)

If i run this with a propper initalization of a FinTS3PinTanClient i get: grafik

When i now try to use the None object of course there is an error.


ERROR:fints.client:Dialog response: 9075 - Dialog abgebrochen - starke Authentifizierung erforderlich.
rhn commented 4 years ago

For chipTAN, the name the bank expects is "cT:" plus the name as displayed in the web interface. If you're feeling hacky, you can determine your methods using the test script from https://python-fints.readthedocs.io/en/latest/trouble.html , then modifying the def init() in dialog.py to also send HKTAB using the code from my example above, and manually search the logs for your TAN medium name (sorry, I didn't save my own logs, can't be more specific).

raphaelm commented 4 years ago

Thank you @rhn, that was a really good pointer. I was finally able to reproduce this today since my 90-day session at Postbank timed out. Unfortunately, I'll only be able to reproduce it the next time in another 90 days ;)

Looking at the spec, you can really read it both ways:

Es wird eine Dialoginitialisierung in Prozessvariante 1 bzw. 2 durchgeführt (vgl. hierzu die Abläufe in Kapitel B.4.3.2 bzw. B.4.3.3). In das
DE Segmentkennung in HKTAN wird der Wert HKTAB eingestellt.
Der vom Kundenprodukt hier als Füllwert gelieferte Inhalt des
Elementes Bezeichnung des TAN-Mediums in HKTAN ist vom
Kreditinstitut in dieser Situation zu ignorieren.

I therefore implemented @rhn's workaround: https://github.com/raphaelm/python-fints/commit/c9d8cbf9ae2369cadca32f394ba382f4ed80cb19

Unfortuantely, that broke Sparkasse, so for the very first time I introduced a bank-specific switch: https://github.com/raphaelm/python-fints/commit/41a2054d6488930972209d3f0ca8108e9be3a691

Can someone check if this fully resolves this issue?

rhn commented 4 years ago

Nice! Did you consider changing the get_tan_medium() function to attempt the same thing in both ways? Something like:

context, method = reuse_dialog
try:
    return _get_tan_media(context, method)
except MissingTAN:
    context, method = new_dialog
    return _get_tan(context, method)

This would be slightly tricky, because the dialog itself would have to mutate, but there would be no need for any bank-specific hacks.

hekrause commented 4 years ago

@raphaelm I tried it again and it seems to work, but i did not check it at the changed points from the last commit.

@rhn I found a reference with the correct tan_medium specifications for Postbank. https://www.postbank.de/firmenkunden/fk_infocenter_bestsign_software.html

This code below triggers the Postbank-app on the smartphone to popup so that i can confirm the "transaction".

from fints.client import FinTS3PinTanClient
from fints.utils import minimal_interactive_cli_bootstrap

# BLZ
bank_identifier = "REPLACE"
# PostbankID
user_id = "REPLACE"
# PostbankID Passwort
pin = "REPLACE"
# Postbank server
server = "https://hbci.postbank.de/banking/hbci.do"
# BestSign Bezeichner
tan_medium = "SO:REPLACE"

f = FinTS3PinTanClient(
    bank_identifier,
    user_id,
    pin,
    server
)

f.selected_tan_medium = tan_medium
f.fetch_tan_mechanisms()
f.set_tan_mechanism(list(f.get_tan_mechanisms().items())[int(0)][0])
minimal_interactive_cli_bootstrap(f)

with f:
    if f.init_tan_response:
        print("A TAN is required", f.init_tan_response.challenge_hhduc)
        tan = input('Please enter TAN:')
        f.send_tan(f.init_tan_response, tan)
    accounts = f.get_sepa_accounts()
raphaelm commented 3 years ago

I was able to reproduce this again and for me, https://github.com/raphaelm/python-fints/commit/3ccba314492507154a1e225128edc3f16e405976 fixed it (feel free to reopen with more info / new logs if it doesn't for someone else)