Implemented Decoupled TAN process (HKTAN 7)
This commit is contained in:
parent
fb70bcd443
commit
504fbaf13b
|
@ -21,6 +21,8 @@ import net.codinux.banking.fints.util.TanMethodSelector
|
||||||
import net.codinux.banking.fints.extensions.minusDays
|
import net.codinux.banking.fints.extensions.minusDays
|
||||||
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
|
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
|
||||||
import net.codinux.banking.fints.extensions.todayAtSystemDefaultTimeZone
|
import net.codinux.banking.fints.extensions.todayAtSystemDefaultTimeZone
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -413,16 +415,60 @@ open class FinTsJobExecutor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) {
|
protected open suspend fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) {
|
||||||
context.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters ->
|
context.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters ->
|
||||||
if (tanResponse.tanProcess == TanProcess.AppTan && decoupledTanMethodParameters.periodicStateRequestsAllowed) {
|
if (decoupledTanMethodParameters.periodicStateRequestsAllowed) {
|
||||||
automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge)
|
val responseAfterApprovingDecoupledTan =
|
||||||
|
automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, decoupledTanMethodParameters)
|
||||||
|
|
||||||
|
if (responseAfterApprovingDecoupledTan != null) {
|
||||||
|
tanChallenge.userApprovedDecoupledTan(responseAfterApprovingDecoupledTan)
|
||||||
|
} else {
|
||||||
|
tanChallenge.userDidNotEnterTan()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge) {
|
protected open suspend fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse, parameters: DecoupledTanMethodParameters): BankResponse? {
|
||||||
log.info { "automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge" }
|
log.info { "automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge" }
|
||||||
|
|
||||||
|
delay(max(5, parameters.initialDelayInSecondsForStateRequest).seconds)
|
||||||
|
|
||||||
|
var iteration = 0
|
||||||
|
val minWaitTime = when {
|
||||||
|
parameters.maxNumberOfStateRequests <= 10 -> 30
|
||||||
|
parameters.maxNumberOfStateRequests <= 24 -> 10
|
||||||
|
else -> 3
|
||||||
|
}
|
||||||
|
val delayForNextStateRequest = max(minWaitTime, parameters.delayInSecondsForNextStateRequest).seconds
|
||||||
|
|
||||||
|
while (iteration < parameters.maxNumberOfStateRequests) {
|
||||||
|
try {
|
||||||
|
val message = messageBuilder.createDecoupledTanStatusMessage(context, tanResponse)
|
||||||
|
|
||||||
|
val response = getAndHandleResponseForMessage(context, message)
|
||||||
|
|
||||||
|
val tanFeedbacks = response.segmentFeedbacks.filter { it.referenceSegmentNumber == MessageBuilder.SignedMessagePayloadFirstSegmentNumber }
|
||||||
|
if (tanFeedbacks.isNotEmpty()) {
|
||||||
|
// new feedback code for Decoupled TAN: 0900 Sicherheitsfreigabe gültig
|
||||||
|
val isTanApproved = tanFeedbacks.any { it.feedbacks.any { it.responseCode == 900 } }
|
||||||
|
if (isTanApproved) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration++
|
||||||
|
// sometimes delayInSecondsForNextStateRequests is only 1 or 2 seconds, that's too fast i think
|
||||||
|
delay(delayForNextStateRequest)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
log.error(e) { "Could not check status of Decoupled TAN" }
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse,
|
protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse,
|
||||||
|
@ -430,19 +476,18 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
if (enteredTanResult.changeTanMethodTo != null) {
|
if (enteredTanResult.changeTanMethodTo != null) {
|
||||||
return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo)
|
return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo)
|
||||||
}
|
} else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) {
|
||||||
else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) {
|
|
||||||
return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo,
|
return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo,
|
||||||
enteredTanResult.changeTanMediumResultCallback)
|
enteredTanResult.changeTanMediumResultCallback)
|
||||||
}
|
} else if (enteredTanResult.userApprovedDecoupledTan == true && enteredTanResult.responseAfterApprovingDecoupledTan != null) {
|
||||||
else if (enteredTanResult.enteredTan == null) {
|
return enteredTanResult.responseAfterApprovingDecoupledTan
|
||||||
|
} else if (enteredTanResult.enteredTan == null) {
|
||||||
// i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank)
|
// i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank)
|
||||||
// but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog
|
// but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog
|
||||||
response.tanRequiredButUserDidNotEnterOne = true
|
response.tanRequiredButUserDidNotEnterOne = true
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse)
|
return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
|
||||||
|
|
||||||
private const val SignatureHeaderSegmentNumber = MessageHeaderSegmentNumber + 1
|
private const val SignatureHeaderSegmentNumber = MessageHeaderSegmentNumber + 1
|
||||||
|
|
||||||
private const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1
|
const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -295,12 +295,22 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
|
||||||
|
|
||||||
val segments = listOf(
|
val segments = listOf(
|
||||||
ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, tanProcess, null,
|
ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, tanProcess, null,
|
||||||
tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier)
|
tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier, tanResponse.segmentVersion)
|
||||||
)
|
)
|
||||||
|
|
||||||
return createSignedMessageBuilderResult(context, MessageType.Tan, createSignedMessage(context, enteredTan, segments), segments)
|
return createSignedMessageBuilderResult(context, MessageType.Tan, createSignedMessage(context, enteredTan, segments), segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun createDecoupledTanStatusMessage(context: JobContext, tanResponse: TanResponse): MessageBuilderResult {
|
||||||
|
|
||||||
|
val segments = listOf(
|
||||||
|
ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, TanProcess.AppTan,
|
||||||
|
jobReference = tanResponse.jobReference, furtherTanFollows = false, segmentVersion = 7, tanMediaIdentifier = tanResponse.tanMediaIdentifier)
|
||||||
|
)
|
||||||
|
|
||||||
|
return createSignedMessageBuilderResult(context, MessageType.CheckDecoupledTanStatus, createSignedMessage(context, null, segments), segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun createBankTransferMessage(context: JobContext, data: BankTransferData, account: AccountData): MessageBuilderResult {
|
open fun createBankTransferMessage(context: JobContext, data: BankTransferData, account: AccountData): MessageBuilderResult {
|
||||||
|
|
||||||
|
@ -521,8 +531,10 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId, segmentNumber: Int): ZweiSchrittTanEinreichung {
|
protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId, segmentNumber: Int): ZweiSchrittTanEinreichung {
|
||||||
|
val bank = context.bank
|
||||||
|
|
||||||
return ZweiSchrittTanEinreichung(segmentNumber, TanProcess.TanProcess4, segmentId,
|
return ZweiSchrittTanEinreichung(segmentNumber, TanProcess.TanProcess4, segmentId,
|
||||||
tanMediaIdentifier = getTanMediaIdentifierIfRequired(context))
|
tanMediaIdentifier = getTanMediaIdentifierIfRequired(context), segmentVersion = bank.selectedTanMethod.hktanVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getTanMediaIdentifierIfRequired(context: JobContext): String? {
|
protected open fun getTanMediaIdentifierIfRequired(context: JobContext): String? {
|
||||||
|
|
|
@ -50,6 +50,11 @@ enum class TanProcess(override val code: String) : ICodeEnum {
|
||||||
*/
|
*/
|
||||||
TanProcess4("4"),
|
TanProcess4("4"),
|
||||||
|
|
||||||
AppTan("S") // TODO: what is this?
|
/**
|
||||||
|
* kann nur nach dem ersten Schritt auftreten. Er dient im DecoupledVerfahren der Statusabfrage der vom Kunden zu
|
||||||
|
* tätigenden Sicherheitsfreigabe auf einem anderen Gerät mittels HKTAN. Dieser Geschäftsvorfall wird mit HITAN,
|
||||||
|
* TAN-Prozess=S beantwortet.
|
||||||
|
*/
|
||||||
|
AppTan("S")
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,16 +19,17 @@ open class ZweiSchrittTanEinreichung(
|
||||||
jobReference: String? = null,
|
jobReference: String? = null,
|
||||||
furtherTanFollows: Boolean? = false,
|
furtherTanFollows: Boolean? = false,
|
||||||
cancelJob: Boolean? = null,
|
cancelJob: Boolean? = null,
|
||||||
tanMediaIdentifier: String? = null
|
tanMediaIdentifier: String? = null,
|
||||||
|
segmentVersion: Int = 6
|
||||||
|
|
||||||
) : Segment(listOf(
|
) : Segment(listOf(
|
||||||
Segmentkopf(CustomerSegmentId.Tan, 6, segmentNumber),
|
Segmentkopf(CustomerSegmentId.Tan, segmentVersion, segmentNumber),
|
||||||
TANProzessDatenelement(process),
|
TANProzessDatenelement(process),
|
||||||
Segmentkennung(segmentIdForWhichTanShouldGetGenerated?.id ?: ""), // M: bei TAN-Prozess=1. M: bei TAN-Prozess=4 und starker Authentifizierung. N: sonst
|
Segmentkennung(segmentIdForWhichTanShouldGetGenerated?.id ?: ""), // M: bei TAN-Prozess=1. M: bei TAN-Prozess=4 und starker Authentifizierung. N: sonst
|
||||||
NotAllowedDatenelement(), // Kontoverbindung // M: bei TAN-Prozess=1 und "Auftraggeberkonto erforderlich"=2 und Kontoverbindung im Auftrag enthalten. N: sonst
|
NotAllowedDatenelement(), // Kontoverbindung // M: bei TAN-Prozess=1 und "Auftraggeberkonto erforderlich"=2 und Kontoverbindung im Auftrag enthalten. N: sonst
|
||||||
AuftragsHashwert(jobHashValue ?: "", Existenzstatus.NotAllowed), // M: bei AuftragsHashwertverfahren<>0 und TAN-Prozess=1. N: sonst
|
AuftragsHashwert(jobHashValue ?: "", Existenzstatus.NotAllowed), // M: bei AuftragsHashwertverfahren<>0 und TAN-Prozess=1. N: sonst
|
||||||
Auftragsreferenz(jobReference ?: "", Existenzstatus.Mandatory), // M: bei TAN-Prozess=2, 3, 4. O: bei TAN-Prozess=1
|
Auftragsreferenz(jobReference ?: "", if (process == TanProcess.TanProcess2 || process == TanProcess.TanProcess3 || process == TanProcess.AppTan) Existenzstatus.Mandatory else Existenzstatus.Optional), // M: bei TAN-Prozess=2, 3, 4. O: bei TAN-Prozess=1
|
||||||
JaNein(furtherTanFollows, if (process == TanProcess.TanProcess1 || process == TanProcess.TanProcess2) Existenzstatus.Mandatory else Existenzstatus.NotAllowed), // M: bei TAN-Prozess=1, 2. N: bei TAN-Prozess=3, 4
|
JaNein(furtherTanFollows, if (process == TanProcess.TanProcess1 || process == TanProcess.TanProcess2 || process == TanProcess.AppTan) Existenzstatus.Mandatory else Existenzstatus.NotAllowed), // M: bei TAN-Prozess=1, 2. N: bei TAN-Prozess=3, 4
|
||||||
JaNein(cancelJob, if (process == TanProcess.TanProcess2 && cancelJob != null) Existenzstatus.Optional else Existenzstatus.NotAllowed), // O: bei TAN-Prozess=2 und „Auftragsstorno erlaubt“=J. N: sonst
|
JaNein(cancelJob, if (process == TanProcess.TanProcess2 && cancelJob != null) Existenzstatus.Optional else Existenzstatus.NotAllowed), // O: bei TAN-Prozess=2 und „Auftragsstorno erlaubt“=J. N: sonst
|
||||||
NotAllowedDatenelement(), // TODO: SMS-Abbuchungskonto // M: Bei TAN-Process=1, 3, 4 und „SMS-Abbuchungskonto erforderlich“=2. O: sonst
|
NotAllowedDatenelement(), // TODO: SMS-Abbuchungskonto // M: Bei TAN-Process=1, 3, 4 und „SMS-Abbuchungskonto erforderlich“=2. O: sonst
|
||||||
NotAllowedDatenelement(), // TODO: Challenge-Klasse // M: bei TAN-Prozess=1 und „Challenge-Klasse erforderlich“=J. N: sonst
|
NotAllowedDatenelement(), // TODO: Challenge-Klasse // M: bei TAN-Prozess=1 und „Challenge-Klasse erforderlich“=J. N: sonst
|
||||||
|
|
|
@ -8,12 +8,12 @@ open class DecoupledTanMethodParameters(
|
||||||
open val manualConfirmationAllowed: Boolean,
|
open val manualConfirmationAllowed: Boolean,
|
||||||
open val periodicStateRequestsAllowed: Boolean,
|
open val periodicStateRequestsAllowed: Boolean,
|
||||||
open val maxNumberOfStateRequests: Int,
|
open val maxNumberOfStateRequests: Int,
|
||||||
open val initialDelayInSecondsForStateRequests: Int,
|
open val initialDelayInSecondsForStateRequest: Int,
|
||||||
open val delayInSecondsForNextStateRequests: Int
|
open val delayInSecondsForNextStateRequest: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "DecoupledTanMethodParameters(manualConfirmationAllowed=$manualConfirmationAllowed, periodicStateRequestsAllowed=$periodicStateRequestsAllowed, maxNumberOfStateRequests=$maxNumberOfStateRequests, initialDelayInSecondsForStateRequests=$initialDelayInSecondsForStateRequests, delayInSecondsForNextStateRequests=$delayInSecondsForNextStateRequests)"
|
return "DecoupledTanMethodParameters(manualConfirmationAllowed=$manualConfirmationAllowed, periodicStateRequestsAllowed=$periodicStateRequestsAllowed, maxNumberOfStateRequests=$maxNumberOfStateRequests, initialDelayInSecondsForStateRequests=$initialDelayInSecondsForStateRequest, delayInSecondsForNextStateRequests=$delayInSecondsForNextStateRequest)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,17 +1,24 @@
|
||||||
package net.codinux.banking.fints.model
|
package net.codinux.banking.fints.model
|
||||||
|
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||||
|
import net.codinux.banking.fints.response.BankResponse
|
||||||
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
||||||
|
|
||||||
|
|
||||||
open class EnterTanResult(
|
open class EnterTanResult(
|
||||||
val enteredTan: String?,
|
val enteredTan: String?,
|
||||||
|
val userApprovedDecoupledTan: Boolean? = null,
|
||||||
|
val responseAfterApprovingDecoupledTan: BankResponse? = null,
|
||||||
val changeTanMethodTo: TanMethod? = null,
|
val changeTanMethodTo: TanMethod? = null,
|
||||||
val changeTanMediumTo: TanMedium? = null,
|
val changeTanMediumTo: TanMedium? = null,
|
||||||
val changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)? = null
|
val changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
if (userApprovedDecoupledTan == true) {
|
||||||
|
return "User approved Decoupled TAN"
|
||||||
|
}
|
||||||
|
|
||||||
if (changeTanMethodTo != null) {
|
if (changeTanMethodTo != null) {
|
||||||
return "User asks to change TAN method to $changeTanMethodTo"
|
return "User asks to change TAN method to $changeTanMethodTo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ enum class MessageType {
|
||||||
|
|
||||||
SynchronizeCustomerSystemId,
|
SynchronizeCustomerSystemId,
|
||||||
|
|
||||||
|
CheckDecoupledTanStatus,
|
||||||
|
|
||||||
Tan,
|
Tan,
|
||||||
|
|
||||||
GetBalance,
|
GetBalance,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.codinux.banking.fints.model
|
package net.codinux.banking.fints.model
|
||||||
|
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||||
|
import net.codinux.banking.fints.response.BankResponse
|
||||||
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,16 +26,20 @@ open class TanChallenge(
|
||||||
this.enterTanResult = EnterTanResult(enteredTan.replace(" ", ""))
|
this.enterTanResult = EnterTanResult(enteredTan.replace(" ", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun userApprovedDecoupledTan(responseAfterApprovingDecoupledTan: BankResponse) {
|
||||||
|
this.enterTanResult = EnterTanResult(null, true, responseAfterApprovingDecoupledTan)
|
||||||
|
}
|
||||||
|
|
||||||
fun userDidNotEnterTan() {
|
fun userDidNotEnterTan() {
|
||||||
this.enterTanResult = EnterTanResult(null)
|
this.enterTanResult = EnterTanResult(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun userAsksToChangeTanMethod(changeTanMethodTo: TanMethod) {
|
fun userAsksToChangeTanMethod(changeTanMethodTo: TanMethod) {
|
||||||
this.enterTanResult = EnterTanResult(null, changeTanMethodTo)
|
this.enterTanResult = EnterTanResult(null, changeTanMethodTo = changeTanMethodTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) {
|
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) {
|
||||||
this.enterTanResult = EnterTanResult(null, null, changeTanMediumTo, changeTanMediumResultCallback)
|
this.enterTanResult = EnterTanResult(null, changeTanMediumTo = changeTanMediumTo, changeTanMediumResultCallback = changeTanMediumResultCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ open class TanMethod(
|
||||||
open val maxTanInputLength: Int? = null,
|
open val maxTanInputLength: Int? = null,
|
||||||
open val allowedTanFormat: AllowedTanFormat? = null,
|
open val allowedTanFormat: AllowedTanFormat? = null,
|
||||||
open val nameOfTanMediumRequired: Boolean = false,
|
open val nameOfTanMediumRequired: Boolean = false,
|
||||||
|
open val hktanVersion: Int = 6,
|
||||||
open val decoupledParameters: DecoupledTanMethodParameters? = null
|
open val decoupledParameters: DecoupledTanMethodParameters? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -176,11 +176,11 @@ open class ModelMapper(
|
||||||
|
|
||||||
protected open fun mapToTanMethods(tanInfo: TanInfo): List<TanMethod> {
|
protected open fun mapToTanMethods(tanInfo: TanInfo): List<TanMethod> {
|
||||||
return tanInfo.tanProcedureParameters.methodParameters.mapNotNull {
|
return tanInfo.tanProcedureParameters.methodParameters.mapNotNull {
|
||||||
mapToTanMethod(it)
|
mapToTanMethod(it, tanInfo.segmentVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun mapToTanMethod(parameters: TanMethodParameters): TanMethod? {
|
protected open fun mapToTanMethod(parameters: TanMethodParameters, hktanVersion: Int): TanMethod? {
|
||||||
val methodName = parameters.methodName
|
val methodName = parameters.methodName
|
||||||
|
|
||||||
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
|
||||||
|
@ -192,7 +192,7 @@ open class ModelMapper(
|
||||||
mapToTanMethodType(parameters) ?: TanMethodType.EnterTan, mapHhdVersion(parameters),
|
mapToTanMethodType(parameters) ?: TanMethodType.EnterTan, mapHhdVersion(parameters),
|
||||||
parameters.maxTanInputLength, parameters.allowedTanFormat,
|
parameters.maxTanInputLength, parameters.allowedTanFormat,
|
||||||
parameters.nameOfTanMediumRequired == BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden,
|
parameters.nameOfTanMediumRequired == BezeichnungDesTanMediumsErforderlich.BezeichnungDesTanMediumsMussAngegebenWerden,
|
||||||
mapDecoupledTanMethodParameters(parameters))
|
hktanVersion, mapDecoupledTanMethodParameters(parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun mapToTanMethodType(parameters: TanMethodParameters): TanMethodType? {
|
protected open fun mapToTanMethodType(parameters: TanMethodParameters): TanMethodType? {
|
||||||
|
@ -276,10 +276,10 @@ open class ModelMapper(
|
||||||
parameters.manualConfirmationAllowedForDecoupled?.let { manualConfirmationAllowed ->
|
parameters.manualConfirmationAllowedForDecoupled?.let { manualConfirmationAllowed ->
|
||||||
return DecoupledTanMethodParameters(
|
return DecoupledTanMethodParameters(
|
||||||
manualConfirmationAllowed,
|
manualConfirmationAllowed,
|
||||||
parameters.periodicStateRequestsAllowedForDecoupled ?: false, // this and the following values are all set when manualConfirmationAllowedForDecoupled is set
|
parameters.periodicDecoupledStateRequestsAllowed ?: false, // this and the following values are all set when manualConfirmationAllowedForDecoupled is set
|
||||||
parameters.maxNumberOfStateRequestsForDecoupled ?: 0,
|
parameters.maxNumberOfStateRequestsForDecoupled ?: 0,
|
||||||
parameters.initialDelayInSecondsForStateRequestsForDecoupled ?: Int.MAX_VALUE,
|
parameters.initialDelayInSecondsForDecoupledStateRequest ?: Int.MAX_VALUE,
|
||||||
parameters.delayInSecondsForNextStateRequestsForDecoupled ?: Int.MAX_VALUE
|
parameters.delayInSecondsForNextDecoupledStateRequests ?: Int.MAX_VALUE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -431,7 +431,7 @@ open class ResponseParser(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedMethodParameters.dkTanMethod == DkTanMethod.Decoupled) {
|
if (parsedMethodParameters.dkTanMethod == DkTanMethod.Decoupled) {
|
||||||
if (parsedMethodParameters.periodicStateRequestsAllowedForDecoupled != null) {
|
if (parsedMethodParameters.periodicDecoupledStateRequestsAllowed != null) {
|
||||||
return 26
|
return 26
|
||||||
}
|
}
|
||||||
else if (parsedMethodParameters.manualConfirmationAllowedForDecoupled != null) {
|
else if (parsedMethodParameters.manualConfirmationAllowedForDecoupled != null) {
|
||||||
|
|
|
@ -27,10 +27,10 @@ open class TanMethodParameters(
|
||||||
val hhdUcResponseRequired: Boolean, // TODO: wird hierueber gesteuert ob eine TAN eingegeben werden muss (z. B. beim EasyTAN Verfahren muss ja keine eingegeben werden)
|
val hhdUcResponseRequired: Boolean, // TODO: wird hierueber gesteuert ob eine TAN eingegeben werden muss (z. B. beim EasyTAN Verfahren muss ja keine eingegeben werden)
|
||||||
val countSupportedActiveTanMedia: Int?,
|
val countSupportedActiveTanMedia: Int?,
|
||||||
val maxNumberOfStateRequestsForDecoupled: Int? = null,
|
val maxNumberOfStateRequestsForDecoupled: Int? = null,
|
||||||
val initialDelayInSecondsForStateRequestsForDecoupled: Int? = null,
|
val initialDelayInSecondsForDecoupledStateRequest: Int? = null,
|
||||||
val delayInSecondsForNextStateRequestsForDecoupled: Int? = null,
|
val delayInSecondsForNextDecoupledStateRequests: Int? = null,
|
||||||
val manualConfirmationAllowedForDecoupled: Boolean? = null,
|
val manualConfirmationAllowedForDecoupled: Boolean? = null,
|
||||||
val periodicStateRequestsAllowedForDecoupled: Boolean? = null
|
val periodicDecoupledStateRequestsAllowed: Boolean? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@ open class TanMethodSelector {
|
||||||
|
|
||||||
|
|
||||||
open fun getSuggestedTanMethod(tanMethods: List<TanMethod>): TanMethod? {
|
open fun getSuggestedTanMethod(tanMethods: List<TanMethod>): TanMethod? {
|
||||||
return tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan && it.type != TanMethodType.ChipTanManuell }
|
return tanMethods.firstOrNull { it.decoupledParameters != null } // decoupled TAN method is the most simplistic TAN method, user only has to confirm the action in her TAN app, no manual TAN entering required
|
||||||
|
?: tanMethods.firstOrNull { it.type == TanMethodType.AppTan } // that's the second most simplistic TAN method: user has to confirm action in her TAN app and then enter the displayed TAN
|
||||||
|
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan && it.type != TanMethodType.ChipTanManuell }
|
||||||
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan }
|
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan }
|
||||||
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb }
|
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb }
|
||||||
?: first(tanMethods)
|
?: first(tanMethods)
|
||||||
|
|
|
@ -799,6 +799,26 @@ class ResponseParserTest : FinTsTestBase() {
|
||||||
?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") }
|
?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseTanInfo7_DecoupledTanMethod() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parse("HITANS:50:7:4+1+1+1+N:N:0:946:2:DECOUPLED:Decoupled::SecureGo plus (Direktfreigabe):::TAN:2048:J:1:N:0:2:N:J:00:0:N:1:150:2:2:J:J'")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertSuccessfullyParsedSegment(result, InstituteSegmentId.TanInfo, 50, 7, 4)
|
||||||
|
|
||||||
|
result.getFirstSegmentById<TanInfo>(InstituteSegmentId.TanInfo)?.let { segment ->
|
||||||
|
val tanMethodParameters = segment.tanProcedureParameters.methodParameters
|
||||||
|
assertSize(1, tanMethodParameters)
|
||||||
|
|
||||||
|
assertTanMethodParameter(tanMethodParameters, 0, Sicherheitsfunktion.PIN_TAN_946, DkTanMethod.Decoupled, "SecureGo plus (Direktfreigabe)")
|
||||||
|
|
||||||
|
assertEquals("DECOUPLED", tanMethodParameters[0].technicalTanMethodIdentification)
|
||||||
|
}
|
||||||
|
?: run { fail("No segment of type TanInfo found in ${result.receivedSegments}") }
|
||||||
|
}
|
||||||
|
|
||||||
private fun assertTanMethodParameter(parsedTanMethodParameters: List<TanMethodParameters>, index: Int, securityFunction: Sicherheitsfunktion,
|
private fun assertTanMethodParameter(parsedTanMethodParameters: List<TanMethodParameters>, index: Int, securityFunction: Sicherheitsfunktion,
|
||||||
tanMethod: DkTanMethod?, methodName: String) {
|
tanMethod: DkTanMethod?, methodName: String) {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue