From 0b15f4bb10c6c1fdcdd5cbe20d9d512540b35628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 23 May 2022 14:19:02 +0200 Subject: [PATCH 1/5] feature/Add okhttp3 library version 4.9.1 --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index b2b4078e..8a6a3a7c 100644 --- a/pom.xml +++ b/pom.xml @@ -148,6 +148,11 @@ signpost-commonshttp4 2.1.1 + + com.squareup.okhttp3 + okhttp + 4.9.1 + com.github.OpenBankProject scala-macros From a56cb82cf4b855b48f1e0ad9df7cde861fdc6f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 23 May 2022 14:20:26 +0200 Subject: [PATCH 2/5] feature/Add nimbus-jose-jwt library version 9.1 --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 8a6a3a7c..d107ceed 100644 --- a/pom.xml +++ b/pom.xml @@ -158,6 +158,11 @@ scala-macros v1.0.0-alpha.3 + + com.nimbusds + nimbus-jose-jwt + 9.1 + com.github.cb372 scalacache-guava_${scala.version} From 32a802bb486de3b78616d758a176053fb4ad4d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 24 May 2022 09:33:41 +0200 Subject: [PATCH 3/5] feature/Add Client Authentication low with Ientity provider --- pom.xml | 5 ++ src/main/scala/code/lib/ObpAPI.scala | 117 +++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index d107ceed..0d0e9495 100644 --- a/pom.xml +++ b/pom.xml @@ -163,6 +163,11 @@ nimbus-jose-jwt 9.1 + + org.bouncycastle + bcpkix-jdk15on + 1.62 + com.github.cb372 scalacache-guava_${scala.version} diff --git a/src/main/scala/code/lib/ObpAPI.scala b/src/main/scala/code/lib/ObpAPI.scala index a954f213..8cfb7760 100644 --- a/src/main/scala/code/lib/ObpAPI.scala +++ b/src/main/scala/code/lib/ObpAPI.scala @@ -1,28 +1,34 @@ package code.lib -import code.lib.ObpAPI.UnknownErrorMessage import java.io._ +import java.security.Security import java.text.SimpleDateFormat +import java.util.UUID.randomUUID import java.util.{Date, UUID} + +import code.lib.ObpAPI.UnknownErrorMessage import code.lib.ObpJson._ import code.util.Helper import code.util.Helper.{MdcLoggable, covertWebpageIdToObpOperationId} import code.util.cache.Caching +import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton +import com.nimbusds.jose.crypto.{ECDSASigner, RSASSASigner} +import com.nimbusds.jose.jwk.{ECKey, JWK, RSAKey} +import com.nimbusds.jose.{JOSEException, JWSAlgorithm, JWSHeader, JWSSigner} +import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT} +import com.tesobe.CacheKeyFromArguments import net.liftweb.common.{Box, Failure, Full, _} import net.liftweb.http.{RequestVar, S, SessionVar} +import net.liftweb.json import net.liftweb.json.JsonAST.{JBool, JValue} -import net.liftweb.json.JsonDSL._ import net.liftweb.json._ import net.liftweb.util.Helpers.{intToTimeSpanBuilder => _, _} +import okhttp3.MediaType -import scala.xml.NodeSeq -import com.tesobe.CacheKeyFromArguments import scala.collection.immutable.{List, Nil} import scala.concurrent.duration._ import scala.language.postfixOps -import java.util.UUID.randomUUID -import net.liftweb.common._ -import net.liftweb.json +import scala.xml.NodeSeq case class Header(key: String, value: String) @@ -562,6 +568,83 @@ object ObpAPI extends Loggable { } + +object IdentityProviderRequest extends MdcLoggable { + + val clientId = Helper.getPropsValue("obp_consumer_key", "") + val clientSecret = Helper.getPropsValue("obp_secret_key", "") + val tokenEndpoint = Helper.getPropsValue("identity_provider_token_endpoint", "http://127.0.0.1:4444/oauth2/token") + val jwsAlg = Helper.getPropsValue("oauth2.jws_alg", "ES256") + val jwkPrivateKey = Helper.getPropsValue("oauth2.jwk_private_key") + + val jwk: Option[JWK] = jwkPrivateKey.map(JWK.parse(_)) + lazy val jwsSigner: Option[JWSSigner] = { + if (jwkPrivateKey.isDefined) { + if (jwsAlg.startsWith("ES")) Some(new ECDSASigner(jwk.get.asInstanceOf[ECKey])) + else { + if (jwsAlg.startsWith("PS")) Security.addProvider(BouncyCastleProviderSingleton.getInstance) + val privateKey = jwk.get.asInstanceOf[RSAKey] + Some(new RSASSASigner(privateKey)) + } + } else { + None + } + } + + + private def signClaims(claimsSet: JWTClaimsSet) = { + val jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.parse(jwsAlg)).keyID(jwk.get.getKeyID).build, claimsSet) + // Sign with private EC key + try jwt.sign(jwsSigner.get) + catch { + case e: JOSEException => + println(e) + } + jwt.serialize + } + + def buildClientAssertion: String = { // JWT claims + //iss: REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client. + //sub: REQUIRED. Subject. This MUST contain the client_id of the OAuth Client. + //aud: REQUIRED. Audience. The aud (audience) Claim. Value that identifies the Authorization Server (ORY Hydra) as an intended audience. The Authorization Server MUST verify that it is an intended audience for the token. The Audience SHOULD be the URL of the Authorization Server's Token Endpoint. + //jti: REQUIRED. JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification. + //exp: REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing. + //iat: OPTIONAL. Time at which the JWT was issued. + val claimsSet = new JWTClaimsSet.Builder() + .issuer(clientId).subject(clientId).audience(tokenEndpoint) + .jwtID(UUID.randomUUID.toString) + .expirationTime(new Date(new Date().getTime + 60 * 1000)) + .issueTime(new Date).build + signClaims(claimsSet) + } + + def isPublicClient() = jwkPrivateKey.isDefined + + def obtainAccessToken() = { + import okhttp3.{OkHttpClient, Request, RequestBody} + val client = new OkHttpClient().newBuilder.build + val mediaType = MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8") + val grantType = "client_credentials" + + + val body = isPublicClient match { + case true => + RequestBody.create(mediaType, s"grant_type=$grantType&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$buildClientAssertion") + case false => + RequestBody.create(mediaType, s"grant_type=$grantType&client_id=$clientId&client_secret=$clientSecret") + } + + val request = new Request.Builder() + .url(tokenEndpoint).method("POST", body) + .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8").build + val response = client.newCall(request).execute + val responseBody = response.body.string + response.close() + responseBody + } + +} + case class ObpError(error :String) object OBPRequest extends MdcLoggable { @@ -569,6 +652,15 @@ object OBPRequest extends MdcLoggable { //returns a tuple of the status code, response body and list of headers def apply(apiPath : String, jsonBody : Option[JValue], method : String, headers : List[Header]) : Box[(Int, String, List[String])] = { logger.debug(s"before $apiPath call:") + + def addAppAccess = { + if (!headers.exists(_.key == "Authorization")) { + Header("Authorization", s"Bearer $obtainAccessToken") :: headers + } else { + headers + } + } + lazy val statusAndBody = tryo { val credentials = OAuthClient.getAuthorizedCredential val apiUrl = OAuthClient.currentApiBaseUrl @@ -593,6 +685,9 @@ object OBPRequest extends MdcLoggable { request.setRequestProperty("Content-Type", "application/json; charset=UTF-8") request.setRequestProperty("Accept", "application/json") request.setRequestProperty("Accept-Charset", "UTF-8") + if(apiPath.contains("/obp/v4.0.0/my/consents/request")) { + request.setRequestProperty("Authorization", s"Bearer $obtainAccessToken") + } //sign the request if we have some credentials to sign it with credentials.foreach(c => c.consumer.sign(request)) @@ -655,6 +750,14 @@ object OBPRequest extends MdcLoggable { logger.debug(s"after $apiPath call:") result } + + private def obtainAccessToken: String = { + val jsonResponse = IdentityProviderRequest.obtainAccessToken() + import net.liftweb.json._ + val jsonAst = parse(jsonResponse) \ "access_token" + val accessToken = compact(render(jsonAst)).stripPrefix("\"").stripSuffix("\"") + accessToken + } } object ObpPut { From 837e82c8923ced2d04ae6275998b8b272ca3106e Mon Sep 17 00:00:00 2001 From: Hongwei Date: Wed, 15 Jun 2022 17:23:20 +0200 Subject: [PATCH 4/5] bugfix/added the Bearer token Header to v5.0.0 creatConsentRequest --- src/main/scala/code/lib/ObpAPI.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/code/lib/ObpAPI.scala b/src/main/scala/code/lib/ObpAPI.scala index 163f54aa..dda8d00e 100644 --- a/src/main/scala/code/lib/ObpAPI.scala +++ b/src/main/scala/code/lib/ObpAPI.scala @@ -686,7 +686,7 @@ object OBPRequest extends MdcLoggable { request.setRequestProperty("Content-Type", "application/json; charset=UTF-8") request.setRequestProperty("Accept", "application/json") request.setRequestProperty("Accept-Charset", "UTF-8") - if(apiPath.contains("/obp/v4.0.0/my/consents/request")) { + if(apiPath.contains("/obp/v4.0.0/my/consents/request") || apiPath.contains("/obp/v5.0.0/my/consents/request")) { request.setRequestProperty("Authorization", s"Bearer $obtainAccessToken") } From a5ba743f1a9c98745aa99710d4c5de8fc6c2cf5b Mon Sep 17 00:00:00 2001 From: Hongwei Date: Wed, 20 Jul 2022 11:54:08 +0200 Subject: [PATCH 5/5] bugfix/tweaked the logo for the below 760px width --- src/main/webapp/templates-hidden/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/templates-hidden/default.html b/src/main/webapp/templates-hidden/default.html index 89ac879b..92be71a5 100755 --- a/src/main/webapp/templates-hidden/default.html +++ b/src/main/webapp/templates-hidden/default.html @@ -64,7 +64,7 @@ along with this program. If not, see www.gnu.org/licenses/
- right logo image + right logo image