diff --git a/obp-api/src/main/scala/code/api/dauth.scala b/obp-api/src/main/scala/code/api/dauth.scala index f432319c12..60bc075c85 100755 --- a/obp-api/src/main/scala/code/api/dauth.scala +++ b/obp-api/src/main/scala/code/api/dauth.scala @@ -134,7 +134,7 @@ object DAuth extends MdcLoggable { // Check if the request (access token or request token) is valid and return a tuple def getDAuthToken(requestHeaders: List[HTTPParam]) : Option[List[String]] = { - requestHeaders.find(_.name==APIUtil.DAuthHeaderKey).map(_.values) + requestHeaders.find(_.name.equalsIgnoreCase(APIUtil.DAuthHeaderKey)).map(_.values) } def getOrCreateResourceUser(jwtPayload: String, callContext: Option[CallContext]) : Box[(User, Option[CallContext])] = { diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 62f51a4274..c3042c4c64 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -239,9 +239,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ def hasDirectLoginHeader(authorization: Box[String]): Boolean = hasHeader("DirectLogin", authorization) - def has2021DirectLoginHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.find(_.name.toLowerCase == "DirectLogin".toLowerCase()).isDefined + def has2021DirectLoginHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.exists(_.name.equalsIgnoreCase("DirectLogin")) - def hasAuthorizationHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.find(_.name == "Authorization").isDefined + def hasAuthorizationHeader(requestHeaders: List[HTTPParam]): Boolean = requestHeaders.exists(_.name.equalsIgnoreCase("Authorization")) /* The OAuth 2.0 Authorization Framework: Bearer Token @@ -262,7 +262,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * Other types: the `GatewayLogin` is in the VALUE * Authorization:GatewayLogin token=xxxx */ - def hasDAuthHeader(requestHeaders: List[HTTPParam]) = requestHeaders.map(_.name).exists(_ ==DAuthHeaderKey) + def hasDAuthHeader(requestHeaders: List[HTTPParam]) = requestHeaders.exists(_.name.equalsIgnoreCase(DAuthHeaderKey)) /** * Helper function which tells us does an "Authorization" request header field has the Type of an authentication scheme @@ -282,9 +282,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * @return the Consent-JWT value from a Request Header as a String */ def getConsentJWT(requestHeaders: List[HTTPParam]): Option[String] = { - requestHeaders.toSet.filter(_.name == RequestHeader.`Consent-JWT`).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`Consent-JWT`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) - case _ => requestHeaders.toSet.filter(_.name == RequestHeader.`Consent-Id`).toList match { + case _ => requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`Consent-Id`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) case _ => None } @@ -296,7 +296,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * @return the Consent-JWT value from a Request Header as a String */ def getConsentIdRequestHeaderValue(requestHeaders: List[HTTPParam]): Option[String] = { - requestHeaders.toSet.filter(_.name == RequestHeader.`Consent-Id`).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`Consent-Id`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) case _ => None } @@ -306,14 +306,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * @return the PSD2-CERT value from a Request Header as a String */ def `getPSD2-CERT`(requestHeaders: List[HTTPParam]): Option[String] = { - requestHeaders.toSet.filter(_.name == RequestHeader.`PSD2-CERT`).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`PSD2-CERT`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) case _ => None } } def getRequestHeader(name: String, requestHeaders: List[HTTPParam]): String = { - requestHeaders.toSet.filter(_.name.toLowerCase == name.toLowerCase).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(name)).toList match { case x :: Nil => x.values.mkString(";") case _ => "" } @@ -329,7 +329,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * @return the Consent-ID value from a Request Header as a String */ def `getConsent-ID`(requestHeaders: List[HTTPParam]): Option[String] = { - requestHeaders.toSet.filter(_.name == RequestHeader.`Consent-ID`).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`Consent-ID`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) case _ => None } @@ -499,13 +499,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ private def checkConditionalRequest(cc: Option[CallContext], httpVerb: String, httpCode: Int, httpBody: Box[String]) = { val requestHeaders: List[HTTPParam] = cc.map(_.requestHeaders).getOrElse(Nil) - requestHeaders.filter(_.name == RequestHeader.`If-None-Match` ).headOption match { + requestHeaders.filter(_.name.equalsIgnoreCase(RequestHeader.`If-None-Match`)).headOption match { case Some(value) => // Handle the If-None-Match HTTP request header checkIfNotMatchHeader(cc, httpCode, httpBody, value.values.mkString("")) case None => // When used in combination with If-None-Match, it is ignored, unless the server doesn't support If-None-Match. // The most common use case is to update a cached entity that has no associated ETag - requestHeaders.filter(_.name == RequestHeader.`If-Modified-Since` ).headOption match { + requestHeaders.filter(_.name.equalsIgnoreCase(RequestHeader.`If-Modified-Since`)).headOption match { case Some(value) => // Handle the If-Modified-Since HTTP request header checkIfModifiedSinceHeader(cc, httpVerb, httpCode, httpBody, value.values.mkString("")) case None => @@ -2842,7 +2842,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ } else if (BerlinGroupCheck.hasUnwantedConsentIdHeaderForBGEndpoint(url, reqHeaders)) { val message = ErrorMessages.InvalidConsentIdUsage Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), Some(cc)) } - } else if (APIUtil.`hasConsent-ID`(reqHeaders)) { // Berlin Group's Consent + } else if (url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix) && APIUtil.`hasConsent-ID`(reqHeaders)) { // Berlin Group's Consent // Choose consumer based on validation method configuration val consumerForConsent = if (method == "CONSUMER_KEY_VALUE" && consumerByConsumerKey.isDefined) { consumerByConsumerKey @@ -4658,7 +4658,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * @return Full(errorResponse) if validate fail */ def validateRequestHeadersKeys(operationId: String, callContext: CallContext): Box[JsonResponse] = { - val headerKeysGrouped: Map[String, List[HTTPParam]] = callContext.requestHeaders.groupBy(x => x.name) + val headerKeysGrouped: Map[String, List[HTTPParam]] = callContext.requestHeaders.groupBy(_.name.toLowerCase(java.util.Locale.ROOT)) headerKeysGrouped.toList.forall(_._2.size == 1) match { case true => Empty case false => @@ -4747,12 +4747,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case (Some(callContext), operationId) if enableForceError => val requestHeaders = callContext.requestHeaders - val forceError = requestHeaders.collectFirst({ - case HTTPParam("Force-Error", value::_) => value - }) - val responseCode = requestHeaders.collectFirst({ - case HTTPParam("Response-Code", value::_) => value - }) + val forceError = requestHeaders.collectFirst { + case HTTPParam(name, value::_) if name.equalsIgnoreCase("Force-Error") => value + } + val responseCode = requestHeaders.collectFirst { + case HTTPParam(name, value::_) if name.equalsIgnoreCase("Response-Code") => value + } if(forceError.isEmpty) { Empty diff --git a/obp-api/src/main/scala/code/api/util/AuthorisationUtil.scala b/obp-api/src/main/scala/code/api/util/AuthorisationUtil.scala index 2f11056f38..feb77fdeab 100644 --- a/obp-api/src/main/scala/code/api/util/AuthorisationUtil.scala +++ b/obp-api/src/main/scala/code/api/util/AuthorisationUtil.scala @@ -32,10 +32,9 @@ import code.api.util.APIUtil.HTTPParam object AuthorisationUtil { def getAuthorisationHeaders(requestHeaders: List[HTTPParam]): List[String] = { - requestHeaders.map(_.name).filter { - case `Consent-Id`| `Consent-ID` | `Consent-JWT` => true - case _ => false - } + requestHeaders.map(_.name).filter(name => + List(`Consent-Id`, `Consent-ID`, `Consent-JWT`).exists(name.equalsIgnoreCase) + ) } diff --git a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala index 54b9fa4c44..91b5c313ce 100644 --- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala +++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala @@ -237,7 +237,7 @@ object Consent extends MdcLoggable { * @return the Consumer-Key value from a Request Header as a String */ def getConsumerKey(requestHeaders: List[HTTPParam]): Option[String] = { - requestHeaders.toSet.filter(_.name == RequestHeader.`Consumer-Key`).toList match { + requestHeaders.toSet.filter(_.name.equalsIgnoreCase(RequestHeader.`Consumer-Key`)).toList match { case x :: Nil => Some(x.values.mkString(", ")) case _ => None } @@ -1551,12 +1551,12 @@ object Consent extends MdcLoggable { // Collect optional headers val headers = callContext.map(_.requestHeaders).getOrElse(Nil) - val tppRedirectUri = headers.find(_.name == RequestHeader.`TPP-Redirect-URI`) - val tppNokRedirectUri = headers.find(_.name == RequestHeader.`TPP-Nok-Redirect-URI`) - val xRequestId = headers.find(_.name == RequestHeader.`X-Request-ID`) - val psuDeviceId = headers.find(_.name == RequestHeader.`PSU-Device-ID`) - val psuIpAddress = headers.find(_.name == RequestHeader.`PSU-IP-Address`) - val psuGeoLocation = headers.find(_.name == RequestHeader.`PSU-Geo-Location`) + val tppRedirectUri = headers.find(_.name.equalsIgnoreCase(RequestHeader.`TPP-Redirect-URI`)) + val tppNokRedirectUri = headers.find(_.name.equalsIgnoreCase(RequestHeader.`TPP-Nok-Redirect-URI`)) + val xRequestId = headers.find(_.name.equalsIgnoreCase(RequestHeader.`X-Request-ID`)) + val psuDeviceId = headers.find(_.name.equalsIgnoreCase(RequestHeader.`PSU-Device-ID`)) + val psuIpAddress = headers.find(_.name.equalsIgnoreCase(RequestHeader.`PSU-IP-Address`)) + val psuGeoLocation = headers.find(_.name.equalsIgnoreCase(RequestHeader.`PSU-Geo-Location`)) def sequenceBoxes[A](boxes: List[Box[A]]): Box[List[A]] = { boxes.foldRight(Full(Nil): Box[List[A]]) { (box, acc) => diff --git a/obp-api/src/main/scala/code/api/util/JwsUtil.scala b/obp-api/src/main/scala/code/api/util/JwsUtil.scala index 8392c88de9..c885dc3e9b 100644 --- a/obp-api/src/main/scala/code/api/util/JwsUtil.scala +++ b/obp-api/src/main/scala/code/api/util/JwsUtil.scala @@ -73,7 +73,7 @@ object JwsUtil extends MdcLoggable { json.parse(s).extractOpt[JwsProtectedHeader] match { case Some(header) => val headers = header.sigD.pars.flatMap( i => - requestHeaders.find(_.name.toLowerCase() == i.toLowerCase()).map(i => s"${i.name.toLowerCase()}: ${i.values.mkString}") + requestHeaders.find(_.name.equalsIgnoreCase(i)).map(i => s"${i.name.toLowerCase()}: ${i.values.mkString}") ) val requestTarget = s"""(request-target): ${verb.toLowerCase()} ${url}\n""" requestTarget + headers.mkString("\n") + "\n" // Add new line after each item @@ -100,14 +100,14 @@ object JwsUtil extends MdcLoggable { headerValue == s"SHA-256=${computeDigest(httpBody)}" } def getDigestHeaderValue(requestHeaders: List[HTTPParam]): String = { - requestHeaders.find(_.name.toLowerCase == "digest").map(_.values.mkString).getOrElse("None") + requestHeaders.find(_.name.equalsIgnoreCase("digest")).map(_.values.mkString).getOrElse("None") } def getJwsHeaderValue(requestHeaders: List[HTTPParam]): String = { - requestHeaders.find(_.name == "x-jws-signature").map(_.values.mkString).getOrElse("None") + requestHeaders.find(_.name.equalsIgnoreCase("x-jws-signature")).map(_.values.mkString).getOrElse("None") } def checkRequestIsSigned(requestHeaders: List[HTTPParam]): Boolean = { - requestHeaders.find(_.name == "x-jws-signature").isDefined || - requestHeaders.find(_.name == "digest").isDefined + requestHeaders.exists(_.name.equalsIgnoreCase("x-jws-signature")) || + requestHeaders.exists(_.name.equalsIgnoreCase("digest")) } private def getDeferredCriticalHeaders() = { val deferredCriticalHeaders = new util.HashSet[String]() diff --git a/obp-api/src/main/scala/code/api/util/PegdownOptions.scala b/obp-api/src/main/scala/code/api/util/PegdownOptions.scala index 4d49d114bd..d5b921f9a0 100644 --- a/obp-api/src/main/scala/code/api/util/PegdownOptions.scala +++ b/obp-api/src/main/scala/code/api/util/PegdownOptions.scala @@ -49,6 +49,8 @@ object PegdownOptions { .replaceAll("&;", "&") .replaceAll("‘", "'") .replaceAll("…", "...") + .replaceAll("–", "–") + .replaceAll("—", "—") // not support make text bold that not at beginning of a line, so here manual convert to it to tag // .replaceAll("""\*\*(.+?)\*\*""", "$1") } diff --git a/obp-api/src/main/scala/code/api/util/WriteMetricUtil.scala b/obp-api/src/main/scala/code/api/util/WriteMetricUtil.scala index 647b308d63..4b283442da 100644 --- a/obp-api/src/main/scala/code/api/util/WriteMetricUtil.scala +++ b/obp-api/src/main/scala/code/api/util/WriteMetricUtil.scala @@ -144,7 +144,7 @@ object WriteMetricUtil extends MdcLoggable { } private def requestHeaderValue(cc: CallContextLight, headerName: String): String = - cc.requestHeaders.find(_.name.toLowerCase() == headerName).map(_.values.mkString(",")).getOrElse("") + cc.requestHeaders.find(_.name.equalsIgnoreCase(headerName)).map(_.values.mkString(",")).getOrElse("") private def saveMetricSafely(cc: CallContextLight, fields: MetricFields): Unit = { import fields._ diff --git a/obp-api/src/test/scala/code/util/APIUtilHeaderTest.scala b/obp-api/src/test/scala/code/util/APIUtilHeaderTest.scala new file mode 100644 index 0000000000..f5584f1b69 --- /dev/null +++ b/obp-api/src/test/scala/code/util/APIUtilHeaderTest.scala @@ -0,0 +1,26 @@ +package code.util + +import code.api.DAuth +import code.api.util.{APIUtil, AuthorisationUtil, Consent, JwsUtil} +import code.api.v4_0_0.V400ServerSetup + +class APIUtilHeaderTest extends V400ServerSetup { + + feature("Consent and PSD2 request header lookup") { + scenario("HTTP/2 lowercase consent and PSD2 header names are accepted") { + APIUtil.getConsentJWT(List(APIUtil.HTTPParam("consent-jwt", List("jwt")))) shouldBe Some("jwt") + APIUtil.getConsentJWT(List(APIUtil.HTTPParam("consent-id", List("consent-id")))) shouldBe Some("consent-id") + APIUtil.getConsentIdRequestHeaderValue(List(APIUtil.HTTPParam("consent-id", List("consent-id")))) shouldBe Some("consent-id") + APIUtil.`getPSD2-CERT`(List(APIUtil.HTTPParam("psd2-cert", List("certificate")))) shouldBe Some("certificate") + APIUtil.`getConsent-ID`(List(APIUtil.HTTPParam("consent-id", List("berlin-group-consent-id")))) shouldBe Some("berlin-group-consent-id") + Consent.getConsumerKey(List(APIUtil.HTTPParam("consumer-key", List("consumer-key")))) shouldBe Some("consumer-key") + APIUtil.getRequestHeader("PSU-ID", List(APIUtil.HTTPParam("psu-id", List("psu")))) shouldBe "psu" + APIUtil.hasAuthorizationHeader(List(APIUtil.HTTPParam("authorization", List("Bearer token")))) shouldBe true + APIUtil.hasDAuthHeader(List(APIUtil.HTTPParam("dauth", List("token")))) shouldBe true + DAuth.getDAuthToken(List(APIUtil.HTTPParam("dauth", List("token")))) shouldBe Some(List("token")) + JwsUtil.getJwsHeaderValue(List(APIUtil.HTTPParam("X-JWS-SIGNATURE", List("signature")))) shouldBe "signature" + JwsUtil.checkRequestIsSigned(List(APIUtil.HTTPParam("DIGEST", List("digest")))) shouldBe true + AuthorisationUtil.getAuthorisationHeaders(List(APIUtil.HTTPParam("consent-jwt", List("jwt")))) shouldBe List("consent-jwt") + } + } +} diff --git a/obp-api/src/test/scala/code/util/PegdownOptionsTest.scala b/obp-api/src/test/scala/code/util/PegdownOptionsTest.scala index 42f670fa26..12107551ac 100644 --- a/obp-api/src/test/scala/code/util/PegdownOptionsTest.scala +++ b/obp-api/src/test/scala/code/util/PegdownOptionsTest.scala @@ -384,6 +384,14 @@ class PegdownOptionsTest extends FlatSpec with Matchers { val html = PegdownOptions.convertGitHubDocMarkdownToHtml(markdownText) } + it should "render typographic dashes as XML-safe characters" taggedAs FunctionsTag in { + val html = convertPegdownToHtmlTweaked("A consent -- and a longer --- separator") + + html should not include "–" + html should not include "—" + stringToNodeSeq(html) + } + "description string" should "test the markdown * -> html
  • tag" taggedAs FunctionsTag in { // This string is from Foobar Property List: format