mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 13:07:02 +00:00
Merge remote-tracking branch 'Simon/develop' into develop
# Conflicts: # obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala
This commit is contained in:
commit
72b91fa1ba
@ -165,8 +165,12 @@ jwt.use.ssl=false
|
||||
# Bypass TPP signature validation
|
||||
# bypass_tpp_signature_validation = false
|
||||
|
||||
## Reject Berlin Group Consents in status "received" after defined time
|
||||
# berlin_group_outdated_consents_interval = 5
|
||||
## Reject Berlin Group consents with status "received" after a defined time (in seconds)
|
||||
# berlin_group_outdated_consents_time_in_seconds = 300
|
||||
# berlin_group_outdated_consents_interval_in_seconds =
|
||||
|
||||
## Expire Berlin Group consents with status "valid"
|
||||
# berlin_group_expired_consents_interval_in_seconds =
|
||||
|
||||
|
||||
## Enable writing API metrics (which APIs are called) to RDBMS
|
||||
@ -1124,7 +1128,7 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
|
||||
#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_headers = Content-Type,Date,Digest,PSU-Device-ID,PSU-Device-Name,PSU-IP-Address,Signature,TPP-Signature-Certificate,X-Request-ID
|
||||
#berlin_group_mandatory_header_consent = TPP-Redirect-URL
|
||||
|
||||
## Berlin Group Create Consent Frequency per Day Upper Limit
|
||||
|
||||
@ -5508,6 +5508,9 @@ object SwaggerDefinitionsJSON {
|
||||
val consumerLogoUrlJson = ConsumerLogoUrlJson(
|
||||
"http://localhost:8888"
|
||||
)
|
||||
val consumerCertificateJson = ConsumerCertificateJson(
|
||||
"QmFnIEF0dHJpYnV0ZXMNCiAgICBsb2NhbEtleUlEOiBFMSA3RiBCMyBCOCBEQiA4QyA2NCBGNiA4QyA1NSAzNCA3QSAyNiBCRSBEMCBCNCBENCBBMyBGRCA2NiANCnN1YmplY3Q9QyA9IE1ELCBPID0gTUFJQiwgQ04gPSBNQUlCIFByaXNhY2FydSBTZXJnaXUgKFRlc3QpDQoNCmlzc3Vlcj1DID0gTUQsIE8gPSBCTk0sIE9VID0gRFRJLCBDTiA9IEJOTSBDQSAodGVzdCksIGVtYWlsQWRkcmVzcyA9IGFkbWluQGJubS5tZA0KDQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0NCk1JSUdoVENDQkcyZ0F3SUJBZ0lDQkRvd0RRWUpLb1pJaHZjTkFRRUZCUUF3WGpFTE1Ba0dBMVVFQmhNQ1RVUXgNCkREQUtCZ05WQkFvTUEwSk9UVEVNTUFvR0ExVUVDd3dEUkZSSk1SWXdGQVlEVlFRRERBMUNUazBnUTBFZ0tIUmwNCmMzUXBNUnN3R1FZSktvWklodmNOQVFrQkZneGhaRzFwYmtCaWJtMHViV1F3SGhjTk1qUXdOREU0TVRFME5qUXgNCldoY05Nall3TkRFNE1URTBOalF4V2pCRE1Rc3dDUVlEVlFRR0V3Sk5SREVOTUFzR0ExVUVDZ3dFVFVGSlFqRWwNCk1DTUdBMVVFQXd3Y1RVRkpRaUJRY21sellXTmhjblVnVTJWeVoybDFJQ2hVWlhOMEtUQ0NBU0l3RFFZSktvWkkNCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTFdYMzlFSmZLNEg5MDZKSVpMbHRxTU56amxDd3NyMm0rZjMNCjVYdHZ4SVY1akEvUWlZSDdDVjBQK0E1U2grKytaNldUb1NnQStQemYwdTdWYWRVbWtyWEZBV0lzOXlPemduUjQNCmZ5TVVSNXR4UWJYdmZYcXVJUS9XQ0ZnRHBIU3I4eWN0UHlsOGdsUjFidVF0UmlTdEdMT0RnalhsTmhTMlhTYTMNCmFwVGhUVHAya3o1dEoyWjBXRnlxa1ZVM1FJNkdNVGU5eWhDdnVZQkI1QWJuUUU4bXVPb2NNaEJkRFREY2ZGdW0NCk5paUozelhLMXZzKzEzNW5sZEMxOXozWnBuaVBSeER2WGthR00wc0xiNnk5T1NIOUdmYTZHcXJnendTTmpubEkNCnZCeWFlK1dtbG16TzlBZXVKNVRaUFhPdzNwcFdpTWdTOVlZOWp1UUtFQUFBQUFBQTQ4c0NBd0VBQWFPQ0FtWXcNCmdnSmlNQkVHQ1dDR1NBR0crRUlCQVFRRUF3SUZvREFwQmdOVkhTVUVJakFnQmdnckJnRUZCUWNEQWdZSUt3WUINCkJRVUhBd1FHQ2lzR0FRUUJnamNVQWdJd0hRWURWUjBPQkJZRUZGR2ptcXM4OXUyMXcvZmNHVlgrb0pNZSsvWTYNCk1JR1FCZ05WSFNNRWdZZ3dnWVdBRkh1ckdvcWhWYVFUVkJwRVlObmNnRUl5Vkd3dG9XS2tZREJlTVFzd0NRWUQNClZRUUdFd0pOUkRFTU1Bb0dBMVVFQ2d3RFFrNU5NUXd3Q2dZRFZRUUxEQU5FVkVreEZqQVVCZ05WQkFNTURVSk8NClRTQkRRU0FvZEdWemRDa3hHekFaQmdrcWhraUc5dzBCQ1FFV0RHRmtiV2x1UUdKdWJTNXRaSUlKQUpuU0UxdVoNCkU1MU5NQlFHQTFVZEVnUU5NQXVCQ1VOQlFHSnViUzV0WkRBMkJnbGdoa2dCaHZoQ0FRUUVLUlluYUhSMGNEb3YNCkwzQnJhUzVpYm0wdWJXUXZjR3RwTDNCMVlpOWpjbXd2WTJGamNtd3VZM0pzTURZR0NXQ0dTQUdHK0VJQkF3UXANCkZpZG9kSFJ3T2k4dmNHdHBMbUp1YlM1dFpDOXdhMmt2Y0hWaUwyTnliQzlqWVdOeWJDNWpjbXd3T0FZRFZSMGYNCkJERXdMekF0b0N1Z0tZWW5hSFIwY0RvdkwzQnJhUzVpYm0wdWJXUXZjR3RwTDNCMVlpOWpjbXd2WTJGamNtd3UNClkzSnNNRU1HQ0NzR0FRVUZCd0VCQkRjd05UQXpCZ2dyQmdFRkJRY3dBb1luYUhSMGNEb3ZMM2QzZHk1aWJtMHUNCmJXUXZjSFZpTDJOaFkyVnlkQzlqWVdObGNuUXVZM0owTUZBR0NXQ0dTQUdHK0VJQkRRUkRGa0ZMWlhrZ1VHRnANCmNpQkhaVzVsY21GMFpXUWdZbmtnVlc1cFEzSjVjSFFnZGk0d0xqWXVPQzQySUdadmNpQlFTME5USXpFeUxXWnANCmJHVWdjM1J2Y21GblpUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJR1FEQU5CZ2txaGtpRzl3MEINCkFRVUZBQU9DQWdFQUpTU0ZhRWZOOWVna2wyYVFEc3QvVEtWWmxSbFdWZWkrVmZwMnM1ZXpWNG9ibnZRUXI5QkcNCmZrNklqaU8zbGZHTjQyTkVZSTV6SGh3SDl2WTRiMjM2ZkdMZWltbmZDc2lGb0FyTEtGUDR6Y0dvS0ZJR2ZBNDINCnQzSmxIcENvbmNpMmxqUzg4MzN2c1k5M2xGSzFTa2NvUjBMT0s0NzdaNlBWMjVtdjVjdmhCN1ZkNWs4SWpLU3MNCllwWkpaSi9STWZNT3dPQUtqeDFhWDNxQUhhNVhTOUNINEJaMEl4SnBYcWZpMm5GUFVNRy8yU0JmSTN4dDhsM1UNClJtVy9qZVRoRG5tL0Vsb05sb3pObzdRS3AvbysyUVBFZDBUWkFBdUljQWFiM09waUptOWlrUlh3c21mNkFmS0INCnIwQmtHcTFiTi9RQk1DMDM4RHA4S1pKZmdmaTYxYnBiVUNFdDRsVWY0R252TW9FdjZnbTh1czE2VTI1d0Y0SUwNCnd5cmFBZHJUVHhaWEVydGY2c3pWY2JBRUY0QmdFM0hCVmF2V2FxbDZac1FFRFJoTGVtWVJwMHhleUtwYXI4d3INClhqN1oycmJteWpFci9ES1hMdHF2UlFIQVVrVDBEQXRST2R4NmpsNUtGSFVvbTM2QUZmeU5UcjJ6a0p2MkZWTlENCmc0TnJMRnk0WldidE84ZDc2M2NoMEpjaWYzZUdadnFmQnVETUs3Q25jUWluamxVcTg1cFpzeGlFUW56VTJOdGgNClRFUzBqZjZ6ZS9ibHpVaUsrRXlyeWpEeWNaYlk3RHlwWWVlTlJJbk9zVUVjZmtFT3BVL3dFTG83dnpNaGY1b2MNCmdjcUFKSzdOQWlEQzVHR0Iyb296ZzNSTTJBbGdPT1ZpRFZwRzRMaUxPenpqVStqaXlyclY3OGs9DQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tDQo="
|
||||
)
|
||||
val consumerNameJson = ConsumerNameJson(
|
||||
"App name"
|
||||
)
|
||||
|
||||
@ -242,10 +242,10 @@ recurringIndicator:
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
_ <- passesPsd2Aisp(callContext)
|
||||
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
|
||||
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
|
||||
unboxFullOrFail(_, callContext, ConsentNotFound)
|
||||
}
|
||||
consent <- Future(Consents.consentProvider.vend.revoke(consentId)) map {
|
||||
_ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map {
|
||||
i => connectorEmptyResponse(i, callContext)
|
||||
}
|
||||
} yield {
|
||||
@ -694,8 +694,10 @@ where the consent was directly managed between ASPSP and PSU e.g. in a re-direct
|
||||
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
|
||||
unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)")
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"${consent.mConsumerId.get} != ${cc.consumer.map(_.consumerId.get).getOrElse("None")}", failCode = 404, cc = cc.callContext) {
|
||||
consent.mConsumerId.get == callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None")
|
||||
consumerIdFromConsent = consent.mConsumerId.get
|
||||
consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None")
|
||||
_ <- Helper.booleanToFuture(failMsg = s"$ConsentNotFound $consumerIdFromConsent != $consumerIdFromCurrentCall", failCode = 403, cc = cc.callContext) {
|
||||
consumerIdFromConsent == consumerIdFromCurrentCall
|
||||
}
|
||||
} yield {
|
||||
(createGetConsentResponseJson(consent), HttpCode.`200`(callContext))
|
||||
@ -767,8 +769,7 @@ This method returns the SCA status of a consent initiation's authorisation sub-r
|
||||
unboxFullOrFail(_, callContext, ConsentNotFound)
|
||||
}
|
||||
} yield {
|
||||
val status = consent.status.toLowerCase()
|
||||
.replace(ConsentStatus.REVOKED.toString.toLowerCase(), "revokedByPsu")
|
||||
val status = consent.status
|
||||
(JSONFactory_BERLIN_GROUP_1_3.ConsentStatusJsonV13(status), HttpCode.`200`(callContext))
|
||||
}
|
||||
|
||||
|
||||
@ -48,19 +48,19 @@ object BgSpecValidation {
|
||||
// Example usage
|
||||
def main(args: Array[String]): Unit = {
|
||||
val testDates = Seq(
|
||||
"2025-05-10", // ❌ More than 180 days ahead
|
||||
"9999-12-31", // ❌ Exceeds max allowed
|
||||
"2015-01-01", // ❌ In the past
|
||||
"invalid-date", // ❌ Invalid format
|
||||
LocalDate.now().plusDays(90).toString, // ✅ Valid (within 180 days)
|
||||
LocalDate.now().plusDays(180).toString, // ✅ Valid (exactly 180 days)
|
||||
LocalDate.now().plusDays(181).toString // ❌ More than 180 days
|
||||
"2025-05-10", // More than 180 days ahead
|
||||
"9999-12-31", // Exceeds max allowed
|
||||
"2015-01-01", // In the past
|
||||
"invalid-date", // Invalid format
|
||||
LocalDate.now().plusDays(90).toString, // Valid (within 180 days)
|
||||
LocalDate.now().plusDays(180).toString, // Valid (exactly 180 days)
|
||||
LocalDate.now().plusDays(181).toString // More than 180 days
|
||||
)
|
||||
|
||||
testDates.foreach { date =>
|
||||
validateValidUntil(date) match {
|
||||
case Right(validDate) => println(s"✅ Valid date: $validDate")
|
||||
case Left(error) => println(s"❌ Error: $error")
|
||||
case Right(validDate) => println(s"Valid date: $validDate")
|
||||
case Left(error) => println(s"Error: $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,16 +254,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Purpose of this helper function is to get the PSD2-CERT value from a Request Headers.
|
||||
* @return the PSD2-CERT value from a Request Header as a String
|
||||
*/
|
||||
def getTppSignatureCertificate(requestHeaders: List[HTTPParam]): Option[String] = {
|
||||
requestHeaders.toSet.filter(_.name == RequestHeader.`TPP-Signature-Certificate`).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 {
|
||||
@ -527,8 +517,16 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
*
|
||||
*/
|
||||
def getRequestHeadersToMirror(callContext: Option[CallContextLight]): CustomResponseHeaders = {
|
||||
val mirrorByProperties = getPropsValue("mirror_request_headers_to_response", "").split(",").toList.map(_.trim)
|
||||
|
||||
val mirrorRequestHeadersToResponse: List[String] =
|
||||
getPropsValue("mirror_request_headers_to_response", "").split(",").toList.map(_.trim)
|
||||
if (callContext.exists(_.url.contains(ApiVersion.berlinGroupV13.urlPrefix))) {
|
||||
// Berlin Group Specification
|
||||
RequestHeader.`X-Request-ID` :: mirrorByProperties
|
||||
} else {
|
||||
mirrorByProperties
|
||||
}
|
||||
|
||||
callContext match {
|
||||
case Some(cc) =>
|
||||
cc.requestHeaders match {
|
||||
@ -536,13 +534,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
case _ =>
|
||||
val headers = cc.requestHeaders
|
||||
.filter(item => mirrorRequestHeadersToResponse.contains(item.name))
|
||||
.map(item => (item.name, item.values.head))
|
||||
.map(item => (item.name, item.values.headOption.getOrElse(""))) // Safe extraction
|
||||
CustomResponseHeaders(headers)
|
||||
}
|
||||
case None =>
|
||||
CustomResponseHeaders(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -2993,15 +2992,23 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
val remoteIpAddress = getRemoteIpAddress()
|
||||
|
||||
val authHeaders = AuthorisationUtil.getAuthorisationHeaders(reqHeaders)
|
||||
val authHeadersWithEmptyValues = RequestHeadersUtil.checkEmptyRequestHeaderValues(reqHeaders)
|
||||
val authHeadersWithEmptyNames = RequestHeadersUtil.checkEmptyRequestHeaderNames(reqHeaders)
|
||||
|
||||
// Identify consumer via certificate
|
||||
val consumerByCertificate = Consent.getCurrentConsumerViaMtls(callContext = cc)
|
||||
val consumerByCertificate = Consent.getCurrentConsumerViaTppSignatureCertOrMtls(callContext = cc)
|
||||
|
||||
val res =
|
||||
if (authHeaders.size > 1) { // Check Authorization Headers ambiguity
|
||||
if (authHeadersWithEmptyValues.nonEmpty) { // Check Authorization Headers Empty Values
|
||||
val message = ErrorMessages.EmptyRequestHeaders + s"Header names: ${authHeadersWithEmptyValues.mkString(", ")}"
|
||||
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), None) }
|
||||
} else if (authHeadersWithEmptyNames.nonEmpty) { // Check Authorization Headers Empty Names
|
||||
val message = ErrorMessages.EmptyRequestHeaders + s"Header values: ${authHeadersWithEmptyNames.mkString(", ")}"
|
||||
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), None) }
|
||||
} else if (authHeaders.size > 1) { // Check Authorization Headers ambiguity
|
||||
Future { (Failure(ErrorMessages.AuthorizationHeaderAmbiguity + s"${authHeaders}"), None) }
|
||||
} else if (APIUtil.`hasConsent-ID`(reqHeaders)) { // Berlin Group's Consent
|
||||
Consent.applyBerlinGroupRules(APIUtil.`getConsent-ID`(reqHeaders), cc)
|
||||
Consent.applyBerlinGroupRules(APIUtil.`getConsent-ID`(reqHeaders), cc.copy(consumer = consumerByCertificate))
|
||||
} else if (APIUtil.hasConsentJWT(reqHeaders)) { // Open Bank Project's Consent
|
||||
val consentValue = APIUtil.getConsentJWT(reqHeaders)
|
||||
Consent.getConsentJwtValueByConsentId(consentValue.getOrElse("")) match {
|
||||
@ -3011,12 +3018,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
// Note: At this point we are getting the Consumer from the Consumer in the Consent.
|
||||
// This may later be cross checked via the value in consumer_validation_method_for_consent.
|
||||
// Get the source of truth for Consumer (e.g. CONSUMER_CERTIFICATE) as early as possible.
|
||||
cc.copy(consumer = Consent.getCurrentConsumerViaMtls(callContext = cc))
|
||||
cc.copy(consumer = consumerByCertificate)
|
||||
)
|
||||
case _ =>
|
||||
JwtUtil.checkIfStringIsJWTValue(consentValue.getOrElse("")).isDefined match {
|
||||
case true => // It's JWT obtained via "Consent-JWT" request header
|
||||
Consent.applyRules(APIUtil.getConsentJWT(reqHeaders), cc)
|
||||
Consent.applyRules(APIUtil.getConsentJWT(reqHeaders), cc.copy(consumer = consumerByCertificate))
|
||||
case false => // Unrecognised consent value
|
||||
Future { (Failure(ErrorMessages.ConsentHeaderValueInvalid), None) }
|
||||
}
|
||||
@ -3115,8 +3122,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
}
|
||||
else if(Option(cc).flatMap(_.user).isDefined) {
|
||||
Future{(cc.user, Some(cc))}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if(hasAuthorizationHeader(reqHeaders)) {
|
||||
// We want to throw error in case of wrong or unsupported header. For instance:
|
||||
// - Authorization: mF_9.B5f-4.1JqM
|
||||
|
||||
@ -243,6 +243,8 @@ object ApiRole extends MdcLoggable{
|
||||
|
||||
case class CanUpdateConsumerLogoUrl(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateConsumerLogoUrl = CanUpdateConsumerLogoUrl()
|
||||
case class CanUpdateConsumerCertificate(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateConsumerCertificate = CanUpdateConsumerCertificate()
|
||||
case class CanUpdateConsumerName(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateConsumerName = CanUpdateConsumerName()
|
||||
|
||||
|
||||
@ -9,8 +9,10 @@ import net.liftweb.http.provider.HTTPParam
|
||||
|
||||
object BerlinGroupCheck {
|
||||
|
||||
|
||||
private val defaultMandatoryHeaders = "Content-Type,Date,Digest,PSU-Device-ID,PSU-Device-Name,PSU-IP-Address,Signature,TPP-Signature-Certificate,X-Request-ID"
|
||||
// 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")
|
||||
private val berlinGroupMandatoryHeaders: List[String] = APIUtil.getPropsValue("berlin_group_mandatory_headers", defaultValue = defaultMandatoryHeaders)
|
||||
.split(",")
|
||||
.map(_.trim.toLowerCase)
|
||||
.toList.filterNot(_.isEmpty)
|
||||
|
||||
@ -56,6 +56,9 @@ object BerlinGroupError {
|
||||
case "401" if message.contains("OBP-20207") => "PSU_CREDENTIALS_INVALID"
|
||||
|
||||
case "401" if message.contains("OBP-20204") => "TOKEN_EXPIRED"
|
||||
case "401" if message.contains("OBP-20215") => "TOKEN_INVALID"
|
||||
case "401" if message.contains("OBP-20205") => "TOKEN_INVALID"
|
||||
case "401" if message.contains("OBP-20204") => "TOKEN_INVALID"
|
||||
|
||||
case "401" if message.contains("OBP-35003") => "CONSENT_EXPIRED"
|
||||
|
||||
@ -66,6 +69,11 @@ object BerlinGroupError {
|
||||
case "401" if message.contains("OBP-35018") => "CONSENT_INVALID"
|
||||
case "401" if message.contains("OBP-35005") => "CONSENT_INVALID"
|
||||
|
||||
case "403" if message.contains("OBP-35001") => "CONSENT_UNKNOWN"
|
||||
|
||||
case "401" if message.contains("OBP-20312") => "CERTIFICATE_INVALID"
|
||||
case "401" if message.contains("OBP-20310") => "SIGNATURE_INVALID"
|
||||
|
||||
case "401" if message.contains("OBP-20060") => "ROLE_INVALID"
|
||||
|
||||
case "400" if message.contains("OBP-35018") => "CONSENT_UNKNOWN"
|
||||
@ -78,6 +86,8 @@ object BerlinGroupError {
|
||||
case "400" if message.contains("OBP-10001") => "FORMAT_ERROR"
|
||||
case "400" if message.contains("OBP-20062") => "FORMAT_ERROR"
|
||||
case "400" if message.contains("OBP-20063") => "FORMAT_ERROR"
|
||||
case "400" if message.contains("OBP-20252") => "FORMAT_ERROR"
|
||||
case "400" if message.contains("OBP-20251") => "FORMAT_ERROR"
|
||||
|
||||
case "429" if message.contains("OBP-10018") => "ACCESS_EXCEEDED"
|
||||
case _ => code
|
||||
|
||||
@ -156,7 +156,8 @@ object BerlinGroupSigning extends MdcLoggable {
|
||||
}
|
||||
|
||||
def getHeaderValue(name: String, requestHeaders: List[HTTPParam]): String = {
|
||||
requestHeaders.find(_.name.toLowerCase() == name.toLowerCase()).map(_.values.mkString).getOrElse("None")
|
||||
requestHeaders.find(_.name.toLowerCase() == name.toLowerCase()).map(_.values.mkString)
|
||||
.getOrElse(SecureRandomUtil.csprng.nextLong().toString)
|
||||
}
|
||||
private def getPem(requestHeaders: List[HTTPParam]): String = {
|
||||
val certificate = getHeaderValue(RequestHeader.`TPP-Signature-Certificate`, requestHeaders)
|
||||
|
||||
@ -4,6 +4,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.{Date, UUID}
|
||||
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ConsentAccessJson, PostConsentJson}
|
||||
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, canCreateEntitlementAtOneBank}
|
||||
import code.api.util.BerlinGroupSigning.getHeaderValue
|
||||
import code.api.util.ErrorMessages.{CouldNotAssignAccountAccess, InvalidConnectorResponse, NoViewReadAccountsBerlinGroup}
|
||||
import code.api.v3_1_0.{PostConsentBodyCommonJson, PostConsentEntitlementJsonV310, PostConsentViewJsonV310}
|
||||
import code.api.v5_0_0.HelperInfoJson
|
||||
@ -29,6 +30,7 @@ import net.liftweb.json.JsonParser.ParseException
|
||||
import net.liftweb.json.{Extraction, MappingException, compactRender, parse}
|
||||
import net.liftweb.mapper.By
|
||||
import net.liftweb.util.{ControlHelpers, Props}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import sh.ory.hydra.model.OAuth2TokenIntrospection
|
||||
|
||||
import scala.collection.immutable.{List, Nil}
|
||||
@ -126,24 +128,46 @@ object Consent extends MdcLoggable {
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Purpose of this helper function is to get the Consumer via MTLS info i.e. PEM certificate.
|
||||
* @return the boxed Consumer
|
||||
*/
|
||||
def getCurrentConsumerViaMtls(callContext: CallContext): Box[Consumer] = {
|
||||
val clientCert: String = APIUtil.`getPSD2-CERT`(callContext.requestHeaders) // MTLS certificate QWAC (Qualified Website Authentication Certificate)
|
||||
.orElse(BerlinGroupSigning.getTppSignatureCertificate(callContext.requestHeaders)) // Signature certificate QSealC (Qualified Electronic Seal Certificate)
|
||||
.getOrElse(SecureRandomUtil.csprng.nextLong().toString) // Force to fail
|
||||
|
||||
{ // 1st search is via the original value
|
||||
logger.debug(s"getConsumerByPemCertificate ${clientCert}")
|
||||
Consumers.consumers.vend.getConsumerByPemCertificate(clientCert)
|
||||
}.or { // 2nd search is via the original value we normalize
|
||||
logger.debug(s"getConsumerByPemCertificate ${CertificateUtil.normalizePemX509Certificate(clientCert)}")
|
||||
Consumers.consumers.vend.getConsumerByPemCertificate(CertificateUtil.normalizePemX509Certificate(clientCert))
|
||||
/**
|
||||
* Retrieves the current Consumer using either the MTLS (QWAC) certificate or the TPP signature certificate (QSealC).
|
||||
* This method checks the request headers for the relevant PEM certificates and searches for the corresponding Consumer.
|
||||
*
|
||||
* @param callContext The request context containing headers.
|
||||
* @return A Box containing the Consumer if found, otherwise Empty.
|
||||
*/
|
||||
def getCurrentConsumerViaTppSignatureCertOrMtls(callContext: CallContext): Box[Consumer] = {
|
||||
{ // Attempt to get the Consumer via the TPP-Signature-Certificate (Qualified Electronic Seal Certificate - QSealC)
|
||||
val tppSignatureCert: String = APIUtil.getRequestHeader(RequestHeader.`TPP-Signature-Certificate`, callContext.requestHeaders)
|
||||
if (tppSignatureCert.isEmpty) {
|
||||
logger.debug(s"| No `TPP-Signature-Certificate` header found |")
|
||||
Empty // No `TPP-Signature-Certificate` header found, continue to MTLS check
|
||||
} else {
|
||||
logger.debug(s"Get Consumer By RequestHeader.`TPP-Signature-Certificate`: $tppSignatureCert")
|
||||
Consumers.consumers.vend.getConsumerByPemCertificate(tppSignatureCert)
|
||||
}
|
||||
}.or { // If TPP certificate is not available, try to get Consumer via MTLS (Qualified Website Authentication Certificate - QWAC)
|
||||
val psd2Cert: String = APIUtil.getRequestHeader(RequestHeader.`PSD2-CERT`, callContext.requestHeaders)
|
||||
if (psd2Cert.isEmpty) {
|
||||
logger.debug(s"| No `PSD2-CERT` header found |")
|
||||
Empty // No `PSD2-CERT` header found
|
||||
} else {
|
||||
val consumerByPsd2Cert: Box[Consumer] = {
|
||||
// First, try to find the Consumer using the original certificate value
|
||||
logger.debug(s"Get Consumer By RequestHeader.`PSD2-CERT`: $psd2Cert")
|
||||
Consumers.consumers.vend.getConsumerByPemCertificate(psd2Cert)
|
||||
}.or {
|
||||
// If the original value lookup fails, normalize the certificate and try again
|
||||
val normalizedCert = CertificateUtil.normalizePemX509Certificate(psd2Cert)
|
||||
logger.debug(s"Get Consumer By RequestHeader.`PSD2-CERT` (normalized): $normalizedCert")
|
||||
Consumers.consumers.vend.getConsumerByPemCertificate(normalizedCert)
|
||||
}
|
||||
consumerByPsd2Cert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private def verifyHmacSignedJwt(jwtToken: String, c: MappedConsent): Boolean = {
|
||||
logger.debug(s"code.api.util.Consent.verifyHmacSignedJwt beginning:: jwtToken($jwtToken), MappedConsent($c)")
|
||||
val result = JwtUtil.verifyHmacSignedJwt(jwtToken, c.secret)
|
||||
@ -151,16 +175,19 @@ object Consent extends MdcLoggable {
|
||||
result
|
||||
}
|
||||
|
||||
private def removeBreakLines(input: String) = input
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
private def checkConsumerIsActiveAndMatched(consent: ConsentJWT, callContext: CallContext): Box[Boolean] = {
|
||||
val consumerBox = Consumers.consumers.vend.getConsumerByConsumerId(consent.aud)
|
||||
logger.debug(s"code.api.util.Consent.checkConsumerIsActiveAndMatched.getConsumerByConsumerId consumerBox:: consumerBox($consumerBox)")
|
||||
consumerBox match {
|
||||
case Full(consumerFromConsent) if consumerFromConsent.isActive.get == true => // Consumer is active
|
||||
val validationMetod = APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_CERTIFICATE")
|
||||
if(validationMetod != "CONSUMER_CERTIFICATE" && Props.mode == Props.RunModes.Production) {
|
||||
logger.warn(s"consumer_validation_method_for_consent is not set to CONSUMER_CERTIFICATE! The current value is: ${validationMetod}")
|
||||
val validationMethod = APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_CERTIFICATE")
|
||||
if(validationMethod != "CONSUMER_CERTIFICATE" && Props.mode == Props.RunModes.Production) {
|
||||
logger.warn(s"consumer_validation_method_for_consent is not set to CONSUMER_CERTIFICATE! The current value is: ${validationMethod}")
|
||||
}
|
||||
validationMetod match {
|
||||
validationMethod match {
|
||||
case "CONSUMER_KEY_VALUE" =>
|
||||
val requestHeaderConsumerKey = getConsumerKey(callContext.requestHeaders)
|
||||
logger.debug(s"code.api.util.Consent.checkConsumerIsActiveAndMatched.consumerBox.requestHeaderConsumerKey:: requestHeaderConsumerKey($requestHeaderConsumerKey)")
|
||||
@ -169,23 +196,29 @@ object Consent extends MdcLoggable {
|
||||
if (reqHeaderConsumerKey == consumerFromConsent.key.get)
|
||||
Full(true) // This consent can be used by current application
|
||||
else // This consent can NOT be used by current application
|
||||
Failure(ErrorMessages.ConsentDoesNotMatchConsumer)
|
||||
Failure(s"${ErrorMessages.ConsentDoesNotMatchConsumer} CONSUMER_KEY_VALUE")
|
||||
case None => Failure(ErrorMessages.ConsumerKeyHeaderMissing) // There is no header `Consumer-Key` in request headers
|
||||
}
|
||||
case "CONSUMER_CERTIFICATE" =>
|
||||
val clientCert: String = APIUtil.`getPSD2-CERT`(callContext.requestHeaders).getOrElse(SecureRandomUtil.csprng.nextLong().toString)
|
||||
logger.debug(s"code.api.util.Consent.checkConsumerIsActiveAndMatched.consumerBox clientCert:: clientCert($clientCert)")
|
||||
def removeBreakLines(input: String) = input
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
val certificate = consumerFromConsent.clientCertificate
|
||||
logger.debug(s"code.api.util.Consent.checkConsumerIsActiveAndMatched.consumer.certificate:: certificate($certificate)")
|
||||
logger.debug(s"code.api.util.Consent.checkConsumerIsActiveAndMatched.consumer.certificate.dbNotNull_?(${certificate.dbNotNull_?})")
|
||||
if (certificate.dbNotNull_? && removeBreakLines(clientCert) == removeBreakLines(certificate.get)) {
|
||||
logger.debug(s"certificate.dbNotNull_? && removeBreakLines(clientCert) == removeBreakLines(consumerFromConsent.clientCertificate.get) result == true")
|
||||
logger.debug(s"| Consent.checkConsumerIsActiveAndMatched | clientCert | $clientCert |")
|
||||
logger.debug(s"| Consent.checkConsumerIsActiveAndMatched | consumerFromConsent.clientCertificate | ${consumerFromConsent.clientCertificate} |")
|
||||
if (removeBreakLines(clientCert) == removeBreakLines(consumerFromConsent.clientCertificate.get)) {
|
||||
logger.debug(s"| removeBreakLines(clientCert) == removeBreakLines(consumerFromConsent.clientCertificate.get | true |")
|
||||
Full(true) // This consent can be used by current application
|
||||
} else // This consent can NOT be used by current application
|
||||
Failure(ErrorMessages.ConsentDoesNotMatchConsumer)
|
||||
} else { // This consent can NOT be used by current application
|
||||
Failure(s"${ErrorMessages.ConsentDoesNotMatchConsumer} CONSUMER_CERTIFICATE")
|
||||
}
|
||||
case "TPP_SIGNATURE_CERTIFICATE" =>
|
||||
val tppSignatureCertificate = getHeaderValue(RequestHeader.`TPP-Signature-Certificate`, callContext.requestHeaders)
|
||||
logger.debug(s"| Consent.checkConsumerIsActiveAndMatched | tppSignatureCertificate | $tppSignatureCertificate |")
|
||||
logger.debug(s"| Consent.checkConsumerIsActiveAndMatched | consumerFromConsent.clientCertificate | ${consumerFromConsent.clientCertificate} |")
|
||||
if (removeBreakLines(tppSignatureCertificate) == removeBreakLines(consumerFromConsent.clientCertificate.get)) {
|
||||
logger.debug(s"""| removeBreakLines(tppSignatureCertificate) == removeBreakLines(consumerFromConsent.clientCertificate.get | true |""")
|
||||
Full(true) // This consent can be used by current application
|
||||
} else { // This consent can NOT be used by current application
|
||||
Failure(s"${ErrorMessages.ConsentDoesNotMatchConsumer} TPP_SIGNATURE_CERTIFICATE")
|
||||
}
|
||||
case "NONE" => // This instance does not require validation method
|
||||
Full(true)
|
||||
case _ => // This instance does not specify validation method
|
||||
@ -225,7 +258,7 @@ object Consent extends MdcLoggable {
|
||||
Failure(s"${ErrorMessages.ConsentStatusIssue}${ConsentStatus.valid.toString}.")
|
||||
case Full(c) if c.mStatus.toString().toUpperCase() != ConsentStatus.ACCEPTED.toString =>
|
||||
Failure(s"${ErrorMessages.ConsentStatusIssue}${ConsentStatus.ACCEPTED.toString}.")
|
||||
case _ =>
|
||||
case _ =>
|
||||
Failure(ErrorMessages.ConsentNotFound)
|
||||
}
|
||||
logger.debug(s"code.api.util.Consent.checkConsent.consentBox.result: result($result)")
|
||||
@ -268,7 +301,7 @@ object Consent extends MdcLoggable {
|
||||
val bankId = if (role.requiresBankId) entitlement.bank_id else ""
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, user.userId, entitlement.role_name) match {
|
||||
case Full(_) => (entitlement, "AddedOrExisted")
|
||||
case _ =>
|
||||
case _ =>
|
||||
(entitlement, "Cannot add the entitlement: " + entitlement)
|
||||
}
|
||||
case true =>
|
||||
@ -291,7 +324,7 @@ object Consent extends MdcLoggable {
|
||||
val failedToAdd: List[(Role, String)] = triedToAdd.filter(_._2 != "AddedOrExisted")
|
||||
failedToAdd match {
|
||||
case Nil => Full(user)
|
||||
case _ =>
|
||||
case _ =>
|
||||
Failure("The entitlements cannot be added. " + failedToAdd.map(i => (i._1, i._2)).mkString(", "))
|
||||
}
|
||||
case _ =>
|
||||
@ -315,7 +348,7 @@ object Consent extends MdcLoggable {
|
||||
Views.views.vend.systemView(ViewId(view.view_id)) match {
|
||||
case Full(systemView) =>
|
||||
Views.views.vend.grantAccessToSystemView(BankId(view.bank_id), AccountId(view.account_id), systemView, user)
|
||||
case _ =>
|
||||
case _ =>
|
||||
// It's not system view
|
||||
Views.views.vend.grantAccessToCustomView(bankIdAccountIdViewId, user)
|
||||
}
|
||||
@ -327,7 +360,7 @@ object Consent extends MdcLoggable {
|
||||
}
|
||||
if (errorMessages.isEmpty) Full(user) else Failure(CouldNotAssignAccountAccess + errorMessages.mkString(", "))
|
||||
}
|
||||
|
||||
|
||||
private def applyConsentRulesCommonOldStyle(consentIdAsJwt: String, calContext: CallContext): Box[User] = {
|
||||
implicit val dateFormats = CustomJsonFormats.formats
|
||||
|
||||
@ -372,8 +405,8 @@ object Consent extends MdcLoggable {
|
||||
case _ =>
|
||||
Failure("Cannot extract data from: " + consentIdAsJwt)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def applyConsentRulesCommon(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = {
|
||||
implicit val dateFormats = CustomJsonFormats.formats
|
||||
|
||||
@ -411,16 +444,13 @@ object Consent extends MdcLoggable {
|
||||
logger.debug(s"applyConsentRulesCommon.Start of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $jsonAsString")
|
||||
val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]
|
||||
logger.debug(s"applyConsentRulesCommon.End of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $consent")
|
||||
// Set Consumer into Call Context
|
||||
val consumer = getCurrentConsumerViaMtls(callContext)
|
||||
val updatedCallContext = callContext.copy(consumer = consumer)
|
||||
checkConsent(consent, consentAsJwt, updatedCallContext) match { // Check is it Consent-JWT expired
|
||||
checkConsent(consent, consentAsJwt, callContext) match { // Check is it Consent-JWT expired
|
||||
case (Full(true)) => // OK
|
||||
applyConsentRules(consent)
|
||||
case failure@Failure(_, _, _) => // Handled errors
|
||||
Future(failure, Some(updatedCallContext))
|
||||
Future(failure, Some(callContext))
|
||||
case _ => // Unexpected errors
|
||||
Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(updatedCallContext))
|
||||
Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(callContext))
|
||||
}
|
||||
} catch { // Possible exceptions
|
||||
case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(callContext))
|
||||
@ -433,7 +463,7 @@ object Consent extends MdcLoggable {
|
||||
Future(Failure("Cannot extract data from: " + consentAsJwt), Some(callContext))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def applyRules(consentJwt: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = {
|
||||
val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false)
|
||||
(consentJwt, allowed) match {
|
||||
@ -442,12 +472,12 @@ object Consent extends MdcLoggable {
|
||||
case (None, _) => Future((Failure(ErrorMessages.ConsentHeaderNotFound), Some(callContext)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def getConsentJwtValueByConsentId(consentId: String): Option[MappedConsent] = {
|
||||
APIUtil.checkIfStringIsUUID(consentId) match {
|
||||
case true => // String is a UUID
|
||||
Consents.consentProvider.vend.getConsentByConsentId(consentId) match {
|
||||
case Full(consent) => Some(consent)
|
||||
case Full(consent) => Some(consent)
|
||||
case _ => None // It's not valid UUID value
|
||||
}
|
||||
case false => None // It's not UUID at all
|
||||
@ -493,7 +523,7 @@ object Consent extends MdcLoggable {
|
||||
(Failure("Cannot create or get the user based on: " + consentId), Some(cc))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def checkFrequencyPerDay(storedConsent: consent.ConsentTrait) = {
|
||||
def isSameDay(date1: Date, date2: Date): Boolean = {
|
||||
val fmt = new SimpleDateFormat("yyyyMMdd")
|
||||
@ -504,7 +534,7 @@ object Consent extends MdcLoggable {
|
||||
case false => // The consent is for one access to the account data
|
||||
if(usesSoFarTodayCounter == 0) // Maximum value is "1".
|
||||
(true, 0) // All good
|
||||
else
|
||||
else
|
||||
(false, 1) // Exceeded rate limit
|
||||
case true => // The consent is for recurring access to the account data
|
||||
if(!isSameDay(storedConsent.usesSoFarTodayCounterUpdatedAt, new Date())) {
|
||||
@ -520,11 +550,9 @@ object Consent extends MdcLoggable {
|
||||
// 1st we need to find a Consent via the field MappedConsent.consentId
|
||||
Consents.consentProvider.vend.getConsentByConsentId(consentId) match {
|
||||
case Full(storedConsent) =>
|
||||
// Set Consumer into Call Context
|
||||
val consumer = getCurrentConsumerViaMtls(callContext)
|
||||
val user = Users.users.vend.getUserByUserId(storedConsent.userId)
|
||||
logger.debug(s"applyBerlinGroupConsentRulesCommon.storedConsent.user : $user")
|
||||
val updatedCallContext = callContext.copy(consumer = consumer).copy(consenter = user)
|
||||
val updatedCallContext = callContext.copy(consenter = user)
|
||||
// This function MUST be called only once per call. I.e. it's date dependent
|
||||
val (canBeUsed, currentCounterState) = checkFrequencyPerDay(storedConsent)
|
||||
if(canBeUsed) {
|
||||
|
||||
@ -267,6 +267,7 @@ object ErrorMessages {
|
||||
|
||||
val AuthorizationHeaderAmbiguity = "OBP-20250: Request headers used for authorization are ambiguous. "
|
||||
val MissingMandatoryBerlinGroupHeaders= "OBP-20251: Missing mandatory request headers. "
|
||||
val EmptyRequestHeaders = "OBP-20252: Empty or null headers are not allowed. "
|
||||
|
||||
// X.509
|
||||
val X509GeneralError = "OBP-20300: PEM Encoded Certificate issue."
|
||||
|
||||
@ -740,8 +740,9 @@ object NewStyle extends MdcLoggable{
|
||||
redirectURL: Option[String] = None,
|
||||
createdByUserId: Option[String] = None,
|
||||
logoURL: Option[String] = None,
|
||||
certificate: Option[String] = None,
|
||||
callContext: Option[CallContext]): Future[Consumer] = {
|
||||
Future(Consumers.consumers.vend.updateConsumer(id, key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, logoURL)) map {
|
||||
Future(Consumers.consumers.vend.updateConsumer(id, key, secret, isActive, name, appType, description, developerEmail, redirectURL, createdByUserId, logoURL, certificate)) map {
|
||||
unboxFullOrFail(_, callContext, UpdateConsumerError, 404)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package code.api.util
|
||||
|
||||
import code.api.RequestHeader._
|
||||
import net.liftweb.http.provider.HTTPParam
|
||||
|
||||
object RequestHeadersUtil {
|
||||
def checkEmptyRequestHeaderValues(requestHeaders: List[HTTPParam]): List[String] = {
|
||||
val emptyValues = requestHeaders
|
||||
.filter(header => header != null && (header.values == null || header.values.isEmpty || header.values.exists(_.trim.isEmpty)))
|
||||
.map(_.name) // Extract header names with empty values
|
||||
|
||||
emptyValues
|
||||
}
|
||||
def checkEmptyRequestHeaderNames(requestHeaders: List[HTTPParam]): List[String] = {
|
||||
val emptyNames = requestHeaders
|
||||
.filter(header => header == null || header.name == null || header.name.trim.isEmpty)
|
||||
.map(_.values.mkString("'")) // List values without names
|
||||
|
||||
emptyNames
|
||||
}
|
||||
|
||||
}
|
||||
@ -954,7 +954,7 @@ trait APIMethods210 {
|
||||
case false => NewStyle.function.ownEntitlement("", u.userId, ApiRole.canDisableConsumers, cc.callContext)
|
||||
}
|
||||
consumer <- Consumers.consumers.vend.getConsumerByPrimaryId(consumerId.toLong)
|
||||
updatedConsumer <- Consumers.consumers.vend.updateConsumer(consumer.id.get, None, None, Some(putData.enabled), None, None, None, None, None, None, None) ?~! "Cannot update Consumer"
|
||||
updatedConsumer <- Consumers.consumers.vend.updateConsumer(consumer.id.get, None, None, Some(putData.enabled), None, None, None, None, None, None, None, None) ?~! "Cannot update Consumer"
|
||||
} yield {
|
||||
// Format the data as json
|
||||
val json = PutEnabledJSON(updatedConsumer.isActive.get)
|
||||
|
||||
@ -5997,7 +5997,7 @@ trait APIMethods310 {
|
||||
}
|
||||
consumer <- NewStyle.function.getConsumerByConsumerId(consumerId, callContext)
|
||||
updatedConsumer <- Future {
|
||||
Consumers.consumers.vend.updateConsumer(consumer.id.get, None, None, Some(putData.enabled), None, None, None, None, None,None, None) ?~! "Cannot update Consumer"
|
||||
Consumers.consumers.vend.updateConsumer(consumer.id.get, None, None, Some(putData.enabled), None, None, None, None, None,None, None, None) ?~! "Cannot update Consumer"
|
||||
}
|
||||
} yield {
|
||||
// Format the data as json
|
||||
|
||||
@ -1773,7 +1773,8 @@ trait APIMethods510 {
|
||||
consent <- Future { Consents.consentProvider.vend.getConsentByConsentId(consentId)} map {
|
||||
unboxFullOrFail(_, cc.callContext, ConsentNotFound, 404)
|
||||
}
|
||||
_ <- Helper.booleanToFuture(failMsg = s"${consent.mConsumerId.get} != ${cc.consumer.map(_.consumerId.get).getOrElse("None")}", failCode = 404, cc = cc.callContext) {
|
||||
errorMessage = s" ${consent.mConsumerId.get} != ${cc.consumer.map(_.consumerId.get).getOrElse("None")}"
|
||||
_ <- Helper.booleanToFuture(failMsg = ConsentNotFound + errorMessage, failCode = 404, cc = cc.callContext) {
|
||||
consent.mConsumerId.get == cc.consumer.map(_.consumerId.get).getOrElse("None")
|
||||
}
|
||||
} yield {
|
||||
@ -3197,6 +3198,53 @@ trait APIMethods510 {
|
||||
}
|
||||
}
|
||||
}
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateConsumerCertificate,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateConsumerCertificate),
|
||||
"PUT",
|
||||
"/management/consumers/CONSUMER_ID/consumer/certificate",
|
||||
"Update Consumer Certificate",
|
||||
s"""Update a Certificate for a Consumer specified by CONSUMER_ID.
|
||||
|
|
||||
| ${consumerDisabledText()}
|
||||
|
|
||||
| CONSUMER_ID can be obtained after you register the application.
|
||||
|
|
||||
| Or use the endpoint 'Get Consumers' to get it
|
||||
|
|
||||
""".stripMargin,
|
||||
consumerCertificateJson,
|
||||
consumerJsonV510,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagConsumer),
|
||||
Some(List(canUpdateConsumerCertificate))
|
||||
)
|
||||
|
||||
lazy val updateConsumerCertificate: OBPEndpoint = {
|
||||
case "management" :: "consumers" :: consumerId :: "consumer" :: "certificate" :: Nil JsonPut json -> _ => {
|
||||
cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
postJson <- NewStyle.function.tryons(InvalidJsonFormat, 400, callContext) {
|
||||
json.extract[ConsumerCertificateJson]
|
||||
}
|
||||
consumer <- NewStyle.function.getConsumerByConsumerId(consumerId, callContext)
|
||||
updatedConsumer <- NewStyle.function.updateConsumer(
|
||||
id = consumer.id.get,
|
||||
certificate = Some(postJson.certificate),
|
||||
callContext = callContext
|
||||
)
|
||||
} yield {
|
||||
(JSONFactory510.createConsumerJSON(updatedConsumer), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateConsumerName,
|
||||
|
||||
@ -559,6 +559,9 @@ case class APITags(
|
||||
case class ConsumerLogoUrlJson(
|
||||
logo_url: String
|
||||
)
|
||||
case class ConsumerCertificateJson(
|
||||
certificate: String
|
||||
)
|
||||
case class ConsumerNameJson(app_name: String)
|
||||
|
||||
case class TransactionRequestJsonV510(
|
||||
|
||||
@ -7081,28 +7081,19 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable {
|
||||
exampleInboundMessage = (
|
||||
InBoundGetRegulatedEntities(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
|
||||
status=MessageDocsSwaggerDefinitions.inboundStatus,
|
||||
data = List(RegulatedEntityTraitCommons(entityId = "0af807d7-3c39-43ef-9712-82bcfde1b9ca",
|
||||
certificateAuthorityCaOwnerId = "CY_CBC",
|
||||
entityName = "EXAMPLE COMPANY LTD",
|
||||
entityCode = "PSD_PICY_CBC!12345",
|
||||
entityCertificatePublicKey =
|
||||
"""-----BEGIN CERTIFICATE-----MIICsjCCAZqgAwIBAgIGAYwQ62R0MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD2
|
||||
|FwcC5leGFtcGxlLmNvbTAeFw0yMzExMjcxMzE1MTFaFw0yNTExMjYxMzE1MTFaMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTCCASIwDQ
|
||||
|YJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9WIodZHWzKyCcf9YfWEhPURbfO6zKuMqzHN27GdqHsVVEGxP4F/J4mso+0ENcRr6ur4u81iRE
|
||||
|aVdCc40rHDHVJNEtniD8Icbz7tcsqAewIVhc/q6WXGqImJpCq7hA0m247dDsaZT0lb/MVBiMoJxDEmAE/GYYnWTEn84R35WhJsMvuQ7QmLvNg6
|
||||
|RkChY6POCT/YKe9NKwa1NqI1U+oA5RFzAaFtytvZCE3jtp+aR0brL7qaGfgxm6B7dEpGyhg0NcVCV7xMQNq2JxZTVdAr6lcsRGaAFulakmW3aN
|
||||
|nmK+L35Wu8uW+OxNxwUuC6f3b4FVBa276FMuUTRfu7gc+k6kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAU5CjEyAoyTn7PgFpQD48ZNPuUsEQ
|
||||
|19gzYgJvHMzFIoZ7jKBodjO5mCzWBcR7A4mpeAsdyiNBl2sTiZscSnNqxk61jVzP5Ba1D7XtOjjr7+3iqowrThj6BY40QqhYh/6BSY9fDzVZQi
|
||||
|Hnvlo6ZUM5kUK6OavZOovKlp5DIl5sGqoP0qAJnpQ4nhB2WVVsKfPlOXc+2KSsbJ23g9l8zaTMr+X0umlvfEKqyEl1Fa2L1dO0y/KFQ+ILmxcZ
|
||||
|LpRdq1hRAjd0quq9qGC8ucXhRWDgM4hslVpau0da68g0aItWNez3mc5lB82b3dcZpFMzO41bgw7gvw10AvvTfQDqEYIuQ==-----END CERTIFICATE----- """.stripMargin,
|
||||
entityType = "PSD_PI",
|
||||
entityAddress = "EXAMPLE COMPANY LTD, 5 SOME STREET",
|
||||
entityTownCity = "SOME CITY",
|
||||
entityPostCode = "1060",
|
||||
entityCountry = "CY",
|
||||
entityWebSite = "www.example.com",
|
||||
services = "PISP,AISP")))
|
||||
),
|
||||
data=List( RegulatedEntityTraitCommons(entityId="string",
|
||||
certificateAuthorityCaOwnerId="string",
|
||||
entityName="string",
|
||||
entityCode="string",
|
||||
entityCertificatePublicKey="string",
|
||||
entityType="string",
|
||||
entityAddress="string",
|
||||
entityTownCity="string",
|
||||
entityPostCode="string",
|
||||
entityCountry="string",
|
||||
entityWebSite="string",
|
||||
services="string")))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
@ -7126,29 +7117,20 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable {
|
||||
),
|
||||
exampleInboundMessage = (
|
||||
InBoundGetRegulatedEntityByEntityId(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
|
||||
status = MessageDocsSwaggerDefinitions.inboundStatus,
|
||||
data = RegulatedEntityTraitCommons(entityId = "0af807d7-3c39-43ef-9712-82bcfde1b9ca",
|
||||
certificateAuthorityCaOwnerId = "CY_CBC",
|
||||
entityName = "EXAMPLE COMPANY LTD",
|
||||
entityCode = "PSD_PICY_CBC!12345",
|
||||
entityCertificatePublicKey =
|
||||
"""-----BEGIN CERTIFICATE-----MIICsjCCAZqgAwIBAgIGAYwQ62R0MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD2
|
||||
|FwcC5leGFtcGxlLmNvbTAeFw0yMzExMjcxMzE1MTFaFw0yNTExMjYxMzE1MTFaMBoxGDAWBgNVBAMMD2FwcC5leGFtcGxlLmNvbTCCASIwDQ
|
||||
|YJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9WIodZHWzKyCcf9YfWEhPURbfO6zKuMqzHN27GdqHsVVEGxP4F/J4mso+0ENcRr6ur4u81iRE
|
||||
|aVdCc40rHDHVJNEtniD8Icbz7tcsqAewIVhc/q6WXGqImJpCq7hA0m247dDsaZT0lb/MVBiMoJxDEmAE/GYYnWTEn84R35WhJsMvuQ7QmLvNg6
|
||||
|RkChY6POCT/YKe9NKwa1NqI1U+oA5RFzAaFtytvZCE3jtp+aR0brL7qaGfgxm6B7dEpGyhg0NcVCV7xMQNq2JxZTVdAr6lcsRGaAFulakmW3aN
|
||||
|nmK+L35Wu8uW+OxNxwUuC6f3b4FVBa276FMuUTRfu7gc+k6kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAU5CjEyAoyTn7PgFpQD48ZNPuUsEQ
|
||||
|19gzYgJvHMzFIoZ7jKBodjO5mCzWBcR7A4mpeAsdyiNBl2sTiZscSnNqxk61jVzP5Ba1D7XtOjjr7+3iqowrThj6BY40QqhYh/6BSY9fDzVZQi
|
||||
|Hnvlo6ZUM5kUK6OavZOovKlp5DIl5sGqoP0qAJnpQ4nhB2WVVsKfPlOXc+2KSsbJ23g9l8zaTMr+X0umlvfEKqyEl1Fa2L1dO0y/KFQ+ILmxcZ
|
||||
|LpRdq1hRAjd0quq9qGC8ucXhRWDgM4hslVpau0da68g0aItWNez3mc5lB82b3dcZpFMzO41bgw7gvw10AvvTfQDqEYIuQ==-----END CERTIFICATE----- """.stripMargin,
|
||||
entityType = "PSD_PI",
|
||||
entityAddress = "EXAMPLE COMPANY LTD, 5 SOME STREET",
|
||||
entityTownCity = "SOME CITY",
|
||||
entityPostCode = "1060",
|
||||
entityCountry = "CY",
|
||||
entityWebSite = "www.example.com",
|
||||
services = "PISP,AISP"))
|
||||
),
|
||||
status=MessageDocsSwaggerDefinitions.inboundStatus,
|
||||
data= RegulatedEntityTraitCommons(entityId="string",
|
||||
certificateAuthorityCaOwnerId="string",
|
||||
entityName="string",
|
||||
entityCode="string",
|
||||
entityCertificatePublicKey="string",
|
||||
entityType="string",
|
||||
entityAddress="string",
|
||||
entityTownCity="string",
|
||||
entityPostCode="string",
|
||||
entityCountry="string",
|
||||
entityWebSite="string",
|
||||
services="string"))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ trait ConsentProvider {
|
||||
def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer] = None): Box[MappedConsent]
|
||||
def setJsonWebToken(consentId: String, jwt: String): Box[MappedConsent]
|
||||
def revoke(consentId: String): Box[MappedConsent]
|
||||
def revokeBerlinGroupConsent(consentId: String): Box[MappedConsent]
|
||||
def checkAnswer(consentId: String, challenge: String): Box[MappedConsent]
|
||||
def createBerlinGroupConsent(
|
||||
user: Option[User],
|
||||
@ -191,7 +192,7 @@ object ConsentStatus extends Enumeration {
|
||||
type ConsentStatus = Value
|
||||
val INITIATED, ACCEPTED, REJECTED, rejected, REVOKED,
|
||||
// The following one only exist in case of BerlinGroup
|
||||
received, valid, REVOKEDBYPSU, revokedByPsu, EXPIRED, expired, TERMINATEDBYTPP, terminatedByTpp,
|
||||
received, valid, revokedByPsu, expired, terminatedByTpp,
|
||||
//these added for UK Open Banking
|
||||
AUTHORISED, AWAITINGAUTHORISATION = Value
|
||||
}
|
||||
|
||||
@ -230,7 +230,24 @@ object MappedConsentProvider extends ConsentProvider {
|
||||
case _ =>
|
||||
Failure(ErrorMessages.UnknownError)
|
||||
}
|
||||
}
|
||||
}
|
||||
override def revokeBerlinGroupConsent(consentId: String): Box[MappedConsent] = {
|
||||
MappedConsent.find(By(MappedConsent.mConsentId, consentId)) match {
|
||||
case Full(consent) if consent.status == ConsentStatus.terminatedByTpp.toString =>
|
||||
Failure(ErrorMessages.ConsentAlreadyRevoked)
|
||||
case Full(consent) =>
|
||||
tryo(consent
|
||||
.mStatus(ConsentStatus.terminatedByTpp.toString)
|
||||
.mLastActionDate(now)
|
||||
.saveMe())
|
||||
case Empty =>
|
||||
Empty ?~! ErrorMessages.ConsentNotFound
|
||||
case Failure(msg, _, _) =>
|
||||
Failure(msg)
|
||||
case _ =>
|
||||
Failure(ErrorMessages.UnknownError)
|
||||
}
|
||||
}
|
||||
override def checkAnswer(consentId: String, challengeAnswer: String): Box[MappedConsent] = {
|
||||
def isAnswerCorrect(expectedAnswerHashed: String, answer: String, salt: String) = {
|
||||
val challengeAnswerHashed = BCrypt.hashpw(answer, salt).substring(0, 44)
|
||||
|
||||
@ -48,6 +48,7 @@ trait ConsumersProvider {
|
||||
redirectURL: Option[String],
|
||||
createdByUserId: Option[String],
|
||||
LogoURL: Option[String],
|
||||
certificate: Option[String],
|
||||
): Box[Consumer]
|
||||
def updateConsumerCallLimits(id: Long, perSecond: Option[String], perMinute: Option[String], perHour: Option[String], perDay: Option[String], perWeek: Option[String], perMonth: Option[String]): Future[Box[Consumer]]
|
||||
def getOrCreateConsumer(consumerId: Option[String],
|
||||
|
||||
@ -238,7 +238,8 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
|
||||
developerEmail: Option[String],
|
||||
redirectURL: Option[String],
|
||||
createdByUserId: Option[String],
|
||||
logoURL: Option[String]
|
||||
logoURL: Option[String],
|
||||
certificate: Option[String],
|
||||
): Box[Consumer] = {
|
||||
val consumer = Consumer.find(By(Consumer.id, id))
|
||||
consumer match {
|
||||
@ -260,6 +261,10 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
|
||||
case Some(v) => c.name(v)
|
||||
case None =>
|
||||
}
|
||||
certificate match {
|
||||
case Some(v) => c.clientCertificate(v)
|
||||
case None =>
|
||||
}
|
||||
appType match {
|
||||
case Some(v) => v match {
|
||||
case Confidential => c.appType(Confidential.toString)
|
||||
|
||||
@ -5,6 +5,7 @@ import code.api.util.APIUtil
|
||||
import code.consent.{ConsentStatus, MappedConsent}
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.openbankproject.commons.util.ApiVersion
|
||||
import net.liftweb.common.Full
|
||||
import net.liftweb.mapper.{By, By_<}
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -21,8 +22,22 @@ object ConsentScheduler extends MdcLoggable {
|
||||
|
||||
// Starts multiple scheduled tasks with different intervals
|
||||
def startAll(): Unit = {
|
||||
startTask(interval = 60, () => unfinishedBerlinGroupConsents()) // Runs every 60 sec
|
||||
startTask(interval = 60, () => expiredBerlinGroupConsents(), 10) // Start 10 seconds after previous job
|
||||
APIUtil.getPropsAsIntValue("berlin_group_outdated_consents_interval_in_seconds") match {
|
||||
case Full(interval) if interval > 0 =>
|
||||
val time = APIUtil.getPropsAsIntValue("berlin_group_outdated_consents_time_in_seconds", 300)
|
||||
startTask(interval = interval, () => unfinishedBerlinGroupConsents(time)) // Runs periodically
|
||||
case _ =>
|
||||
logger.warn("|---> Skipping unfinishedBerlinGroupConsents task: berlin_group_outdated_consents_interval_in_seconds not set or invalid")
|
||||
}
|
||||
|
||||
APIUtil.getPropsAsIntValue("berlin_group_expired_consents_interval_in_seconds") match {
|
||||
case Full(interval) if interval > 0 =>
|
||||
startTask(interval = interval, () => expiredBerlinGroupConsents(), 10) // Delay for 10 seconds
|
||||
case _ =>
|
||||
logger.warn("|---> Skipping expiredBerlinGroupConsents task: berlin_group_expired_consents_interval_in_seconds not set or invalid")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Generic method to schedule a task
|
||||
@ -37,21 +52,20 @@ object ConsentScheduler extends MdcLoggable {
|
||||
}
|
||||
|
||||
// Calculate the timestamp 5 minutes ago
|
||||
private val someMinutesAgo: Date = {
|
||||
val minutes = APIUtil.getPropsAsIntValue("berlin_group_outdated_consents_interval", 5)
|
||||
private def someSecondsAgo(seconds: Int): Date = {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.add(Calendar.MINUTE, -minutes)
|
||||
cal.add(Calendar.SECOND, -seconds)
|
||||
cal.getTime
|
||||
}
|
||||
|
||||
private def unfinishedBerlinGroupConsents(): Unit = {
|
||||
private def unfinishedBerlinGroupConsents(seconds: Int): Unit = {
|
||||
Try {
|
||||
logger.debug("|---> Checking for outdated Berlin Group consents...")
|
||||
|
||||
val outdatedConsents = MappedConsent.findAll(
|
||||
By(MappedConsent.mStatus, ConsentStatus.received.toString),
|
||||
By(MappedConsent.mApiStandard, ApiVersion.berlinGroupV13.apiStandard),
|
||||
By_<(MappedConsent.updatedAt, someMinutesAgo)
|
||||
By_<(MappedConsent.updatedAt, someSecondsAgo(seconds))
|
||||
)
|
||||
|
||||
logger.debug(s"|---> Found ${outdatedConsents.size} outdated consents")
|
||||
|
||||
@ -75,8 +75,8 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
|
||||
}
|
||||
}
|
||||
private object accessAccountsDefinedVar extends SessionVar(true)
|
||||
private object accessBalancesDefinedVar extends SessionVar(true)
|
||||
private object accessTransactionsDefinedVar extends SessionVar(true)
|
||||
private object accessBalancesDefinedVar extends SessionVar(false)
|
||||
private object accessTransactionsDefinedVar extends SessionVar(false)
|
||||
/**
|
||||
* Creates a ConsentAccessJson object from lists of IBANs for accounts, balances, and transactions.
|
||||
*
|
||||
@ -167,9 +167,9 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
|
||||
|
||||
// Determine which IBANs the user can access for accounts, balances, and transactions
|
||||
val canReadAccountsIbans: List[String] = json.access.accounts match {
|
||||
case Some(accounts) if accounts.isEmpty => // Access is requested
|
||||
case Some(accounts) if accounts.isEmpty => // Access is requested via "accounts": []
|
||||
updateConsentPayloadValue.set(true)
|
||||
accessAccountsDefinedVar.set(true)
|
||||
accessAccountsDefinedVar.set(true) // only account details access will be provided
|
||||
List()
|
||||
case Some(accounts) if accounts.flatMap(_.iban).toSet.subsetOf(userIbans) => // Access is requested for specific IBANs
|
||||
accessAccountsDefinedVar.set(true)
|
||||
@ -183,8 +183,10 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
|
||||
List()
|
||||
}
|
||||
val canReadBalancesIbans: List[String] = json.access.balances match {
|
||||
case Some(balances) if balances.isEmpty => // Access is requested
|
||||
case Some(balances) if balances.isEmpty => // Access is requested via "balances": []
|
||||
updateConsentPayloadValue.set(true)
|
||||
// access to account details and balances will be provided
|
||||
accessAccountsDefinedVar.set(true)
|
||||
accessBalancesDefinedVar.set(true)
|
||||
List()
|
||||
case Some(balances) if balances.flatMap(_.iban).toSet.subsetOf(userIbans) => // Access is requested for specific IBANs
|
||||
@ -199,8 +201,10 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
|
||||
List()
|
||||
}
|
||||
val canReadTransactionsIbans: List[String] = json.access.transactions match {
|
||||
case Some(transactions) if transactions.isEmpty => // Access is requested
|
||||
case Some(transactions) if transactions.isEmpty => // Access is requested via "transactions": []
|
||||
updateConsentPayloadValue.set(true)
|
||||
// access to account details and transactions will be provided
|
||||
accessAccountsDefinedVar.set(true)
|
||||
accessTransactionsDefinedVar.set(true)
|
||||
List()
|
||||
case Some(transactions) if transactions.flatMap(_.iban).toSet.subsetOf(userIbans) => // Access is requested for specific IBANs
|
||||
|
||||
@ -8,6 +8,7 @@ import code.api.util.APIUtil
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.v4_0_0.PostViewJsonV400
|
||||
import code.consent.ConsentStatus
|
||||
import code.model.dataAccess.{BankAccountRouting, MappedBankAccount}
|
||||
import code.setup.{APIResponse, DefaultUsers}
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
@ -262,7 +263,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
Then("We should get a 201 ")
|
||||
response.code should equal(201)
|
||||
response.body.extract[PostConsentResponseJson].consentId should not be (empty)
|
||||
response.body.extract[PostConsentResponseJson].consentStatus should be ("received")
|
||||
response.body.extract[PostConsentResponseJson].consentStatus should be (ConsentStatus.received.toString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,10 +303,16 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
val consentId =response.body.extract[PostConsentResponseJson].consentId
|
||||
|
||||
Then("We test the delete consent ")
|
||||
val requestDelete = (V1_3_BG / "consents"/consentId ).DELETE <@ (user1)
|
||||
val requestDelete = (V1_3_BG / "consents"/ consentId ).DELETE <@ (user1)
|
||||
val responseDelete = makeDeleteRequest(requestDelete)
|
||||
responseDelete.code should be (204)
|
||||
|
||||
Then(s"We test the $getConsentStatus")
|
||||
val requestGetStatus = (V1_3_BG / "consents" / consentId / "status").GET <@ (user1)
|
||||
val responseGetStatus = makeGetRequest(requestGetStatus)
|
||||
responseGetStatus.code should be(200)
|
||||
responseGetStatus.body.extract[ConsentStatusJsonV13].consentStatus should be(ConsentStatus.terminatedByTpp.toString)
|
||||
|
||||
//TODO We can not delete one consent two time, will fix it later.
|
||||
// val responseDeleteSecondTime = makeDeleteRequest(requestDelete)
|
||||
// responseDeleteSecondTime.code should be (400)
|
||||
@ -350,13 +357,13 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
val requestGet = (V1_3_BG / "consents"/consentId ).GET <@ (user1)
|
||||
val responseGet = makeGetRequest(requestGet)
|
||||
responseGet.code should be (200)
|
||||
responseGet.body.extract[GetConsentResponseJson].consentStatus should be ("received")
|
||||
responseGet.body.extract[GetConsentResponseJson].consentStatus should be (ConsentStatus.received.toString)
|
||||
|
||||
Then(s"We test the $getConsentStatus")
|
||||
val requestGetStatus = (V1_3_BG / "consents"/consentId /"status" ).GET <@ (user1)
|
||||
val responseGetStatus = makeGetRequest(requestGetStatus)
|
||||
responseGetStatus.code should be (200)
|
||||
responseGetStatus.body.extract[ConsentStatusJsonV13].consentStatus should be ("received")
|
||||
responseGetStatus.body.extract[ConsentStatusJsonV13].consentStatus should be (ConsentStatus.received.toString)
|
||||
|
||||
}
|
||||
}
|
||||
@ -398,7 +405,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
val requestStartConsentAuthorisation = (V1_3_BG / "consents"/consentId /"authorisations" ).POST <@ (user1)
|
||||
val responseStartConsentAuthorisation = makePostRequest(requestStartConsentAuthorisation, """{"scaAuthenticationData":""}""")
|
||||
responseStartConsentAuthorisation.code should be (201)
|
||||
responseStartConsentAuthorisation.body.extract[StartConsentAuthorisationJson].scaStatus should be ("received")
|
||||
responseStartConsentAuthorisation.body.extract[StartConsentAuthorisationJson].scaStatus should be (ConsentStatus.received.toString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,7 +463,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
val requestStartConsentAuthorisation = (V1_3_BG / "consents"/consentId /"authorisations" ).POST <@ (user1)
|
||||
val responseStartConsentAuthorisation = makePostRequest(requestStartConsentAuthorisation, """{"scaAuthenticationData":""}""")
|
||||
responseStartConsentAuthorisation.code should be (201)
|
||||
responseStartConsentAuthorisation.body.extract[StartConsentAuthorisationJson].scaStatus should be ("received")
|
||||
responseStartConsentAuthorisation.body.extract[StartConsentAuthorisationJson].scaStatus should be (ConsentStatus.received.toString)
|
||||
|
||||
Then(s"We test the $getConsentAuthorisation")
|
||||
val requestGetConsentAuthorisation = (V1_3_BG / "consents"/consentId /"authorisations" ).GET<@ (user1)
|
||||
@ -469,7 +476,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
val requestGetConsentScaStatus = (V1_3_BG / "consents"/consentId /"authorisations"/authorisationId ).GET <@ (user1)
|
||||
val responseGetConsentScaStatus = makeGetRequest(requestGetConsentScaStatus)
|
||||
responseGetConsentScaStatus.code should be (200)
|
||||
responseGetConsentScaStatus.body.extract[ScaStatusJsonV13].scaStatus should be ("received")
|
||||
responseGetConsentScaStatus.body.extract[ScaStatusJsonV13].scaStatus should be (ConsentStatus.received.toString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ package code.api.v5_1_0
|
||||
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.api.util.ApiRole.{canCreateConsumer, canGetConsumers, canUpdateConsumerLogoUrl, canUpdateConsumerName, canUpdateConsumerRedirectUrl}
|
||||
import code.api.util.ApiRole.{canCreateConsumer, canGetConsumers, canUpdateConsumerCertificate, canUpdateConsumerLogoUrl, canUpdateConsumerName, canUpdateConsumerRedirectUrl}
|
||||
import code.api.util.ErrorMessages.{InvalidJsonFormat, UserNotLoggedIn}
|
||||
import code.api.v3_1_0.ConsumerJsonV310
|
||||
import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0
|
||||
@ -52,10 +52,11 @@ class ConsumerTest extends V510ServerSetup {
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.updateConsumerRedirectURL))
|
||||
object ApiEndpoint4 extends Tag(nameOf(Implementations5_1_0.updateConsumerLogoURL))
|
||||
object UpdateConsumerName extends Tag(nameOf(Implementations5_1_0.updateConsumerName))
|
||||
object UpdateConsumerCertificate extends Tag(nameOf(Implementations5_1_0.updateConsumerCertificate))
|
||||
object GetConsumer extends Tag(nameOf(Implementations5_1_0.getConsumer))
|
||||
|
||||
feature("Test all error cases ") {
|
||||
scenario("We test the authentication errors", UpdateConsumerName, GetConsumer, ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
|
||||
scenario("We test the authentication errors", UpdateConsumerName, GetConsumer, ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, UpdateConsumerCertificate, VersionOfApi) {
|
||||
When("We make a request v5.1.0")
|
||||
lazy val postApiCollectionJson = SwaggerDefinitionsJSON.postApiCollectionJson400
|
||||
val requestApiEndpoint1 = (v5_1_0_Request / "management" / "consumers").POST
|
||||
@ -73,6 +74,9 @@ class ConsumerTest extends V510ServerSetup {
|
||||
val requestApiUpdateConsumerName = (v5_1_0_Request /"management" / "consumers" / "CONSUMER_ID" / "consumer" / "name").PUT
|
||||
val responseApiUpdateConsumerName = makePutRequest(requestApiUpdateConsumerName, write(postApiCollectionJson))
|
||||
|
||||
val requestApiUpdateConsumerCertificate = (v5_1_0_Request /"management" / "consumers" / "CONSUMER_ID" / "consumer" / "certificate").PUT
|
||||
val responseApiUpdateConsumerCertificate = makePutRequest(requestApiUpdateConsumerCertificate, write(postApiCollectionJson))
|
||||
|
||||
Then(s"we should get the error messages")
|
||||
responseApiEndpoint1.code should equal(401)
|
||||
responseApiEndpoint2.code should equal(401)
|
||||
@ -86,6 +90,9 @@ class ConsumerTest extends V510ServerSetup {
|
||||
responseApiUpdateConsumerName.code should equal(401)
|
||||
responseApiUpdateConsumerName.body.toString contains(s"$UserNotLoggedIn") should be (true)
|
||||
|
||||
responseApiUpdateConsumerCertificate.code should equal(401)
|
||||
responseApiUpdateConsumerCertificate.body.toString contains(s"$UserNotLoggedIn") should be (true)
|
||||
|
||||
// Endpoint GetConsumer
|
||||
val requestApiEndpoint5 = (v5_1_0_Request / "management" / "consumers" / "whatever").GET
|
||||
val responseApiEndpoint5 = makeGetRequest(requestApiEndpoint5)
|
||||
@ -93,7 +100,7 @@ class ConsumerTest extends V510ServerSetup {
|
||||
responseApiEndpoint5.body.toString contains(s"$UserNotLoggedIn") should be (true)
|
||||
}
|
||||
|
||||
scenario("We test the missing roles errors", UpdateConsumerName, GetConsumer, ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) {
|
||||
scenario("We test the missing roles errors", UpdateConsumerName, GetConsumer, ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, UpdateConsumerCertificate, VersionOfApi) {
|
||||
When("We make a request v5.1.0")
|
||||
|
||||
lazy val wrongJsonForTesting = SwaggerDefinitionsJSON.routing
|
||||
@ -111,7 +118,10 @@ class ConsumerTest extends V510ServerSetup {
|
||||
|
||||
val requestApiUpdateConsumerName = (v5_1_0_Request /"management" / "consumers" / "CONSUMER_ID" / "consumer" / "name").PUT<@ (user1)
|
||||
val responseApiUpdateConsumerName = makePutRequest(requestApiUpdateConsumerName, write(wrongJsonForTesting))
|
||||
|
||||
|
||||
val requestApiUpdateConsumerCertificate = (v5_1_0_Request / "management" / "consumers" / "CONSUMER_ID" / "consumer" / "certificate").PUT <@ (user1)
|
||||
val responseApiUpdateConsumerCertificate = makePutRequest(requestApiUpdateConsumerCertificate, write(wrongJsonForTesting))
|
||||
|
||||
Then(s"we should get the error messages")
|
||||
responseApiEndpoint1.code should equal(403)
|
||||
responseApiEndpoint1.body.toString contains(s"$canCreateConsumer") should be (true)
|
||||
@ -124,6 +134,9 @@ class ConsumerTest extends V510ServerSetup {
|
||||
responseApiUpdateConsumerName.code should equal(403)
|
||||
responseApiUpdateConsumerName.body.toString contains(s"$canUpdateConsumerName") should be (true)
|
||||
|
||||
responseApiUpdateConsumerCertificate.code should equal(403)
|
||||
responseApiUpdateConsumerCertificate.body.toString contains(s"$canUpdateConsumerCertificate") should be (true)
|
||||
|
||||
// Endpoint GetConsumer
|
||||
val requestApiEndpoint5 = (v5_1_0_Request / "management" / "consumers" / "whatever").GET <@ user1
|
||||
val responseApiEndpoint5 = makeGetRequest(requestApiEndpoint5)
|
||||
@ -137,6 +150,7 @@ class ConsumerTest extends V510ServerSetup {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerLogoUrl.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerRedirectUrl.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerName.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerCertificate.toString)
|
||||
|
||||
When("We make a request v5.1.0")
|
||||
lazy val wrongJsonForTesting = SwaggerDefinitionsJSON.postApiCollectionJson400
|
||||
@ -152,6 +166,9 @@ class ConsumerTest extends V510ServerSetup {
|
||||
val requestApiUpdateConsumerName = (v5_1_0_Request / "management" / "consumers" / "CONSUMER_ID" / "consumer" / "name").PUT <@ (user1)
|
||||
val responseApiUpdateConsumerName = makePutRequest(requestApiUpdateConsumerName, write(wrongJsonForTesting))
|
||||
|
||||
val requestApiUpdateConsumerCertificate = (v5_1_0_Request / "management" / "consumers" / "CONSUMER_ID" / "consumer" / "certificate").PUT <@ (user1)
|
||||
val responseApiUpdateConsumerCertificate = makePutRequest(requestApiUpdateConsumerCertificate, write(wrongJsonForTesting))
|
||||
|
||||
Then(s"we should get the error messages")
|
||||
responseApiEndpoint1.code should equal(400)
|
||||
responseApiEndpoint1.body.toString contains(s"$InvalidJsonFormat") should be (true)
|
||||
@ -161,6 +178,9 @@ class ConsumerTest extends V510ServerSetup {
|
||||
responseApiEndpoint4.body.toString contains(s"$InvalidJsonFormat") should be (true)
|
||||
responseApiUpdateConsumerName.code should equal(400)
|
||||
responseApiUpdateConsumerName.body.toString contains(s"$InvalidJsonFormat") should be (true)
|
||||
|
||||
responseApiUpdateConsumerCertificate.code should equal(400)
|
||||
responseApiUpdateConsumerCertificate.body.toString contains(s"$InvalidJsonFormat") should be (true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,11 +193,13 @@ class ConsumerTest extends V510ServerSetup {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerLogoUrl.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerRedirectUrl.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerName.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateConsumerCertificate.toString)
|
||||
|
||||
lazy val createConsumerRequestJsonV510 = SwaggerDefinitionsJSON.createConsumerRequestJsonV510
|
||||
lazy val consumerRedirectUrlJSON = SwaggerDefinitionsJSON.consumerRedirectUrlJSON
|
||||
lazy val consumerLogoUrlJson = SwaggerDefinitionsJSON.consumerLogoUrlJson
|
||||
lazy val consumerNameJson = SwaggerDefinitionsJSON.consumerNameJson
|
||||
lazy val consumerCertificateJson = SwaggerDefinitionsJSON.consumerCertificateJson
|
||||
val requestApiEndpoint1 = (v5_1_0_Request / "management" / "consumers").POST<@ (user1)
|
||||
val responseApiEndpoint1 = makePostRequest(requestApiEndpoint1, write(createConsumerRequestJsonV510))
|
||||
val consumerId = responseApiEndpoint1.body.extract[ConsumerJsonV510].consumer_id
|
||||
@ -200,6 +222,11 @@ class ConsumerTest extends V510ServerSetup {
|
||||
val responseApiUpdateConsumerName = makePutRequest(requestApiUpdateConsumerName, write(consumerNameJson))
|
||||
val name = responseApiUpdateConsumerName.body.extract[ConsumerJsonV510].app_name
|
||||
name shouldBe(consumerNameJson.app_name)
|
||||
|
||||
val requestApiUpdateConsumerCertificate = (v5_1_0_Request / "management" / "consumers" / consumerId / "consumer" / "certificate").PUT <@ (user1)
|
||||
val responseApiUpdateConsumerCertificate = makePutRequest(requestApiUpdateConsumerCertificate, write(consumerCertificateJson))
|
||||
val certificatePem = responseApiUpdateConsumerCertificate.body.extract[ConsumerJsonV510].certificate_pem
|
||||
certificatePem shouldBe(consumerCertificateJson.certificate)
|
||||
|
||||
Then(s"we should get the error messages")
|
||||
responseApiEndpoint1.code should equal(201)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user