From 4cbbbfbe485f797ab893e19b594b1d6d2f07240d Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 12 Aug 2020 14:48:42 +0200 Subject: [PATCH] Implemented initializing dialog without strong customer authentication (but HKTAN is sent anyway) which is required that is essential for authenticated dialogs, e.g. TAN media which require some banks to be able to initialize an authenticated dialog --- .../net/dankito/banking/fints/FinTsClient.kt | 75 +++++++++++++------ .../banking/fints/messages/MessageBuilder.kt | 48 +++++++++++- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt index 1d429cb0..6d5acdc0 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt @@ -10,6 +10,7 @@ import net.dankito.banking.fints.messages.datenelemente.implementierte.Kundensys import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.* +import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.model.* import net.dankito.banking.fints.response.GetUserTanProceduresResponse import net.dankito.banking.fints.response.InstituteSegmentId @@ -446,21 +447,25 @@ open class FinTsClient( open fun getTanMediaList(bank: BankData, customer: CustomerData, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, callback: (GetTanMediaListResponse) -> Unit) { - sendMessageAndHandleResponse(bank, customer, true, { dialogContext -> + sendMessageAndHandleResponse(bank, customer, true, CustomerSegmentId.TanMediaList, { dialogContext -> messageBuilder.createGetTanMediaListMessage(dialogContext, tanMediaKind, tanMediumClass) }) { response -> - // TAN media list (= TAN generator list) is only returned for users with chipTAN TAN procedures - val tanMediaList = if (response.successful == false ) null - else response.getFirstSegmentById(InstituteSegmentId.TanMediaList) - - tanMediaList?.let { - customer.tanMedia = it.tanMedia - } - - callback(GetTanMediaListResponse(response, tanMediaList)) + handleGetTanMediaListResponse(response, customer, callback) } } + private fun handleGetTanMediaListResponse(response: Response, customer: CustomerData, callback: (GetTanMediaListResponse) -> Unit) { + // TAN media list (= TAN generator list) is only returned for users with chipTAN TAN procedures + val tanMediaList = if (response.successful == false) null + else response.getFirstSegmentById(InstituteSegmentId.TanMediaList) + + tanMediaList?.let { + customer.tanMedia = it.tanMedia + } + + callback(GetTanMediaListResponse(response, tanMediaList)) + } + open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData, callback: (FinTsClientResponse) -> Unit) { @@ -483,7 +488,7 @@ open class FinTsClient( protected open fun sendChangeTanMediumMessage(bank: BankData, customer: CustomerData, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?, callback: (FinTsClientResponse) -> Unit) { - sendMessageAndHandleResponse(bank, customer, false, { dialogContext -> + sendMessageAndHandleResponse(bank, customer, false, null, { dialogContext -> messageBuilder.createChangeTanMediumMessage(newActiveTanMedium, dialogContext, enteredAtc?.tan, enteredAtc?.atc) }) { response -> callback(FinTsClientResponse(response)) @@ -494,7 +499,7 @@ open class FinTsClient( open fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, customer: CustomerData, account: AccountData, callback: (FinTsClientResponse) -> Unit) { - sendMessageAndHandleResponse(bank, customer, true, { dialogContext -> + sendMessageAndHandleResponse(bank, customer, true, null, { dialogContext -> messageBuilder.createBankTransferMessage(bankTransferData, account, dialogContext) }) { response -> callback(FinTsClientResponse(response)) @@ -503,22 +508,35 @@ open class FinTsClient( protected open fun sendMessageAndHandleResponse(bank: BankData, customer: CustomerData, messageMayRequiresTan: Boolean = true, + segmentForNonStrongCustomerAuthenticationTwoStepTanProcess: CustomerSegmentId? = null, createMessage: (DialogContext) -> MessageBuilderResult, callback: (Response) -> Unit) { val dialogContext = DialogContext(bank, customer, product) - initDialog(dialogContext) { initDialogResponse -> - if (initDialogResponse.successful == false) { - callback(initDialogResponse) + if (segmentForNonStrongCustomerAuthenticationTwoStepTanProcess == null) { + initDialog(dialogContext) { initDialogResponse -> + sendMessageAndHandleResponseAfterDialogInitialization(dialogContext, initDialogResponse, createMessage, callback) } - else { - val message = createMessage(dialogContext) + } + else { + initInitDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(dialogContext, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess) { initDialogResponse -> + sendMessageAndHandleResponseAfterDialogInitialization(dialogContext, initDialogResponse, createMessage, callback) + } + } + } - getAndHandleResponseForMessage(message, dialogContext) { response -> - closeDialog(dialogContext) + private fun sendMessageAndHandleResponseAfterDialogInitialization(dialogContext: DialogContext, initDialogResponse: Response, createMessage: (DialogContext) -> MessageBuilderResult, callback: (Response) -> Unit) { - callback(response) - } + if (initDialogResponse.successful == false) { + callback(initDialogResponse) + } + else { + val message = createMessage(dialogContext) + + getAndHandleResponseForMessage(message, dialogContext) { response -> + closeDialog(dialogContext) + + callback(response) } } } @@ -561,6 +579,21 @@ open class FinTsClient( } } + protected open fun initInitDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(dialogContext: DialogContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?, + callback: (Response) -> Unit) { + + val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(dialogContext, segmentIdForTwoStepTanProcess) + + getAndHandleResponseForMessage(message, dialogContext) { response -> + if (response.successful) { + updateBankData(dialogContext.bank, response) + updateCustomerData(dialogContext.customer, dialogContext.bank, response) + } + + callback(response) + } + } + protected open fun closeDialog(dialogContext: DialogContext) { // bank already closed dialog -> there's no need to send dialog end message diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt index c8a0744e..46016726 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt @@ -66,13 +66,58 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg open fun createInitDialogMessage(dialogContext: DialogContext): MessageBuilderResult { + return createInitDialogMessage(dialogContext, null) + } + + /** + * Im Rahmen der PIN/TAN-Management-Geschäftsvorfälle (vgl. Kapitel C.3) ist in be- + stimmten Situationen eine Einreichung ohne starke Kundenauthentifizierung erfor- + derlich (Authentifizierungsklasse 4, vgl. Kapitel B.3). Daher wird in einem solchen + Fall das Element Segmentkennung in HKTAN ab #6 mit der Segmentkennung des + jeweiligen Geschäftsvorfalls belegt, der dann isoliert in diesem Dialog eingereicht + wird. + (PinTan S. 35) + + * Beim Erstzugang mit einem neuen TAN-Verfahren liegt einem Kundenprodukt + ggf. noch keine TAN-Medien-Bezeichnung für dieses Verfahren vor. In diesem + Fall muss der Geschäftsvorfall Anzeige der verfügbaren TAN-Medien + (HKTAB) ohne starke Kundenauthentifizierung durchführbar sein. (..) + + 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. (..) + + Anschließend hat das Kundensystem den Dialog durch Senden einer + Dialogendenachricht (HKEND) zu beenden. + + Zweiter Dialog – Starke Kundenauthentifizierung + o Nun wird unter Verwendung eines zugelassenen TAN-Verfahrens + und TAN-Mediums ein zweiter Dialog zum Durchführen einer starken + Kundenauthentifizierung eröffnet. Die SCA ist obligatorisch, da es + sich um die erste Nutzung dieses TAN-Verfahrens inkl. des gewähl- + ten TAN-Mediums handelt. + o Im Rahmen dieses Dialoges können nach erfolgreicher Durchführung + der starken Kundenauthentifizierung beliebige Geschäftsvorfälle + durchgeführt werden. + + (PinTan S. 37/38) + */ + open fun createInitDialogMessageWithoutStrongCustomerAuthentication(dialogContext: DialogContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?): MessageBuilderResult { + return createInitDialogMessage(dialogContext, segmentIdForTwoStepTanProcess) + } + + protected open fun createInitDialogMessage(dialogContext: DialogContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?): MessageBuilderResult { val segments = mutableListOf( IdentifikationsSegment(generator.resetSegmentNumber(2), dialogContext), Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), dialogContext) ) - if (dialogContext.customer.isTanProcedureSelected) { + if (segmentIdForTwoStepTanProcess != null) { + segments.add(ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentIdForTwoStepTanProcess)) + } + else if (dialogContext.customer.isTanProcedureSelected) { segments.add(ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Identification)) } @@ -175,6 +220,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg return result } + // TODO: no HKTAN needed? open fun createChangeTanMediumMessage(newActiveTanMedium: TanGeneratorTanMedium, dialogContext: DialogContext, tan: String? = null, atc: Int? = null): MessageBuilderResult {