diff --git a/README.md b/README.md index 831a95511e..0617550840 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,10 @@ Run one test mvn -DwildcardSuites=code.api.directloginTest test +Run all test and save output to a file + + export MAVEN_OPTS="-Xss128m" && mvn clean test | tee obp-api-test-results.txt + ## Ubuntu If you use Ubuntu (or a derivate) and encrypted home directories (e.g. you have ~/.Private), you might run into the following error when the project is built: diff --git a/obp-api/src/main/scala/code/api/OAuth2.scala b/obp-api/src/main/scala/code/api/OAuth2.scala index f40c5e5f03..e78befb655 100644 --- a/obp-api/src/main/scala/code/api/OAuth2.scala +++ b/obp-api/src/main/scala/code/api/OAuth2.scala @@ -27,8 +27,10 @@ TESOBE (http://www.tesobe.com/) package code.api import java.net.URI +import java.util + import code.api.util.ErrorMessages._ -import code.api.util.{APIUtil, CallContext, JwtUtil} +import code.api.util.{APIUtil, CallContext, CertificateUtil, JwtUtil} import code.consumer.Consumers import code.consumer.Consumers.consumers import code.loginattempts.LoginAttempt @@ -161,8 +163,8 @@ object OAuth2Login extends RestHelper with MdcLoggable { // hydra update client endpoint have bug, So here delete and create to do update hydraAdmin.deleteOAuth2Client(clientId) hydraAdmin.createOAuth2Client(oAuth2Client) - } else if(stringNotEq(certInConsumer, cert)) { - // Cannot match the value from PSD2-CERT header and the database value Consumer.clientCertificate + } else if(!CertificateUtil.comparePemX509Certificates(certInConsumer, cert)) { + // Cannot mat.ch the value from PSD2-CERT header and the database value Consumer.clientCertificate logger.debug("Cert in Consumer: " + certInConsumer) logger.debug("Cert in Request: " + cert) logger.debug(s"Token: $value") @@ -207,15 +209,6 @@ object OAuth2Login extends RestHelper with MdcLoggable { applyRules(value, cc) } - /** - * check whether two string equal, ignore line break type - * @param str1 - * @param str2 - * @return true if two string are different - */ - private def stringNotEq(str1: String, str2: String): Boolean = - str1.trim != str2.trim && - str1.trim.replace("\r\n", "\n") != str2.trim.replace("\r\n", "\n") } trait OAuth2Util { diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index 5683e417b6..096de0a477 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -545,11 +545,11 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { GetConsentResponseJson( access = access, recurringIndicator = createdConsent.recurringIndicator, - validUntil = new SimpleDateFormat(DateWithDay).format(createdConsent.validUntil), + validUntil = if(createdConsent.validUntil == null) null else new SimpleDateFormat(DateWithDay).format(createdConsent.validUntil), frequencyPerDay = createdConsent.frequencyPerDay, combinedServiceIndicator= createdConsent.combinedServiceIndicator, - lastActionDate= new SimpleDateFormat(DateWithDay).format(createdConsent.lastActionDate), - consentStatus= createdConsent.status.toLowerCase() + lastActionDate = if(createdConsent.lastActionDate == null) null else new SimpleDateFormat(DateWithDay).format(createdConsent.lastActionDate), + consentStatus = createdConsent.status.toLowerCase() ) } diff --git a/obp-api/src/main/scala/code/api/openidconnect.scala b/obp-api/src/main/scala/code/api/openidconnect.scala index f05adb7eb3..b341f9d8d8 100644 --- a/obp-api/src/main/scala/code/api/openidconnect.scala +++ b/obp-api/src/main/scala/code/api/openidconnect.scala @@ -183,7 +183,8 @@ object OpenIdConnect extends OBPRestHelper with MdcLoggable { } private def extractParams(s: S): (String, String, String) = { - val code = ObpS.param("code") + // TODO Figure out why ObpS does not contain response parameter code + val code = s.param("code") val state = ObpS.param("state") val sessionState = OpenIDConnectSessionState.get (code.getOrElse(""), state.getOrElse("0"), sessionState.map(_.toString).getOrElse("1")) diff --git a/obp-api/src/main/scala/code/api/util/CertificateUtil.scala b/obp-api/src/main/scala/code/api/util/CertificateUtil.scala index 9b170461da..cfa38eae20 100644 --- a/obp-api/src/main/scala/code/api/util/CertificateUtil.scala +++ b/obp-api/src/main/scala/code/api/util/CertificateUtil.scala @@ -225,6 +225,40 @@ object CertificateUtil extends MdcLoggable { jwtParsed.getJWTClaimsSet } + // Remove all whitespace characters including spaces, tabs, newlines, and carriage returns + def normalizePemX509Certificate(pem: String): String = { + val pemHeader = "-----BEGIN CERTIFICATE-----" + val pemFooter = "-----END CERTIFICATE-----" + + def extractContent(pem: String): Option[String] = { + val start = pem.indexOf(pemHeader) + val end = pem.indexOf(pemFooter) + + if (start >= 0 && end > start) { + Some(pem.substring(start + pemHeader.length, end)) + } else { + None + } + } + + extractContent(pem).map { content => // Extract content from PEM representation of X509 certificate + val normalizedContent = content.replaceAll("\\s+", "") + s"$pemHeader$normalizedContent$pemFooter" + }.getOrElse(pem) // In case the extraction cannot be done default the input value we try to normalize + } + + def comparePemX509Certificates(pem1: String, pem2: String): Boolean = { + val normalizedPem1 = normalizePemX509Certificate(pem1) + val normalizedPem2 = normalizePemX509Certificate(pem2) + + val result = normalizedPem1 == normalizedPem2 + if(!result) { + logger.debug(s"normalizedPem1: ${normalizedPem1}") + logger.debug(s"normalizedPem2: ${normalizedPem2}") + } + result + } + def main(args: Array[String]): Unit = { 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 e78704fe30..d17cbd25cb 100644 --- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala +++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala @@ -126,12 +126,14 @@ object Consent extends MdcLoggable { def getCurrentConsumerViaMtls(callContext: CallContext): Box[Consumer] = { val clientCert: String = APIUtil.`getPSD2-CERT`(callContext.requestHeaders) .getOrElse(SecureRandomUtil.csprng.nextLong().toString) - def removeBreakLines(input: String) = input - .replace("\n", "") - .replace("\r", "") - Consumers.consumers.vend.getConsumerByPemCertificate(clientCert).or( - Consumers.consumers.vend.getConsumerByPemCertificate(removeBreakLines(clientCert)) - ) + + { // 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)) + } } private def verifyHmacSignedJwt(jwtToken: String, c: MappedConsent): Boolean = { @@ -314,7 +316,9 @@ object Consent extends MdcLoggable { JwtUtil.getSignedPayloadAsJson(consentIdAsJwt) match { case Full(jsonAsString) => try { + logger.debug(s"Start of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $jsonAsString") val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] + logger.debug(s"End of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $consent") checkConsent(consent, consentIdAsJwt, calContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK applyConsentRules(consent) @@ -369,7 +373,9 @@ object Consent extends MdcLoggable { JwtUtil.getSignedPayloadAsJson(consentAsJwt) match { case Full(jsonAsString) => try { + logger.debug(s"Start of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $jsonAsString") val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] + logger.debug(s"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) @@ -488,7 +494,9 @@ object Consent extends MdcLoggable { JwtUtil.getSignedPayloadAsJson(storedConsent.jsonWebToken) match { case Full(jsonAsString) => try { + logger.debug(s"Start of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $jsonAsString") val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] + logger.debug(s"End of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT]: $consent") checkConsent(consent, storedConsent.jsonWebToken, updatedCallContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK // Update MappedConsent.usesSoFarTodayCounter field diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 721fe5a14a..4bbf563a27 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -976,7 +976,7 @@ trait APIMethods510 { implementedInApiVersion, nameOf(getConsentByConsentId), "GET", - "/consumer/consents/CONSENT_ID", + "/user/current/consents/CONSENT_ID", "Get Consent By Consent Id", s""" | @@ -993,7 +993,7 @@ trait APIMethods510 { ), List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2)) lazy val getConsentByConsentId: OBPEndpoint = { - case "consumer" :: "consents" :: consentId :: Nil JsonGet _ => { + case "user" :: "current" :: "consents" :: consentId :: Nil JsonGet _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { consent <- Future { Consents.consentProvider.vend.getConsentByConsentId(consentId)} map { @@ -1007,6 +1007,44 @@ trait APIMethods510 { } } } + + + staticResourceDocs += ResourceDoc( + getConsentByConsentIdViaConsumer, + implementedInApiVersion, + nameOf(getConsentByConsentIdViaConsumer), + "GET", + "/consumer/current/consents/CONSENT_ID", + "Get Consent By Consent Id", + s""" + | + |This endpoint gets the Consent By consent id. + | + |${authenticationRequiredMessage(true)} + | + """.stripMargin, + EmptyBody, + consentJsonV500, + List( + $UserNotLoggedIn, + UnknownError + ), + List(apiTagConsent, apiTagPSD2AIS, apiTagPsd2)) + lazy val getConsentByConsentIdViaConsumer: OBPEndpoint = { + case "consumer" :: "current" :: "consents" :: consentId :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + 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) { + consent.mConsumerId.get == cc.consumer.map(_.consumerId.get).getOrElse("None") + } + } yield { + (JSONFactory510.getConsentInfoJson(consent), HttpCode.`200`(cc)) + } + } + } staticResourceDocs += ResourceDoc( revokeConsentAtBank, diff --git a/obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala index 4dc53a08ab..e6b3aeaa05 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala @@ -82,7 +82,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{ def getConsentRequestUrl(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId).GET<@(user1) def createConsentByConsentRequestIdEmail(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId/"EMAIL"/"consents").POST<@(user1) def getConsentByRequestIdUrl(requestId:String) = (v5_1_0_Request / "consumer"/ "consent-requests"/requestId/"consents").GET<@(user1) - def getConsentByIdUrl(requestId:String) = (v5_1_0_Request / "consumer" / "consents" / requestId ).GET<@(user1) + def getConsentByIdUrl(requestId:String) = (v5_1_0_Request / "consumer" / "current" / "consents" / requestId ).GET<@(user1) def revokeConsentUrl(consentId: String) = (v5_1_0_Request / "banks" / bankId / "consents" / consentId).DELETE feature(s"test $ApiEndpoint6 version $VersionOfApi - Unauthorized access") { diff --git a/pom.xml b/pom.xml index a0fb316aae..270f475b7b 100644 --- a/pom.xml +++ b/pom.xml @@ -163,10 +163,11 @@ + org.scalatest scalatest-maven-plugin - 2.0.0 + 2.2.0