Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consents #2409

Merged
merged 18 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d10fc11
feature/Tweak function createGetConsentResponseJson
constantine2nd Jun 24, 2024
7d7efd1
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jun 25, 2024
e84db05
feature/Introduce function comparePemX509Certificates
constantine2nd Jun 26, 2024
b689ba4
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jun 26, 2024
2b70289
feature/Add endpoint getConsentByConsentIdViaConsumer v5.1.0
constantine2nd Jun 26, 2024
5f6c943
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jul 1, 2024
a2e17ba
docfix/Add how to run all tests via terminal
constantine2nd Jul 1, 2024
28740ee
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jul 3, 2024
75cdea0
feature/Tweak the path of endpoint getConsentByConsentId v5.1.0
constantine2nd Jul 5, 2024
d32bf27
feature/Tweak the path of endpoint getConsentByConsentIdViaConsumer …
constantine2nd Jul 5, 2024
74be5ba
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jul 5, 2024
9005c05
docfix/Put debug logging at ConsentUtil
constantine2nd Jul 10, 2024
d9cf31f
docfix/Put debug logging at ConsentUtil 2
constantine2nd Jul 11, 2024
49348c9
test/Fix failing Consent test
constantine2nd Jul 11, 2024
6874d95
Merge remote-tracking branch 'upstream/develop' into develop
constantine2nd Jul 11, 2024
817d09d
Merge remote-tracking branch 'origin/develop' into develop
constantine2nd Jul 11, 2024
d33312a
bugfix/Google registration on API Sandbox does not work
constantine2nd Jul 12, 2024
61727b3
feature/Bump scalatest-maven-plugin to 2.2.0
constantine2nd Jul 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 5 additions & 12 deletions obp-api/src/main/scala/code/api/OAuth2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}

Expand Down
3 changes: 2 additions & 1 deletion obp-api/src/main/scala/code/api/openidconnect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
34 changes: 34 additions & 0 deletions obp-api/src/main/scala/code/api/util/CertificateUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
20 changes: 14 additions & 6 deletions obp-api/src/main/scala/code/api/util/ConsentUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
42 changes: 40 additions & 2 deletions obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
|
Expand All @@ -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 {
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion obp-api/src/test/scala/code/api/v5_1_0/ConsentsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,11 @@
</execution>
</executions>
</plugin>
<!-- https://mvnrepository.com/artifact/org.scalatest/scalatest-maven-plugin -->
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>2.0.0</version>
<version>2.2.0</version>
</plugin>
</plugins>
</pluginManagement>
Expand Down
Loading