diff options
author | Antoine A <> | 2024-01-05 12:17:47 +0000 |
---|---|---|
committer | Antoine A <> | 2024-01-05 12:17:47 +0000 |
commit | 55006bcb8c3f4fb8986fb93deaa0e2213eeb4832 (patch) | |
tree | 12601d38d00fbbb19fc2f8f80745c346335b2bd3 | |
parent | 9aea450a7173fb10e0bf299eb1685e17c909f705 (diff) | |
download | libeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.tar.gz libeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.tar.bz2 libeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.zip |
Simplify authentification logic and improve documentation
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt | 182 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt | 6 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 71 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 16 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 11 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt | 10 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 18 | ||||
-rw-r--r-- | build.gradle | 5 | ||||
-rw-r--r-- | util/src/main/kotlin/HTTP.kt | 42 | ||||
-rw-r--r-- | util/src/main/kotlin/strings.kt | 5 |
11 files changed, 150 insertions, 221 deletions
@@ -101,3 +101,8 @@ check: install-nobuild-bank-files .PHONY: test test: install-nobuild-bank-files ./gradlew test --tests $(test) -i + +.PHONY: doc +doc: + ./gradlew dokkaHtmlMultiModule + open build/dokka/htmlMultiModule/index.html diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt index 4a83e94e..7f0fac7b 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt @@ -34,13 +34,35 @@ import tech.libeufin.util.* private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Authentication") +/** Used to store if the currenly authenticated user is admin */ private val AUTH_IS_ADMIN = AttributeKey<Boolean>("is_admin"); +/** Used to store used auth token */ +private val AUTH_TOKEN = AttributeKey<ByteArray>("auth_token"); -/** Restrict route access to admin */ +/** Get username of the request account */ +val ApplicationCall.username: String get() = expectParameter("USERNAME") +/** Get username of the request account */ +val PipelineContext<Unit, ApplicationCall>.username: String get() = call.username + +/** Check if current auth account is admin */ +val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: false +/** Check if current auth account is admin */ +val PipelineContext<Unit, ApplicationCall>.isAdmin: Boolean get() = call.isAdmin + +/** Check auth token used for authentification */ +val ApplicationCall.authToken: ByteArray? get() = attributes.getOrNull(AUTH_TOKEN) + +/** + * Create an admin authenticated route for [scope]. + * + * If [enforce], only admin can access this route. + * + * You can check is the currently authenticated user is admin using [isAdmin]. + **/ fun Route.authAdmin(db: Database, scope: TokenScope, enforce: Boolean = true, callback: Route.() -> Unit): Route = intercept(callback) { if (enforce) { - val login = context.authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login") + val login = context.authenticateBankRequest(db, scope) if (login != "admin") { throw unauthorized("Only administrator allowed") } @@ -56,10 +78,17 @@ fun Route.authAdmin(db: Database, scope: TokenScope, enforce: Boolean = true, ca } -/** Authenticate and check access rights */ +/** + * Create an authenticated route for [scope]. + * + * If [allowAdmin], admin is allowed to auth for any user. + * If [requireAdmin], only admin can access this route. + * + * You can check is the currently authenticated user is admin using [isAdmin]. + **/ fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, requireAdmin: Boolean = false, callback: Route.() -> Unit): Route = intercept(callback) { - val authLogin = context.authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login") + val authLogin = context.authenticateBankRequest(db, scope) if (requireAdmin && authLogin != "admin") { if (authLogin != "admin") { throw unauthorized("Only administrator allowed") @@ -73,134 +102,87 @@ fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, req context.attributes.put(AUTH_IS_ADMIN, authLogin == "admin") } -val PipelineContext<Unit, ApplicationCall>.username: String get() = call.username -val PipelineContext<Unit, ApplicationCall>.isAdmin: Boolean get() = call.isAdmin -val ApplicationCall.username: String get() = expectUriComponent("USERNAME") -val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: false - /** - * This function tries to authenticate the call according - * to the scheme that is mentioned in the Authorization header. - * The allowed schemes are either 'HTTP basic auth' or 'bearer token'. + * Authenticate an HTTP request for [requiredScope] according to the scheme that is mentioned + * in the Authorization header. + * The allowed schemes are either 'Basic' or 'Bearer'. * - * requiredScope can be either "readonly" or "readwrite". - * - * Returns the authenticated customer login, or null if they failed. + * Returns the authenticated customer login. */ -private suspend fun ApplicationCall.authenticateBankRequest(db: Database, requiredScope: TokenScope): String? { - // Extracting the Authorization header. - val header = getAuthorizationRawHeader(this.request) +private suspend fun ApplicationCall.authenticateBankRequest(db: Database, requiredScope: TokenScope): String { + val header = request.headers["Authorization"] + + // Basic auth challenge if (header == null) { - // Basic auth challenge response.header(HttpHeaders.WWWAuthenticate, "Basic") throw unauthorized( - "Authorization header not found.", + "Authorization header not found", TalerErrorCode.GENERIC_PARAMETER_MISSING ) } - - val authDetails = getAuthorizationDetails(header) ?: throw badRequest( - "Authorization is invalid.", - TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED - ) - return when (authDetails.scheme) { - "Basic" -> doBasicAuth(db, authDetails.content) - "Bearer" -> doTokenAuth(db, authDetails.content, requiredScope) - else -> throw unauthorized("Authorization method wrong or not supported.") - } -} -// Get the auth token (stripped of the bearer-token:-prefix) -// IF the call was authenticated with it. -fun ApplicationCall.getAuthToken(): String? { - val h = getAuthorizationRawHeader(this.request) ?: return null - val authDetails = getAuthorizationDetails(h) ?: throw badRequest( - "Authorization header is malformed.", TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED - ) - if (authDetails.scheme == "Bearer") return splitBearerToken(authDetails.content) ?: throw throw badRequest( - "Authorization header is malformed (could not strip the prefix from Bearer token).", + // Parse header + val (scheme, content) = header.splitOnce(" ") ?: throw badRequest( + "Authorization is invalid", TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED ) - return null // Not a Bearer token case. + return when (scheme) { + "Basic" -> doBasicAuth(db, content) + "Bearer" -> doTokenAuth(db, content, requiredScope) + else -> throw unauthorized("Authorization method wrong or not supported") + } } - /** - * Performs the HTTP basic authentication. Returns the - * authenticated customer login on success, or null otherwise. + * Performs the HTTP Basic Authentication. + * + * Returns the authenticated customer login */ -private suspend fun doBasicAuth(db: Database, encodedCredentials: String): String? { - val plainUserAndPass = String(base64ToBytes(encodedCredentials), Charsets.UTF_8) // :-separated - val userAndPassSplit = plainUserAndPass.split( - ":", - /** - * this parameter allows colons to occur in passwords. - * Without this, passwords that have colons would be split - * and become meaningless. - */ - limit = 2 - ) - if (userAndPassSplit.size != 2) throw badRequest( - "Malformed Basic auth credentials found in the Authorization header.", +private suspend fun doBasicAuth(db: Database, encoded: String): String { + val decoded = String(base64ToBytes(encoded), Charsets.UTF_8) + val (login, plainPassword) = decoded.splitOnce(":") ?: throw badRequest( + "Malformed Basic auth credentials found in the Authorization header", TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED ) - val (login, plainPassword) = userAndPassSplit - val passwordHash = db.account.passwordHash(login) ?: throw unauthorized("Bad password") - if (!CryptoUtil.checkpw(plainPassword, passwordHash)) return null + val hash = db.account.passwordHash(login) ?: throw unauthorized("Unknown account") + if (!CryptoUtil.checkpw(plainPassword, hash)) throw unauthorized("Bad password") return login } /** - * This function takes a prefixed Bearer token, removes the - * secret-token:-prefix and returns it. Returns null, if the - * input is invalid. + * Performs the secret-token HTTP Bearer Authentication. + * + * Returns the authenticated customer login */ -private fun splitBearerToken(tok: String): String? { - val tokenSplit = tok.split(":", limit = 2) - if (tokenSplit.size != 2) return null - if (tokenSplit[0] != "secret-token") return null - return tokenSplit[1] -} - -/* Performs the secret-token authentication. Returns the - * authenticated customer login on success, null otherwise. */ -private suspend fun doTokenAuth( +private suspend fun ApplicationCall.doTokenAuth( db: Database, - token: String, + bearer: String, requiredScope: TokenScope, -): String? { - val bareToken = splitBearerToken(token) ?: throw badRequest( +): String { + if (!bearer.startsWith("secret-token:")) throw badRequest( "Bearer token malformed", TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED ) - val tokenBytes = try { - Base32Crockford.decode(bareToken) + val decoded = try { + Base32Crockford.decode(bearer.slice(13..bearer.length-1)) } catch (e: Exception) { throw badRequest( e.message, TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED ) } - val maybeToken: BearerToken? = db.token.get(tokenBytes) - if (maybeToken == null) { - logger.error("Auth token not found") - return null - } - if (maybeToken.expirationTime.isBefore(Instant.now())) { - logger.error("Auth token is expired") - return null - } - if (maybeToken.scope == TokenScope.readonly && requiredScope == TokenScope.readwrite) { - logger.error("Auth token has insufficient scope") - return null - } - if (!maybeToken.isRefreshable && requiredScope == TokenScope.refreshable) { - logger.error("Could not refresh unrefreshable token") - return null + val token: BearerToken = db.token.get(decoded) ?: throw unauthorized("Unknown token") + when { + token.expirationTime.isBefore(Instant.now()) + -> throw unauthorized("Expired auth token") + + token.scope == TokenScope.readonly && requiredScope == TokenScope.readwrite + -> throw unauthorized("Auth token has insufficient scope") + + !token.isRefreshable && requiredScope == TokenScope.refreshable + -> throw unauthorized("Unrefreshable token") } - // Getting the related username. - return db.account.login(maybeToken.bankCustomer) ?: throw libeufinError( - HttpStatusCode.InternalServerError, - "Customer not found, despite token mentions it.", - TalerErrorCode.GENERIC_INTERNAL_INVARIANT_FAILURE - ) + + attributes.put(AUTH_TOKEN, decoded) + + return token.login }
\ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt index 22c573ae..ff3e9105 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt @@ -40,7 +40,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { // Note: wopid acts as an authentication token. get("/taler-integration/withdrawal-operation/{wopid}") { - val uuid = call.uuidUriComponent("wopid") + val uuid = call.uuidParameter("wopid") val params = StatusParams.extract(call.request.queryParameters) val op = db.withdrawal.pollStatus(uuid, params) ?: throw notFound( "Withdrawal operation '$uuid' not found", @@ -57,7 +57,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { )) } post("/taler-integration/withdrawal-operation/{wopid}") { - val opId = call.uuidUriComponent("wopid") + val opId = call.uuidParameter("wopid") val req = call.receive<BankWithdrawalOperationPostRequest>() val res = db.withdrawal.setDetails( @@ -100,7 +100,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { } } post("/taler-integration/withdrawal-operation/{wopid}/abort") { - val opId = call.uuidUriComponent("wopid") + val opId = call.uuidParameter("wopid") when (db.withdrawal.abort(opId)) { AbortResult.UnknownOperation -> throw notFound( "Withdrawal operation $opId not found", diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt index 9966edf5..58fb208a 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -77,41 +77,36 @@ private fun Routing.coreBankTokenApi(db: Database) { val TOKEN_DEFAULT_DURATION: Duration = Duration.ofDays(1L) auth(db, TokenScope.refreshable) { post("/accounts/{USERNAME}/token") { - val maybeAuthToken = call.getAuthToken() + val existingToken = call.authToken val req = call.receive<TokenRequest>() - /** - * This block checks permissions ONLY IF the call was authenticated with a token. Basic auth - * gets always granted. - */ - if (maybeAuthToken != null) { - val tokenBytes = Base32Crockford.decode(maybeAuthToken) - val refreshingToken = - db.token.get(tokenBytes) - ?: throw internalServerError( - "Token used to auth not found in the database!" - ) + + if (existingToken != null) { + // This block checks permissions ONLY IF the call was authenticated with a token + val refreshingToken = db.token.get(existingToken) ?: throw internalServerError( + "Token used to auth not found in the database!" + ) if (refreshingToken.scope == TokenScope.readonly && req.scope == TokenScope.readwrite) - throw forbidden( - "Cannot generate RW token from RO", - TalerErrorCode.GENERIC_TOKEN_PERMISSION_INSUFFICIENT - ) + throw forbidden( + "Cannot generate RW token from RO", + TalerErrorCode.GENERIC_TOKEN_PERMISSION_INSUFFICIENT + ) } val tokenBytes = ByteArray(32).apply { Random.nextBytes(this) } val tokenDuration: Duration = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION val creationTime = Instant.now() - val expirationTimestamp = - if (tokenDuration == ChronoUnit.FOREVER.duration) { - logger.debug("Creating 'forever' token.") - Instant.MAX - } else { - try { - logger.debug("Creating token with days duration: ${tokenDuration.toDays()}") - creationTime.plus(tokenDuration) - } catch (e: Exception) { - throw badRequest("Bad token duration: ${e.message}") - } + val expirationTimestamp = + if (tokenDuration == ChronoUnit.FOREVER.duration) { + logger.debug("Creating 'forever' token.") + Instant.MAX + } else { + try { + logger.debug("Creating token with days duration: ${tokenDuration.toDays()}") + creationTime.plus(tokenDuration) + } catch (e: Exception) { + throw badRequest("Bad token duration: ${e.message}") } + } if (!db.token.create( login = username, content = tokenBytes, @@ -132,8 +127,8 @@ private fun Routing.coreBankTokenApi(db: Database) { } auth(db, TokenScope.readonly) { delete("/accounts/{USERNAME}/token") { - val token = call.getAuthToken() ?: throw badRequest("Basic auth not supported here.") - db.token.delete(Base32Crockford.decode(token)) + val token = call.authToken ?: throw badRequest("Basic auth not supported here.") + db.token.delete(token) call.respond(HttpStatusCode.NoContent) } } @@ -349,7 +344,7 @@ private fun Routing.coreBankTransactionsApi(db: Database, ctx: BankConfig) { } } get("/accounts/{USERNAME}/transactions/{T_ID}") { - val tId = call.longUriComponent("T_ID") + val tId = call.longParameter("T_ID") val tx = db.transaction.get(tId, username) ?: throw notFound( "Bank transaction '$tId' not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND @@ -424,7 +419,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { } } post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/abort") { - val opId = call.uuidUriComponent("withdrawal_id") + val opId = call.uuidParameter("withdrawal_id") when (db.withdrawal.abort(opId)) { AbortResult.UnknownOperation -> throw notFound( "Withdrawal operation $opId not found", @@ -438,7 +433,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { } } post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/confirm") { - val opId = call.uuidUriComponent("withdrawal_id") + val opId = call.uuidParameter("withdrawal_id") when (db.withdrawal.confirm(opId, Instant.now())) { WithdrawalConfirmationResult.UnknownOperation -> throw notFound( "Withdrawal operation $opId not found", @@ -465,7 +460,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { } } get("/withdrawals/{withdrawal_id}") { - val uuid = call.uuidUriComponent("withdrawal_id") + val uuid = call.uuidParameter("withdrawal_id") val params = StatusParams.extract(call.request.queryParameters) val op = db.withdrawal.pollInfo(uuid, params) ?: throw notFound( "Withdrawal operation '$uuid' not found", @@ -474,7 +469,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { call.respond(op) } post("/withdrawals/{withdrawal_id}/abort") { - val opId = call.uuidUriComponent("withdrawal_id") + val opId = call.uuidParameter("withdrawal_id") when (db.withdrawal.abort(opId)) { AbortResult.UnknownOperation -> throw notFound( "Withdrawal operation $opId not found", @@ -488,7 +483,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { } } post("/withdrawals/{withdrawal_id}/confirm") { - val opId = call.uuidUriComponent("withdrawal_id") + val opId = call.uuidParameter("withdrawal_id") when (db.withdrawal.confirm(opId, Instant.now())) { WithdrawalConfirmationResult.UnknownOperation -> throw notFound( "Withdrawal operation $opId not found", @@ -590,7 +585,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio } } post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/abort") { - val id = call.longUriComponent("CASHOUT_ID") + val id = call.longParameter("CASHOUT_ID") when (db.cashout.abort(id, username)) { AbortResult.UnknownOperation -> throw notFound( "Cashout operation $id not found", @@ -605,7 +600,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio } post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/confirm") { val req = call.receive<CashoutConfirm>() - val id = call.longUriComponent("CASHOUT_ID") + val id = call.longParameter("CASHOUT_ID") when (db.cashout.confirm( id = id, login = username, @@ -647,7 +642,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio } auth(db, TokenScope.readonly) { get("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}") { - val id = call.longUriComponent("CASHOUT_ID") + val id = call.longParameter("CASHOUT_ID") val cashout = db.cashout.get(id, username) ?: throw notFound( "Cashout operation $id not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt index 72633c73..c827d406 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -247,24 +247,12 @@ enum class TokenScope { refreshable // Not spec'd as a scope! } -/** - * Convenience type to set/get authentication tokens to/from - * the database. - */ data class BearerToken( - val content: ByteArray, val scope: TokenScope, - val isRefreshable: Boolean = false, + val isRefreshable: Boolean, val creationTime: Instant, val expirationTime: Instant, - /** - * Serial ID of the database row that hosts the bank customer - * that is associated with this token. NOTE: if the token is - * refreshed by a client that doesn't have a user+password login - * in the system, the creator remains always the original bank - * customer that created the very first token. - */ - val bankCustomer: Long + val login: String ) @Serializable diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt index 276e5e56..5743eb8a 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -348,17 +348,6 @@ class AccountDAO(private val db: Database) { } } - /** Get login of account [id] */ - suspend fun login(id: Long): String? = db.conn { conn -> - val stmt = conn.prepareStatement(""" - SELECT login FROM customers WHERE customer_id=? - """) - stmt.setLong(1, id) - stmt.oneOrNull { - it.getString(1) - } - } - /** Get bank info of account [login] */ suspend fun bankInfo(login: String): BankInfo? = db.conn { conn -> val stmt = conn.prepareStatement(""" diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt index 2791d92f..19c214cc 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt @@ -63,21 +63,21 @@ class TokenDAO(private val db: Database) { suspend fun get(token: ByteArray): BearerToken? = db.conn { conn -> val stmt = conn.prepareStatement(""" SELECT - expiration_time, creation_time, - bank_customer, + expiration_time, + login, scope, is_refreshable FROM bearer_tokens + JOIN customers ON bank_customer=customer_id WHERE content=?; """) stmt.setBytes(1, token) stmt.oneOrNull { BearerToken( - content = token, - creationTime = it.getLong("creation_time").microsToJavaInstant() ?: throw faultyTimestampByBank(), + creationTime = it.getLong("creation_time").microsToJavaInstant() ?: throw faultyDurationByClient(), expirationTime = it.getLong("expiration_time").microsToJavaInstant() ?: throw faultyDurationByClient(), - bankCustomer = it.getLong("bank_customer"), + login = it.getString("login"), scope = TokenScope.valueOf(it.getString("scope")), isRefreshable = it.getBoolean("is_refreshable") ) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt index c8e96e0e..36c49f1f 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -41,9 +41,9 @@ import tech.libeufin.util.* private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.helpers") -fun ApplicationCall.expectUriComponent(componentName: String) = - maybeUriComponent(componentName) ?: throw badRequest( - "No username found in the URI", +fun ApplicationCall.expectParameter(name: String) = + parameters[name] ?: throw badRequest( + "Missing '$name' param", TalerErrorCode.GENERIC_PARAMETER_MISSING ) @@ -90,17 +90,17 @@ fun getWithdrawalConfirmUrl( return baseUrl.replace("{woid}", wopId.toString()) } -fun ApplicationCall.uuidUriComponent(name: String): UUID { +fun ApplicationCall.uuidParameter(name: String): UUID { try { - return UUID.fromString(expectUriComponent(name)) + return UUID.fromString(expectParameter(name)) } catch (e: Exception) { throw badRequest("UUID uri component malformed: ${e.message}") } } -fun ApplicationCall.longUriComponent(name: String): Long { +fun ApplicationCall.longParameter(name: String): Long { try { - return expectUriComponent(name).toLong() + return expectParameter(name).toLong() } catch (e: Exception) { throw badRequest("Long uri component malformed: ${e.message}") } @@ -153,4 +153,6 @@ fun Route.conditional(implemented: Boolean, callback: Route.() -> Unit): Route = if (!implemented) { throw libeufinError(HttpStatusCode.NotImplemented, "API not implemented", TalerErrorCode.END) } - }
\ No newline at end of file + } + +
\ No newline at end of file diff --git a/build.gradle b/build.gradle index 632f7c52..01941c05 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { id("org.jetbrains.kotlin.jvm") version "1.9.20" + id("org.jetbrains.dokka") version "1.9.10" id("idea") id("java-library") id("maven-publish") @@ -33,6 +34,10 @@ allprojects { } } +subprojects { + apply plugin: 'org.jetbrains.dokka' +} + idea { module { excludeDirs += file("frontend") diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt index 20ec3817..cedd07b6 100644 --- a/util/src/main/kotlin/HTTP.kt +++ b/util/src/main/kotlin/HTTP.kt @@ -45,46 +45,4 @@ fun ApplicationRequest.getBaseUrl(): String? { encodedPath = "/" } } -} - -fun ApplicationCall.maybeUriComponent(name: String): String? { - val ret: String? = this.parameters[name] - if (ret == null) { - logger.error("Component $name not found in URI") - return null - } - return ret -} - -// Extracts the Authorization:-header line, or returns null if not found. -fun getAuthorizationRawHeader(request: ApplicationRequest): String? { - return request.headers["Authorization"] ?: run { - logger.error("Authorization header not found") - return null - } -} - -/** - * Holds the details contained in an Authorization header. - * The content is held as it was found in the header and supposed - * to be processed according to the scheme. - */ -data class AuthorizationDetails( - val scheme: String, - val content: String -) - -// Returns the authorization scheme mentioned in the Auth header, -// or null if that could not be found. -fun getAuthorizationDetails(authorizationHeader: String): AuthorizationDetails? { - val split = authorizationHeader.split(" ") - if (split.isEmpty()) { - logger.error("malformed Authorization header: contains no space") - return null - } - if (split.size != 2) { - logger.error("malformed Authorization header: contains more than one space") - return null - } - return AuthorizationDetails(scheme = split[0], content = split[1]) }
\ No newline at end of file diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt index 54d6d5a3..1d7b49a4 100644 --- a/util/src/main/kotlin/strings.kt +++ b/util/src/main/kotlin/strings.kt @@ -83,3 +83,8 @@ fun getQueryParam(uriQueryString: String, param: String): String? { return null } +fun String.splitOnce(pat: String): Pair<String, String>? { + val split = split(pat, limit=2); + if (split.size != 2) return null + return Pair(split[0], split[1]) +}
\ No newline at end of file |