From 1859fb25750d1a7373e599ef21abbe1395d29941 Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 13 May 2020 20:29:53 +0200 Subject: [PATCH] Implemented parsing PinInfo and setting HKTAN only if required according to PinInfo --- .../kotlin/net/dankito/fints/FinTsClient.kt | 4 +++ .../dankito/fints/messages/MessageBuilder.kt | 34 ++++++++++++------- .../implementierte/tan/JobTanConfiguration.kt | 13 +++++++ .../net/dankito/fints/model/BankData.kt | 2 ++ .../fints/response/InstituteSegmentId.kt | 2 ++ .../dankito/fints/response/ResponseParser.kt | 26 ++++++++++++++ .../fints/response/segments/PinInfo.kt | 15 ++++++++ .../fints/response/ResponseParserTest.kt | 28 +++++++++++++-- 8 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 fints4k/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/JobTanConfiguration.kt create mode 100644 fints4k/src/main/kotlin/net/dankito/fints/response/segments/PinInfo.kt diff --git a/fints4k/src/main/kotlin/net/dankito/fints/FinTsClient.kt b/fints4k/src/main/kotlin/net/dankito/fints/FinTsClient.kt index ad010e00..7be02dd2 100644 --- a/fints4k/src/main/kotlin/net/dankito/fints/FinTsClient.kt +++ b/fints4k/src/main/kotlin/net/dankito/fints/FinTsClient.kt @@ -746,6 +746,10 @@ open class FinTsClient @JvmOverloads constructor( // bank.bic = bankParameters. // TODO: where's the BIC? } + response.getFirstSegmentById(InstituteSegmentId.PinInfo)?.let { pinInfo -> + bank.pinInfo = pinInfo + } + response.getFirstSegmentById(InstituteSegmentId.TanInfo)?.let { tanInfo -> bank.supportedTanProcedures = mapToTanProcedures(tanInfo) } diff --git a/fints4k/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt b/fints4k/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt index f3ec7193..14290cb6 100644 --- a/fints4k/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt +++ b/fints4k/src/main/kotlin/net/dankito/fints/messages/MessageBuilder.kt @@ -105,10 +105,9 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(generator.resetSegmentNumber(2), parameter, account) else KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), parameter, account) - val segments = listOf( - transactionsJob, - ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.AccountTransactionsMt940) - ) + val segments = mutableListOf(transactionsJob) + + addTanSegmentIfRequired(CustomerSegmentId.AccountTransactionsMt940, dialogContext, segments) return createMessageBuilderResult(dialogContext, segments) } @@ -133,10 +132,9 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg val balanceJob = if (result.isAllowed(5)) SaldenabfrageVersion5(generator.resetSegmentNumber(2), account) else SaldenabfrageVersion7(generator.resetSegmentNumber(2), account, dialogContext.bank) - val segments = listOf( - balanceJob, - ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Balance) - ) + val segments = mutableListOf(balanceJob) + + addTanSegmentIfRequired(CustomerSegmentId.Balance, dialogContext, segments) return createMessageBuilderResult(dialogContext, segments) } @@ -208,10 +206,10 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg val urn = messageBuilderResultAndNullableUrn.second if (result.isJobVersionSupported && urn != null) { - val segments = listOf( - SepaBankTransferBase(segmentId, generator.resetSegmentNumber(2), urn, dialogContext.customer, account, dialogContext.bank.bic, data), - ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentId) - ) + val segments = mutableListOf(SepaBankTransferBase(segmentId, generator.resetSegmentNumber(2), + urn, dialogContext.customer, account, dialogContext.bank.bic, data)) + + addTanSegmentIfRequired(segmentId, dialogContext, segments) return createMessageBuilderResult(dialogContext, segments) } @@ -401,6 +399,18 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg return MessageBuilderResult(false) } + protected open fun addTanSegmentIfRequired(segmentId: CustomerSegmentId, dialogContext: DialogContext, segments: MutableList) { + if (isTanRequiredForJob(segmentId, dialogContext)) { + segments.add(ZweiSchrittTanEinreichung( + generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentId)) + } + } + + protected open fun isTanRequiredForJob(segmentId: CustomerSegmentId, dialogContext: DialogContext): Boolean { + return dialogContext.bank.pinInfo?.jobTanConfiguration?.first { it.segmentId == segmentId.id }?.tanRequired + ?: false // TODO: actually in this case it's not allowed to execute job via PIN/TAN at all + } + protected open fun getSepaUrnFor(segmentId: CustomerSegmentId, account: AccountData, sepaDataFormat: String): String? { return getAllowedJobs(segmentId, account) diff --git a/fints4k/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/JobTanConfiguration.kt b/fints4k/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/JobTanConfiguration.kt new file mode 100644 index 00000000..a2b64c0e --- /dev/null +++ b/fints4k/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/JobTanConfiguration.kt @@ -0,0 +1,13 @@ +package net.dankito.fints.messages.datenelemente.implementierte.tan + + +open class JobTanConfiguration( + val segmentId: String, + val tanRequired: Boolean +) { + + override fun toString(): String { + return "$segmentId requires TAN? $tanRequired" + } + +} \ No newline at end of file diff --git a/fints4k/src/main/kotlin/net/dankito/fints/model/BankData.kt b/fints4k/src/main/kotlin/net/dankito/fints/model/BankData.kt index 5c58900d..f4d47fd6 100644 --- a/fints4k/src/main/kotlin/net/dankito/fints/model/BankData.kt +++ b/fints4k/src/main/kotlin/net/dankito/fints/model/BankData.kt @@ -5,6 +5,7 @@ import net.dankito.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.fints.messages.datenelemente.implementierte.HbciVersion import net.dankito.fints.response.segments.ChangeTanMediaParameters import net.dankito.fints.response.segments.JobParameters +import net.dankito.fints.response.segments.PinInfo open class BankData( @@ -24,6 +25,7 @@ open class BankData( var supportedHbciVersions: List = listOf(), var supportedTanProcedures: List = listOf(), var changeTanMediumParameters: ChangeTanMediaParameters? = null, + var pinInfo: PinInfo? = null, var supportedLanguages: List = listOf(), var supportedJobs: List = listOf() ) { diff --git a/fints4k/src/main/kotlin/net/dankito/fints/response/InstituteSegmentId.kt b/fints4k/src/main/kotlin/net/dankito/fints/response/InstituteSegmentId.kt index 45ca7972..b941b65e 100644 --- a/fints4k/src/main/kotlin/net/dankito/fints/response/InstituteSegmentId.kt +++ b/fints4k/src/main/kotlin/net/dankito/fints/response/InstituteSegmentId.kt @@ -25,6 +25,8 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId { SepaAccountInfoParameters("HISPAS"), + PinInfo("HIPINS"), + TanInfo("HITANS"), Tan("HITAN"), diff --git a/fints4k/src/main/kotlin/net/dankito/fints/response/ResponseParser.kt b/fints4k/src/main/kotlin/net/dankito/fints/response/ResponseParser.kt index c0e9f29c..ecb9f1b8 100644 --- a/fints4k/src/main/kotlin/net/dankito/fints/response/ResponseParser.kt +++ b/fints4k/src/main/kotlin/net/dankito/fints/response/ResponseParser.kt @@ -89,6 +89,7 @@ open class ResponseParser @JvmOverloads constructor( InstituteSegmentId.SepaAccountInfo.id -> parseSepaAccountInfo(segment, dataElementGroups) InstituteSegmentId.SepaAccountInfoParameters.id -> parseSepaAccountInfoParameters(segment, segmentId, dataElementGroups) + InstituteSegmentId.PinInfo.id -> parsePinInfo(segment, segmentId, dataElementGroups) InstituteSegmentId.TanInfo.id -> parseTanInfo(segment, segmentId, dataElementGroups) InstituteSegmentId.Tan.id -> parseTanResponse(segment, dataElementGroups) InstituteSegmentId.TanMediaList.id -> parseTanMediaList(segment, dataElementGroups) @@ -304,6 +305,31 @@ open class ResponseParser @JvmOverloads constructor( } + protected open fun parsePinInfo(segment: String, segmentId: String, dataElementGroups: List): PinInfo { + val jobParameters = parseJobParameters(segment, segmentId, dataElementGroups) + + val dataElements = getDataElements(dataElementGroups[4]) + + val minPinLength = parseIntToNullIfEmpty(dataElements[0]) + val maxPinLength = parseIntToNullIfEmpty(dataElements[1]) + val minTanLength = parseIntToNullIfEmpty(dataElements[2]) + val userIdHint = parseStringToNullIfEmpty(dataElements[3]) + val customerIdHint = parseStringToNullIfEmpty(dataElements[4]) + + return PinInfo(jobParameters, minPinLength, maxPinLength, minTanLength, userIdHint, customerIdHint, + parseJobTanConfigurations(dataElements.subList(5, dataElements.size))) + } + + protected open fun parseJobTanConfigurations(dataElementGroups: List): List { + return dataElementGroups.chunked(2).map { + JobTanConfiguration( + parseString(it[0]), + parseBoolean(it[1]) + ) + } + } + + protected open fun parseTanInfo(segment: String, segmentId: String, dataElementGroups: List): TanInfo? { val jobParameters = parseJobParameters(segment, segmentId, dataElementGroups) diff --git a/fints4k/src/main/kotlin/net/dankito/fints/response/segments/PinInfo.kt b/fints4k/src/main/kotlin/net/dankito/fints/response/segments/PinInfo.kt new file mode 100644 index 00000000..032ec200 --- /dev/null +++ b/fints4k/src/main/kotlin/net/dankito/fints/response/segments/PinInfo.kt @@ -0,0 +1,15 @@ +package net.dankito.fints.response.segments + +import net.dankito.fints.messages.datenelemente.implementierte.tan.JobTanConfiguration + + +open class PinInfo( + parameters: JobParameters, + val minPinLength: Int?, + val maxPinLength: Int?, + val minTanLength: Int?, + val userIdHint: String?, + val customerIdHint: String?, + val jobTanConfiguration: List +) + : JobParameters(parameters) \ No newline at end of file diff --git a/fints4k/src/test/kotlin/net/dankito/fints/response/ResponseParserTest.kt b/fints4k/src/test/kotlin/net/dankito/fints/response/ResponseParserTest.kt index b397abd9..6a52e20d 100644 --- a/fints4k/src/test/kotlin/net/dankito/fints/response/ResponseParserTest.kt +++ b/fints4k/src/test/kotlin/net/dankito/fints/response/ResponseParserTest.kt @@ -660,6 +660,30 @@ class ResponseParserTest : FinTsTestBase() { } + @Test + fun parsePinInfo() { + + // when + val result = underTest.parse("HIPINS:49:1:4+1+1+0+5:20:6:VR-NetKey oder Alias::HKTAN:N:HKKAZ:J:HKSAL:N:HKEKA:N:HKPAE:J:HKPSP:N:HKQTG:N:HKCSA:J:HKCSB:N:HKCSL:J:HKCSE:J:HKCCS:J:HKSPA:N:HKCCM:J:HKCDB:N:HKCDL:J:HKPPD:J:HKCDN:J:HKDSB:N:HKCUB:N:HKCUM:J:HKCDE:J:HKDSW:J:HKCME:J:HKCMB:N:HKCML:J:HKWPD:N:HKWDU:N:HKKAU:N:HKKIF:N:HKCAZ:J:HKKAA:N:HKPOF:N:HKIPS:N:HKIPZ:J:GKVPU:N:GKVPD:N'") + + // then + assertSuccessfullyParsedSegment(result, InstituteSegmentId.PinInfo, 49, 1, 4) + + result.getFirstSegmentById(InstituteSegmentId.PinInfo)?.let { segment -> + assertThat(segment.minPinLength).isEqualTo(5) + assertThat(segment.maxPinLength).isEqualTo(20) + assertThat(segment.minTanLength).isEqualTo(6) + assertThat(segment.userIdHint).isEqualTo("VR-NetKey oder Alias") + assertThat(segment.customerIdHint).isNull() + + assertThat(segment.jobTanConfiguration).hasSize(37) + assertThat(segment.jobTanConfiguration.first { it.segmentId == "HKKAZ" }.tanRequired).isTrue() + assertThat(segment.jobTanConfiguration.first { it.segmentId == "HKSAL" }.tanRequired).isFalse() + } + ?: run { Assert.fail("No segment of type PinInfo found in ${result.receivedSegments}") } + } + + @Test fun parseTanInfo() { @@ -936,7 +960,7 @@ class ResponseParserTest : FinTsTestBase() { assertThat(segment.accountProductName).isEqualTo(accountProductName) assertThat(segment.balanceOfPreBookedTransactions).isNull() } - ?: run { Assert.fail("No segment of type Balance found in ${result.receivedSegments}") } + ?: run { Assert.fail("No segment of type BalanceSegment found in ${result.receivedSegments}") } } @Test @@ -963,7 +987,7 @@ class ResponseParserTest : FinTsTestBase() { assertThat(segment.accountProductName).isEqualTo(accountProductName) assertThat(segment.balanceOfPreBookedTransactions).isNull() } - ?: run { Assert.fail("No segment of type Balance found in ${result.receivedSegments}") } + ?: run { Assert.fail("No segment of type BalanceSegment found in ${result.receivedSegments}") } }