commit be4ffcbcc55d656fbbf983de10d4e483949b127d
parent 47e778d7d05c43e3f6b489143b2163082bae127b
Author: Florian Dold <florian.dold@gmail.com>
Date: Fri, 19 Jun 2020 17:55:35 +0530
allow timezones, use ISO time stamp for last fetch date
Diffstat:
7 files changed, 120 insertions(+), 35 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -220,9 +220,14 @@ object NexusBankAccountsTable : IdTable<String>() {
val bankCode = text("bankCode")
val defaultBankConnection = reference("defaultBankConnection", NexusBankConnectionsTable).nullable()
- val lastStatementCreationTimestamp = long("lastStatementCreationTimestamp").nullable()
- val lastReportCreationTimestamp = long("lastReportCreationTimestamp").nullable()
- val lastNotificationCreationTimestamp = long("lastNotificationCreationTimestamp").nullable()
+ // ISO-8601 zoned date time
+ val lastStatementCreationTimestamp = text("lastStatementCreationTimestamp").nullable()
+
+ // ISO-8601 zoned date time
+ val lastReportCreationTimestamp = text("lastReportCreationTimestamp").nullable()
+
+ // ISO-8601 zoned date time
+ val lastNotificationCreationTimestamp = text("lastNotificationCreationTimestamp").nullable()
// Highest bank message ID that this bank account is aware of.
val highestSeenBankMessageId = integer("highestSeenBankMessageId")
@@ -239,6 +244,9 @@ class NexusBankAccountEntity(id: EntityID<String>) : Entity<String>(id) {
var defaultBankConnection by NexusBankConnectionEntity optionalReferencedOn NexusBankAccountsTable.defaultBankConnection
var highestSeenBankMessageId by NexusBankAccountsTable.highestSeenBankMessageId
var pain001Counter by NexusBankAccountsTable.pain001Counter
+ var lastStatementCreationTimestamp by NexusBankAccountsTable.lastStatementCreationTimestamp
+ var lastReportCreationTimestamp by NexusBankAccountsTable.lastReportCreationTimestamp
+ var lastNotificationCreationTimestamp by NexusBankAccountsTable.lastNotificationCreationTimestamp
}
object EbicsSubscribersTable : IntIdTable() {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -236,10 +236,10 @@ fun addPaymentInitiation(paymentData: Pain001Data, debitorAccount: NexusBankAcco
suspend fun fetchBankAccountTransactions(
client: HttpClient,
fetchSpec: FetchSpecJson,
- accountid: String
+ accountId: String
) {
val res = transaction {
- val acct = NexusBankAccountEntity.findById(accountid)
+ val acct = NexusBankAccountEntity.findById(accountId)
if (acct == null) {
throw NexusError(
HttpStatusCode.NotFound,
@@ -263,7 +263,8 @@ suspend fun fetchBankAccountTransactions(
fetchEbicsBySpec(
fetchSpec,
client,
- res.connectionName
+ res.connectionName,
+ accountId
)
}
else -> throw NexusError(
@@ -271,6 +272,6 @@ suspend fun fetchBankAccountTransactions(
"Connection type '${res.connectionType}' not implemented"
)
}
- ingestBankMessagesIntoAccount(res.connectionName, accountid)
+ ingestBankMessagesIntoAccount(res.connectionName, accountId)
ingestTalerTransactions()
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -51,8 +51,10 @@ import tech.libeufin.util.ebics_h004.HTDResponseOrderData
import java.io.ByteArrayOutputStream
import java.security.interfaces.RSAPrivateCrtKey
import java.security.interfaces.RSAPublicKey
-import java.time.LocalDate
+import java.time.Instant
import java.time.LocalDateTime
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import javax.crypto.EncryptedPrivateKeyInfo
@@ -63,37 +65,88 @@ private data class EbicsFetchSpec(
val orderParams: EbicsOrderParams
)
-suspend fun fetchEbicsBySpec(fetchSpec: FetchSpecJson, client: HttpClient, bankConnectionId: String) {
+suspend fun fetchEbicsBySpec(
+ fetchSpec: FetchSpecJson,
+ client: HttpClient,
+ bankConnectionId: String,
+ accountId: String
+) {
val subscriberDetails = transaction { getEbicsSubscriberDetails(bankConnectionId) }
+ val lastTimes = transaction {
+ val acct = NexusBankAccountEntity.findById(accountId)
+ if (acct == null) {
+ throw NexusError(
+ HttpStatusCode.NotFound,
+ "Account not found"
+ )
+ }
+ object {
+ val lastStatement = acct.lastStatementCreationTimestamp?.let {
+ ZonedDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME)
+ }
+ val lastReport = acct.lastReportCreationTimestamp?.let {
+ ZonedDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME)
+ }
+ }
+ }
val specs = mutableListOf<EbicsFetchSpec>()
+
+ fun addForLevel(l: FetchLevel, p: EbicsOrderParams) {
+ when (fetchSpec.level) {
+ FetchLevel.ALL -> {
+ specs.add(EbicsFetchSpec("C52", p))
+ specs.add(EbicsFetchSpec("C53", p))
+ }
+ FetchLevel.REPORT -> {
+ specs.add(EbicsFetchSpec("C52", p))
+ }
+ FetchLevel.STATEMENT -> {
+ specs.add(EbicsFetchSpec("C53", p))
+ }
+ }
+ }
+
when (fetchSpec) {
is FetchSpecLatestJson -> {
val p = EbicsStandardOrderParams()
- when (fetchSpec.level) {
- FetchLevel.ALL -> {
- specs.add(EbicsFetchSpec("C52", p))
- specs.add(EbicsFetchSpec("C53", p))
- }
- FetchLevel.REPORT -> {
- specs.add(EbicsFetchSpec("C52", p))
- }
- FetchLevel.STATEMENT -> {
- specs.add(EbicsFetchSpec("C53", p))
- }
- }
+ addForLevel(fetchSpec.level, p)
}
is FetchSpecAllJson -> {
- val p = EbicsStandardOrderParams(EbicsDateRange(LocalDate.EPOCH, LocalDate.now()))
+ val p = EbicsStandardOrderParams(
+ EbicsDateRange(
+ ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
+ ZonedDateTime.now(ZoneOffset.UTC)
+ )
+ )
+ addForLevel(fetchSpec.level, p)
+ }
+ is FetchSpecSinceLastJson -> {
+ val pRep = EbicsStandardOrderParams(
+ EbicsDateRange(
+ lastTimes.lastReport ?: ZonedDateTime.ofInstant(
+ Instant.EPOCH,
+ ZoneOffset.UTC
+ ), ZonedDateTime.now(ZoneOffset.UTC)
+ )
+ )
+ val pStmt = EbicsStandardOrderParams(
+ EbicsDateRange(
+ lastTimes.lastStatement ?: ZonedDateTime.ofInstant(
+ Instant.EPOCH,
+ ZoneOffset.UTC
+ ), ZonedDateTime.now(ZoneOffset.UTC)
+ )
+ )
when (fetchSpec.level) {
FetchLevel.ALL -> {
- specs.add(EbicsFetchSpec("C52", p))
- specs.add(EbicsFetchSpec("C53", p))
+ specs.add(EbicsFetchSpec("C52", pRep))
+ specs.add(EbicsFetchSpec("C53", pStmt))
}
FetchLevel.REPORT -> {
- specs.add(EbicsFetchSpec("C52", p))
+ specs.add(EbicsFetchSpec("C52", pRep))
}
FetchLevel.STATEMENT -> {
- specs.add(EbicsFetchSpec("C53", p))
+ specs.add(EbicsFetchSpec("C53", pStmt))
}
}
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/JSON.kt
@@ -26,7 +26,14 @@ import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.JsonNode
import tech.libeufin.nexus.BankTransaction
import tech.libeufin.util.*
-import java.time.LocalDate
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.DateTimeFormatterBuilder
+import java.time.temporal.ChronoField
+
data class BackupRequestJson(
val passphrase: String
@@ -62,6 +69,15 @@ class EbicsStandardOrderParamsEmptyJson : EbicsOrderParamsJson() {
}
}
+object EbicsDateFormat {
+ var fmt = DateTimeFormatterBuilder()
+ .append(DateTimeFormatter.ISO_DATE)
+ .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
+ .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
+ .parseDefaulting(ChronoField.OFFSET_SECONDS, ZoneId.systemDefault().rules.getOffset(Instant.now()).totalSeconds.toLong())
+ .toFormatter()
+}
+
@JsonTypeName("standard-date-range")
class EbicsStandardOrderParamsDateJson(
val start: String,
@@ -70,8 +86,8 @@ class EbicsStandardOrderParamsDateJson(
override fun toOrderParams(): EbicsOrderParams {
val dateRange: EbicsDateRange? =
EbicsDateRange(
- LocalDate.parse(this.start),
- LocalDate.parse(this.end)
+ ZonedDateTime.parse(this.start, EbicsDateFormat.fmt),
+ ZonedDateTime.parse(this.end, EbicsDateFormat.fmt)
)
return EbicsStandardOrderParams(dateRange)
}
diff --git a/nexus/src/test/kotlin/authentication.kt b/nexus/src/test/kotlin/authentication.kt
@@ -2,6 +2,7 @@ package tech.libeufin.nexus
import org.junit.Test
import junit.framework.TestCase.assertEquals
+import tech.libeufin.nexus.server.extractUserAndPassword
class AuthenticationTest {
@Test
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
@@ -33,14 +33,15 @@ import java.math.BigInteger
import java.security.SecureRandom
import java.security.interfaces.RSAPrivateCrtKey
import java.security.interfaces.RSAPublicKey
-import java.time.LocalDate
+import java.time.ZonedDateTime
import java.util.*
import java.util.zip.DeflaterInputStream
import javax.xml.datatype.DatatypeFactory
+import javax.xml.datatype.XMLGregorianCalendar
data class EbicsProtocolError(val statusCode: HttpStatusCode, val reason: String) : Exception(reason)
-data class EbicsDateRange(val start: LocalDate, val end: LocalDate)
+data class EbicsDateRange(val start: ZonedDateTime, val end: ZonedDateTime)
sealed class EbicsOrderParams
@@ -86,6 +87,11 @@ private fun getNonce(size: Int): ByteArray {
return ret
}
+private fun getXmlDate(d: ZonedDateTime): XMLGregorianCalendar {
+ return DatatypeFactory.newInstance()
+ .newXMLGregorianCalendar(d.year, d.monthValue, d.dayOfMonth, 0, 0, 0, 0, d.offset.totalSeconds / 60)
+}
+
private fun makeOrderParams(orderParams: EbicsOrderParams): EbicsRequest.OrderParams {
return when (orderParams) {
is EbicsStandardOrderParams -> {
@@ -93,8 +99,8 @@ private fun makeOrderParams(orderParams: EbicsOrderParams): EbicsRequest.OrderPa
val r = orderParams.dateRange
if (r != null) {
this.dateRange = EbicsRequest.DateRange().apply {
- this.start = DatatypeFactory.newInstance().newXMLGregorianCalendar(r.start.toString())
- this.end = DatatypeFactory.newInstance().newXMLGregorianCalendar(r.end.toString())
+ this.start = getXmlDate(r.start)
+ this.end = getXmlDate(r.end)
}
}
}
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
@@ -27,11 +27,11 @@ fun LocalDateTime.toZonedString(): String {
}
fun LocalDateTime.toDashedDate(): String {
- return DateTimeFormatter.ISO_DATE.format(this)
+ return DateTimeFormatter.ISO_OFFSET_DATE.format(this)
}
fun parseDashedDate(date: String): LocalDateTime {
- val dtf = DateTimeFormatter.ISO_DATE
+ val dtf = DateTimeFormatter.ISO_OFFSET_DATE
val asDate = LocalDate.parse(date, dtf)
return asDate.atStartOfDay()
}