libeufin

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

commit f9bac0535f6891afa21842a001075927d0402f0f
parent dcc1089eb5857a368b544b8aff6360bef38b5ed6
Author: ms <ms@taler.net>
Date:   Thu, 14 Oct 2021 23:25:37 +0200

Fix HTTP basic auth at Sandbox and CLI.

Diffstat:
Mcli/bin/libeufin-cli | 21+++++++++++++--------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 88++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mutil/src/main/kotlin/HTTP.kt | 6++++--
Mutil/src/main/kotlin/XMLUtil.kt | 2+-
4 files changed, 69 insertions(+), 48 deletions(-)

diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli @@ -19,7 +19,7 @@ from getpass import getpass # was received. def check_response_status(resp, expected_status_code=200): if resp.status_code != expected_status_code: - print("Unexpected response status: {}".format(resp.status_code)) + print("Unexpected response status: {}".format(resp.status_code), file=sys.stderr) sys.exit(1) @@ -284,8 +284,9 @@ class SandboxContext: sandbox_username = os.environ.get("LIBEUFIN_SANDBOX_USERNAME") sandbox_password = os.environ.get("LIBEUFIN_SANDBOX_PASSWORD") if not sandbox_username or not sandbox_password: - raise click.UsageError( - "Please define LIBEUFIN_SANDBOX_USERNAME and LIBEUFIN_SANDBOX_PASSWORD in the environment" + print( + "LIBEUFIN_SANDBOX_USERNAME and LIBEUFIN_SANDBOX_PASSWORD\n" \ + "not found in the environment, assuming tests are being run..." ) return sandbox_username, sandbox_password @@ -1052,7 +1053,7 @@ def make_ebics_host(obj, host_id): resp = post( url, json=dict(hostID=host_id, ebicsVersion="2.5"), - auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password), + auth=auth.HTTPBasicAuth(obj.username, obj.password), ) except Exception as e: print(e) @@ -1097,7 +1098,7 @@ def create_ebics_subscriber(obj, host_id, partner_id, user_id): resp = post( url, json=dict(hostID=host_id, partnerID=partner_id, userID=user_id), - auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password), + auth=auth.HTTPBasicAuth(obj.username, obj.password), ) except Exception as e: print(e) @@ -1171,7 +1172,7 @@ def associate_bank_account( try: resp = post( url, json=body, - auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password), + auth=auth.HTTPBasicAuth(obj.username, obj.password), ) except Exception as e: print(e) @@ -1229,10 +1230,14 @@ def transactions_list(obj, account_label): def bankaccount_generate_transactions(obj, account_label): sandbox_base_url = obj.require_sandbox_base_url() url = urljoin_nodrop( - sandbox_base_url, f"/admin/bank-accounts/{account_label}/generate-transactions" + sandbox_base_url, + f"/admin/bank-accounts/{account_label}/generate-transactions" ) try: - resp = post(url) + resp = post( + url, + auth=auth.HTTPBasicAuth(obj.username, obj.password) + ) except Exception as e: print(e) print("Could not reach sandbox") diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -394,32 +394,6 @@ val sandboxApp: Application.() -> Unit = { io.ktor.http.HttpStatusCode.InternalServerError ) } - exception<EbicsRequestError> { cause -> - val resp = tech.libeufin.util.ebics_h004.EbicsResponse.createForUploadWithError( - cause.errorText, - cause.errorCode, - // assuming that the phase is always transfer, - // as errors during initialization should have - // already been caught by the chunking logic. - tech.libeufin.util.ebics_h004.EbicsTypes.TransactionPhaseType.TRANSFER - ) - - val hostAuthPriv = transaction { - val host = tech.libeufin.sandbox.EbicsHostEntity.find { - tech.libeufin.sandbox.EbicsHostsTable.hostID.upperCase() eq call.attributes.get(tech.libeufin.sandbox.EbicsHostIdAttribute) - .uppercase() - }.firstOrNull() ?: throw SandboxError( - io.ktor.http.HttpStatusCode.InternalServerError, - "Requested Ebics host ID not found." - ) - tech.libeufin.util.CryptoUtil.loadRsaPrivateKey(host.authenticationPrivateKey.bytes) - } - call.respondText( - tech.libeufin.util.XMLUtil.signEbicsResponse(resp, hostAuthPriv), - io.ktor.http.ContentType.Application.Xml, - io.ktor.http.HttpStatusCode.OK - ) - } exception<SandboxError> { cause -> logger.error("Exception while handling '${call.request.uri}', ${cause.reason}") call.respond( @@ -444,6 +418,32 @@ val sandboxApp: Application.() -> Unit = { ) ) } + exception<EbicsRequestError> { e -> + logger.debug("Handling EbicsRequestError: $e") + val resp = tech.libeufin.util.ebics_h004.EbicsResponse.createForUploadWithError( + e.errorText, + e.errorCode, + // assuming that the phase is always transfer, + // as errors during initialization should have + // already been caught by the chunking logic. + tech.libeufin.util.ebics_h004.EbicsTypes.TransactionPhaseType.TRANSFER + ) + val hostAuthPriv = transaction { + val host = EbicsHostEntity.find { + tech.libeufin.sandbox.EbicsHostsTable.hostID.upperCase() eq call.attributes.get(tech.libeufin.sandbox.EbicsHostIdAttribute) + .uppercase() + }.firstOrNull() ?: throw SandboxError( + io.ktor.http.HttpStatusCode.InternalServerError, + "Requested Ebics host ID not found." + ) + tech.libeufin.util.CryptoUtil.loadRsaPrivateKey(host.authenticationPrivateKey.bytes) + } + call.respondText( + tech.libeufin.util.XMLUtil.signEbicsResponse(resp, hostAuthPriv), + io.ktor.http.ContentType.Application.Xml, + io.ktor.http.HttpStatusCode.OK + ) + } exception<Throwable> { cause -> logger.error("Exception while handling '${call.request.uri}'", cause) call.respondText( @@ -454,13 +454,20 @@ val sandboxApp: Application.() -> Unit = { } } intercept(ApplicationCallPipeline.Setup) { - logger.info("Going Setup phase.") val ac: ApplicationCall = call ac.attributes.put(WITH_AUTH_ATTRIBUTE_KEY, WITH_AUTH) - if (adminPassword != null) { - ac.attributes.put(AttributeKey("adminPassword"), adminPassword) + if (WITH_AUTH) { + if(adminPassword == null) { + throw internalServerError( + "Sandbox has no admin password defined." + + " Please define LIBEUFIN_SANDBOX_ADMIN_PASSWORD in the environment." + ) + } + ac.attributes.put( + ADMIN_PASSWORD_ATTRIBUTE_KEY, + adminPassword + ) } - logger.info("Finish Setup phase.") return@intercept } intercept(ApplicationCallPipeline.Fallback) { @@ -839,8 +846,12 @@ val sandboxApp: Application.() -> Unit = { call.ebicsweb() } /** - * Those errors were all detected by the bank's logic. + * The catch blocks below act as translators from + * generic error types to EBICS-formatted responses. */ + catch (e: UtilError) { + throw EbicsProcessingError("Serving EBICS threw unmanaged UtilError: ${e.reason}") + } catch (e: SandboxError) { // Should translate to EBICS error code. when (e.errorCode) { @@ -848,15 +859,18 @@ val sandboxApp: Application.() -> Unit = { tech.libeufin.util.LibeufinErrorCode.LIBEUFIN_EC_INCONSISTENT_STATE -> throw EbicsProcessingError("Inconsistent bank state.") else -> throw EbicsProcessingError("Unknown LibEuFin error code: ${e.errorCode}.") } - } - /** - * An error occurred, but it wasn't explicitly thrown by the bank. - */ + catch (e: EbicsRequestError) { + // Preventing the last catch-all block + // from capturing a known type. + throw e + } catch (e: Exception) { - throw EbicsProcessingError("Unmanaged error: $e") + if (e !is EbicsRequestError) { + throw EbicsProcessingError("Unmanaged error: $e") + } } - + return@post } /** diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt @@ -37,7 +37,7 @@ fun extractToken(authHeader: String): String { return "${tokenSplit[0]}:${URLDecoder.decode(tokenSplit[1], Charsets.UTF_8)}" } -internal fun internalServerError( +fun internalServerError( reason: String, libeufinErrorCode: LibeufinErrorCode? = LibeufinErrorCode.LIBEUFIN_EC_NONE ): UtilError { @@ -97,11 +97,13 @@ fun ApplicationRequest.basicAuth() { val credentials = getHTTPBasicAuthCredentials(this) if (credentials.first == "admin") { // env must contain the admin password, because --with-auth is true. - val adminPassword = this.call.ensureAttribute(ADMIN_PASSWORD_ATTRIBUTE_KEY) + val adminPassword: String = this.call.ensureAttribute(ADMIN_PASSWORD_ATTRIBUTE_KEY) if (credentials.second != adminPassword) throw unauthorized( "Admin authentication failed" ) + return } + throw unauthorized("Demobank customers not implemented yet!") /** * TODO: extract customer hashed password from the database and check. */ diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt @@ -480,7 +480,7 @@ class XMLUtil private constructor() { dvc.setProperty("javax.xml.crypto.dsig.cacheReference", true) dvc.uriDereferencer = EbicsSigUriDereferencer() val sig = fac.unmarshalXMLSignature(dvc) - // FIXME: check that parameters are okay!s + // FIXME: check that parameters are okay! val valResult = sig.validate(dvc) sig.signedInfo.references[0].validate(dvc) return valResult