libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 0f3bbe826267e59f96c58b973316439fac7b3e58
parent caf3bfaed25c450d18ce2b392554638928bc1b11
Author: MS <ms@taler.net>
Date:   Wed, 15 Nov 2023 18:07:03 +0100

util DCE

Diffstat:
Mutil/src/main/kotlin/Config.kt | 24------------------------
Mutil/src/main/kotlin/DB.kt | 44--------------------------------------------
Mutil/src/main/kotlin/Ebics.kt | 363+------------------------------------------------------------------------------
Dutil/src/main/kotlin/Errors.kt | 46----------------------------------------------
Mutil/src/main/kotlin/HTTP.kt | 12------------
Mutil/src/main/kotlin/IbanPayto.kt | 28----------------------------
Dutil/src/main/kotlin/JSON.kt | 104-------------------------------------------------------------------------------
Mutil/src/main/kotlin/TalerConfig.kt | 1+
Mutil/src/main/kotlin/UnixDomainSocket.kt | 12+-----------
Mutil/src/main/kotlin/XMLUtil.kt | 3---
Dutil/src/main/kotlin/amounts.kt | 34----------------------------------
Dutil/src/main/kotlin/exec.kt | 37-------------------------------------
Mutil/src/main/kotlin/iban.kt | 16++++++++++++++--
Mutil/src/main/kotlin/startServer.kt | 2--
Mutil/src/main/kotlin/strings.kt | 94+++++--------------------------------------------------------------------------
15 files changed, 24 insertions(+), 796 deletions(-)

diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt @@ -1,9 +1,6 @@ package tech.libeufin.util -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.LoggerContext import ch.qos.logback.core.util.Loader -import io.ktor.util.* import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -16,29 +13,8 @@ val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util") * Note: putting Sandbox and Nexus as Utils dependencies would result * into circular dependency. */ -val WITH_AUTH_ATTRIBUTE_KEY = AttributeKey<Boolean>("withAuth") -val ADMIN_PASSWORD_ATTRIBUTE_KEY = AttributeKey<String>("adminPassword") - fun getVersion(): String { return Loader.getResource( "version.txt", ClassLoader.getSystemClassLoader() ).readText() -} - -/** - * Set level of any logger belonging to LibEuFin (= has "libeufin" in name) - * _and_ found under the calling classpath (= obeying to the same logback.xml) - */ -fun setLogLevel(logLevel: String?) { - when (logLevel) { - is String -> { - val ctx = LoggerFactory.getILoggerFactory() as LoggerContext - val loggers: List<ch.qos.logback.classic.Logger> = ctx.loggerList - loggers.forEach { - if (it.name.contains("libeufin")) { - it.level = Level.toLevel(logLevel) - } - } - } - } } \ No newline at end of file diff --git a/util/src/main/kotlin/DB.kt b/util/src/main/kotlin/DB.kt @@ -37,50 +37,6 @@ fun getCurrentUser(): String = System.getProperty("user.name") // Check GANA (https://docs.gnunet.org/gana/index.html) for numbers allowance. -/** - * Note: every domain is ALWAYS meant to be salted with - * a unique identifier that points to the user waiting for - * a notification. The reference function for salting is: - * "buildChannelName()", in this file. - */ -enum class NotificationsChannelDomains(val value: Int) { - // When payments with well-formed Taler subject arrive. - LIBEUFIN_TALER_INCOMING(3000), - - // A transaction happened for a particular user. The payload - // informs about the direction. - LIBEUFIN_REGIO_TX(3001), - - // When an incoming fiat payment is downloaded from Nexus. - // Happens when a customer wants to withdraw Taler coins in the - // regional currency. - LIBEUFIN_SANDBOX_FIAT_INCOMING(3002), - - // When Nexus has ingested a new transactions from the bank it - // is connected to. This event carries incoming and outgoing - // payments, and it specifies that in its payload. The direction - // codename is the same as CaMt (DBIT, CRDT), as that is also - // used in the database. - LIBEUFIN_NEXUS_TX(3003) -} - -/** - * Helper that builds a LISTEN-NOTIFY channel name. - * 'salt' should be any value that would uniquely deliver the - * message to its receiver. IBANs are ideal, but they cost DB queries. - */ - -fun buildChannelName( - domain: NotificationsChannelDomains, - salt: String, - separator: String = "_" -): String { - val channelElements = "${domain.value}$separator$salt" - val ret = "X${Base32Crockford.encode(CryptoUtil.hashStringSHA256(channelElements))}" - logger.debug("Defining db channel name for salt: $salt, domain: ${domain.name}, resulting in: $ret") - return ret -} - /** * This function converts postgresql:// URIs to JDBC URIs. diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt @@ -25,15 +25,9 @@ package tech.libeufin.util import io.ktor.http.HttpStatusCode -import org.slf4j.Logger -import org.slf4j.LoggerFactory import tech.libeufin.util.ebics_h004.* -import tech.libeufin.util.ebics_h005.Ebics3Request import tech.libeufin.util.ebics_h005.Ebics3Response -import tech.libeufin.util.ebics_hev.HEVRequest -import tech.libeufin.util.ebics_hev.HEVResponse import tech.libeufin.util.ebics_s001.UserSignatureData -import java.math.BigInteger import java.security.SecureRandom import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey @@ -41,7 +35,6 @@ import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime import java.util.* -import java.util.zip.DeflaterInputStream import javax.xml.bind.JAXBElement import javax.xml.datatype.DatatypeFactory import javax.xml.datatype.XMLGregorianCalendar @@ -198,33 +191,6 @@ fun signOrderEbics3( return userSignatureData } -fun createEbicsRequestForDownloadReceipt( - subscriberDetails: EbicsClientSubscriberDetails, - transactionID: String?, - withEbics3: Boolean = false -): String { - val doc = if (withEbics3) { - val req = Ebics3Request.createForDownloadReceiptPhase( - transactionID, - subscriberDetails.hostId - ) - XMLUtil.convertJaxbToDocument(req) - - } else { - val req = EbicsRequest.createForDownloadReceiptPhase( - transactionID, - subscriberDetails.hostId - ) - XMLUtil.convertJaxbToDocument(req) - } - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv, - withEbics3 - ) - return XMLUtil.convertDomToString(doc) -} - data class PreparedUploadData( val transactionKey: ByteArray, val userSignatureDataEncrypted: ByteArray, @@ -252,176 +218,6 @@ data class PreparedUploadData( } } -// Creates the EBICS 3 upload init request. -fun createEbicsRequestForUploadInitialization( - subscriberDetails: EbicsClientSubscriberDetails, - ebics3OrderService: Ebics3Request.OrderDetails.Service, - orderParams: EbicsOrderParams? = null, - preparedUploadData: PreparedUploadData -): String { - val nonce = getNonce(128) - val req = Ebics3Request.createForUploadInitializationPhase( - preparedUploadData.transactionKey, - preparedUploadData.userSignatureDataEncrypted, - preparedUploadData.dataDigest, - subscriberDetails.hostId, - nonce, - subscriberDetails.partnerId, - subscriberDetails.userId, - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()), - subscriberDetails.bankAuthPub!!, - subscriberDetails.bankEncPub!!, - BigInteger.ONE, - ebics3OrderService - ) - val doc = XMLUtil.convertJaxbToDocument( - req, - withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd" - ) - logger.debug("Created EBICS 3 document for upload initialization," + - " nonce: ${nonce.toHexString()}") - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv, - withEbics3 = true - ) - return XMLUtil.convertDomToString(doc) -} - -/** - * Create an EBICS request for the initialization phase of an upload EBICS transaction. - * - * The payload is only passed to generate the signature. - */ -fun createEbicsRequestForUploadInitialization( - subscriberDetails: EbicsClientSubscriberDetails, - orderType: String, - orderParams: EbicsOrderParams, - preparedUploadData: PreparedUploadData -): String { - val nonce = getNonce(128) - val req = EbicsRequest.createForUploadInitializationPhase( - preparedUploadData.transactionKey, - preparedUploadData.userSignatureDataEncrypted, - subscriberDetails.hostId, - nonce, - subscriberDetails.partnerId, - subscriberDetails.userId, - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()), - subscriberDetails.bankAuthPub!!, - subscriberDetails.bankEncPub!!, - BigInteger.ONE, - orderType, - makeOrderParams(orderParams) - ) - val doc = XMLUtil.convertJaxbToDocument(req) - logger.debug("Created EBICS $orderType document for upload initialization," + - " nonce: ${nonce.toHexString()}") - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv - ) - return XMLUtil.convertDomToString(doc) -} - -// Generates a EBICS 2.5 signed document for the download init phase. -fun createEbicsRequestForDownloadInitialization( - subscriberDetails: EbicsClientSubscriberDetails, - orderType: String, - orderParams: EbicsOrderParams -): String { - val nonce = getNonce(128) - val req = EbicsRequest.createForDownloadInitializationPhase( - subscriberDetails.userId, - subscriberDetails.partnerId, - subscriberDetails.hostId, - nonce, - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar( - TimeZone.getTimeZone(ZoneId.systemDefault()) - )), - subscriberDetails.bankEncPub ?: throw EbicsProtocolError( - HttpStatusCode.BadRequest, - "Invalid subscriber state 'bankEncPub' missing, please send HPB first" - ), - subscriberDetails.bankAuthPub ?: throw EbicsProtocolError( - HttpStatusCode.BadRequest, - "Invalid subscriber state 'bankAuthPub' missing, please send HPB first" - ), - orderType, - makeOrderParams(orderParams) - ) - logger.debug("Created EBICS document for download initialization, nonce: ${nonce.toHexString()}") - val doc = XMLUtil.convertJaxbToDocument(req) - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv, - withEbics3 = false - ) - return XMLUtil.convertDomToString(doc) -} - -fun createEbicsRequestForDownloadTransferPhase( - subscriberDetails: EbicsClientSubscriberDetails, - transactionID: String?, - segmentNumber: Int, - numSegments: Int, - withEbics3: Boolean = false -): String { - val doc = if (withEbics3) { - val req = Ebics3Request.createForDownloadTransferPhase( - subscriberDetails.hostId, - transactionID, - segmentNumber, - numSegments - ) - XMLUtil.convertJaxbToDocument(req) - } else { - val req = EbicsRequest.createForDownloadTransferPhase( - subscriberDetails.hostId, - transactionID, - segmentNumber, - numSegments - ) - XMLUtil.convertJaxbToDocument(req) - } - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv, - withEbics3 = withEbics3 - ) - return XMLUtil.convertDomToString(doc) -} - -fun createEbicsRequestForUploadTransferPhase( - subscriberDetails: EbicsClientSubscriberDetails, - transactionID: String?, - preparedUploadData: PreparedUploadData, - chunkIndex: Int, - withEbics3: Boolean = false -): String { - val doc = if (withEbics3) { - val req = Ebics3Request.createForUploadTransferPhase( - subscriberDetails.hostId, - transactionID, - // chunks are 1-indexed - BigInteger.valueOf(chunkIndex.toLong() + 1), - preparedUploadData.encryptedPayloadChunks[chunkIndex] - ) - XMLUtil.convertJaxbToDocument(req) - } else { - val req = EbicsRequest.createForUploadTransferPhase( - subscriberDetails.hostId, - transactionID, - // chunks are 1-indexed - BigInteger.valueOf(chunkIndex.toLong() + 1), - preparedUploadData.encryptedPayloadChunks[chunkIndex] - ) - XMLUtil.convertJaxbToDocument(req) - } - XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv, withEbics3) - return XMLUtil.convertDomToString(doc) -} - data class DataEncryptionInfo( val transactionKey: ByteArray, val bankPubDigest: ByteArray @@ -496,33 +292,6 @@ data class EbicsKeyManagementResponseContent( val orderData: ByteArray? ) -fun parseAndDecryptEbicsKeyManagementResponse( - subscriberDetails: EbicsClientSubscriberDetails, - responseStr: String -): EbicsKeyManagementResponseContent { - val resp = try { - XMLUtil.convertStringToJaxb<EbicsKeyManagementResponse>(responseStr) - } catch (e: Exception) { - throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML received from bank") - } - val retCode = EbicsReturnCode.lookup(resp.value.header.mutable.returnCode) - - val daeXml = resp.value.body.dataTransfer?.dataEncryptionInfo - val orderData = if (daeXml != null) { - val dae = DataEncryptionInfo(daeXml.transactionKey, daeXml.encryptionPubKeyDigest.value) - val encOrderData = resp.value.body.dataTransfer?.orderData?.value ?: throw EbicsProtocolError( - HttpStatusCode.InternalServerError, "Invalid XML/orderData received from bank" - ) - decryptAndDecompressResponse(subscriberDetails, dae, listOf(encOrderData)) - } else { - null - } - - val bankReturnCodeStr = resp.value.body.returnCode.value - val bankReturnCode = EbicsReturnCode.lookup(bankReturnCodeStr) - - return EbicsKeyManagementResponseContent(retCode, bankReturnCode, orderData) -} class HpbResponseData( val hostID: String, @@ -627,37 +396,6 @@ fun ebics25toInternalRepr(response: String): EbicsResponseContent { segmentNumber = resp.value.header.mutable.segmentNumber?.value?.toInt() ) } -fun parseAndValidateEbicsResponse( - subscriberDetails: EbicsClientSubscriberDetails, - responseStr: String, - withEbics3: Boolean = false -): EbicsResponseContent { - val responseDocument = try { - XMLUtil.parseStringIntoDom(responseStr) - } catch (e: Exception) { - throw EbicsProtocolError( - HttpStatusCode.InternalServerError, - "Invalid XML (as EbicsResponse) received from bank" - ) - } - if (!XMLUtil.verifyEbicsDocument( - responseDocument, - subscriberDetails.bankAuthPub ?: throw EbicsProtocolError( - HttpStatusCode.InternalServerError, - "Bank's signature verification failed" - ), - withEbics3 = withEbics3 - ) - ) { - throw EbicsProtocolError( - HttpStatusCode.InternalServerError, - "Bank's signature verification failed" - ) - } - if (withEbics3) - return ebics3toInternalRepr(responseStr) - return ebics25toInternalRepr(responseStr) -} /** * Get the private key that matches the given public key digest. @@ -676,32 +414,6 @@ fun getDecryptionKey(subscriberDetails: EbicsClientSubscriberDetails, pubDigest: throw EbicsProtocolError(HttpStatusCode.NotFound, "Could not find customer's public key") } -/** - * Wrapper around the lower decryption routine, that takes a EBICS response - * object containing a encrypted payload, and return the plain version of it - * (including decompression). - */ -fun decryptAndDecompressResponse( - subscriberDetails: EbicsClientSubscriberDetails, - encryptionInfo: DataEncryptionInfo, - chunks: List<String> -): ByteArray { - val privateKey = getDecryptionKey(subscriberDetails, encryptionInfo.bankPubDigest) - val buf = StringBuilder() - chunks.forEach { buf.append(it) } - val decoded = Base64.getDecoder().decode(buf.toString()) - val er = CryptoUtil.EncryptionResult( - encryptionInfo.transactionKey, - encryptionInfo.bankPubDigest, - decoded - ) - val dataCompr = CryptoUtil.decryptEbicsE002( - er, - privateKey - ) - return EbicsOrderUtil.decodeOrderData(dataCompr) -} - data class EbicsVersionSpec( val protocol: String, val version: String @@ -709,75 +421,4 @@ data class EbicsVersionSpec( data class EbicsHevDetails( val versions: List<EbicsVersionSpec> -) - -fun makeEbicsHEVRequest(subscriberDetails: EbicsClientSubscriberDetails): String { - val req = HEVRequest().apply { - hostId = subscriberDetails.hostId - } - val doc = XMLUtil.convertJaxbToDocument(req) - return XMLUtil.convertDomToString(doc) -} - -fun makeEbicsHEVRequestRaw(hostID: String): String { - val h = hostID - val req = HEVRequest().apply { - hostId = h - } - val doc = XMLUtil.convertJaxbToDocument(req) - return XMLUtil.convertDomToString(doc) -} - -fun parseEbicsHEVResponse(respStr: String): EbicsHevDetails { - val resp = try { - XMLUtil.convertStringToJaxb<HEVResponse>(respStr) - } catch (e: Exception) { - logger.error("Exception while parsing HEV response", e) - throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid HEV received from bank") - } - val versions = resp.value.versionNumber.map { versionNumber -> - EbicsVersionSpec(versionNumber.protocolVersion, versionNumber.value) - } - return EbicsHevDetails(versions) -} - -fun makeEbicsIniRequest(subscriberDetails: EbicsClientSubscriberDetails): String { - val iniRequest = EbicsUnsecuredRequest.createIni( - subscriberDetails.hostId, - subscriberDetails.userId, - subscriberDetails.partnerId, - subscriberDetails.customerSignPriv - ) - val doc = XMLUtil.convertJaxbToDocument(iniRequest) - return XMLUtil.convertDomToString(doc) -} - -fun makeEbicsHiaRequest(subscriberDetails: EbicsClientSubscriberDetails): String { - val hiaRequest = EbicsUnsecuredRequest.createHia( - subscriberDetails.hostId, - subscriberDetails.userId, - subscriberDetails.partnerId, - subscriberDetails.customerAuthPriv, - subscriberDetails.customerEncPriv - ) - val doc = XMLUtil.convertJaxbToDocument(hiaRequest) - return XMLUtil.convertDomToString(doc) -} - -fun makeEbicsHpbRequest(subscriberDetails: EbicsClientSubscriberDetails): String { - val hpbRequest = EbicsNpkdRequest.createRequest( - subscriberDetails.hostId, - subscriberDetails.partnerId, - subscriberDetails.userId, - getNonce(128), - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()) - ) - val doc = XMLUtil.convertJaxbToDocument(hpbRequest) - XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv) - return XMLUtil.convertDomToString(doc) -} - -fun dumpEbicsSubscriber(ebicsHeader: EbicsUnsecuredRequest.StaticHeaderType): String { - return "userID: ${ebicsHeader.userID}, partnerID: ${ebicsHeader.partnerID}, systemID: ${ebicsHeader.systemID}" - -} -\ No newline at end of file +) +\ No newline at end of file diff --git a/util/src/main/kotlin/Errors.kt b/util/src/main/kotlin/Errors.kt @@ -1,45 +0,0 @@ -import kotlin.system.exitProcess -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -/* - * This file is part of LibEuFin. - * Copyright (C) 2019 Stanisci and Dold. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util") -/** - * Helper function that wraps throwable code and - * (1) prints the error message and (2) terminates - * the current process, should one exception occur. - * - * Note: should be called when it is REALLY required - * to stop the process when the exception cannot be - * handled. Notably, when the database cannot be reached. - */ -fun execThrowableOrTerminate(func: () -> Unit) { - try { - func() - } catch (e: Exception) { - println(e.message) - exitProcess(1) - } -} - -fun printLnErr(errorMessage: String) { - System.err.println(errorMessage) -} -\ No newline at end of file diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt @@ -4,7 +4,6 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.util.* -import logger // Get the base URL of a request, returns null if any problem occurs. fun ApplicationRequest.getBaseUrl(): String? { @@ -85,15 +84,4 @@ fun getAuthorizationDetails(authorizationHeader: String): AuthorizationDetails? return null } return AuthorizationDetails(scheme = split[0], content = split[1]) -} - -// Gets a long from the URI param named 'uriParamName', -// or null if that is not found. -fun ApplicationCall.maybeLong(uriParamName: String): Long? { - val maybeParam = this.parameters[uriParamName] ?: return null - return try { maybeParam.toLong() } - catch (e: Exception) { - logger.error("Could not convert '$uriParamName' to Long") - return null - } } \ No newline at end of file diff --git a/util/src/main/kotlin/IbanPayto.kt b/util/src/main/kotlin/IbanPayto.kt @@ -1,9 +1,7 @@ package tech.libeufin.util -import logger import java.net.URI import java.net.URLDecoder -import java.net.URLEncoder // Payto information. data class IbanPayto( @@ -80,30 +78,4 @@ fun parsePayto(payto: String): IbanPayto? { message = getQueryParamOrNull("message", params), receiverName = getQueryParamOrNull("receiver-name", params) ) -} - -fun buildIbanPaytoUri( - iban: String, - bic: String, - receiverName: String, - message: String? = null -): String { - val nameUrlEnc = URLEncoder.encode(receiverName, "utf-8") - val ret = "payto://iban/$bic/$iban?receiver-name=$nameUrlEnc" - if (message != null) { - val messageUrlEnc = URLEncoder.encode(message, "utf-8") - return "$ret&message=$messageUrlEnc" - } - return ret -} - -/** - * Strip a payto://iban URI of everything except the IBAN. - * Return null on an invalid URI, letting the caller decide - * how to handle the problem. - */ -fun stripIbanPayto(paytoUri: String): String? { - val parsedPayto = parsePayto(paytoUri) ?: return null - val canonIban = parsedPayto.iban.uppercase() - return "payto://iban/${canonIban}" } \ No newline at end of file diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt @@ -1,103 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2020 Taler Systems S.A. - * - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - * - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -package tech.libeufin.util - -enum class XLibeufinBankDirection(val direction: String) { - DEBIT("debit"), - CREDIT("credit"); - fun exportAsCamtDirection(): String = - when(this) { - CREDIT -> "CRDT" - DEBIT -> "DBIT" - } -} - -data class XLibeufinBankPaytoReq( - /** - * This Payto address MUST contain the wire transfer - * subject among its query parameters -- 'message' parameter. - */ - val paytoUri: String, - // $currency:X.Y format - val amount: String?, - /** - * This value MAY be specified by the payment submitter to - * help reconcile the payment when they later download new - * transactions. The name is only borrowed from CaMt terminology. - */ - val pmtInfId: String? -) -data class XLibeufinBankTransaction( - val creditorIban: String, - val creditorBic: String?, - val creditorName: String, - val debtorIban: String, - val debtorBic: String?, - val debtorName: String, - val amount: String, - val currency: String, - val subject: String, - // Milliseconds since the Epoch. - val date: String, - val uid: String, - val direction: XLibeufinBankDirection, - /** - * The following two values are rather CAMT/PAIN - * specific, therefore do not need to be returned - * along every API call using this object. - */ - val pmtInfId: String? = null, - val msgId: String? = null, - val endToEndId: String? = null -) -data class IncomingPaymentInfo( - val debtorIban: String, - val debtorBic: String?, - val debtorName: String, - /** - * A stringified number, no currency required. This - * one will be extracted from the demobank configuration. - */ - val amount: String, - val subject: String -) - -data class TWGAdminAddIncoming( - val amount: String, - val reserve_pub: String, - val debit_account: String -) - -data class PaymentInfo( - val accountLabel: String, - val creditorIban: String, - val creditorBic: String?, - val creditorName: String, - val debtorIban: String, - val debtorBic: String?, - val debtorName: String, - val amount: String, - val currency: String, - val subject: String, - val date: String? = null, - val creditDebitIndicator: String, - val accountServicerReference: String, - val paymentInformationId: String?, -) -\ No newline at end of file diff --git a/util/src/main/kotlin/TalerConfig.kt b/util/src/main/kotlin/TalerConfig.kt @@ -17,6 +17,7 @@ * <http://www.gnu.org/licenses/> */ +import tech.libeufin.util.logger import java.io.File import java.nio.file.Paths import kotlin.io.path.Path diff --git a/util/src/main/kotlin/UnixDomainSocket.kt b/util/src/main/kotlin/UnixDomainSocket.kt @@ -9,29 +9,19 @@ import io.ktor.server.engine.* import io.ktor.server.testing.* import io.ktor.utils.io.pool.* import io.netty.bootstrap.ServerBootstrap -import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufInputStream import io.netty.buffer.Unpooled -import io.netty.buffer.UnpooledDirectByteBuf import io.netty.channel.* import io.netty.channel.epoll.EpollEventLoopGroup import io.netty.channel.epoll.EpollServerDomainSocketChannel import io.netty.channel.unix.DomainSocketAddress -import io.netty.handler.codec.LengthFieldPrepender import io.netty.handler.codec.http.* import io.netty.handler.codec.http.DefaultHttpResponse -import io.netty.handler.codec.http.HttpMessage -import io.netty.handler.logging.LogLevel import io.netty.handler.logging.LoggingHandler -import io.netty.handler.stream.ChunkedInput import io.netty.handler.stream.ChunkedStream import io.netty.handler.stream.ChunkedWriteHandler -import io.netty.util.AttributeKey -import io.netty.util.ReferenceCountUtil -import org.slf4j.LoggerFactory +import tech.libeufin.util.logger import java.io.ByteArrayInputStream -import java.io.InputStream -import java.nio.charset.Charset fun startServer( unixSocketPath: String, diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt @@ -21,8 +21,6 @@ package tech.libeufin.util import com.sun.xml.bind.marshaller.NamespacePrefixMapper import io.ktor.http.* -import org.slf4j.Logger -import org.slf4j.LoggerFactory import org.w3c.dom.Document import org.w3c.dom.Node import org.w3c.dom.NodeList @@ -61,7 +59,6 @@ import javax.xml.validation.Validator import javax.xml.xpath.XPath import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathFactory -import logger class DefaultNamespaces : NamespacePrefixMapper() { override fun getPreferredPrefix(namespaceUri: String?, suggestion: String?, requirePrefix: Boolean): String? { diff --git a/util/src/main/kotlin/amounts.kt b/util/src/main/kotlin/amounts.kt @@ -1,33 +0,0 @@ -package tech.libeufin.util - -import java.math.BigDecimal -import java.math.RoundingMode - -/* - * This file is part of LibEuFin. - * Copyright (C) 2021 Taler Systems S.A. - * - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - * - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -const val plainAmountRe = "^([0-9]+(\\.[0-9][0-9]?)?)$" -const val plainAmountReWithSign = "^-?([0-9]+(\\.[0-9][0-9]?)?)$" -const val amountWithCurrencyRe = "^([A-Z]+):([0-9]+(\\.[0-9][0-9]?)?)$" - -fun BigDecimal.roundToTwoDigits(): BigDecimal { - // val twoDigitsRounding = MathContext(2) - // return this.round(twoDigitsRounding) - return this.setScale(2, RoundingMode.HALF_UP) -} -\ No newline at end of file diff --git a/util/src/main/kotlin/exec.kt b/util/src/main/kotlin/exec.kt @@ -1,36 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2019 Stanisci and Dold. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -package tech.libeufin.util - -/** - * Wrapper around the ProcessBuilder API. It executes a - * command and (by default) throws exception if the result is not zero. - * It returns the exit code. - */ -fun execCommand(cmd: List<String>, throwIfFails: Boolean = true): Int { - val result: Int = ProcessBuilder(cmd) - .redirectOutput(ProcessBuilder.Redirect.INHERIT) - .redirectError(ProcessBuilder.Redirect.INHERIT) - .start() - .waitFor() - if (result != 0 && throwIfFails) - throw Exception("Command '$cmd' failed.") - return result -} -\ No newline at end of file diff --git a/util/src/main/kotlin/iban.kt b/util/src/main/kotlin/iban.kt @@ -1,7 +1,5 @@ package tech.libeufin.util -import java.math.BigInteger - fun getIban(): String { val ccNoCheck = "131400" // DE00 val bban = (0..10).map { @@ -12,4 +10,18 @@ fun getIban(): String { checkDigits = "0${checkDigits}" } return "DE$checkDigits$bban" +} + +// Taken from the ISO20022 XSD schema +private val bicRegex = Regex("^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$") + +fun validateBic(bic: String): Boolean { + return bicRegex.matches(bic) +} + +// Taken from the ISO20022 XSD schema +private val ibanRegex = Regex("^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{1,30}$") + +fun validateIban(iban: String): Boolean { + return ibanRegex.matches(iban) } \ No newline at end of file diff --git a/util/src/main/kotlin/startServer.kt b/util/src/main/kotlin/startServer.kt @@ -4,8 +4,6 @@ import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.netty.channel.unix.Errors -import logger -import java.net.BindException import kotlin.system.exitProcess const val EAFNOSUPPORT = -97 // Netty defines errors negatively. diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt @@ -19,7 +19,6 @@ package tech.libeufin.util -import logger import net.taler.wallet.crypto.Base32Crockford import java.math.BigInteger import java.util.* @@ -75,88 +74,15 @@ fun BigInteger.toUnsignedHexString(): String { return bytes.toHexString() } -/** - * Inserts spaces every 2 characters, and a newline after 8 pairs. - */ -fun chunkString(input: String): String { - val ret = StringBuilder() - var columns = 0 - for (i in input.indices) { - if ((i + 1).rem(2) == 0) { - if (columns == 15) { - ret.append(input[i] + "\n") - columns = 0 - continue - } - ret.append(input[i] + " ") - columns++ - continue - } - ret.append(input[i]) - } - return ret.toString().uppercase() -} - -data class AmountWithCurrency( - val currency: String, - val amount: String -) - -fun getRandomString(length: Int): String { - val allowedChars = ('A' .. 'Z') + ('0' .. '9') - return (1 .. length) - .map { allowedChars.random() } - .joinToString("") -} - -// Taken from the ISO20022 XSD schema -private val bicRegex = Regex("^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$") - -fun validateBic(bic: String): Boolean { - return bicRegex.matches(bic) -} - -// Taken from the ISO20022 XSD schema -private val ibanRegex = Regex("^[A-Z]{2}[0-9]{2}[a-zA-Z0-9]{1,30}$") - -fun validateIban(iban: String): Boolean { - return ibanRegex.matches(iban) -} - -fun isValidResourceName(name: String): Boolean { - return name.matches(Regex("[a-z]([-a-z0-9]*[a-z0-9])?")) -} - -// Sanity-check user's credentials. -fun sanityCheckCredentials(credentials: Pair<String, String>): Boolean { - val allowedChars = Regex("^[a-zA-Z0-9]+$") - if (!allowedChars.matches(credentials.first)) return false - if (!allowedChars.matches(credentials.second)) return false - return true -} - -/** - * Parses string into java.util.UUID format or throws 400 Bad Request. - * The output is usually consumed in database queries. - */ -fun parseUuid(maybeUuid: String): UUID? { - val uuid = try { - UUID.fromString(maybeUuid) - } catch (e: Exception) { - logger.error("'$maybeUuid' is an invalid UUID.") - return null +fun getQueryParam(uriQueryString: String, param: String): String? { + uriQueryString.split('&').forEach { + val kv = it.split('=') + if (kv[0] == param) + return kv[1] } - return uuid -} - -fun hasWopidPlaceholder(captchaUrl: String): Boolean { - if (captchaUrl.contains("{wopid}", ignoreCase = true)) - return true - return false + return null } -// Tries to extract a valid reserve public key from the raw subject line -// or returns null if the input is invalid. fun extractReservePubFromSubject(rawSubject: String): String? { val re = "\\b[a-z0-9A-Z]{52}\\b".toRegex() val result = re.find(rawSubject.replace("[\n]+".toRegex(), "")) ?: return null @@ -169,11 +95,3 @@ fun extractReservePubFromSubject(rawSubject: String): String? { return result.value.uppercase() } -fun getQueryParam(uriQueryString: String, param: String): String? { - uriQueryString.split('&').forEach { - val kv = it.split('=') - if (kv[0] == param) - return kv[1] - } - return null -}