Merge pull request #2484 from constantine2nd/develop

VRP Consent Acceptance
This commit is contained in:
Simon Redfern 2025-01-31 14:29:28 +01:00 committed by GitHub
commit 92de2cdc61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 651 additions and 25 deletions

View File

@ -490,7 +490,12 @@
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.12.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->

View File

@ -764,14 +764,22 @@ display_internal_errors=false
# URL of Public server JWK set used for validating bearer JWT access tokens
# It can contain more than one URL i.e. list of uris. Values are comma separated.
# oauth2.jwk_set.url=http://localhost:8080/jwk.json,https://www.googleapis.com/oauth2/v3/certs
# ------------------------------------------------------------------------------ OAuth 2 ------
# -- Keycloak OAuth 2 ---------------------------------------------------------------------------
# integrate_with_keycloak = false
# Keycloak Identity Provider Host
# oauth2.keycloak.host=http://localhost:7070
# Keycloak access token to make a call to Admin APIs (This props is likely to change)
# keycloak.admin.access_token =
# Keycloak Identity Provider Realm (Multi-Tenancy Support)
# oauth2.keycloak.realm = master
# oauth2.keycloak.well_known=http://localhost:7070/realms/master/.well-known/openid-configuration
# Used to sync IAM of OBP-API and IAM of Keycloak
# oauth2.keycloak.source_of_truth = false
# Resource access object allowed to sync IAM of OBP-API and IAM of Keycloak
# oauth2.keycloak.resource_access_key_name_to_trust = open-bank-project
# ------------------------------------------------------------------------------ OAuth 2 ------
# ------------------------------------------------------------------------ Keycloak OAuth 2 ------
# -- PSU Authentication methods --------------------------------------------------------------
# The EBA notes that there would appear to currently be three main ways or methods

View File

@ -594,6 +594,8 @@ class Boot extends MdcLoggable {
Menu.i("Introduction") / "introduction",
Menu.i("add-user-auth-context-update-request") / "add-user-auth-context-update-request",
Menu.i("confirm-user-auth-context-update-request") / "confirm-user-auth-context-update-request",
Menu.i("confirm-bg-consent-request") / "confirm-bg-consent-request" >> AuthUser.loginFirst,//OAuth consent page,
Menu.i("confirm-bg-consent-request-sca") / "confirm-bg-consent-request-sca" >> AuthUser.loginFirst,//OAuth consent page,
Menu.i("confirm-vrp-consent-request") / "confirm-vrp-consent-request" >> AuthUser.loginFirst,//OAuth consent page,
Menu.i("confirm-vrp-consent") / "confirm-vrp-consent" >> AuthUser.loginFirst //OAuth consent page
) ++ accountCreation ++ Admin.menus

View File

@ -0,0 +1,36 @@
package code.api.util
import java.time.Duration
object DateTimeUtil {
/*
Examples:
println(formatDuration(90000)) // "1 day, 1 hour"
println(formatDuration(86400)) // "1 day"
println(formatDuration(172800)) // "2 days"
println(formatDuration(7200)) // "2 hours"
println(formatDuration(3661)) // "1 hour, 1 minute, 1 second"
println(formatDuration(120)) // "2 minutes"
println(formatDuration(30)) // "30 seconds"
println(formatDuration(0)) // "less than a second"
*/
def formatDuration(seconds: Long): String = {
val days = seconds / 86400
val hours = (seconds % 86400) / 3600
val minutes = (seconds % 3600) / 60
val secs = seconds % 60
def plural(value: Long, unit: String): Option[String] =
if (value > 0) Some(s"$value ${unit}${if (value > 1) "s" else ""}") else None
val parts = List(
plural(days, "day"),
plural(hours, "hour"),
plural(minutes, "minute"),
plural(secs, "second")
).flatten // Remove None values
if (parts.isEmpty) "less than a second" else parts.mkString(", ")
}
}

View File

@ -0,0 +1,107 @@
package code.api.util
import code.api.OAuth2Login.Keycloak
import code.model.{AppType, Consumer}
import net.liftweb.common.{Box, Failure, Full}
import okhttp3._
import okhttp3.logging.HttpLoggingInterceptor
import org.slf4j.LoggerFactory
object KeycloakAdmin {
// Initialize Logback logger
private val logger = LoggerFactory.getLogger("okhttp3")
val integrateWithKeycloak = APIUtil.getPropsAsBoolValue("integrate_with_keycloak", defaultValue = false)
// Define variables (replace with actual values)
private val keycloakHost = Keycloak.keycloakHost
private val realm = APIUtil.getPropsValue(nameOfProperty = "oauth2.keycloak.realm", "master")
private val accessToken = APIUtil.getPropsValue(nameOfProperty = "keycloak.admin.access_token", "")
def createHttpClientWithLogback(): OkHttpClient = {
val builder = new OkHttpClient.Builder()
val logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger {
override def log(message: String): Unit = logger.debug(message)
})
logging.setLevel(HttpLoggingInterceptor.Level.BODY) // Log full request/response details
builder.addInterceptor(logging)
builder.build()
}
// Create OkHttp client with logging
val client = createHttpClientWithLogback()
def createKeycloakConsumer(consumer: Consumer): Box[Boolean] = {
val isPublic =
AppType.valueOf(consumer.appType.get) match {
case AppType.Confidential => false
case _ => true
}
createClient(
clientId = consumer.key.get,
secret = consumer.secret.get,
name = consumer.name.get,
description = consumer.description.get,
redirectUri = consumer.redirectURL.get,
isPublic = isPublic,
)
}
def createClient(clientId: String,
secret: String,
name: String,
description: String,
redirectUri: String,
isPublic: Boolean,
realm: String = realm
) = {
val url = s"$keycloakHost/admin/realms/$realm/clients"
// JSON request body
val jsonBody =
s"""{
| "clientId": "$clientId",
| "name": "$name",
| "description": "$description",
| "redirectUris": ["$redirectUri"],
| "enabled": true,
| "clientAuthenticatorType": "client-secret",
| "directAccessGrantsEnabled": true,
| "standardFlowEnabled": true,
| "implicitFlowEnabled": false,
| "serviceAccountsEnabled": true,
| "publicClient": $isPublic,
| "secret": "$secret"
|}""".stripMargin
// Define the request with headers and JSON body
val requestBody = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonBody)
val request = new Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Authorization", s"Bearer $accessToken")
.addHeader("Content-Type", "application/json")
.build()
makeAndHandleHttpCall(request)
}
private def makeAndHandleHttpCall(request: Request): Box[Boolean] = {
// Execute the request
try {
val response = client.newCall(request).execute()
if (response.isSuccessful) {
logger.debug(s"Response: ${response.body.string}")
Full(response.isSuccessful)
} else {
logger.error(s"Request failed with status code: ${response.code}")
logger.debug(s"Response: ${response}")
Failure(s"code: ${response.code} message: ${response.message}")
}
} catch {
case e: Exception =>
logger.error(s"Error occurred: ${e.getMessage}")
Failure(e.getMessage)
}
}
}

View File

@ -0,0 +1,132 @@
/**
Open Bank Project - API
Copyright (C) 2011-2019, TESOBE GmbH.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH.
Osloer Strasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.snippet
import code.api.RequestHeader
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{GetConsentResponseJson, createGetConsentResponseJson}
import code.api.util.{ConsentJWT, CustomJsonFormats, JwtUtil}
import code.api.v3_1_0.APIMethods310
import code.api.v5_0_0.APIMethods500
import code.api.v5_1_0.APIMethods510
import code.consent.{ConsentStatus, Consents, MappedConsent}
import code.consumer.Consumers
import code.model.dataAccess.AuthUser
import code.util.Helper.{MdcLoggable, ObpS}
import net.liftweb.common.{Box, Failure, Full}
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{RequestVar, S, SHtml, SessionVar}
import net.liftweb.json.{Formats, parse}
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
import scala.collection.immutable
class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 with APIMethods500 with APIMethods310 {
protected implicit override def formats: Formats = CustomJsonFormats.formats
private object otpValue extends RequestVar("123456")
private object redirectUriValue extends SessionVar("")
def confirmBerlinGroupConsentRequest: CssSel = {
callGetConsentByConsentId() match {
case Full(consent) =>
val json: GetConsentResponseJson = createGetConsentResponseJson(consent)
val consumer = Consumers.consumers.vend.getConsumerByConsumerId(consent.consumerId)
val consentJwt: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken).map(parse(_)
.extract[ConsentJWT])
val tppRedirectUri: immutable.Seq[String] = consentJwt.map{ h =>
h.request_headers.filter(h => h.name == RequestHeader.`TPP-Redirect-URL`)
}.getOrElse(Nil).map((_.values.mkString("")))
val consumerRedirectUri: Option[String] = consumer.map(_.redirectURL.get).toOption
val uri: String = tppRedirectUri.headOption.orElse(consumerRedirectUri).getOrElse("https://not.defined.com")
redirectUriValue.set(uri)
val formText =
s"""I, ${AuthUser.currentUser.map(_.firstName.get).getOrElse("")} ${AuthUser.currentUser.map(_.lastName.get).getOrElse("")}, consent to the service provider ${consumer.map(_.name.get).getOrElse("")} making actions on my behalf.
|
|This consent must respects the following actions:
|
| 1) Can read accounts: ${json.access.accounts.getOrElse(Nil).flatMap(_.iban).mkString(", ")}
| 2) Can read balances: ${json.access.balances.getOrElse(Nil).flatMap(_.iban).mkString(", ")}
| 3) Can read transactions: ${json.access.transactions.getOrElse(Nil).flatMap(_.iban).mkString(", ")}
|
|This consent will end on date ${json.validUntil}.
|
|I understand that I can revoke this consent at any time.
|""".stripMargin
"#confirm-bg-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"#confirm-bg-consent-request-form-text *" #> s"""$formText""" &
"#confirm-bg-consent-request-confirm-submit-button" #> SHtml.onSubmitUnit(confirmConsentRequestProcess)
"#confirm-bg-consent-request-deny-submit-button" #> SHtml.onSubmitUnit(denyConsentRequestProcess)
case everythingElse =>
S.error(everythingElse.toString)
"#confirm-bg-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"type=submit" #> ""
}
}
private def callGetConsentByConsentId(): Box[MappedConsent] = {
val requestParam = List(
ObpS.param("CONSENT_ID"),
)
if (requestParam.count(_.isDefined) < requestParam.size) {
Failure("Parameter CONSENT_ID is missing, please set it in the URL")
} else {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
Consents.consentProvider.vend.getConsentByConsentId(consentId)
}
}
private def confirmConsentRequestProcess() = {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
S.redirectTo(
s"/confirm-bg-consent-request-sca?CONSENT_ID=${consentId}"
)
}
private def denyConsentRequestProcess() = {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)
S.redirectTo(
s"$redirectUriValue?CONSENT_ID=${consentId}"
)
}
private def confirmConsentRequestProcessSca() = {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)
S.redirectTo(
s"$redirectUriValue?CONSENT_ID=${consentId}"
)
}
def confirmBgConsentRequest: CssSel = {
"#otp-value" #> SHtml.textElem(otpValue) &
"type=submit" #> SHtml.onSubmitUnit(confirmConsentRequestProcessSca)
}
}

View File

@ -27,9 +27,8 @@ TESOBE (http://www.tesobe.com/)
package code.snippet
import java.util
import code.api.{Constant, DirectLogin}
import code.api.util.{APIUtil, ErrorMessages, X509}
import code.api.util.{APIUtil, ErrorMessages, KeycloakAdmin, X509}
import code.consumer.Consumers
import code.model.dataAccess.AuthUser
import code.model.{Consumer, _}
@ -176,6 +175,10 @@ class ConsumerRegistration extends MdcLoggable {
oAuth2Client
})
}
// In case we use Keycloak as Identity Provider we create corresponding client at Keycloak side a well
if(KeycloakAdmin.integrateWithKeycloak) KeycloakAdmin.createKeycloakConsumer(consumer)
val registerConsumerSuccessMessageWebpage = getWebUiPropsValue(
"webui_register_consumer_success_message_webpage",
"Thanks for registering your consumer with the Open Bank Project API! Here is your developer information. Please save it in a secure location.")

View File

@ -28,24 +28,28 @@ package code.snippet
import code.api.util.APIUtil._
import code.api.util.ErrorMessages.InvalidJsonFormat
import code.api.util.{APIUtil, CustomJsonFormats}
import code.api.util.{APIUtil, CustomJsonFormats, DateTimeUtil}
import code.api.v5_1_0.{APIMethods510, ConsentJsonV510}
import code.api.v5_0_0.{APIMethods500, ConsentJsonV500, ConsentRequestResponseJson}
import code.api.v3_1_0.{APIMethods310, ConsentChallengeJsonV310, ConsumerJsonV310}
import code.consent.{ConsentStatus}
import code.consent.ConsentStatus
import code.consumer.Consumers
import code.model.dataAccess.AuthUser
import code.util.Helper.{MdcLoggable, ObpS}
import net.liftweb.common.Full
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{GetRequest, PostRequest, RequestVar, S, SHtml}
import net.liftweb.http.{GetRequest, PostRequest, RequestVar, S, SHtml, SessionVar}
import net.liftweb.json
import net.liftweb.json.Formats
import net.liftweb.util.CssSel
import net.liftweb.util.Helpers._
class VrpConsentCreation extends MdcLoggable with RestHelper with APIMethods510 with APIMethods500 with APIMethods310 {
protected implicit override def formats: Formats = CustomJsonFormats.formats
private object otpValue extends RequestVar("123456")
private object consentRequestIdValue extends SessionVar("")
def confirmVrpConsentRequest = {
getConsentRequest match {
case Left(error) => {
@ -57,11 +61,58 @@ class VrpConsentCreation extends MdcLoggable with RestHelper with APIMethods510
case Right(response) => {
tryo {json.parse(response).extract[ConsentRequestResponseJson]} match {
case Full(consentRequestResponseJson) =>
val jsonAst = consentRequestResponseJson.payload
val currency = (jsonAst \ "to_account" \ "limit" \ "currency").extract[String]
val ttl: Long = (jsonAst \ "time_to_live").extract[Long]
val consumer = Consumers.consumers.vend.getConsumerByConsumerId(consentRequestResponseJson.consumer_id)
val formText =
s"""I, ${AuthUser.currentUser.map(_.firstName.get).getOrElse("")} ${AuthUser.currentUser.map(_.lastName.get).getOrElse("")}, consent to the service provider ${consumer.map(_.name.get).getOrElse("")} making transfers on my behalf from my bank account number ${(jsonAst \ "from_account" \ "account_routing" \ "address").extract[String]}, to the beneficiary ${(jsonAst \ "to_account" \ "counterparty_name").extract[String]}, account number ${(jsonAst \ "to_account" \ "account_routing" \ "address").extract[String]} at bank code ${(jsonAst \ "to_account" \ "bank_routing" \ "address").extract[String]}.
|
|The transfers governed by this consent must respect the following rules:
|
| 1) The grand total amount will not exceed {limit ($currency), (${(jsonAst \ "to_account" \ "limit" \ "max_total_amount").extract[String]})}
| 2) Any single amount will not exceed {limit ($currency), (${(jsonAst \ "to_account" \ "limit" \ "max_single_amount").extract[String]})}
| 3) The maximum amount per month that can be transferred is {limit ($currency), (${(jsonAst \ "to_account" \ "limit" \ "max_monthly_amount").extract[String]})} over {limit (${(jsonAst \ "to_account" \ "limit" \ "max_number_of_monthly_transactions").extract[String]}) transactions.
| 4) The maximum amount per year that can be transferred is {limit ($currency), (${(jsonAst \ "to_account" \ "limit" \ "max_yearly_amount").extract[String]})} over {limit (${(jsonAst \ "to_account" \ "limit" \ "max_number_of_yearly_transactions").extract[String]}) transactions.
|
|This consent will start on date {${(jsonAst \ "valid_from").extract[String]}} and be valid for ${DateTimeUtil.formatDuration(ttl)}.
|
|I understand that I can revoke this consent at any time.
|""".stripMargin
"#confirm-vrp-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"#confirm-vrp-consent-request-response-json *" #> s"""${json.prettyRender(json.Extraction.decompose(consentRequestResponseJson.payload))}""" &
"#confirm-vrp-consent-request-confirm-submit-button" #> SHtml.onSubmitUnit(confirmConsentRequestProcess)
"#confirm-vrp-consent-request-form-text *" #> s"""$formText""" &
"#from_bank_routing_scheme [value]" #> s"${(jsonAst \ "from_account" \ "bank_routing" \ "scheme").extract[String]}" &
"#from_bank_routing_address [value]" #> s"${(jsonAst \ "from_account" \ "bank_routing" \ "address").extract[String]}" &
"#from_branch_routing_scheme [value]" #> s"${(jsonAst \ "from_account" \ "branch_routing" \ "scheme").extract[String]}" &
"#from_branch_routing_address [value]" #> s"${(jsonAst \ "from_account" \ "branch_routing" \ "address").extract[String]}" &
"#from_routing_scheme [value]" #> s"${(jsonAst \ "from_account" \ "account_routing" \ "scheme").extract[String]}" &
"#from_routing_address [value]" #> s"${(jsonAst \ "from_account" \ "account_routing" \ "address").extract[String]}" &
"#to_bank_routing_scheme [value]" #> s"${(jsonAst \ "to_account" \ "bank_routing" \ "scheme").extract[String]}" &
"#to_bank_routing_address [value]" #> s"${(jsonAst \ "to_account" \ "bank_routing" \ "address").extract[String]}" &
"#to_branch_routing_scheme [value]" #> s"${(jsonAst \ "to_account" \ "branch_routing" \ "scheme").extract[String]}" &
"#to_branch_routing_address [value]" #> s"${(jsonAst \ "to_account" \ "branch_routing" \ "address").extract[String]}" &
"#to_routing_scheme [value]" #> s"${(jsonAst \ "to_account" \ "account_routing" \ "scheme").extract[String]}" &
"#to_routing_address [value]" #> s"${(jsonAst \ "to_account" \ "account_routing" \ "address").extract[String]}" &
"#counterparty_name [value]" #> s"${(jsonAst \ "to_account" \ "counterparty_name").extract[String]}" &
"#currency [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "currency").extract[String]}" &
"#max_single_amount [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_single_amount").extract[String]}" &
"#max_monthly_amount [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_monthly_amount").extract[String]}" &
"#max_yearly_amount [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_yearly_amount").extract[String]}" &
"#max_total_amount [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_total_amount").extract[String]}" &
"#max_number_of_monthly_transactions [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_number_of_monthly_transactions").extract[String]}" &
"#max_number_of_yearly_transactions [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_number_of_yearly_transactions").extract[String]}" &
"#max_number_of_transactions [value]" #> s"${(jsonAst \ "to_account" \ "limit" \ "max_number_of_transactions").extract[String]}" &
"#time_to_live_in_seconds [value]" #> s"${(jsonAst \ "time_to_live").extract[String]}" &
"#valid_from [value]" #> s"${(jsonAst \ "valid_from").extract[String]}" &
"#email [value]" #> s"${(jsonAst \ "email").extract[String]}" &
"#phone_number [value]" #> s"${(jsonAst \ "phone_number").extract[String]}" &
showHideElements &
"#confirm-vrp-consent-request-confirm-submit-button" #> SHtml.onSubmitUnit(confirmConsentRequestProcess)
case _ =>
"#confirm-vrp-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"#confirm-vrp-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"#confirm-vrp-consent-request-form-title *" #> s"Please confirm or deny the following consent request:" &
"#confirm-vrp-consent-request-response-json *" #>
s"""$InvalidJsonFormat The Json body should be the $ConsentRequestResponseJson.
|Please check `Get Consent Request` endpoint separately! """.stripMargin &
@ -71,6 +122,25 @@ class VrpConsentCreation extends MdcLoggable with RestHelper with APIMethods510
}
}
def showHideElements: CssSel = {
if (ObpS.param("format").isEmpty) {
"#confirm-vrp-consent-request-form-text-div [style]" #> "display:block" &
"#confirm-vrp-consent-request-form-fields [style]" #> "display:none"
} else if(ObpS.param("format").contains("1")) {
"#confirm-vrp-consent-request-form-text-div [style]" #> "display:none" &
"#confirm-vrp-consent-request-form-fields [style]" #> "display:block"
} else if(ObpS.param("format").contains("2")) {
"#confirm-vrp-consent-request-form-text-div [style]" #> "display:block" &
"#confirm-vrp-consent-request-form-fields [style]" #> "display:none"
} else if(ObpS.param("format").contains("3")) {
"#confirm-vrp-consent-request-form-text-div [style]" #> "display:block" &
"#confirm-vrp-consent-request-form-fields [style]" #> "display:block"
} else {
"#confirm-vrp-consent-request-form-text-div [style]" #> "display:block" &
"#confirm-vrp-consent-request-form-fields [style]" #> "display:none"
}
}
def confirmVrpConsent = {
"#otp-value" #> SHtml.textElem(otpValue) &
@ -218,7 +288,7 @@ class VrpConsentCreation extends MdcLoggable with RestHelper with APIMethods510
}
private def getConsentRequest: Either[(String, Int), String] = {
val requestParam = List(
ObpS.param("CONSENT_REQUEST_ID"),
)
@ -226,11 +296,14 @@ class VrpConsentCreation extends MdcLoggable with RestHelper with APIMethods510
if(requestParam.count(_.isDefined) < requestParam.size) {
return Left(("Parameter CONSENT_REQUEST_ID is missing, please set it in the URL", 500))
}
val consentRequestId = ObpS.param("CONSENT_REQUEST_ID")openOr("")
consentRequestIdValue.set(consentRequestId)
val pathOfEndpoint = List(
"consumer",
"consent-requests",
ObpS.param("CONSENT_REQUEST_ID")openOr("")
consentRequestId
)
val authorisationsResult = callEndpoint(Implementations5_0_0.getConsentRequest, pathOfEndpoint, GetRequest)

View File

@ -217,7 +217,9 @@ object Helper extends Loggable {
"/dummy-user-tokens","/create-sandbox-account",
"/add-user-auth-context-update-request","/otp",
"/terms-and-conditions", "/privacy-policy",
"/confirm-vrp-consent-request",
"/confirm-bg-consent-request",
"/confirm-bg-consent-request-sca",
"/confirm-vrp-consent-request",
"/confirm-vrp-consent",
"/consent-screen",
"/consent",

View File

@ -0,0 +1,49 @@
<!--
Open Bank Project - API
Copyright (C) 2011-2017, TESOBE GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Sebastian Henschel : sebastian AT tesobe DOT com
-->
<div data-lift="surround?with=default;at=content">
<div id="confirm-bg-consent-request-sca" data-lift="BerlinGroupConsent.confirmBgConsentRequest">
<form class="login" method="post">
<div class="form-group">
<h3>Please enter the One Time Password (OTP) that we just sent to you</h3>
<p>Please check your phone or email for the value to enter.</p>
<input class="form-control" id="otp-value" type="text" value="123" tabindex="0" autofocus
autocomplete="off" aria-label="One Time Password"/>
</div>
<div class="row">
<input id="authorise-submit-button" class="btn btn-danger pull-right" type="submit" value="Submit"
tabindex="0"/>
</div>
<br>
</form>
</div>
</div>

View File

@ -0,0 +1,57 @@
<!--
Open Bank Project - API
Copyright (C) 2011-2025, TESOBE GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Hongwei Zhang : Hongwei AT tesobe DOT com
-->
<div data-lift="surround?with=default;at=content">
<div id="confirm-bg-consent-request" style="width: 90%; margin: 0 auto;" data-lift="BerlinGroupConsent.confirmBerlinGroupConsentRequest">
<div class="form-group">
<h3 id="confirm-bg-consent-request-form-title">Please check the Berlin Group Consent Request: </h3>
<div id="confirm-bg-consent-request-form-text-div">
<pre id="confirm-bg-consent-request-form-text"></pre>
</div>
</div>
<form method="post">
<div class="row">
<a id="confirm-bg-consent-request-deny-submit-button" class="btn btn-danger" href="/">Deny</span></a>
<input id="confirm-bg-consent-request-confirm-submit-button" class="btn btn-danger" type="submit" value="Confirm"
tabindex="0"/>
</div>
<br>
<style>
pre {
white-space: pre-wrap; /* Preserve whitespace and wrap at spaces */
word-break: normal; /* Prevent breaking words */
overflow-wrap: normal; /* Prevent breaking words */
overflow-x: auto; /* Allow horizontal scrolling if necessary */
}
</style>
</form>
</div>
</div>

View File

@ -28,23 +28,172 @@ Berlin 13359, Germany
-->
<div data-lift="surround?with=default;at=content">
<div id="confirm-vrp-consent-request-div" data-lift="VrpConsentCreation.confirmVrpConsentRequest">
<div id="confirm-vrp-consent-request" style="width: 90%; margin: 0 auto;" data-lift="VrpConsentCreation.confirmVrpConsentRequest">
<div class="form-group">
<h3 id="confirm-vrp-consent-request-form-title">Please check the VRP Consent Request: </h3>
<div id="confirm-vrp-consent-request-form-text-div">
<pre id="confirm-vrp-consent-request-form-text"></pre>
</div>
</div>
<form method="post">
<div class="form-group">
<h3 id="confirm-vrp-consent-request-form-title">Please check the VRP Consent Request: </h3>
<div class="wrap-text" id="confirm-vrp-consent-request-form-fields">
<div class="form-group col-md-6">
<h4>From Account</h4>
<div class="form-row">
<div class="form-group col-md-6">
<label for="from_bank_routing_scheme">Bank Scheme</label>
<input type="text" name="from_bank_routing_scheme" id="from_bank_routing_scheme" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="from_bank_routing_address">Bank Address</label>
<input type="text" name="from_bank_routing_address" id="from_bank_routing_address" class="form-control">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="from_branch_routing_scheme">Branch Scheme</label>
<input type="text" name="from_branch_routing_scheme" id="from_branch_routing_scheme" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="from_branch_routing_address">Branch Address</label>
<input type="text" name="from_branch_routing_address" id="from_branch_routing_address" class="form-control">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="from_routing_scheme">Account Scheme</label>
<input type="text" name="from_routing_scheme" id="from_routing_scheme" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="from_routing_address">Account Address</label>
<input type="text" name="from_routing_address" id="from_routing_address" class="form-control">
</div>
</div>
</div>
<div class="form-group col-md-6">
<h4>To Account</h4>
<div class="form-row">
<div class="form-group col-md-6">
<label for="to_bank_routing_scheme">Bank Scheme</label>
<input type="text" name="to_bank_routing_scheme" id="to_bank_routing_scheme" class="form-control" >
</div>
<div class="form-group col-md-6">
<label for="to_bank_routing_address">Bank Address</label>
<input type="text" name="to_bank_routing_address" id="to_bank_routing_address" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="to_branch_routing_scheme">Branch Scheme</label>
<input type="text" name="to_branch_routing_scheme" id="to_branch_routing_scheme" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="to_branch_routing_address">Branch Address</label>
<input type="text" name="to_branch_routing_address" id="to_branch_routing_address" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="to_routing_scheme">Account Scheme</label>
<input type="text" name="to_routing_scheme" id="to_routing_scheme" class="form-control" >
</div>
<div class="form-group col-md-6">
<label for="to_routing_address">Account Address</label>
<input type="text" name="to_routing_address" id="to_routing_address" class="form-control">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-12">
<label for="counterparty_name">Counterparty Name (For your reference.)</label>
<input type="text" name="counterparty_name" id="counterparty_name" class="form-control">
</div>
</div>
</div>
<h4>Limits</h4>
<div class="form-group col-md-12">
<div class="form-group col-md-4">
<div class="form-row">
<div class="form-group col-md-12">
<label for="currency">Currency</label>
<input type="text" name="currency" id="currency" class="form-control">
</div>
</div>
</div>
<div class="form-group col-md-4">
<div class="form-row">
<div class="form-group col-md-12">
<label for="max_single_amount">Max single amount</label>
<input type="number" min="0" value="100" name="max_single_amount" id="max_single_amount" class="form-control">
</div>
<div class="form-group col-md-12">
<label for="max_monthly_amount">Max monthly amount</label>
<input type="number" min="0" value="100" name="max_monthly_amount" id="max_monthly_amount" class="form-control">
</div>
<div class="form-group col-md-12">
<label for="max_yearly_amount">Max yearly amount</label>
<input type="number" min="0" value="1200" name="max_yearly_amount" id="max_yearly_amount" class="form-control">
</div>
<div class="form-group col-md-12">
<label for="max_total_amount">Max total amount</label>
<input type="number" min="0" value="1200" name="max_total_amount" id="max_total_amount" class="form-control">
</div>
</div>
</div>
<div class="form-group col-md-4">
<div class="form-group col-md-12">
<label for="max_number_of_monthly_transactions">Max number of monthly transactions</label>
<input type="number" min="0" value="1" name="max_number_of_monthly_transactions" id="max_number_of_monthly_transactions" class="form-control">
</div>
<div class="form-group col-md-12">
<label for="max_number_of_yearly_transactions">Max number of yearly transactions</label>
<input type="number" min="0" value="12" name="max_number_of_yearly_transactions" id="max_number_of_yearly_transactions" class="form-control">
</div>
<div class="form-group col-md-12">
<label for="max_number_of_transactions">Max number of total transactions</label>
<input type="number" min="0" value="12" name="max_number_of_transactions" id="max_number_of_transactions" class="form-control">
</div>
</div>
</div>
<h4>Other</h4>
<div class="form-group col-md-12">
<div class="form-group col-md-6">
<label for="valid_from">Valid from</label>
<input type="text" name="valid_from" id="valid_from" class="form-control" data-date-format="YYYY-MM-DDTHH:mm:ss">
</div>
<div class="form-group col-md-6">
<label for="time_to_live_in_seconds">Time to live in seconds</label>
<input type="number" min="1" value="31556926" name="time_to_live_in_seconds" id="time_to_live_in_seconds" class="form-control">
</div>
</div>
<hr>
<div class="form-group col-md-12">
<div class="form-group col-md-6">
<label for="phone_number">Phone number</label>
<input type="text" name="phone_number" id="phone_number" class="form-control">
</div>
<div class="form-group col-md-6">
<label for="email">Email</label>
<input type="text" name="email" id="email" class="form-control">
</div>
</div>
</div>
<pre id="confirm-vrp-consent-request-response-json">
</pre>
<div class="row">
<input id="confirm-vrp-consent-request-confirm-submit-button" class="btn btn-danger pull-right" type="submit" value="Confirm"
<a id="confirm-vrp-consent-request-deny-submit-button" class="btn btn-danger" href="/">Deny</span></a>
<input id="confirm-vrp-consent-request-confirm-submit-button" class="btn btn-danger" type="submit" value="Confirm"
tabindex="0"/>
<a id="confirm-vrp-consent-request-deny-submit-button" class="btn btn-danger pull-right" href="/">Deny</span></a>
</div>
<br>
<script>
// Apply the `readonly` attribute dynamically
const elements = document.querySelectorAll('#confirm-vrp-consent-request input, #confirm-vrp-consent-request textarea');
elements.forEach(el => el.setAttribute('readonly', 'readonly'));
</script>
<style>
pre {
white-space: pre-wrap; /* Preserve whitespace and wrap at spaces */
word-break: normal; /* Prevent breaking words */
overflow-wrap: normal; /* Prevent breaking words */
overflow-x: auto; /* Allow horizontal scrolling if necessary */
}
</style>
</form>
</div>
</div>

View File

@ -408,6 +408,7 @@ input{
}
#add-user-auth-context-update-request-div form,
#confirm-bg-consent-request-sca form,
#confirm-user-auth-context-update-request-div form{
max-width: 500px;
margin: 0 auto;
@ -415,6 +416,7 @@ input{
}
#add-user-auth-context-update-request-div #identifier-error-div,
#confirm-bg-consent-request-sca #identifier-error-div,
#confirm-user-auth-context-update-request-div #otp-value-error-div{
text-align: justify;
color: black;
@ -430,6 +432,7 @@ input{
}
#add-user-auth-context-update-request-div #identifier-error .error,
#confirm-bg-consent-request-sca #identifier-error .error,
#confirm-user-auth-context-update-request-div #otp-value-error .error{
background-color: white;
font-family: Roboto-Regular,sans-serif;