Merge pull request #2511 from constantine2nd/develop

Check mandatory headers of Berlin Group
This commit is contained in:
Simon Redfern 2025-03-19 16:08:31 +01:00 committed by GitHub
commit 65bbd80251
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 4 deletions

View File

@ -1120,6 +1120,10 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
# Show the path inside of Berlin Group error message
#berlin_group_error_message_show_path = true
# Check presence of the mandatory headers
#berlin_group_mandatory_headers = X-Request-ID,PSU-IP-Address,PSU-Device-ID,PSU-Device-Name
#berlin_group_mandatory_header_consent = TPP-Redirect-URL
## Berlin Group Create Consent Frequency per Day Upper Limit
#berlin_group_frequency_per_day_upper_limit = 4

View File

@ -3244,8 +3244,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val verb = result._2.map(_.verb).getOrElse("None")
val body = result._2.flatMap(_.httpBody)
val reqHeaders = result._2.map(_.requestHeaders).getOrElse(Nil)
// Verify signed request (Berlin Group)
BerlinGroupSigning.verifySignedRequest(body, verb, url, reqHeaders, result)
// Berlin Group checks
BerlinGroupCheck.validate(body, verb, url, reqHeaders, result)
} map {
result =>
val excludeFunctions = getPropsValue("rate_limiting.exclude_endpoints", "root").split(",").toList
@ -3300,8 +3300,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val verb = result._2.map(_.verb).getOrElse("None")
val body = result._2.flatMap(_.httpBody)
val reqHeaders = result._2.map(_.requestHeaders).getOrElse(Nil)
// Verify signed request if need be
BerlinGroupSigning.verifySignedRequest(body, verb, url, reqHeaders, result)
// Berlin Group checks
BerlinGroupCheck.validate(body, verb, url, reqHeaders, result)
} map { result =>
result._1 match {
case Empty if result._2.flatMap(_.consumer).isDefined => // There is no error and Consumer is defined

View File

@ -0,0 +1,44 @@
package code.api.util
import code.api.RequestHeader
import com.openbankproject.commons.model.User
import net.liftweb.common.{Box, Empty, Failure}
import net.liftweb.http.provider.HTTPParam
object BerlinGroupCheck {
// Parse mandatory headers from a comma-separated string
private val berlinGroupMandatoryHeaders: List[String] = APIUtil.getPropsValue("berlin_group_mandatory_headers", defaultValue = "X-Request-ID,PSU-IP-Address,PSU-Device-ID,PSU-Device-Name")
.split(",")
.map(_.trim.toLowerCase)
.toList.filterNot(_.isEmpty)
private val berlinGroupMandatoryHeaderConsent = APIUtil.getPropsValue("berlin_group_mandatory_header_consent", defaultValue = "TPP-Redirect-URL")
.split(",")
.map(_.trim.toLowerCase)
.toList.filterNot(_.isEmpty)
private def validateHeaders(verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = {
val headerMap = reqHeaders.map(h => h.name.toLowerCase -> h).toMap
val missingHeaders = if(url.contains("berlin-group") && url.endsWith("/consent"))
berlinGroupMandatoryHeaders.filterNot(headerMap.contains)
else
(berlinGroupMandatoryHeaders ++ berlinGroupMandatoryHeaderConsent).filterNot(headerMap.contains)
if (missingHeaders.isEmpty) {
forwardResult // All mandatory headers are present
} else {
(Failure(s"Missing mandatory headers: ${missingHeaders.mkString(", ")}"), forwardResult._2)
}
}
def validate(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = {
validateHeaders(verb, url, reqHeaders, forwardResult) match {
case (user, _) if user.isDefined || user == Empty => // All good. Chain another check
// Verify signed request (Berlin Group)
BerlinGroupSigning.verifySignedRequest(body, verb, url, reqHeaders, forwardResult)
case forwardError => // Forward error case
forwardError
}
}
}