mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 16:36:54 +00:00
Merge pull request #2484 from constantine2nd/develop
VRP Consent Acceptance
This commit is contained in:
commit
92de2cdc61
@ -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 -->
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
36
obp-api/src/main/scala/code/api/util/DateTimeUtil.scala
Normal file
36
obp-api/src/main/scala/code/api/util/DateTimeUtil.scala
Normal 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(", ")
|
||||
}
|
||||
}
|
||||
107
obp-api/src/main/scala/code/api/util/KeycloakAdmin.scala
Normal file
107
obp-api/src/main/scala/code/api/util/KeycloakAdmin.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
132
obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala
Normal file
132
obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
@ -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.")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
49
obp-api/src/main/webapp/confirm-bg-consent-request-sca.html
Normal file
49
obp-api/src/main/webapp/confirm-bg-consent-request-sca.html
Normal 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>
|
||||
|
||||
57
obp-api/src/main/webapp/confirm-bg-consent-request.html
Normal file
57
obp-api/src/main/webapp/confirm-bg-consent-request.html
Normal 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>
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user