From 62597274fcddea724234a7e37111b587b405035b Mon Sep 17 00:00:00 2001 From: dankito Date: Tue, 15 Oct 2019 23:36:59 +0200 Subject: [PATCH] Fixed determining supported tan procedures for user --- .../kotlin/net/dankito/fints/FinTsClient.kt | 94 ++++++++++--------- .../dankito/fints/messages/MessageBuilder.kt | 16 +++- .../segmente/implementierte/Signaturkopf.kt | 2 +- .../implementierte/Verschluesselungskopf.kt | 2 +- .../net/dankito/fints/model/BankData.kt | 5 + .../net/dankito/fints/model/CustomerData.kt | 22 ++++- .../net/dankito/fints/FinTsClientTest.kt | 5 + 7 files changed, 94 insertions(+), 52 deletions(-) diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt index 47a61265..c061209c 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/FinTsClient.kt @@ -2,10 +2,10 @@ package net.dankito.fints import net.dankito.fints.messages.MessageBuilder import net.dankito.fints.messages.MessageBuilderResult -import net.dankito.fints.messages.datenelemente.implementierte.BPDVersion import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte +import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion import net.dankito.fints.model.* import net.dankito.fints.response.InstituteSegmentId import net.dankito.fints.response.Response @@ -86,6 +86,29 @@ open class FinTsClient @JvmOverloads constructor( } + open fun getBankAndCustomerInfoForNewUser(bank: BankData, customer: CustomerData): FinTsClientResponse { + val dialogData = DialogData() + + // just to ensure settings are in its initial state and that bank sends use bank parameter (BPD), + // user parameter (UPD) and allowed tan procedures for user (therefore the resetSelectedTanProcedure()) + bank.resetBpdVersion() + customer.resetUpdVersion() + /** + * Sind dem Kundenprodukt die konkreten, für den Benutzer zugelassenen Sicherheitsverfahren nicht bekannt, so können + * diese über eine Dialoginitialisierung mit Sicherheitsfunktion=999 angefordert werden. Die konkreten Verfahren + * werden dann über den Rückmeldungscode=3920 zurückgemeldet. Im Rahmen dieses Prozesses darf keine UPD + * zurückgeliefert werden und die Durchführung anderer Geschäftsvorfälle ist in einem solchen Dialog nicht erlaubt. + */ + customer.resetSelectedTanProcedure() + + val initDialogResponse = initDialogWithoutChecks(bank, customer, dialogData, false) + + closeDialog(bank, customer, dialogData) + + return FinTsClientResponse(initDialogResponse) + } + + /** * Some banks support that according to PSD2 account transactions may be retrieved without * a TAN (= no strong customer authorization needed). @@ -245,7 +268,7 @@ open class FinTsClient @JvmOverloads constructor( protected open fun initDialog(bank: BankData, customer: CustomerData, dialogData: DialogData): Response { // we first need to retrieve supported tan procedures and jobs before we can do anything - val retrieveBasicBankDataResponse = ensureBasicBankDataRetrieved(bank) + val retrieveBasicBankDataResponse = ensureBasicBankDataRetrieved(bank, customer) if (retrieveBasicBankDataResponse.successful == false) { return retrieveBasicBankDataResponse } @@ -257,8 +280,13 @@ open class FinTsClient @JvmOverloads constructor( return tanProcedureSelectedResponse } + return initDialogWithoutChecks(bank, customer, dialogData, true) + } - val requestBody = messageBuilder.createInitDialogMessage(bank, customer, product, dialogData) + protected open fun initDialogWithoutChecks(bank: BankData, customer: CustomerData, dialogData: DialogData, + useStrongAuthentication: Boolean = true): Response { + + val requestBody = messageBuilder.createInitDialogMessage(bank, customer, product, dialogData, useStrongAuthentication) val response = getAndHandleResponseForMessage(requestBody, bank) @@ -322,11 +350,11 @@ open class FinTsClient @JvmOverloads constructor( } - protected open fun ensureBasicBankDataRetrieved(bank: BankData): Response { + protected open fun ensureBasicBankDataRetrieved(bank: BankData, customer: CustomerData): Response { if (bank.supportedTanProcedures.isEmpty() || bank.supportedJobs.isEmpty()) { - bank.bpdVersion = BPDVersion.VersionNotReceivedYet + bank.resetBpdVersion() - val getBankInfoResponse = getAnonymousBankInfo(bank) + val getBankInfoResponse = getBankAndCustomerInfoForNewUser(bank, customer) if (getBankInfoResponse.isSuccessful == false || bank.supportedTanProcedures.isEmpty() || bank.supportedJobs.isEmpty()) { @@ -339,37 +367,23 @@ open class FinTsClient @JvmOverloads constructor( return Response(true) } - // TODO: glatt ziehen protected open fun ensureTanProcedureIsSelected(bank: BankData, customer: CustomerData): Response { - var askWithProceduresSupportedByBank = false + if (customer.isTanProcedureSelected == false) { + if (customer.supportedTanProcedures.isEmpty()) { + getBankAndCustomerInfoForNewUser(bank, customer) + } - if (bank.supportedTanProcedures.isEmpty() && customer.selectedTanProcedure == null) { // no tan procedures ever received - getAnonymousBankInfo(bank) + if (customer.supportedTanProcedures.isEmpty()) { // could not retrieve supported tan procedures for user + return Response(false, noTanProcedureSelected = true) + } - if (bank.supportedTanProcedures.isNotEmpty()) { // TODO: what if we didn't receive any? find a workaround for this - - customer.selectedTanProcedure = - callback.askUserForTanProcedure(bank.supportedTanProcedures) // a bit problematic as user is not necessarily allowed to use all procedures bank supports - - askWithProceduresSupportedByBank = true + // we know user's supported tan procedure, now ask user which one to select + callback.askUserForTanProcedure(customer.supportedTanProcedures)?.let { + customer.selectedTanProcedure = it } } - if (customer.selectedTanProcedure == null) { - if (customer.supportedTanProcedures.isNotEmpty()) { - customer.selectedTanProcedure = - callback.askUserForTanProcedure(customer.supportedTanProcedures) - } - - else if (askWithProceduresSupportedByBank == false && bank.supportedTanProcedures.isNotEmpty()) { - customer.selectedTanProcedure = - callback.askUserForTanProcedure(bank.supportedTanProcedures) - } - } - - val noTanProcedureSelected = customer.selectedTanProcedure == null - - return Response(!!!noTanProcedureSelected, noTanProcedureSelected = noTanProcedureSelected) + return Response(customer.isTanProcedureSelected, noTanProcedureSelected = !!!customer.isTanProcedureSelected) } @@ -511,20 +525,16 @@ open class FinTsClient @JvmOverloads constructor( } } - response.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { tanInfo -> - customer.supportedTanProcedures = mapToTanProcedures(tanInfo) - - customer.selectedTanProcedure?.let { selectedProcedure -> - if (customer.supportedTanProcedures.isNotEmpty() && - customer.supportedTanProcedures.contains(selectedProcedure) == false) { - // currently selected tan procedure is not in list of supported procedures -> ask user the next time when needed - customer.selectedTanProcedure = null - } - } + if (response.supportedTanProceduresForUser.isNotEmpty()) { + customer.supportedTanProcedures = response.supportedTanProceduresForUser.mapNotNull { findTanProcedure(it, bank) } } } - private fun setAllowedJobsForAccount(account: AccountData, supportedJobs: List) { + protected open fun findTanProcedure(securityFunction: Sicherheitsfunktion, bank: BankData): TanProcedure? { + return bank.supportedTanProcedures.firstOrNull { it.securityFunction == securityFunction } + } + + protected open fun setAllowedJobsForAccount(account: AccountData, supportedJobs: List) { val allowedJobsForAccount = mutableListOf() for (job in supportedJobs) { diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt index f846a04b..dad92178 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt @@ -61,13 +61,19 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg } - open fun createInitDialogMessage(bank: BankData, customer: CustomerData, product: ProductData, dialogData: DialogData): String { + open fun createInitDialogMessage(bank: BankData, customer: CustomerData, product: ProductData, + dialogData: DialogData, useStrongAuthentication: Boolean = true): String { - return createSignedMessage(bank, customer, dialogData, listOf( + val segments = mutableListOf( IdentifikationsSegment(generator.resetSegmentNumber(2), bank, customer), - Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bank, customer, product), - ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Identification) - )) + Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bank, customer, product) + ) + + if (useStrongAuthentication) { + segments.add(ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Identification)) + } + + return createSignedMessage(bank, customer, dialogData, segments) } open fun createSynchronizeCustomerSystemIdMessage(bank: BankData, customer: CustomerData, product: ProductData, dialogData: DialogData): String { diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Signaturkopf.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Signaturkopf.kt index 47e7f3d9..3b1eca7d 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Signaturkopf.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Signaturkopf.kt @@ -37,7 +37,7 @@ open class Signaturkopf( ) : Segment(listOf( Segmentkopf(MessageSegmentId.SignatureHeader, 4, segmentNumber), // allowed Sicherheitsprofil(Sicherheitsverfahren.PIN_TAN_Verfahren, VersionDesSicherheitsverfahrens.PIN_Zwei_Schritt), // fints4java only supports Pin/Tan and PSD2 requires two step tan procedure - SicherheitsfunktionKodiert(customer.selectedTanProcedure?.securityFunction!!), // allowed: 1, 2 + SicherheitsfunktionKodiert(customer.selectedTanProcedure.securityFunction), Sicherheitskontrollreferenz(securityControlReference), // allowed: <>0 BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ? RolleDesSicherheitslieferantenKodiert(), // allowed: 1 diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Verschluesselungskopf.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Verschluesselungskopf.kt index 68221fef..6c83946e 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Verschluesselungskopf.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/segmente/implementierte/Verschluesselungskopf.kt @@ -46,7 +46,7 @@ open class Verschluesselungskopf( ) : Segment(listOf( Segmentkopf(MessageSegmentId.EncryptionHeader, 3, 998), Sicherheitsprofil(Sicherheitsverfahren.PIN_TAN_Verfahren, VersionDesSicherheitsverfahrens.PIN_Zwei_Schritt), // fints4java only supports Pin/Tan and PSD2 requires two step tan procedure - SicherheitsfunktionKodiert(Sicherheitsfunktion.Klartext), // allowed: 4 + SicherheitsfunktionKodiert(Sicherheitsfunktion.Klartext), RolleDesSicherheitslieferantenKodiert(), // allowed: 1, 4 SicherheitsidentifikationDetails(customer.customerSystemId), SicherheitsdatumUndUhrzeit(date, time), diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt index a5d5871f..ebc7b3b9 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt @@ -27,6 +27,11 @@ open class BankData( ) { + open fun resetBpdVersion() { + bpdVersion = BPDVersion.VersionNotReceivedYet + } + + override fun toString(): String { return "$name ($bankCode)" } diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt index 5514d894..95f83a34 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt @@ -1,8 +1,7 @@ package net.dankito.fints.model import net.dankito.fints.messages.datenelemente.implementierte.* -import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsverfahren -import net.dankito.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens +import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion open class CustomerData( @@ -14,7 +13,7 @@ open class CustomerData( var accounts: List = listOf(), var updVersion: Int = UPDVersion.VersionNotReceivedYet, var supportedTanProcedures: List = listOf(), - var selectedTanProcedure: TanProcedure? = null, + var selectedTanProcedure: TanProcedure = TanProcedureNotSelected, var selectedLanguage: Dialogsprache = Dialogsprache.Default, var customerSystemId: String = KundensystemID.Anonymous, var customerSystemStatus: KundensystemStatusWerte = KundensystemStatus.SynchronizingCustomerSystemId, @@ -23,6 +22,10 @@ open class CustomerData( ) { companion object { + val SecurityFunctionNotSelected = Sicherheitsfunktion.Einschritt_Verfahren + + val TanProcedureNotSelected = TanProcedure("NOT_SELECTED", SecurityFunctionNotSelected, TanProcedureType.EnterTan) + val Anonymous = CustomerData(KundenID.Anonymous, "", customerSystemStatus = KundensystemStatusWerte.NichtBenoetigt) } @@ -31,6 +34,19 @@ open class CustomerData( constructor(customerId: String, pin: String) : this(customerId, pin, customerId) + val isTanProcedureSelected: Boolean + get() = selectedTanProcedure != TanProcedureNotSelected + + + open fun resetSelectedTanProcedure() { + selectedTanProcedure = TanProcedureNotSelected + } + + open fun resetUpdVersion() { + updVersion = UPDVersion.VersionNotReceivedYet + } + + override fun toString(): String { return customerId } diff --git a/fints4javaLib/src/test/kotlin/net/dankito/fints/FinTsClientTest.kt b/fints4javaLib/src/test/kotlin/net/dankito/fints/FinTsClientTest.kt index e9341eb8..54ec8f9a 100644 --- a/fints4javaLib/src/test/kotlin/net/dankito/fints/FinTsClientTest.kt +++ b/fints4javaLib/src/test/kotlin/net/dankito/fints/FinTsClientTest.kt @@ -56,6 +56,11 @@ class FinTsClientTest { // then assertThat(result.isSuccessful).isTrue() + assertThat(BankDataAnonymous.supportedHbciVersions).isNotEmpty() + assertThat(BankDataAnonymous.supportedTanProcedures).isNotEmpty() + assertThat(BankDataAnonymous.supportedJobs).isNotEmpty() + assertThat(BankDataAnonymous.supportedLanguages).isNotEmpty() + assertThat(BankDataAnonymous.name).isNotEmpty() }