Fixed determining supported tan procedures for user

This commit is contained in:
dankito 2019-10-15 23:36:59 +02:00 committed by dankito
parent c0403b0be7
commit 62597274fc
7 changed files with 94 additions and 52 deletions

View File

@ -2,10 +2,10 @@ package net.dankito.fints
import net.dankito.fints.messages.MessageBuilder import net.dankito.fints.messages.MessageBuilder
import net.dankito.fints.messages.MessageBuilderResult 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.Dialogsprache
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID import net.dankito.fints.messages.datenelemente.implementierte.KundensystemID
import net.dankito.fints.messages.datenelemente.implementierte.KundensystemStatusWerte 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.model.*
import net.dankito.fints.response.InstituteSegmentId import net.dankito.fints.response.InstituteSegmentId
import net.dankito.fints.response.Response 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 * Some banks support that according to PSD2 account transactions may be retrieved without
* a TAN (= no strong customer authorization needed). * 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 { 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 // 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) { if (retrieveBasicBankDataResponse.successful == false) {
return retrieveBasicBankDataResponse return retrieveBasicBankDataResponse
} }
@ -257,8 +280,13 @@ open class FinTsClient @JvmOverloads constructor(
return tanProcedureSelectedResponse 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) 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()) { 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() if (getBankInfoResponse.isSuccessful == false || bank.supportedTanProcedures.isEmpty()
|| bank.supportedJobs.isEmpty()) { || bank.supportedJobs.isEmpty()) {
@ -339,37 +367,23 @@ open class FinTsClient @JvmOverloads constructor(
return Response(true) return Response(true)
} }
// TODO: glatt ziehen
protected open fun ensureTanProcedureIsSelected(bank: BankData, customer: CustomerData): Response { 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 if (customer.supportedTanProcedures.isEmpty()) { // could not retrieve supported tan procedures for user
getAnonymousBankInfo(bank) return Response(false, noTanProcedureSelected = true)
}
if (bank.supportedTanProcedures.isNotEmpty()) { // TODO: what if we didn't receive any? find a workaround for this // we know user's supported tan procedure, now ask user which one to select
callback.askUserForTanProcedure(customer.supportedTanProcedures)?.let {
customer.selectedTanProcedure = customer.selectedTanProcedure = it
callback.askUserForTanProcedure(bank.supportedTanProcedures) // a bit problematic as user is not necessarily allowed to use all procedures bank supports
askWithProceduresSupportedByBank = true
} }
} }
if (customer.selectedTanProcedure == null) { return Response(customer.isTanProcedureSelected, noTanProcedureSelected = !!!customer.isTanProcedureSelected)
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)
} }
@ -511,20 +525,16 @@ open class FinTsClient @JvmOverloads constructor(
} }
} }
response.getFirstSegmentById<TanInfo>(InstituteSegmentId.TanInfo)?.let { tanInfo -> if (response.supportedTanProceduresForUser.isNotEmpty()) {
customer.supportedTanProcedures = mapToTanProcedures(tanInfo) customer.supportedTanProcedures = response.supportedTanProceduresForUser.mapNotNull { findTanProcedure(it, bank) }
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
}
}
} }
} }
private fun setAllowedJobsForAccount(account: AccountData, supportedJobs: List<SupportedJob>) { protected open fun findTanProcedure(securityFunction: Sicherheitsfunktion, bank: BankData): TanProcedure? {
return bank.supportedTanProcedures.firstOrNull { it.securityFunction == securityFunction }
}
protected open fun setAllowedJobsForAccount(account: AccountData, supportedJobs: List<SupportedJob>) {
val allowedJobsForAccount = mutableListOf<SupportedJob>() val allowedJobsForAccount = mutableListOf<SupportedJob>()
for (job in supportedJobs) { for (job in supportedJobs) {

View File

@ -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), IdentifikationsSegment(generator.resetSegmentNumber(2), bank, customer),
Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bank, customer, product), Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), bank, customer, product)
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Identification) )
))
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 { open fun createSynchronizeCustomerSystemIdMessage(bank: BankData, customer: CustomerData, product: ProductData, dialogData: DialogData): String {

View File

@ -37,7 +37,7 @@ open class Signaturkopf(
) : Segment(listOf( ) : Segment(listOf(
Segmentkopf(MessageSegmentId.SignatureHeader, 4, segmentNumber), // allowed 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 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 Sicherheitskontrollreferenz(securityControlReference), // allowed: <>0
BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ? BereichDerSicherheitsapplikationKodiert(BereichDerSicherheitsapplikation.SignaturkopfUndHBCINutzdaten), // allowed: 1 ?
RolleDesSicherheitslieferantenKodiert(), // allowed: 1 RolleDesSicherheitslieferantenKodiert(), // allowed: 1

View File

@ -46,7 +46,7 @@ open class Verschluesselungskopf(
) : Segment(listOf( ) : Segment(listOf(
Segmentkopf(MessageSegmentId.EncryptionHeader, 3, 998), 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 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 RolleDesSicherheitslieferantenKodiert(), // allowed: 1, 4
SicherheitsidentifikationDetails(customer.customerSystemId), SicherheitsidentifikationDetails(customer.customerSystemId),
SicherheitsdatumUndUhrzeit(date, time), SicherheitsdatumUndUhrzeit(date, time),

View File

@ -27,6 +27,11 @@ open class BankData(
) { ) {
open fun resetBpdVersion() {
bpdVersion = BPDVersion.VersionNotReceivedYet
}
override fun toString(): String { override fun toString(): String {
return "$name ($bankCode)" return "$name ($bankCode)"
} }

View File

@ -1,8 +1,7 @@
package net.dankito.fints.model package net.dankito.fints.model
import net.dankito.fints.messages.datenelemente.implementierte.* import net.dankito.fints.messages.datenelemente.implementierte.*
import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsverfahren import net.dankito.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.dankito.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
open class CustomerData( open class CustomerData(
@ -14,7 +13,7 @@ open class CustomerData(
var accounts: List<AccountData> = listOf(), var accounts: List<AccountData> = listOf(),
var updVersion: Int = UPDVersion.VersionNotReceivedYet, var updVersion: Int = UPDVersion.VersionNotReceivedYet,
var supportedTanProcedures: List<TanProcedure> = listOf(), var supportedTanProcedures: List<TanProcedure> = listOf(),
var selectedTanProcedure: TanProcedure? = null, var selectedTanProcedure: TanProcedure = TanProcedureNotSelected,
var selectedLanguage: Dialogsprache = Dialogsprache.Default, var selectedLanguage: Dialogsprache = Dialogsprache.Default,
var customerSystemId: String = KundensystemID.Anonymous, var customerSystemId: String = KundensystemID.Anonymous,
var customerSystemStatus: KundensystemStatusWerte = KundensystemStatus.SynchronizingCustomerSystemId, var customerSystemStatus: KundensystemStatusWerte = KundensystemStatus.SynchronizingCustomerSystemId,
@ -23,6 +22,10 @@ open class CustomerData(
) { ) {
companion object { companion object {
val SecurityFunctionNotSelected = Sicherheitsfunktion.Einschritt_Verfahren
val TanProcedureNotSelected = TanProcedure("NOT_SELECTED", SecurityFunctionNotSelected, TanProcedureType.EnterTan)
val Anonymous = CustomerData(KundenID.Anonymous, "", customerSystemStatus = KundensystemStatusWerte.NichtBenoetigt) val Anonymous = CustomerData(KundenID.Anonymous, "", customerSystemStatus = KundensystemStatusWerte.NichtBenoetigt)
} }
@ -31,6 +34,19 @@ open class CustomerData(
constructor(customerId: String, pin: String) : this(customerId, pin, customerId) 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 { override fun toString(): String {
return customerId return customerId
} }

View File

@ -56,6 +56,11 @@ class FinTsClientTest {
// then // then
assertThat(result.isSuccessful).isTrue() assertThat(result.isSuccessful).isTrue()
assertThat(BankDataAnonymous.supportedHbciVersions).isNotEmpty()
assertThat(BankDataAnonymous.supportedTanProcedures).isNotEmpty()
assertThat(BankDataAnonymous.supportedJobs).isNotEmpty()
assertThat(BankDataAnonymous.supportedLanguages).isNotEmpty()
assertThat(BankDataAnonymous.name).isNotEmpty()
} }