diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 232c6507d..8e948718b 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -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 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 6abdfd059..682c2c4b5 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -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 diff --git a/obp-api/src/main/scala/code/api/util/BerlinGroupCheck.scala b/obp-api/src/main/scala/code/api/util/BerlinGroupCheck.scala new file mode 100644 index 000000000..22a7118a8 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/BerlinGroupCheck.scala @@ -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 + } + } + +}