Merge remote-tracking branch 'Simon/develop' into develop

# Conflicts:
#	obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala
This commit is contained in:
hongwei 2025-05-27 15:33:49 +02:00
commit d7e30ec856
43 changed files with 1329 additions and 190 deletions

View File

@ -1,5 +1,10 @@
FROM jetty:9.4-jdk11-alpine
ENV JMX_EXPORTER_VERSION=1.2.0
# To enable add "-javaagent:$JETTY_BASE/jmx-exporter.jar=8090:$JETTY_BASE/prometheus_config.yml" to the JAVA_OPTIONS
RUN wget https://github.com/prometheus/jmx_exporter/releases/download/$JMX_EXPORTER_VERSION/jmx_prometheus_javaagent-$JMX_EXPORTER_VERSION.jar -o /var/lib/jetty/jmx-exporter.jar
COPY .github/jmx_exporter.config /var/lib/jetty/prometheus_config.yml
# Copy OBP source code
# Copy build artifact (.war file) into jetty from 'maven' stage.
COPY /obp-api/target/obp-api-1.*.war /var/lib/jetty/webapps/ROOT.war

15
.github/jmx_exporter.config vendored Normal file
View File

@ -0,0 +1,15 @@
---
lowercaseOutputLabelNames: true
lowercaseOutputName: true
whitelistObjectNames: ["java.lang:type=OperatingSystem"]
blacklistObjectNames: []
rules:
- pattern: 'java.lang<type=OperatingSystem><>(committed_virtual_memory|free_physical_memory|free_swap_space|total_physical_memory|total_swap_space)_size:'
name: os_$1_bytes
type: GAUGE
attrNameSnakeCase: true
- pattern: 'java.lang<type=OperatingSystem><>((?!process_cpu_time)\w+):'
name: os_$1
type: GAUGE
attrNameSnakeCase: true
- pattern: ".*"

View File

@ -0,0 +1,35 @@
name: Regular base image update check
on:
schedule:
- cron: "0 5 * * *"
workflow_dispatch:
env:
## Sets environment variable
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Docker Image Update Checker
id: baseupdatecheck
uses: lucacome/docker-image-update-checker@v2.0.0
with:
base-image: jetty:9.4-jdk11-alpine
image: ${{ env.DOCKER_HUB_ORGANIZATION }}/obp-api:latest
- name: Trigger build_container_develop_branch workflow
uses: actions/github-script@v6
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build_container_develop_branch.yml',
ref: 'refs/heads/develop'
});
if: steps.baseupdatecheck.outputs.needs-updating == 'true'

View File

@ -3,6 +3,7 @@ name: Build and publish container develop
# read-write repo token
# access to secrets
on:
workflow_dispatch:
push:
branches:
- develop

View File

@ -28,12 +28,12 @@ jobs:
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Extract branch name
shell: bash
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
- name: Set up JDK 11
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'adopt'

View File

@ -73,7 +73,7 @@ starConnector_supported_types=mapped,internal
#endpointMapping.cache.ttl.seconds=0
## webui props cache time-to-live in seconds
#webui.props.cache.ttl.seconds=20
#webui.props.cache.ttl.seconds=0
## DynamicEntity cache time-to-live in seconds, default is 30, the value is 0 at test environment
## no 0 value will cause new dynamic entity will be shown after that seconds
@ -183,9 +183,9 @@ jwt.use.ssl=false
## Enable writing API metrics (which APIs are called) to RDBMS
write_metrics=true
write_metrics=false
## Enable writing connector metrics (which methods are called)to RDBMS
write_connector_metrics=true
write_connector_metrics=false
## ElasticSearch
#allow_elasticsearch=true
@ -304,7 +304,7 @@ sandbox_data_import_secret=change_me
payments_enabled=true
## Transaction requests are replacing simple payments starting from 1.4.0
transactionRequests_enabled=true
transactionRequests_enabled=false
transactionRequests_connector=mapped
## Transaction Request Types that are supported on this server. Possible values might include SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM
@ -1147,6 +1147,9 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
# the alias prefix path for BerlinGroupV1.3 (OBP built-in is berlin-group/v1.3), the format must be xxx/yyy, eg: 0.6/v1
#berlin_group_v1_3_alias_path=
# Berlin Group URL version
#berlin_group_version_1_canonical_path=v1.3
# Show the path inside of Berlin Group error message
#berlin_group_error_message_show_path = true
@ -1157,12 +1160,18 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
## Berlin Group Create Consent Frequency per Day Upper Limit
#berlin_group_frequency_per_day_upper_limit = 4
## Berlin Group Create Consent ASPSP-SCA-Approach response header value
#berlin_group_aspsp_sca_approach = redirect
# Support multiple brands on one instance. Note this needs checking on a clustered environment
#brands_enabled=false
# Support removing the app type checkbox during consumer registration
#consumer_registration.display_app_type=true
# Default logo URL during of consumer
#consumer_default_logo_url=
# if set this props, we can automatically grant the Entitlements required to use all the Dynamic Endpoint roles belonging
# to the bank_ids (Spaces) the User has access to via their validated email domain. Entitlements are generated /refreshed
# both following manual login and Direct Login token generation (POST).

View File

@ -567,6 +567,8 @@ class Boot extends MdcLoggable {
Menu.i("Consents") / "consents",
Menu.i("Debug") / "debug",
Menu.i("debug-basic") / "debug" / "debug-basic",
Menu.i("debug-default-header") / "debug" / "debug-default-header",
Menu.i("debug-default-footer") / "debug" / "debug-default-footer",
Menu.i("debug-localization") / "debug" / "debug-localization",
Menu.i("debug-plain") / "debug" / "debug-plain",
Menu.i("debug-webui") / "debug" / "debug-webui",

View File

@ -1,6 +1,15 @@
package code.api.berlin.group
import code.api.util.APIUtil
import com.openbankproject.commons.util.ApiVersion.berlinGroupV13
import com.openbankproject.commons.util.ScannedApiVersion
import net.liftweb.common.Full
object ConstantsBG {
val berlinGroupVersion1: ScannedApiVersion = APIUtil.getPropsValue("berlin_group_version_1_canonical_path") match {
case Full(props) => berlinGroupV13.copy(apiShortVersion = props)
case _ => berlinGroupV13
}
object SigningBasketsStatus extends Enumeration {
type SigningBasketsStatus = Value
// Only the codes

View File

@ -2,6 +2,7 @@ package code.api.builder.AccountInformationServiceAISApi
import code.api.APIFailureNewStyle
import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID}
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _}
import code.api.berlin.group.v1_3.model._
import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass}
@ -9,8 +10,8 @@ import code.api.util.APIUtil.{passesPsd2Aisp, _}
import code.api.util.ApiTag._
import code.api.util.ErrorMessages._
import code.api.util.NewStyle.HttpCode
import code.api.util.newstyle.BalanceNewStyle
import code.api.util._
import code.api.util.newstyle.BalanceNewStyle
import code.bankconnectors.Connector
import code.consent.{ConsentStatus, Consents}
import code.context.{ConsentAuthContextProvider, UserAuthContextProvider}
@ -22,7 +23,6 @@ import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model._
import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb
import net.liftweb.common.{Empty, Full}
import net.liftweb.http.js.JE.JsRaw
@ -34,7 +34,7 @@ import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
object APIMethods_AccountInformationServiceAISApi extends RestHelper {
val apiVersion = ApiVersion.berlinGroupV13
val apiVersion = ConstantsBG.berlinGroupVersion1
val resourceDocs = ArrayBuffer[ResourceDoc]()
val apiRelations = ArrayBuffer[ApiRelation]()
protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what)
@ -156,11 +156,28 @@ recurringIndicator:
consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostConsentJson]
}
_ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessIsEmpty, cc=callContext) {
consentJson.access.accounts.isDefined ||
consentJson.access.balances.isDefined ||
consentJson.access.transactions.isDefined
_ <- if (consentJson.access.availableAccounts.isDefined) {
for {
_ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessAvailableAccounts, cc = callContext) {
consentJson.access.availableAccounts.contains("allAccounts")
}
_ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessRecurringIndicator, cc = callContext) {
!consentJson.recurringIndicator
}
_ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessFrequencyPerDay, cc = callContext) {
consentJson.frequencyPerDay == 1
}
} yield Full(())
} else {
Helper.booleanToFuture(
failMsg = BerlinGroupConsentAccessIsEmpty, cc = callContext) {
consentJson.access.accounts.isDefined ||
consentJson.access.balances.isDefined ||
consentJson.access.transactions.isDefined
}
}
upperLimit = APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4)
_ <- Helper.booleanToFuture(failMsg = FrequencyPerDayError, cc=callContext) {
consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit
@ -242,10 +259,15 @@ recurringIndicator:
case "consents" :: consentId :: Nil JsonDelete _ => {
cc =>
for {
(Full(user), callContext) <- authenticatedAccess(cc)
(_, callContext) <- applicationAccess(cc)
_ <- passesPsd2Aisp(callContext)
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, ConsentNotFound)
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, ConsentNotFound, 403)
}
consumerIdFromConsent = consent.mConsumerId.get
consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None")
_ <- Helper.booleanToFuture(failMsg = s"$ConsentNotFound $consumerIdFromConsent != $consumerIdFromCurrentCall", failCode = 403, cc = cc.callContext) {
consumerIdFromConsent == consumerIdFromCurrentCall
}
_ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map {
i => connectorEmptyResponse(i, callContext)
@ -755,7 +777,7 @@ This method returns the SCA status of a consent initiation's authorisation sub-r
(_, callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Aisp(callContext)
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)")
unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)", 403)
}
(challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext)
} yield {
@ -787,10 +809,10 @@ This method returns the SCA status of a consent initiation's authorisation sub-r
case "consents" :: consentId:: "status" :: Nil JsonGet _ => {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(_, callContext) <- applicationAccess(cc)
_ <- passesPsd2Aisp(callContext)
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, ConsentNotFound)
unboxFullOrFail(_, callContext, ConsentNotFound, 403)
}
} yield {
val status = consent.status
@ -1137,7 +1159,7 @@ using the extended forms as indicated above.
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Aisp(callContext)
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, ConsentNotFound)
unboxFullOrFail(_, callContext, ConsentNotFound, 403)
}
(challenges, callContext) <- NewStyle.function.createChallengesC2(
List(u.userId),
@ -1300,7 +1322,7 @@ Maybe in a later version the access path will change.
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Aisp(callContext)
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, callContext, ConsentNotFound)
unboxFullOrFail(_, callContext, ConsentNotFound, 403)
}
failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation "
updateJson <- NewStyle.function.tryons(failMsg, 400, callContext) {

View File

@ -34,7 +34,7 @@ object BgSpecValidation {
if (date.isBefore(today)) {
Left(s"$InvalidDateFormat The `validUntil` date ($dateStr) cannot be in the past!")
} else if (date.isAfter(MaxValidDays)) {
} else if (date.isEqual(MaxValidDays) || date.isAfter(MaxValidDays)) {
Left(s"$InvalidDateFormat The `validUntil` date ($dateStr) exceeds the maximum allowed period of 180 days (until $MaxValidDays).")
} else {
Right(date) // Valid date

View File

@ -1,5 +1,6 @@
package code.api.builder.CommonServicesApi
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3}
import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi
import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi
@ -14,7 +15,7 @@ import scala.collection.mutable.ArrayBuffer
//TODO maybe we can remove this common services, it just show other apis in this tag. no new ones.
object APIMethods_CommonServicesApi extends RestHelper {
val apiVersion = ApiVersion.berlinGroupV13
val apiVersion = ConstantsBG.berlinGroupVersion1
val resourceDocs = ArrayBuffer[ResourceDoc]()
val apiRelations = ArrayBuffer[ApiRelation]()
val codeContext = CodeContext(resourceDocs, apiRelations)

View File

@ -1,5 +1,6 @@
package code.api.builder.ConfirmationOfFundsServicePIISApi
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._
import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3}
import code.api.util.APIUtil._
@ -20,7 +21,7 @@ import scala.collection.immutable.Nil
import scala.collection.mutable.ArrayBuffer
object APIMethods_ConfirmationOfFundsServicePIISApi extends RestHelper {
val apiVersion = ApiVersion.berlinGroupV13
val apiVersion = ConstantsBG.berlinGroupVersion1
val resourceDocs = ArrayBuffer[ResourceDoc]()
val apiRelations = ArrayBuffer[ApiRelation]()
protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what)

View File

@ -1,5 +1,6 @@
package code.api.berlin.group.v1_3
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.model.TransactionStatus.mapTransactionStatus
import code.api.berlin.group.v1_3.model._
import code.api.util.APIUtil._
@ -63,7 +64,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
case class CoreAccountJsonV13(
resourceId: String,
iban: String,
bban: String,
bban: Option[String],
currency: String,
name: String,
product: String,
@ -138,6 +139,10 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
iban: String,
currency : Option[String] = None,
)
case class FromAccountJson(
iban: String,
currency : Option[String] = None,
)
case class TransactionJsonV13(
transactionId: String,
creditorName: String,
@ -191,7 +196,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
)
case class TransactionsJsonV13(
account:FromAccount,
account: FromAccountJson,
transactions:TransactionsV13Transactions,
)
@ -260,7 +265,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
recurringIndicator: Boolean,
validUntil: String,
frequencyPerDay: Int,
combinedServiceIndicator: Boolean,
combinedServiceIndicator: Option[Boolean],
lastActionDate: String,
consentStatus: String
)
@ -346,12 +351,12 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
CoreAccountJsonV13(
resourceId = x.accountId.value,
iban = iBan,
bban = bBan,
bban = None,
currency = x.currency,
name = x.name,
cashAccountType = cashAccountType,
product = x.accountType,
balances = accountBalances,
balances = if(canReadBalances) accountBalances else None,
_links = CoreAccountLinksJsonV13(
balances = if(canReadBalances) Some(balanceRef) else None,
transactions = if(canReadTransactions) Some(transactionRef) else None,
@ -377,7 +382,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
CoreAccountJsonV13(
resourceId = x.accountId.value,
iban = iBan,
bban = bBan,
bban = None,
currency = x.currency,
name = x.name,
cashAccountType = x.accountType,
@ -454,14 +459,17 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
))
}
def createTransactionJSON(bankAccount: BankAccount, transaction : ModeratedTransaction, creditorAccount: CreditorAccountJson) : TransactionJsonV13 = {
val bookingDate = transaction.startDate.getOrElse(null)
val valueDate = transaction.finishDate.getOrElse(null)
def createTransactionJSON(bankAccount: BankAccount, transaction : ModeratedTransaction) : TransactionJsonV13 = {
val bookingDate = transaction.startDate.orNull
val valueDate = transaction.finishDate.orNull
val creditorName = bankAccount.label
TransactionJsonV13(
transactionId = transaction.id.value,
creditorName = creditorName,
creditorAccount = creditorAccount,
creditorAccount = CreditorAccountJson(
transaction.otherBankAccount.map(_.iban.orNull).orNull,
transaction.currency
),
transactionAmount = AmountOfMoneyV13(APIUtil.stringOptionOrNull(transaction.currency), transaction.amount.get.toString().trim.stripPrefix("-")),
bookingDate = BgSpecValidation.formatToISODate(bookingDate) ,
valueDate = BgSpecValidation.formatToISODate(valueDate),
@ -490,16 +498,19 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
}
def createTransactionFromRequestJSON(bankAccount: BankAccount, transactionRequest : TransactionRequest, creditorAccount: CreditorAccountJson) : TransactionJsonV13 = {
def createTransactionFromRequestJSON(bankAccount: BankAccount, tr : TransactionRequest) : TransactionJsonV13 = {
val creditorName = bankAccount.accountHolder
val remittanceInformationUnstructured = stringOrNull(transactionRequest.body.description)
val remittanceInformationUnstructured = stringOrNull(tr.body.description)
TransactionJsonV13(
transactionId = transactionRequest.id.value,
transactionId = tr.id.value,
creditorName = creditorName,
creditorAccount = creditorAccount,
transactionAmount = AmountOfMoneyV13(transactionRequest.charge.value.currency, transactionRequest.charge.value.amount.trim.stripPrefix("-")),
bookingDate = BgSpecValidation.formatToISODate(transactionRequest.start_date),
valueDate = BgSpecValidation.formatToISODate(transactionRequest.end_date),
creditorAccount = CreditorAccountJson(
if (tr.other_account_routing_scheme == "IBAN") tr.other_account_routing_address else "",
Some(tr.body.value.currency)
),
transactionAmount = AmountOfMoneyV13(tr.charge.value.currency, tr.charge.value.amount.trim.stripPrefix("-")),
bookingDate = BgSpecValidation.formatToISODate(tr.start_date),
valueDate = BgSpecValidation.formatToISODate(tr.end_date),
remittanceInformationUnstructured = remittanceInformationUnstructured
)
}
@ -508,18 +519,16 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
val accountId = bankAccount.accountId.value
val (iban: String, bban: String) = getIbanAndBban(bankAccount)
val creditorAccount = CreditorAccountJson(
val account = FromAccountJson(
iban = iban,
currency = Some(bankAccount.currency)
)
TransactionsJsonV13(
FromAccount(
iban = iban,
),
account,
TransactionsV13Transactions(
booked= transactions.map(transaction => createTransactionJSON(bankAccount, transaction, creditorAccount)),
pending = transactionRequests.filter(_.status!="COMPLETED").map(transactionRequest => createTransactionFromRequestJSON(bankAccount, transactionRequest, creditorAccount)),
_links = TransactionsV13TransactionsLinks(LinkHrefJson(s"/v1.3/accounts/$accountId"))
booked= transactions.map(transaction => createTransactionJSON(bankAccount, transaction)),
pending = transactionRequests.filter(_.status!="COMPLETED").map(transactionRequest => createTransactionFromRequestJSON(bankAccount, transactionRequest)),
_links = TransactionsV13TransactionsLinks(LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/accounts/$accountId"))
)
)
}
@ -565,7 +574,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
CardTransactionsV13Transactions(
booked= transactions.map(t => createCardTransactionJson(t)),
pending = Nil,
_links = CardTransactionsLinksV13(LinkHrefJson(s"/v1.3/card-accounts/$accountId"))
_links = CardTransactionsLinksV13(LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/card-accounts/$accountId"))
)
)
}
@ -576,7 +585,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisation = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
startAuthorisation = Some(Href(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations"))
)
)
}
@ -595,9 +604,9 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
scaRedirect = Some(Href(s"$scaRedirectUrl")),
status = Some(Href(s"/v1.3/consents/${consent.consentId}/status")),
status = Some(Href(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/status")),
// TODO Introduce a working link
// scaStatus = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations/AUTHORISATIONID")),
// scaStatus = Some(Href(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations/AUTHORISATIONID")),
)
)
case Full("redirection_with_dedicated_start_of_authorization") =>
@ -607,7 +616,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisationWithPsuAuthentication = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
startAuthorisationWithPsuAuthentication = Some(Href(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations"))
)
)
case Full("decoupled") =>
@ -615,7 +624,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
consentId = consent.consentId,
consentStatus = consent.status.toLowerCase(),
_links = ConsentLinksV13(
startAuthorisationWithPsuIdentification = Some(Href(s"/v1.3/consents/${consent.consentId}/authorisations"))
startAuthorisationWithPsuIdentification = Some(Href(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations"))
)
)
case _ =>
@ -626,7 +635,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
def createPutConsentResponseJson(consent: ConsentTrait) : ScaStatusResponse = {
ScaStatusResponse(
scaStatus = consent.status.toLowerCase(),
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/consents/${consent.consentId}/authorisations")))))
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations")))))
)
}
@ -640,7 +649,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
recurringIndicator = createdConsent.recurringIndicator,
validUntil = if(createdConsent.validUntil == null) null else new SimpleDateFormat(DateWithDay).format(createdConsent.validUntil),
frequencyPerDay = createdConsent.frequencyPerDay,
combinedServiceIndicator= createdConsent.combinedServiceIndicator,
combinedServiceIndicator = None,
lastActionDate = if(createdConsent.lastActionDate == null) null else new SimpleDateFormat(DateWithDay).format(createdConsent.lastActionDate),
consentStatus = createdConsent.status.toLowerCase()
)
@ -651,7 +660,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
scaStatus = challenge.scaStatus.map(_.toString).getOrElse("None"),
authorisationId = challenge.authenticationMethodId.getOrElse("None"),
pushMessage = "started", //TODO Not implement how to fill this.
_links = ScaStatusJsonV13(s"/v1.3/consents/${consent.consentId}/authorisations/${challenge.challengeId}")//TODO, Not sure, what is this for??
_links = ScaStatusJsonV13(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/consents/${consent.consentId}/authorisations/${challenge.challengeId}")//TODO, Not sure, what is this for??
)
}
@ -704,9 +713,9 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
paymentId = paymentId,
_links = InitiatePaymentResponseLinks(
scaRedirect = LinkHrefJson(s"$scaRedirectUrl/$paymentId"),
self = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/$paymentId"),
status = LinkHrefJson(s"/v1.3/payments/$paymentId/status"),
scaStatus = LinkHrefJson(s"/v1.3/payments/$paymentId/authorisations/${paymentId}")
self = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/$paymentId"),
status = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/$paymentId/status"),
scaStatus = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/$paymentId/authorisations/${paymentId}")
)
)
}
@ -715,9 +724,9 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
CancelPaymentResponseJson(
"ACTC",
_links = CancelPaymentResponseLinks(
self = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/$paymentId"),
status = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/$paymentId/status"),
startAuthorisation = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/cancellation-authorisations/${paymentId}")
self = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/$paymentId"),
status = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/$paymentId/status"),
startAuthorisation = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/cancellation-authorisations/${paymentId}")
)
)
}
@ -731,7 +740,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
scaStatus = challenge.scaStatus.map(_.toString).getOrElse(""),
authorisationId = challenge.challengeId,
psuMessage = "Please check your SMS at a mobile device.",
_links = ScaStatusJsonV13(s"/v1.3/payments/sepa-credit-transfers/${challenge.challengeId}")
_links = ScaStatusJsonV13(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/${challenge.challengeId}")
)
}
@ -739,7 +748,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
ScaStatusResponse(
scaStatus = challenge.scaStatus.map(_.toString).getOrElse(""),
psuMessage = Some("Please check your SMS at a mobile device."),
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/payments/sepa-credit-transfers/${challenge.challengeId}"))))
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/payments/sepa-credit-transfers/${challenge.challengeId}"))))
)
)
}
@ -751,7 +760,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
ScaStatusResponse(
scaStatus = challenge.scaStatus.map(_.toString).getOrElse(""),
psuMessage = Some("Please check your SMS at a mobile device."),
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/${paymentService}/${paymentProduct}/${paymentId}/cancellation-authorisations/${challenge.challengeId}"))))
_links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/${paymentService}/${paymentProduct}/${paymentId}/cancellation-authorisations/${challenge.challengeId}"))))
)
)
}
@ -767,7 +776,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
authorisationId = Some(challenge.challengeId),
psuMessage = Some("Please check your SMS at a mobile device."),
_links = Some(LinksUpdatePsuAuthentication(
scaStatus = Some(HrefType(Some(s"/v1.3/${paymentService}/${paymentProduct}/${paymentId}/cancellation-authorisations/${challenge.challengeId}"))))
scaStatus = Some(HrefType(Some(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/${paymentService}/${paymentProduct}/${paymentId}/cancellation-authorisations/${challenge.challengeId}"))))
)
)
}
@ -778,7 +787,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
scaStatus = challenge.scaStatus.map(_.toString).getOrElse(""),
authorisationId = challenge.challengeId,
psuMessage = "Please check your SMS at a mobile device.",
_links = ScaStatusJsonV13(s"/v1.3/signing-baskets/${basketId}/authorisations/${challenge.challengeId}")
_links = ScaStatusJsonV13(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/signing-baskets/${basketId}/authorisations/${challenge.challengeId}")
)
}
@ -787,9 +796,9 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
basketId = basket.basketId,
transactionStatus = basket.status.toLowerCase(),
_links = SigningBasketLinksV13(
self = LinkHrefJson(s"/v1.3/signing-baskets/${basket.basketId}"),
status = LinkHrefJson(s"/v1.3/signing-baskets/${basket.basketId}/status"),
startAuthorisation = LinkHrefJson(s"/v1.3/signing-baskets/${basket.basketId}/authorisations")
self = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/signing-baskets/${basket.basketId}"),
status = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/signing-baskets/${basket.basketId}/status"),
startAuthorisation = LinkHrefJson(s"/${ConstantsBG.berlinGroupVersion1.apiShortVersion}/signing-baskets/${basket.basketId}/authorisations")
)
)
}

View File

@ -32,6 +32,7 @@
package code.api.berlin.group.v1_3
import code.api.OBPRestHelper
import code.api.berlin.group.ConstantsBG
import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi
import code.api.builder.CommonServicesApi.APIMethods_CommonServicesApi
import code.api.builder.ConfirmationOfFundsServicePIISApi.APIMethods_ConfirmationOfFundsServicePIISApi
@ -40,7 +41,7 @@ import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints}
import code.api.util.ScannedApis
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion,ApiVersionStatus}
import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion}
import scala.collection.mutable.ArrayBuffer
@ -52,7 +53,7 @@ This file defines which endpoints from all the versions are available in v1
*/
object OBP_BERLIN_GROUP_1_3 extends OBPRestHelper with MdcLoggable with ScannedApis {
override val apiVersion = ApiVersion.berlinGroupV13
override val apiVersion = ConstantsBG.berlinGroupVersion1
val versionStatus = ApiVersionStatus.DRAFT.toString
val endpoints =

View File

@ -47,7 +47,7 @@ object OBP_BERLIN_GROUP_1_3_Alias extends OBPRestHelper with MdcLoggable with Sc
override val allResourceDocs: ArrayBuffer[ResourceDoc] = if(berlinGroupV13AliasPath.nonEmpty){
OBP_BERLIN_GROUP_1_3.allResourceDocs.map(resourceDoc => resourceDoc.copy(
implementedInApiVersion = apiVersion,
implementedInApiVersion = apiVersion.copy(apiStandard = resourceDoc.implementedInApiVersion.apiStandard),
))
} else ArrayBuffer.empty[ResourceDoc]

View File

@ -1,5 +1,6 @@
package code.api.builder.PaymentInitiationServicePISApi
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{CancelPaymentResponseJson, CancelPaymentResponseLinks, LinkHrefJson, UpdatePaymentPsuDataJson, checkAuthorisationConfirmation, checkSelectPsuAuthenticationMethod, checkTransactionAuthorisation, checkUpdatePsuAuthentication, createCancellationTransactionRequestJson}
import code.api.berlin.group.v1_3.model.TransactionStatus.mapTransactionStatus
import code.api.berlin.group.v1_3.model._
@ -30,7 +31,7 @@ import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
object APIMethods_PaymentInitiationServicePISApi extends RestHelper {
val apiVersion = ApiVersion.berlinGroupV13
val apiVersion = ConstantsBG.berlinGroupVersion1
val resourceDocs = ArrayBuffer[ResourceDoc]()
val apiRelations = ArrayBuffer[ApiRelation]()
protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what)

View File

@ -28,7 +28,7 @@ import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
object APIMethods_SigningBasketsApi extends RestHelper {
val apiVersion = ApiVersion.berlinGroupV13
val apiVersion = ConstantsBG.berlinGroupVersion1
val resourceDocs = ArrayBuffer[ResourceDoc]()
val apiRelations = ArrayBuffer[ApiRelation]()
protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what)
@ -260,7 +260,7 @@ This method returns the SCA status of a signing basket's authorisation sub-resou
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Pisp(callContext)
_ <- Future(SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId)) map {
unboxFullOrFail(_, callContext, s"$ConsentNotFound ($basketId)")
unboxFullOrFail(_, callContext, s"$ConsentNotFound ($basketId)", 403)
}
(challenges, callContext) <- NewStyle.function.getChallengesByBasketId(basketId, callContext)
} yield {

View File

@ -149,6 +149,10 @@ object RequestHeader {
final lazy val `PSD2-CERT` = "PSD2-CERT"
final lazy val `If-None-Match` = "If-None-Match"
final lazy val `PSU-Geo-Location` = "PSU-Geo-Location" // Berlin Group
final lazy val `PSU-Device-Name` = "PSU-Device-Name" // Berlin Group
final lazy val `PSU-Device-ID` = "PSU-Device-ID" // Berlin Group
final lazy val `PSU-IP-Address` = "PSU-IP-Address" // Berlin Group
final lazy val `X-Request-ID` = "X-Request-ID" // Berlin Group
final lazy val `TPP-Redirect-URI` = "TPP-Redirect-URI" // Berlin Group
final lazy val `TPP-Nok-Redirect-URI` = "TPP-Nok-Redirect-URI" // Redirect URI in case of an error.
@ -171,6 +175,7 @@ object RequestHeader {
final lazy val `If-Modified-Since` = "If-Modified-Since"
}
object ResponseHeader {
final lazy val `ASPSP-SCA-Approach` = "ASPSP-SCA-Approach" // Berlin Group
final lazy val `Correlation-Id` = "Correlation-Id"
final lazy val `WWW-Authenticate` = "WWW-Authenticate"
final lazy val ETag = "ETag"

View File

@ -34,6 +34,7 @@ import code.api.OAuthHandshake._
import code.api.UKOpenBanking.v2_0_0.OBP_UKOpenBanking_200
import code.api.UKOpenBanking.v3_1_0.OBP_UKOpenBanking_310
import code.api._
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ErrorMessageBG, ErrorMessagesBG}
import code.api.cache.Caching
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
@ -448,8 +449,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
private def getHeadersNewStyle(cc: Option[CallContextLight]) = {
CustomResponseHeaders(
getGatewayLoginHeader(cc).list :::
getRateLimitHeadersNewStyle(cc).list :::
getGatewayLoginHeader(cc).list :::
getRequestHeadersBerlinGroup(cc).list :::
getRateLimitHeadersNewStyle(cc).list :::
getPaginationHeadersNewStyle(cc).list :::
getRequestHeadersToMirror(cc).list :::
getRequestHeadersToEcho(cc).list
@ -520,7 +522,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val mirrorByProperties = getPropsValue("mirror_request_headers_to_response", "").split(",").toList.map(_.trim)
val mirrorRequestHeadersToResponse: List[String] =
if (callContext.exists(_.url.contains(ApiVersion.berlinGroupV13.urlPrefix))) {
if (callContext.exists(_.url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix))) {
// Berlin Group Specification
RequestHeader.`X-Request-ID` :: mirrorByProperties
} else {
@ -555,6 +557,18 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
CustomResponseHeaders(Nil)
}
}
def getRequestHeadersBerlinGroup(callContext: Option[CallContextLight]): CustomResponseHeaders = {
val aspspScaApproach = getPropsValue("berlin_group_aspsp_sca_approach", defaultValue = "redirect")
callContext match {
case Some(cc) if cc.url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix) && cc.url.endsWith("/consents") =>
CustomResponseHeaders(List(
(ResponseHeader.`ASPSP-SCA-Approach`, aspspScaApproach)
))
case _ =>
CustomResponseHeaders(Nil)
}
}
/**
*
* @param jwt is a JWT value extracted from GatewayLogin Authorization Header.
@ -725,7 +739,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
}
def composeErrorMessage() = {
val path = callContextLight.map(_.url).getOrElse("")
if (path.contains(ApiVersion.berlinGroupV13.urlPrefix)) {
if (path.contains(ConstantsBG.berlinGroupVersion1.urlPrefix)) {
val path =
if(APIUtil.getPropsAsBoolValue("berlin_group_error_message_show_path", defaultValue = true))
callContextLight.map(_.url)
@ -3001,12 +3015,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val res =
if (authHeadersWithEmptyValues.nonEmpty) { // Check Authorization Headers Empty Values
val message = ErrorMessages.EmptyRequestHeaders + s"Header names: ${authHeadersWithEmptyValues.mkString(", ")}"
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), None) }
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), Some(cc)) }
} else if (authHeadersWithEmptyNames.nonEmpty) { // Check Authorization Headers Empty Names
val message = ErrorMessages.EmptyRequestHeaders + s"Header values: ${authHeadersWithEmptyNames.mkString(", ")}"
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), None) }
Future { (fullBoxOrException(Empty ~> APIFailureNewStyle(message, 400, Some(cc.toLight))), Some(cc)) }
} else if (authHeaders.size > 1) { // Check Authorization Headers ambiguity
Future { (Failure(ErrorMessages.AuthorizationHeaderAmbiguity + s"${authHeaders}"), None) }
Future { (Failure(ErrorMessages.AuthorizationHeaderAmbiguity + s"${authHeaders}"), Some(cc)) }
} else if (APIUtil.`hasConsent-ID`(reqHeaders)) { // Berlin Group's Consent
Consent.applyBerlinGroupRules(APIUtil.`getConsent-ID`(reqHeaders), cc.copy(consumer = consumerByCertificate))
} else if (APIUtil.hasConsentJWT(reqHeaders)) { // Open Bank Project's Consent

View File

@ -1,7 +1,9 @@
package code.api.util
import code.api.APIFailureNewStyle
import code.api.berlin.group.ConstantsBG
import code.api.{APIFailureNewStyle, RequestHeader}
import code.api.util.APIUtil.{OBPReturnType, fullBoxOrException}
import code.api.util.BerlinGroupSigning.getHeaderValue
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.model.User
import com.openbankproject.commons.util.ApiVersion
@ -27,7 +29,7 @@ object BerlinGroupCheck extends MdcLoggable {
private def validateHeaders(verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = {
val headerMap = reqHeaders.map(h => h.name.toLowerCase -> h).toMap
val missingHeaders = if(url.contains(ApiVersion.berlinGroupV13.urlPrefix) && url.endsWith("/consents"))
val missingHeaders = if(url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix) && url.endsWith("/consents"))
(berlinGroupMandatoryHeaders ++ berlinGroupMandatoryHeaderConsent).filterNot(headerMap.contains)
else
berlinGroupMandatoryHeaders.filterNot(headerMap.contains)
@ -43,8 +45,22 @@ object BerlinGroupCheck extends MdcLoggable {
}
}
def isTppRequestsWithoutPsuInvolvement(requestHeaders: List[HTTPParam]): Boolean = {
val psuIpAddress = getHeaderValue(RequestHeader.`PSU-IP-Address`, requestHeaders)
val psuDeviceId = getHeaderValue(RequestHeader.`PSU-Device-ID`, requestHeaders)
val psuDeviceNAme = getHeaderValue(RequestHeader.`PSU-Device-Name`, requestHeaders)
if(psuIpAddress == "0.0.0.0" || psuDeviceId == "no-psu-involved" || psuDeviceNAme == "no-psu-involved") {
logger.debug(s"isTppRequestsWithoutPsuInvolvement.psuIpAddress: $psuIpAddress")
logger.debug(s"isTppRequestsWithoutPsuInvolvement.psuDeviceId: $psuDeviceId")
logger.debug(s"isTppRequestsWithoutPsuInvolvement.psuDeviceNAme: $psuDeviceNAme")
true
} else {
false
}
}
def validate(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): OBPReturnType[Box[User]] = {
if(url.contains(ApiVersion.berlinGroupV13.urlPrefix)) {
if(url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix)) {
validateHeaders(verb, url, reqHeaders, forwardResult) match {
case (user, _) if user.isDefined || user == Empty => // All good. Chain another check
// Verify signed request (Berlin Group)

View File

@ -69,8 +69,6 @@ object BerlinGroupError {
case "401" if message.contains("OBP-35018") => "CONSENT_INVALID"
case "401" if message.contains("OBP-35005") => "CONSENT_INVALID"
case "403" if message.contains("OBP-35001") => "CONSENT_UNKNOWN"
case "401" if message.contains("OBP-20300") => "CERTIFICATE_BLOCKED"
case "401" if message.contains("OBP-20312") => "CERTIFICATE_INVALID"
case "401" if message.contains("OBP-20300") => "CERTIFICATE_INVALID"
@ -80,6 +78,7 @@ object BerlinGroupError {
case "400" if message.contains("OBP-35018") => "CONSENT_UNKNOWN"
case "400" if message.contains("OBP-35001") => "CONSENT_UNKNOWN"
case "403" if message.contains("OBP-35001") => "CONSENT_UNKNOWN"
case "404" if message.contains("OBP-30076") => "RESOURCE_UNKNOWN"
@ -91,6 +90,9 @@ object BerlinGroupError {
case "400" if message.contains("OBP-20252") => "FORMAT_ERROR"
case "400" if message.contains("OBP-20251") => "FORMAT_ERROR"
case "400" if message.contains("OBP-20088") => "FORMAT_ERROR"
case "400" if message.contains("OBP-20089") => "FORMAT_ERROR"
case "400" if message.contains("OBP-20090") => "FORMAT_ERROR"
case "400" if message.contains("OBP-20091") => "FORMAT_ERROR"
case "429" if message.contains("OBP-10018") => "ACCESS_EXCEEDED"
case _ => code

View File

@ -160,14 +160,15 @@ object BerlinGroupSigning extends MdcLoggable {
val certificate = getCertificateFromTppSignatureCertificate(requestHeaders)
X509.validateCertificate(certificate) match {
case Full(true) => // PEM certificate is ok
val digest = generateDigest(body.getOrElse(""))
if(digest == getHeaderValue(RequestHeader.Digest, requestHeaders)) { // Verifying the Hash in the Digest Field
val generatedDigest = generateDigest(body.getOrElse(""))
val requestHeaderDigest = getHeaderValue(RequestHeader.Digest, requestHeaders)
if(generatedDigest == requestHeaderDigest) { // Verifying the Hash in the Digest Field
val signatureHeaderValue = getHeaderValue(RequestHeader.Signature, requestHeaders)
val signature = parseSignatureHeader(signatureHeaderValue).getOrElse("signature", "NONE")
val headersToSign = parseSignatureHeader(signatureHeaderValue).getOrElse("headers", "").split(" ").toList
val headers = headersToSign.map(h =>
if (h.toLowerCase() == RequestHeader.Digest.toLowerCase()) {
s"$h: $digest"
s"$h: $generatedDigest"
} else {
s"$h: ${getHeaderValue(h, requestHeaders)}"
}
@ -183,6 +184,8 @@ object BerlinGroupSigning extends MdcLoggable {
case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
}
} else { // The two DIGEST hashes do NOT match, the integrity of the request body is NOT confirmed.
logger.debug(s"Generated digest: $generatedDigest")
logger.debug(s"Request header digest: $requestHeaderDigest")
(Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
}
case Failure(msg, t, c) => (Failure(msg, t, c), forwardResult._2) // PEM certificate is not valid
@ -298,7 +301,8 @@ object BerlinGroupSigning extends MdcLoggable {
developerEmail = extractedEmail,
redirectURL = None,
createdByUserId = None,
certificate = None
certificate = None,
logoUrl = APIUtil.getPropsValue("consumer_default_logo_url")
)
// Set or update certificate

View File

@ -1,21 +1,28 @@
package code.api.util
import code.accountholders.AccountHolders
import code.api.berlin.group.ConstantsBG
import java.text.SimpleDateFormat
import java.util.{Date, UUID}
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ConsentAccessJson, PostConsentJson}
import code.api.util.APIUtil.fullBoxOrException
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, canCreateEntitlementAtOneBank}
import code.api.util.BerlinGroupSigning.getHeaderValue
import code.api.util.ErrorMessages.{CouldNotAssignAccountAccess, InvalidConnectorResponse, NoViewReadAccountsBerlinGroup}
import code.api.v3_1_0.{PostConsentBodyCommonJson, PostConsentEntitlementJsonV310, PostConsentViewJsonV310}
import code.api.v5_0_0.HelperInfoJson
import code.api.{APIFailure, Constant, RequestHeader}
import code.api.{APIFailure, APIFailureNewStyle, Constant, RequestHeader}
import code.bankconnectors.Connector
import code.consent
import code.consent.ConsentStatus.ConsentStatus
import code.consent.{ConsentStatus, Consents, MappedConsent}
import code.consumer.Consumers
import code.context.{ConsentAuthContextProvider, UserAuthContextProvider}
import code.entitlement.Entitlement
import code.model.Consumer
import code.model.dataAccess.BankAccountRouting
import code.scheduler.ConsentScheduler.logger
import code.users.Users
import code.util.Helper.MdcLoggable
import code.util.HydraUtil
@ -253,7 +260,7 @@ object Consent extends MdcLoggable {
case false =>
Failure(ErrorMessages.ConsentVerificationIssue)
}
case Full(c) if c.apiStandard == ApiVersion.berlinGroupV13.apiStandard && // Berlin Group Consent
case Full(c) if c.apiStandard == ConstantsBG.berlinGroupVersion1.apiStandard && // Berlin Group Consent
c.status.toLowerCase() != ConsentStatus.valid.toString =>
Failure(s"${ErrorMessages.ConsentStatusIssue}${ConsentStatus.valid.toString}.")
case Full(c) if c.mStatus.toString().toUpperCase() != ConsentStatus.ACCEPTED.toString =>
@ -566,9 +573,11 @@ object Consent extends MdcLoggable {
logger.debug(s"End of net.liftweb.json.parse(jsonAsString).extract[ConsentJWT].checkConsent.consentBox: $consent")
consentBox match { // Check is it Consent-JWT expired
case (Full(true)) => // OK
// Update MappedConsent.usesSoFarTodayCounter field
val consentUpdatedBox = Consents.consentProvider.vend.updateBerlinGroupConsent(consentId, currentCounterState + 1)
logger.debug(s"applyBerlinGroupConsentRulesCommon.consentUpdatedBox: $consentUpdatedBox")
if(BerlinGroupCheck.isTppRequestsWithoutPsuInvolvement(callContext.requestHeaders)) {
// Update MappedConsent.usesSoFarTodayCounter field
val consentUpdatedBox = Consents.consentProvider.vend.updateBerlinGroupConsent(consentId, currentCounterState + 1)
logger.debug(s"applyBerlinGroupConsentRulesCommon.consentUpdatedBox: $consentUpdatedBox")
}
applyConsentRules(consent, updatedCallContext)
case failure@Failure(_, _, _) => // Handled errors
Future(failure, Some(updatedCallContext))
@ -595,12 +604,14 @@ object Consent extends MdcLoggable {
Future(Failure("Cannot extract data from: " + consentId), Some(updatedCallContext))
}
} else {
Future(Failure(ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"), Some(updatedCallContext))
val errorMessage = ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"
Future(fullBoxOrException(Empty ~> APIFailureNewStyle(errorMessage, 429, Some(callContext.toLight))), Some(callContext))
}
case failure@Failure(_, _, _) =>
Future(failure, Some(callContext))
case _ =>
Future(Failure(ErrorMessages.ConsentNotFound + s" ($consentId)"), Some(callContext))
val errorMessage = ErrorMessages.ConsentNotFound + s" ($consentId)"
Future(fullBoxOrException(Empty ~> APIFailureNewStyle(errorMessage, 400, Some(callContext.toLight))), Some(callContext))
}
}
def applyBerlinGroupRules(consentId: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = {
@ -772,6 +783,10 @@ object Consent extends MdcLoggable {
}
val tppRedirectUri: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`TPP-Redirect-URI`)
val tppNokRedirectUri: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`TPP-Nok-Redirect-URI`)
val xRequestId: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`X-Request-ID`)
val psuDeviceId: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`PSU-Device-ID`)
val psuIpAddress: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`PSU-IP-Address`)
val psuGeoLocation: Option[HTTPParam] = callContext.map(_.requestHeaders).getOrElse(Nil).find(_.name == RequestHeader.`PSU-Geo-Location`)
Future.sequence(accounts ::: balances ::: transactions) map { views =>
val json = ConsentJWT(
createdByUserId = user.map(_.userId).getOrElse(""),
@ -782,7 +797,12 @@ object Consent extends MdcLoggable {
iat = currentTimeInSeconds,
nbf = currentTimeInSeconds,
exp = validUntilTimeInSeconds,
request_headers = tppRedirectUri.toList ::: tppNokRedirectUri.toList,
request_headers = tppRedirectUri.toList :::
tppNokRedirectUri.toList :::
xRequestId.toList :::
psuDeviceId.toList :::
psuIpAddress.toList :::
psuGeoLocation.toList,
name = None,
email = None,
entitlements = Nil,
@ -855,19 +875,78 @@ object Consent extends MdcLoggable {
}
}
}
def updateUserIdOfBerlinGroupConsentJWT(createdByUserId: String,
consent: MappedConsent,
callContext: Option[CallContext]): Future[Box[String]] = {
def updateViewsOfBerlinGroupConsentJWT(user: User,
consent: MappedConsent,
callContext: Option[CallContext]): Future[Box[MappedConsent]] = {
implicit val dateFormats = CustomJsonFormats.formats
val payloadToUpdate: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken) // Payload as JSON string
.map(net.liftweb.json.parse(_).extract[ConsentJWT]) // Extract case class
Future {
val updatedPayload = payloadToUpdate.map(i => i.copy(createdByUserId = createdByUserId)) // Update only the field "createdByUserId"
val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload))
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
Full(CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret))
val availableAccountsUserIbans: List[String] = payloadToUpdate match {
case Full(consentJwt) =>
val availableAccountsUserIbans: List[String] =
if (consentJwt.access.map(_.availableAccounts.contains("allAccounts")).isDefined) {
// Get all accounts held by the current user
val userAccounts: List[BankIdAccountId] =
AccountHolders.accountHolders.vend.getAccountsHeldByUser(user, Some(null)).toList
userAccounts.flatMap { acc =>
BankAccountRouting.find(
By(BankAccountRouting.BankId, acc.bankId.value),
By(BankAccountRouting.AccountId, acc.accountId.value),
By(BankAccountRouting.AccountRoutingScheme, "IBAN")
).map(_.AccountRoutingAddress.get)
}
} else {
val emptyList: List[String] = Nil
emptyList
}
availableAccountsUserIbans
case _ =>
val emptyList: List[String] = Nil
emptyList
}
// 1. Add access
val availableAccounts: List[Future[ConsentView]] = availableAccountsUserIbans.distinct map { iban =>
Connector.connector.vend.getBankAccountByIban(iban, callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.accounts.bankAccount: $bankAccount")
val error = s"${InvalidConnectorResponse} IBAN: ${iban} ${handleBox(bankAccount._1)}"
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).openOrThrowException(error),
view_id = Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID,
None
)
}
}
Future.sequence(availableAccounts) map { views =>
if(views.isEmpty) {
Empty
} else {
val updatedPayload = payloadToUpdate.map(i =>
i.copy(views = views) // Update the field "views"
)
val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload))
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
val jwt = CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret)
Consents.consentProvider.vend.setJsonWebToken(consent.consentId, jwt)
}
}
}
def updateUserIdOfBerlinGroupConsentJWT(createdByUserId: String,
consent: MappedConsent,
callContext: Option[CallContext]): Box[String] = {
implicit val dateFormats = CustomJsonFormats.formats
val payloadToUpdate: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken) // Payload as JSON string
.map(net.liftweb.json.parse(_).extract[ConsentJWT]) // Extract case class
val updatedPayload = payloadToUpdate.map(i => i.copy(createdByUserId = createdByUserId)) // Update only the field "createdByUserId"
val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload))
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
Full(CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret))
}
def createUKConsentJWT(
@ -1036,6 +1115,26 @@ object Consent extends MdcLoggable {
consentsOfBank
}
def expireAllPreviousValidBerlinGroupConsents(consent: MappedConsent, updateTostatus: ConsentStatus): Boolean = {
if(updateTostatus == ConsentStatus.valid &&
consent.apiStandard == ConstantsBG.berlinGroupVersion1.apiStandard) {
MappedConsent.findAll( // Find all
By(MappedConsent.mApiStandard, ConstantsBG.berlinGroupVersion1.apiStandard), // Berlin Group
By(MappedConsent.mRecurringIndicator, true), // recurring
By(MappedConsent.mStatus, ConsentStatus.valid.toString), // and valid consents
By(MappedConsent.mUserId, consent.userId), // for the same PSU
By(MappedConsent.mConsumerId, consent.consumerId), // from the same TPP
).filterNot(_.consentId == consent.consentId) // Exclude current consent
.map{ c => // Set to expired
val changedStatus = c.mStatus(ConsentStatus.expired.toString).mLastActionDate(new Date()).save
if(changedStatus) logger.warn(s"|---> Changed status to ${ConsentStatus.expired.toString} for consent ID: ${c.id}")
changedStatus
}.forall(_ == true)
} else {
true
}
}
/*
// Example Usage
val box1: Box[String] = Full("Hello, World!")

View File

@ -244,7 +244,10 @@ object ErrorMessages {
s"OBP-20087: The current source view.can_revoke_access_to_custom_views is false."
val BerlinGroupConsentAccessIsEmpty = s"OBP-20088: An access must be requested."
val BerlinGroupConsentAccessRecurringIndicator = s"OBP-20089: Recurring indicator must be false when availableAccounts is used."
val BerlinGroupConsentAccessFrequencyPerDay = s"OBP-20090: Frequency per day must be 1 when availableAccounts is used."
val BerlinGroupConsentAccessAvailableAccounts = s"OBP-20091: availableAccounts must be exactly 'allAccounts'."
val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements:"
val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user."
val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider."
@ -268,6 +271,7 @@ object ErrorMessages {
val OneTimePasswordExpired = "OBP-20211: The One Time Password (OTP) has expired. "
val Oauth2IsNotRecognized = "OBP-20214: OAuth2 Access Token is not recognised at this instance."
val Oauth2ValidateAccessTokenError = "OBP-20215: There was a problem validating the OAuth2 access token. "
val OneTimePasswordInvalid = "OBP-20216: The One Time Password (OTP) is invalid. "
val AuthorizationHeaderAmbiguity = "OBP-20250: Request headers used for authorization are ambiguous. "
val MissingMandatoryBerlinGroupHeaders= "OBP-20251: Missing mandatory request headers. "

View File

@ -2,6 +2,7 @@ package code.api.util
import java.math.BigInteger
import net.liftweb.common.Box
import org.iban4j.IbanUtil
object HashUtil {
def Sha256Hash(in: String): String = {
@ -25,5 +26,8 @@ object HashUtil {
val hashedText = Sha256Hash(plainText)
println("Password: " + plainText)
println("Hashed password: " + hashedText)
println("BBAN: " + IbanUtil.getBban("AT483200000012345864"))
println("Bank code: " + IbanUtil.getBankCode("AT483200000012345864"))
println("Country code: " + IbanUtil.getCountryCode("AT483200000012345864"))
}
}

View File

@ -8081,7 +8081,9 @@ trait APIMethods400 extends MdcLoggable {
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
i => connectorEmptyResponse(i, callContext)
}
_ <- Helper.booleanToFuture(ConsentUserAlreadyAdded, cc=callContext) { consent.userId != null }
_ <- Helper.booleanToFuture(ConsentUserAlreadyAdded, cc = cc.callContext) {
Option(consent.userId).forall(_.isBlank) // checks whether userId is not populated
}
consent <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, user)) map {
i => connectorEmptyResponse(i, callContext)
}

View File

@ -1439,7 +1439,7 @@ trait APIMethods510 {
nameOf(updateConsentUserIdByConsentId),
"PUT",
"/management/banks/BANK_ID/consents/CONSENT_ID/created-by-user",
"Update Consent Created by User by CONSENT_ID",
"Update Created by User of Consent by CONSENT_ID",
s"""
|
|This endpoint is used to Update the User bound to a consent.
@ -1493,11 +1493,11 @@ trait APIMethods510 {
consent <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, user)) map {
i => connectorEmptyResponse(i, cc.callContext)
}
consentJWT <- Consent.updateUserIdOfBerlinGroupConsentJWT(
consentJWT <- Future(Consent.updateUserIdOfBerlinGroupConsentJWT(
consentJson.user_id,
consent,
cc.callContext
) map {
)) map {
i => connectorEmptyResponse(i, cc.callContext)
}
updatedConsent <- Future(Consents.consentProvider.vend.setJsonWebToken(consent.consentId, consentJWT)) map {

View File

@ -27,6 +27,7 @@
package code.api.v5_1_0
import code.api.Constant
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.ConsentAccessJson
import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, gitCommit, stringOrNull}
import code.api.util._
@ -943,8 +944,8 @@ object JSONFactory510 extends CustomJsonFormats {
last_action_date = if (c.lastActionDate != null) new SimpleDateFormat(DateWithDay).format(c.lastActionDate) else null,
last_usage_date = if (c.usesSoFarTodayCounterUpdatedAt != null) new SimpleDateFormat(DateWithSeconds).format(c.usesSoFarTodayCounterUpdatedAt) else null,
jwt_payload = jwtPayload,
frequency_per_day = if(c.apiStandard == ApiVersion.berlinGroupV13.apiStandard) Some(c.frequencyPerDay) else None,
remaining_requests = if(c.apiStandard == ApiVersion.berlinGroupV13.apiStandard) Some(c.frequencyPerDay - c.usesSoFarTodayCounter) else None,
frequency_per_day = if(c.apiStandard == ConstantsBG.berlinGroupVersion1.apiStandard) Some(c.frequencyPerDay) else None,
remaining_requests = if(c.apiStandard == ConstantsBG.berlinGroupVersion1.apiStandard) Some(c.frequencyPerDay - c.usesSoFarTodayCounter) else None,
api_standard = c.apiStandard,
api_version = c.apiVersion
)

View File

@ -0,0 +1,154 @@
import java.io.{File, PrintWriter}
import scala.sys.process._
import java.security.MessageDigest
object CardanoMetadataWriter {
// Function to generate SHA-256 hash of a string
def generateHash(transactionData: String): String = {
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(transactionData.getBytes("UTF-8"))
hashBytes.map("%02x".format(_)).mkString
}
// Function to write metadata JSON file
def writeMetadataFile(transactionHash: String, filePath: String): Unit = {
val jsonContent =
s"""
|{
| "674": {
| "transaction_hash": "$transactionHash"
| }
|}
|""".stripMargin
val file = new File(filePath)
val writer = new PrintWriter(file)
writer.write(jsonContent)
writer.close()
println(s"Metadata file written to: $filePath")
}
// Function to submit transaction to Cardano
def submitHashToCardano(transactionHash: String, txIn: String, txOut: String, signingKey: String, network: String): Unit = {
val metadataFilePath = "metadata.json"
// Write metadata to file
writeMetadataFile(transactionHash, metadataFilePath)
// Build transaction
val buildCommand = s"cardano-cli transaction build-raw --tx-in $txIn --tx-out $txOut --metadata-json-file $metadataFilePath --out-file tx.raw"
buildCommand.!
// Sign transaction
val signCommand = s"cardano-cli transaction sign --tx-body-file tx.raw --signing-key-file $signingKey --$network --out-file tx.signed"
signCommand.!
// Submit transaction
val submitCommand = s"cardano-cli transaction submit --tx-file tx.signed --$network"
submitCommand.!
println("Transaction submitted to Cardano blockchain.")
}
// Example Usage
def main(args: Array[String]): Unit = {
val transactionData = "123|100.50|EUR|2025-03-16 12:30:00"
val transactionHash = generateHash(transactionData)
val txIn = "8c293647e5cb51c4d29e57e162a0bb4a0500096560ce6899a4b801f2b69f2813:0" // This is a tx_id:0 ///"YOUR_UTXO_HERE" // Replace with actual UTXO
val txOut = "addr_test1qruvtthh7mndxu2ncykn47tksar9yqr3u97dlkq2h2dhzwnf3d755n99t92kp4rydpzgv7wmx4nx2j0zzz0g802qvadqtczjhn:1234" // "YOUR_RECEIVER_ADDRESS+LOVELACE" // Replace with receiver address and amount
val signingKey = "payment.skey" // Path to your signing key file
val network = "--testnet-magic" // "--testnet-magic 1097911063" // Use --mainnet for mainnet transactions
submitHashToCardano(transactionHash, txIn, txOut, signingKey, network)
}
}
// TODO
// Create second wallet
// Find version of Pre Prod i'm running
// Get CLI for that version
// Use faucet to get funds
/*
import com.bloxbean.cardano.client.account.Account
import com.bloxbean.cardano.client.api.UtxoSupplier
import com.bloxbean.cardano.client.backend.impl.local.LocalNodeBackendService
import com.bloxbean.cardano.client.backend.api.TransactionService
import com.bloxbean.cardano.client.backend.api.UtxoService
import com.bloxbean.cardano.client.backend.model.Utxo
import com.bloxbean.cardano.client.common.model.Network
import com.bloxbean.cardano.client.metadata.cbor.CBORMetadata
import com.bloxbean.cardano.client.transaction.spec.Transaction
import com.bloxbean.cardano.client.api.helper.TransactionBuilder
import java.security.MessageDigest
object CardanoMetadataWriter {
// Function to generate SHA-256 hash
def generateHash(transactionData: String): String = {
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(transactionData.getBytes("UTF-8"))
hashBytes.map("%02x".format(_)).mkString
}
// Function to submit metadata transaction
def submitMetadataToCardano(mnemonic: String, transactionData: String): Unit = {
val network = Network.TESTNET // Change to Network.MAINNET for mainnet
// Load Daedalus wallet from mnemonic
val account = new Account(network, mnemonic)
// Generate hash of transaction data
val transactionHash = generateHash(transactionData)
println(s"Generated Hash: $transactionHash")
// Create metadata object
val metadata = new CBORMetadata()
metadata.put("674", Map("transaction_hash" -> transactionHash))
// Initialize local Cardano node backend
val backendService = new LocalNodeBackendService("http://localhost:8080")
val transactionService: TransactionService = backendService.getTransactionService
val utxoService: UtxoService = backendService.getUtxoService
// Get available UTXOs from the wallet
val utxos: java.util.List[Utxo] = utxoService.getUtxos(account.baseAddress, 1, 10).getValue
if (utxos.isEmpty) {
println("No UTXOs found. Please fund your wallet.")
return
}
// Build transaction
val transaction = TransactionBuilder.create()
.account(account)
.metadata(metadata)
.utxos(utxos)
.changeAddress(account.baseAddress)
.network(network)
.build()
// Sign transaction
val signedTransaction: Transaction = account.sign(transaction)
// Submit transaction
val txHash: String = transactionService.submitTransaction(signedTransaction).getValue
println(s"✅ Transaction submitted! TxHash: $txHash")
}
// Main method
def main(args: Array[String]): Unit = {
val mnemonic = "YOUR_12_OR_24_WORD_MNEMONIC_HERE"
val transactionData = "123|100.50|USD|2025-03-16 12:30:00"
submitMetadataToCardano(mnemonic, transactionData)
}
}
*/

View File

@ -30,6 +30,7 @@ object MappedConsentProvider extends ConsentProvider {
override def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent] = {
MappedConsent.find(By(MappedConsent.mConsentId, consentId)) match {
case Full(consent) =>
Consent.expireAllPreviousValidBerlinGroupConsents(consent, status)
tryo(consent
.mStatus(status.toString)
.mLastActionDate(now) //maybe not right, but for the create we use the `now`, we need to update it later.

View File

@ -71,7 +71,9 @@ trait ConsumersProvider {
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String],
certificate: Option[String] = None): Box[Consumer]
certificate: Option[String] = None,
logoUrl: Option[String] = None
): Box[Consumer]
def populateMissingUUIDs(): Boolean
}

View File

@ -389,7 +389,9 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
developerEmail: Option[String],
redirectURL: Option[String],
createdByUserId: Option[String],
certificate: Option[String]): Box[Consumer] = {
certificate: Option[String],
logoUrl: Option[String],
): Box[Consumer] = {
val consumer: Box[Consumer] =
// 1st try to find via UUID issued by OBP-API back end
@ -473,6 +475,10 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable {
case Some(v) => c.clientCertificate(v)
case None =>
}
logoUrl match {
case Some(v) => c.logoUrl(v)
case None =>
}
consumerId match {
case Some(v) => c.consumerId(v)
case None =>

View File

@ -1,5 +1,6 @@
package code.scheduler
import code.api.berlin.group.ConstantsBG
import code.api.util.APIUtil
import code.consent.{ConsentStatus, MappedConsent}
import code.util.Helper.MdcLoggable
@ -52,7 +53,7 @@ object ConsentScheduler extends MdcLoggable {
val outdatedConsents = MappedConsent.findAll(
By(MappedConsent.mStatus, ConsentStatus.received.toString),
By(MappedConsent.mApiStandard, ApiVersion.berlinGroupV13.apiStandard),
By(MappedConsent.mApiStandard, ConstantsBG.berlinGroupVersion1.apiStandard),
By_<(MappedConsent.updatedAt, SchedulerUtil.someSecondsAgo(seconds))
)
@ -78,7 +79,7 @@ object ConsentScheduler extends MdcLoggable {
val expiredConsents = MappedConsent.findAll(
By(MappedConsent.mStatus, ConsentStatus.valid.toString),
By(MappedConsent.mApiStandard, ApiVersion.berlinGroupV13.apiStandard),
By(MappedConsent.mApiStandard, ConstantsBG.berlinGroupVersion1.apiStandard),
By_<(MappedConsent.mValidUntil, new Date())
)

View File

@ -110,7 +110,7 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
for {
// Fetch the consent by ID
consent: MappedConsent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
APIUtil.unboxFullOrFail(_, None, s"$ConsentNotFound ($consentId)", 404)
APIUtil.unboxFullOrFail(_, None, s"$ConsentNotFound ($consentId)", 400)
}
// Update the consent JWT with new access details
consentJWT <- Consent.updateAccountAccessOfBerlinGroupConsentJWT(
@ -171,6 +171,19 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
// Select all IBANs
selectedAccountsIbansValue.set(userIbans)
var canReadAccountsIbansAvailableAccounts: List[String] = List()
if(json.access.availableAccounts.contains("allAccounts")) { //
/*
Access is requested via:
"access":
{
"availableAccounts": "allAccounts"
}
*/
accessAccountsDefinedVar.set(true)
canReadAccountsIbansAvailableAccounts = userIbans.toList
}
// Determine which IBANs the user can access for accounts, balances, and transactions
val canReadAccountsIbans: List[String] = json.access.accounts match {
case Some(accounts) if accounts.isEmpty => // Access is requested via "accounts": []
@ -226,7 +239,7 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
}
// all Selected IBANs
val ibansFromGetConsentResponseJson = (canReadAccountsIbans ::: canReadBalancesIbans ::: canReadTransactionsIbans).distinct
val ibansFromGetConsentResponseJson = (canReadAccountsIbansAvailableAccounts ::: canReadAccountsIbans ::: canReadBalancesIbans ::: canReadTransactionsIbans).distinct
/**
* Generates toggle switches for IBAN lists.
@ -385,10 +398,19 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
*/
private def denyConsentRequestProcess() = {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)
S.redirectTo(
s"$redirectUriValue?CONSENT_ID=${consentId}"
)
Consents.consentProvider.vend.getConsentByConsentId(consentId) match {
case Full(consent) if otpValue.is == consent.challenge =>
updateConsentUser(consent)
updateConsentJwt(consent) map { i =>
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)
}
S.redirectTo(
s"$redirectUriValue?CONSENT_ID=${consentId}"
)
case _ =>
S.error(ErrorMessages.ConsentNotFound)
}
}
/**
@ -398,15 +420,29 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510
val consentId = ObpS.param("CONSENT_ID") openOr ("")
Consents.consentProvider.vend.getConsentByConsentId(consentId) match {
case Full(consent) if otpValue.is == consent.challenge =>
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)
updateConsentUser(consent)
updateConsentJwt(consent) map { i =>
Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)
}
S.redirectTo(
s"/confirm-bg-consent-request-redirect-uri?CONSENT_ID=${consentId}"
)
case _ =>
S.error("Wrong OTP value")
S.error(ErrorMessages.OneTimePasswordInvalid)
}
}
private def updateConsentUser(consent: MappedConsent): Box[MappedConsent] = {
val loggedInUser = AuthUser.currentUser.flatMap(_.user.foreign).openOrThrowException(ErrorMessages.UserNotLoggedIn)
Consents.consentProvider.vend.updateConsentUser(consent.consentId, loggedInUser)
val jwt = Consent.updateUserIdOfBerlinGroupConsentJWT(loggedInUser.userId, consent, None).openOrThrowException(ErrorMessages.InvalidConnectorResponse)
Consents.consentProvider.vend.setJsonWebToken(consent.consentId, jwt)
}
private def updateConsentJwt(consent: MappedConsent) = {
val loggedInUser = AuthUser.currentUser.flatMap(_.user.foreign).openOrThrowException(ErrorMessages.UserNotLoggedIn)
Consent.updateViewsOfBerlinGroupConsentJWT(loggedInUser, consent, None)
}
private def getTppRedirectUri() = {
val consentId = ObpS.param("CONSENT_ID") openOr ("")
s"$redirectUriValue?CONSENT_ID=${consentId}"

View File

@ -31,7 +31,9 @@ Berlin 13359, Germany
<H1>Here are the debugging pages.</H1>
<h3><a href="/debug/debug-plain">debug-plain -- no Liftweb involved. </a></h3>
<h3><a href="/debug/debug-basic">debug-basic -- call LiftWeb code 'surround'.</a></h3>
<h3><a href="/debug/debug-basic">debug-basic (default)-- call LiftWeb default code 'surround'.</a></h3>
<h3><a href="/debug/debug-default-header">debug-default-header -- call LiftWeb default header code 'surround'.</a></h3>
<h3><a href="/debug/debug-default-footer">debug-default-footer -- call LiftWeb default footer code 'surround'.</a></h3>
<h3><a href="/debug/debug-localization">debug-localization -- call Localization 'lift:loc' method.</a></h3>
<h3><a href="/debug/debug-webui">debug-webui -- call webui method 'apiDocumentationLink' method.</a></h3>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head><title>Basic Liftweb Suround with default</title></head>
<div id="main" data-lift="surround?with=default-footer;at=content" tabindex="-1">
<h1>I call LiftWeb code surround</h1>
with a <a href="http://www.example.com">link</a>
<h1>Link to static</h1><a href="/static">static image</a>
<h1>Link to SDKs</h1><a href="/sdks">SDKs</a>
</div>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head><title>Basic Liftweb Suround with default</title></head>
<div id="main" data-lift="surround?with=default-header;at=content" tabindex="-1">
<h1>I call LiftWeb code surround</h1>
with a <a href="http://www.example.com">link</a>
<h1>Link to static</h1><a href="/static">static image</a>
<h1>Link to SDKs</h1><a href="/sdks">SDKs</a>
</div>
</body>
</html>

View File

@ -0,0 +1,275 @@
<!--
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
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Webapp of the Open Bank Project API">
<meta name="keywords" content="OBP, Open Bank Project, API, bank, fintech">
<meta name="author" content="TESOBE">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title data-lift="WebUI.pageTitle">Open Bank Project: </title>
<link href="/media/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link id="favicon_link" rel="favicon icon" data-lift="WebUI.faviconLink" href="/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/media/css/select2.min.css">
<link href="/media/css/toastr.min.css" rel="stylesheet" type="text/css" />
<link href="/media/css/cookies-consent.css" rel="stylesheet">
<link href="/media/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<link id="main_style_sheet" data-lift="WebUI.mainStyleSheet" href="/media/css/website.css?201707241207" rel="stylesheet" type="text/css" />
<link id="override_style_sheet" data-lift="WebUI.overrideStyleSheet" href="" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="/font-awesome/css/all.min.css">
<script src="/media/js/jquery.min.js" type="text/javascript"></script>
<script src="/media/js/bootstrap.min.js" type="text/javascript"></script>
<script src="/media/js/select2.min.js"></script>
<script src="/media/js/toastr.min.js" type="text/javascript"></script>
<script src="/media/js/website.js" type="text/javascript"></script>
<script src="/media/js/cookies-consent.js"></script>
<script src="/media/js/moment-with-locales.min.js"></script>
<script src="/media/js/bootstrap-datetimepicker.min.js"></script>
<script src="/media/js/popper.min.js"></script>
<script type="module" defer src="/media/js/inactivity.js"></script> <!-- The script loads “in the background”, and then runs when the DOM is fully built. -->
</head>
<body id="page_init">
<div id="cookies-consent" data-lift="WebUI.cookieConsent">
<div id="cookies-consent-font"> We use cookies to support session management.</div>
<input id="cookies-consent-button" class="btn btn-default" type="button" value="Accept and close" onclick="removeByIdAndSaveIndicatorCookie('cookies-consent')"/>
</div>
<div id="toast-container" class="toast-top-right" aria-live="assertive" role="alert" aria-atomic="true" aria-relevant="additions removals"></div>
<div class="container">
<!---
<header>
<div id="header-decoration"></div>
<div id="cookie-ipaddress-concurrent-logins" data-lift="WebUI.concurrentLoginsCookiesCheck"></div>
<div id="top-text" data-lift="WebUI.topText"></div>
<table id="table-header" aria-describedby="Home Page Logo">
<tr class="row">
<td>
<th scope="col"></th>
</td>
<td id="td-logo-left-xs">
<div data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="Home Page"><img src="" id="logo-left-xs" align="left" alt="left logo image"></a>
</div>
</td>
<td id="td-logo-right-xs">
<div data-lift="WebUI.headerLogoRight">
<a href="/" aria-label="Home Page"><img src="" id="logo-right-xs" align="right" alt="right logo image"></a>
</div>
</td>
<td></td>
</tr>
</table>
<div id="table-header-script" data-lift="WebUI.headerContentLoader"></div>
<div id="messages-container" data-lift="Msgs"></div>
</header>
-->
<!--
<a class="sr-only sr-only-focusable" href ="#main" id="index-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#register-consumer" id="consumer-registration-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#api_documentation_content" id="introduction-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#authorise" id="logon-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#recover-password" id="lost-password-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#signup" id="sign-up-page">Skip to main content</a>
-->
<!--
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="navitem" data-lift="WebUI.headerLogoLeft">
<a id ="navitem-logo" class="navlink " href="/" ><img src="/media/images/logo.png" alt="homepage logo"></a>
</li>
<li class="navitem">
<a class="navlink api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">
<lift:loc locid="api_explorer">API Explorer</lift:loc>
</a>
</li>
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<li class="navitem">
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
<lift:loc locid="introduction">Introduction</lift:loc>
</a>
</li>
<li class="navitem">
<a class="navlink" id="technical-faqs-anchor" data-lift="WebUI.technicalFaqsAnchor" href="">
<lift:loc locid="support">Support</lift:loc>
</a>
</li>
<li class="navitem" data-lift="WebUI.subscriptionsButton" >
<a class="navlink subscriptions-button" href="">
<lift:loc locid="subscriptions"><div class ="subscriptions-button-text"></div></lift:loc>
</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li data-lift="Nav.item?name=Login" class="navitem">
<p data-lift="WebUI.userIsLoggedIn">
<a href="/user_mgt/sign_up" class="navbar-btn" id ="register-link"><lift:loc locid="register">Register</lift:loc></a><a data-lift="Login.loggedOut" href="#" class="btn btn-danger login"><lift:loc locid="logon">Log on</lift:loc></a>
</p>
</li>
<li class="navitem" data-lift="Login.loggedIn" >
<p class="navbar-btn"><a href="/user-information"><span id="loggedIn-username">username</span></a><a href="#" class="btn btn-default logout">Log off <span id="countdown-timer-span" class="badge text-bg-primary"></span></a></p>
</li>
</ul>
</div>
</div>
</nav>
-->
<!--
<div id="small-screen-navbar">
<button tabindex="0" id="small-nav-collapse" onclick="openNav()" aria-label ="Open Navigation" aria-hidden="false"></button>
<div id="small-nav-logo" data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="HomePage Logo"><img src="" class="logo-responsive-design-mobile" alt ="Home Page Logo"></a>
</div>
<div id="small-nav-log-on-button" data-lift="Nav.item?name=Login">
<a data-lift="Login.loggedOut" href="#" class="btn btn-danger login">Log on</a>
</div>
</div>
<div id="obp-sidebar" class="sidebar">
<div id="small-screen-navbar">
<button tabindex="0" id="small-nav-cross" onclick="closeNav()" aria-hidden="true" aria-label ="Close Navigation"></button>
<div id="small-nav-logo" data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="HomePage Logo"><img src="" class="logo-responsive-design-mobile" alt ="Home Page Logo"></a>
</div>
<div id="small-nav-log-on-button" data-lift="Nav.item?name=Login">
<a data-lift="Login.loggedOut" href="#" class="btn btn-danger login">Log on</a>
</div>
</div>
<ul>
<li>
<div class="navitem">
<a class="navlink api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">API Explorer</a>
</div>
</li>
<li>
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</div>
</li>
<li>
<div class="navitem">
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">Introduction</a>
</div>
</li>
<li>
<div class="navitem" id="technical-faqs-anchor-nav">
<a class="navlink" id="technical-faqs-anchor" data-lift="WebUI.technicalFaqsAnchor" href="">
<lift:loc locid="support">Support</lift:loc></a>
</div>
</li>
<li>
<div class="navitem" id="register-link-nav">
<a data-lift="WebUI.userIsLoggedIn" href="/user_mgt/sign_up" class="navbar-btn" id ="register-link">Register</a>
<a data-lift="Login.loggedIn" href="#" class="logout">Log off</a></p>
</div>
</li>
</ul>
</div>
-->
<section id="content">
<lift:bind name="content"/>
The main content gets bound here
</section>
<footer>
<div id="footer-div">
<ul>
<li>
<a class="termsAndConditions-link" data-lift="WebUI.termsAndConditions" href="">
<lift:loc locid="terms_conditions">Terms and Conditions</lift:loc></a>
</li>
<li>
<a class="privacy-policy-link" data-lift="WebUI.privacyPolicyLink" href="https://openbankproject.com/privacy-policy">
<lift:loc locid="privacy_policy">Privacy Policy</lift:loc></a>
</li>
<li>
<a id="footer-div-social" href="http://twitter.com/#!/OpenBankProject" data-lift="WebUI.footerSocialLink">Twitter</a>
</li>
<li>
<a href="https://github.com/OpenBankProject/OBP-API/">Github</a>
</li>
<li>
<a class="api-documentation-link" data-lift="WebUI.apiDocumentationLink" href="">
<lift:loc locid="api_documentation">API Documentation</lift:loc></a>
</li>
<li>
<a class="sofi-link" data-lift="WebUI.sofiLink" href="">Sofit</a>
</li>
<li>
<a href="/user_mgt/sign_up?after-signup=link-to-customer" class="navbar-btn" id ="register-link">
<lift:loc locid="register">On Board</lift:loc></a>
</li>
<li>
<a class="api-link" data-lift="WebUI.apiLinkHuman" href="">
<lift:loc locid="api_host">This API Host</lift:loc></a>
</li>
<li>
<a class="commit-id-link" data-lift="WebUI.commitIdLink" href="">
<lift:loc locid="api_host">GitHub commit</lift:loc></a>
</li>
</ul>
<br>
<div class="language-tag" data-lift="WebUI.currentPage">
<span><a href="#">Language</a></span>
<div id="supported-language-list"></div>
</div>
<div id="copyright">
<a href="http://openbankproject.com"><lift:loc locid="open_bank_project_is">Open Bank Project is &copy;2011 - </lift:loc> <span id="copyright-year" data-lift="WebUI.currentYearText">2018</span> </a> <a href="http://tesobe.com"><lift:loc locid="and_commercial_licenses">TESOBE and distributed under the AGPL and commercial licenses. </lift:loc></a>
</div>
</div>
</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,264 @@
<!--
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
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Webapp of the Open Bank Project API">
<meta name="keywords" content="OBP, Open Bank Project, API, bank, fintech">
<meta name="author" content="TESOBE">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title data-lift="WebUI.pageTitle">Open Bank Project: </title>
<link href="/media/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link id="favicon_link" rel="favicon icon" data-lift="WebUI.faviconLink" href="/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/media/css/select2.min.css">
<link href="/media/css/toastr.min.css" rel="stylesheet" type="text/css" />
<link href="/media/css/cookies-consent.css" rel="stylesheet">
<link href="/media/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<link id="main_style_sheet" data-lift="WebUI.mainStyleSheet" href="/media/css/website.css?201707241207" rel="stylesheet" type="text/css" />
<link id="override_style_sheet" data-lift="WebUI.overrideStyleSheet" href="" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="/font-awesome/css/all.min.css">
<script src="/media/js/jquery.min.js" type="text/javascript"></script>
<script src="/media/js/bootstrap.min.js" type="text/javascript"></script>
<script src="/media/js/select2.min.js"></script>
<script src="/media/js/toastr.min.js" type="text/javascript"></script>
<script src="/media/js/website.js" type="text/javascript"></script>
<script src="/media/js/cookies-consent.js"></script>
<script src="/media/js/moment-with-locales.min.js"></script>
<script src="/media/js/bootstrap-datetimepicker.min.js"></script>
<script src="/media/js/popper.min.js"></script>
<script type="module" defer src="/media/js/inactivity.js"></script> <!-- The script loads “in the background”, and then runs when the DOM is fully built. -->
</head>
<body id="page_init">
<div id="cookies-consent" data-lift="WebUI.cookieConsent">
<div id="cookies-consent-font"> We use cookies to support session management.</div>
<input id="cookies-consent-button" class="btn btn-default" type="button" value="Accept and close" onclick="removeByIdAndSaveIndicatorCookie('cookies-consent')"/>
</div>
<div id="toast-container" class="toast-top-right" aria-live="assertive" role="alert" aria-atomic="true" aria-relevant="additions removals"></div>
<div class="container">
<header>
<div id="header-decoration"></div>
<div id="cookie-ipaddress-concurrent-logins" data-lift="WebUI.concurrentLoginsCookiesCheck"></div>
<div id="top-text" data-lift="WebUI.topText"></div>
<table id="table-header" aria-describedby="Home Page Logo">
<tr class="row">
<td>
<th scope="col"></th>
</td>
<td id="td-logo-left-xs">
<div data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="Home Page"><img src="" id="logo-left-xs" align="left" alt="left logo image"></a>
</div>
</td>
<td id="td-logo-right-xs">
<div data-lift="WebUI.headerLogoRight">
<a href="/" aria-label="Home Page"><img src="" id="logo-right-xs" align="right" alt="right logo image"></a>
</div>
</td>
<td></td>
</tr>
</table>
<div id="table-header-script" data-lift="WebUI.headerContentLoader"></div>
<div id="messages-container" data-lift="Msgs"></div>
</header>
<a class="sr-only sr-only-focusable" href ="#main" id="index-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#register-consumer" id="consumer-registration-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#api_documentation_content" id="introduction-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#authorise" id="logon-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#recover-password" id="lost-password-page">Skip to main content</a>
<a class="sr-only sr-only-focusable" href ="#signup" id="sign-up-page">Skip to main content</a>
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<!-- <div class="navbar-header">-->
<!-- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">-->
<!-- <span class="sr-only">Toggle navigation</span>-->
<!-- <span class="icon-bar"></span>-->
<!-- <span class="icon-bar"></span>-->
<!-- <span class="icon-bar"></span>-->
<!-- </button>-->
<!-- </div>-->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="navitem" data-lift="WebUI.headerLogoLeft">
<a id ="navitem-logo" class="navlink " href="/" ><img src="/media/images/logo.png" alt="homepage logo"></a>
</li>
<li class="navitem">
<a class="navlink api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">
<lift:loc locid="api_explorer">API Explorer</lift:loc>
</a>
</li>
<li data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" class="navitem">
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</li>
<li class="navitem">
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">
<lift:loc locid="introduction">Introduction</lift:loc>
</a>
</li>
<li class="navitem">
<a class="navlink" id="technical-faqs-anchor" data-lift="WebUI.technicalFaqsAnchor" href="">
<lift:loc locid="support">Support</lift:loc>
</a>
</li>
<li class="navitem" data-lift="WebUI.subscriptionsButton" >
<a class="navlink subscriptions-button" href="">
<lift:loc locid="subscriptions"><div class ="subscriptions-button-text"></div></lift:loc>
</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!-- login state -->
<li data-lift="Nav.item?name=Login" class="navitem">
<!-- LOGGED OUT -->
<p data-lift="WebUI.userIsLoggedIn">
<a href="/user_mgt/sign_up" class="navbar-btn" id ="register-link"><lift:loc locid="register">Register</lift:loc></a><a data-lift="Login.loggedOut" href="#" class="btn btn-danger login"><lift:loc locid="logon">Log on</lift:loc></a>
</p>
</li>
<li class="navitem" data-lift="Login.loggedIn" >
<!-- LOGGED IN -->
<p class="navbar-btn"><a href="/user-information"><span id="loggedIn-username">username</span></a><a href="#" class="btn btn-default logout">Log off <span id="countdown-timer-span" class="badge text-bg-primary"></span></a></p>
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div id="small-screen-navbar">
<button tabindex="0" id="small-nav-collapse" onclick="openNav()" aria-label ="Open Navigation" aria-hidden="false"></button>
<div id="small-nav-logo" data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="HomePage Logo"><img src="" class="logo-responsive-design-mobile" alt ="Home Page Logo"></a>
</div>
<div id="small-nav-log-on-button" data-lift="Nav.item?name=Login">
<a data-lift="Login.loggedOut" href="#" class="btn btn-danger login">Log on</a>
</div>
</div>
<div id="obp-sidebar" class="sidebar">
<div id="small-screen-navbar">
<button tabindex="0" id="small-nav-cross" onclick="closeNav()" aria-hidden="true" aria-label ="Close Navigation"></button>
<div id="small-nav-logo" data-lift="WebUI.headerLogoLeft">
<a href="/" aria-label="HomePage Logo"><img src="" class="logo-responsive-design-mobile" alt ="Home Page Logo"></a>
</div>
<div id="small-nav-log-on-button" data-lift="Nav.item?name=Login">
<a data-lift="Login.loggedOut" href="#" class="btn btn-danger login">Log on</a>
</div>
</div>
<ul>
<li>
<div class="navitem">
<a class="navlink api-explorer-link" data-lift="WebUI.apiExplorerLink" href="">API Explorer</a>
</div>
</li>
<li>
<div class="navitem" data-lift="Nav.item?name=Consumer%20Registration&showEvenIfRestricted=true" id="sideba-api-key-div">
<a id ="get-api-key-link" class="navlink" href="#">Link name. Has class "selected" if it's the current page.</a>
</div>
</li>
<li>
<div class="navitem">
<a class="navlink" id="sandbox-introduction-link" data-lift="WebUI.sandboxIntroductionLink" href="">Introduction</a>
</div>
</li>
<li>
<div class="navitem" id="technical-faqs-anchor-nav">
<a class="navlink" id="technical-faqs-anchor" data-lift="WebUI.technicalFaqsAnchor" href="">
<lift:loc locid="support">Support</lift:loc></a>
</div>
</li>
<li>
<div class="navitem" id="register-link-nav">
<a data-lift="WebUI.userIsLoggedIn" href="/user_mgt/sign_up" class="navbar-btn" id ="register-link">Register</a>
<a data-lift="Login.loggedIn" href="#" class="logout">Log off</a></p>
</div>
</li>
</ul>
</div>
<section id="content">
<lift:bind name="content"/>
The main content gets bound here
</section>
<!--
<footer>
<div id="footer-div">
<ul>
<li>
<a class="termsAndConditions-link" data-lift="WebUI.termsAndConditions" href="">
<lift:loc locid="terms_conditions">Terms and Conditions</lift:loc></a>
</li>
<li>
<a class="privacy-policy-link" data-lift="WebUI.privacyPolicyLink" href="https://openbankproject.com/privacy-policy">
<lift:loc locid="privacy_policy">Privacy Policy</lift:loc></a>
</li>
<li>
<a id="footer-div-social" href="http://twitter.com/#!/OpenBankProject" data-lift="WebUI.footerSocialLink">Twitter</a>
</li>
<li>
<a href="https://github.com/OpenBankProject/OBP-API/">Github</a>
</li>
<li>
<a class="api-documentation-link" data-lift="WebUI.apiDocumentationLink" href="">
<lift:loc locid="api_documentation">API Documentation</lift:loc></a>
</li>
<li>
<a class="sofi-link" data-lift="WebUI.sofiLink" href="">Sofit</a>
</li>
<li>
<a href="/user_mgt/sign_up?after-signup=link-to-customer" class="navbar-btn" id ="register-link">
<lift:loc locid="register">On Board</lift:loc></a>
</li>
<li>
<a class="api-link" data-lift="WebUI.apiLinkHuman" href="">
<lift:loc locid="api_host">This API Host</lift:loc></a>
</li>
<li>
<a class="commit-id-link" data-lift="WebUI.commitIdLink" href="">
<lift:loc locid="api_host">GitHub commit</lift:loc></a>
</li>
</ul>
<br>
<div class="language-tag" data-lift="WebUI.currentPage">
<span><a href="#">Language</a></span>
<div id="supported-language-list"></div>
</div>
<div id="copyright">
<a href="http://openbankproject.com"><lift:loc locid="open_bank_project_is">Open Bank Project is &copy;2011 - </lift:loc> <span id="copyright-year" data-lift="WebUI.currentYearText">2018</span> </a> <a href="http://tesobe.com"><lift:loc locid="and_commercial_licenses">TESOBE and distributed under the AGPL and commercial licenses. </lift:loc></a>
</div>
</div>
</footer>
-->
</div>
</body>
</html>

View File

@ -1,6 +1,7 @@
package code.api.ResourceDocs1_4_0
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
import code.api.berlin.group.ConstantsBG
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.APIUtil.OAuth._
@ -283,7 +284,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with
}
scenario(s"We will test ${ApiEndpoint1.name} Api -v1.3", ApiEndpoint1, VersionOfApi) {
val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.3" / "obp").GET
val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / ConstantsBG.berlinGroupVersion1.apiShortVersion / "obp").GET
val responseGetObp = makeGetRequest(requestGetObp)
And("We should get 200 and the response can be extract to case classes")
val responseDocs = responseGetObp.body.extract[ResourceDocsJson]
@ -293,7 +294,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with
}
scenario(s"We will test ${ApiEndpoint1.name} Api -BGv1.3", ApiEndpoint1, VersionOfApi) {
val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "BGv1.3" / "obp").GET
val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / s"BG${ConstantsBG.berlinGroupVersion1.apiShortVersion}" / "obp").GET
val responseGetObp = makeGetRequest(requestGetObp)
And("We should get 200 and the response can be extract to case classes")
val responseDocs = responseGetObp.body.extract[ResourceDocsJson]
@ -540,7 +541,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with
}
scenario(s"We will test ${ApiEndpoint2.name} Api -v1.3", ApiEndpoint1, VersionOfApi) {
val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v1.3" / "obp").GET
val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / ConstantsBG.berlinGroupVersion1.apiShortVersion / "obp").GET
val responseGetObp = makeGetRequest(requestGetObp)
And("We should get 200 and the response can be extract to case classes")
val responseDocs = responseGetObp.body.extract[ResourceDocsJson]
@ -550,7 +551,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with
}
scenario(s"We will test ${ApiEndpoint2.name} Api -BGv1.3", ApiEndpoint1, VersionOfApi) {
val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "BGv1.3" / "obp").GET
val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / s"BG${ConstantsBG.berlinGroupVersion1.apiShortVersion}" / "obp").GET
val responseGetObp = makeGetRequest(requestGetObp)
And("We should get 200 and the response can be extract to case classes")
val responseDocs = responseGetObp.body.extract[ResourceDocsJson]

View File

@ -1,7 +1,7 @@
package code.api.berlin.group.v1_3
import code.api.Constant
import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID}
import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID}
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._
import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi
import code.api.util.APIUtil
@ -9,10 +9,9 @@ import code.api.util.APIUtil.OAuth._
import code.api.util.ErrorMessages._
import code.api.v4_0_0.PostViewJsonV400
import code.consent.ConsentStatus
import code.model.dataAccess.{BankAccountRouting, MappedBankAccount}
import code.model.dataAccess.BankAccountRouting
import code.setup.{APIResponse, DefaultUsers}
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.{AccountId, BankId, ErrorMessage}
import com.openbankproject.commons.model.enums.AccountRoutingScheme
import net.liftweb.json.Serialization.write
import net.liftweb.mapper.By
@ -103,13 +102,40 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
user1,
PostViewJsonV400(view_id = SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, is_system = true)
)
grantUserAccessToViewViaEndpoint(
bankId,
accountId,
resourceUser1.userId,
user1,
PostViewJsonV400(view_id = SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, is_system = true)
)
grantUserAccessToViewViaEndpoint(
bankId,
accountId,
resourceUser1.userId,
user1,
PostViewJsonV400(view_id = SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID, is_system = true)
)
val requestGet = (V1_3_BG / "accounts" / accountId).GET <@ (user1)
val response = makeGetRequest(requestGet)
Then("We should get a 200 ")
response.code should equal(200)
response.body.extract[AccountDetailsJsonV13].account.resourceId should be (accountId)
val jsonResponse = response.body.extract[AccountDetailsJsonV13]
jsonResponse.account.resourceId should be (accountId)
jsonResponse.account._links.balances match {
case Some(link) =>
link.href.contains(berlinGroupVersion1) shouldBe true
case None => // Nothing to check
}
jsonResponse.account._links.transactions match {
case Some(link) =>
link.href.contains(berlinGroupVersion1) shouldBe true
case None => // Nothing to check
}
}
}
@ -232,6 +258,68 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
}
}
feature(s"BG v1.3 - $createConsent - postJsonBodyAvailableAccounts") {
lazy val postJsonBody = PostConsentJson(
access = ConsentAccessJson(
accounts = None,
balances = None,
transactions = None,
availableAccounts = Some("allAccounts"),
allPsd2 = None
),
recurringIndicator = false,
validUntil = getNextMonthDate(),
frequencyPerDay = 1,
combinedServiceIndicator = Some(false)
)
val postJsonBodyWrong1 = postJsonBody.copy(
access = postJsonBody.access.copy(
availableAccounts = Some("wrong")
)
)
val postJsonBodyWrong2 = postJsonBody.copy(
frequencyPerDay = 2
)
val postJsonBodyWrong3 = postJsonBody.copy(
recurringIndicator = true
)
scenario("Authentication User, test failed due to availableAccounts wrong value", BerlinGroupV1_3, createConsent) {
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong1))
Then("We should get a 400")
response.code should equal(400)
response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessAvailableAccounts)
}
scenario("Authentication User, test failed due to frequency per day", BerlinGroupV1_3, createConsent) {
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong2))
Then("We should get a 400")
response.code should equal(400)
response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessFrequencyPerDay)
}
scenario("Authentication User, test failed due to recurringIndicator = true", BerlinGroupV1_3, createConsent) {
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong3))
Then("We should get a 400")
response.code should equal(400)
response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessRecurringIndicator)
}
scenario("Authentication User, test succeed", BerlinGroupV1_3, createConsent) {
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
Then("We should get a 201 ")
response.code should equal(201)
val jsonResponse = response.body.extract[PostConsentResponseJson]
jsonResponse.consentId should not be (empty)
jsonResponse.consentStatus should be (ConsentStatus.received.toString)
}
}
feature(s"BG v1.3 - $createConsent") {
scenario("Authentication User, test succeed", BerlinGroupV1_3, createConsent) {
val testBankId = testAccountId1
@ -262,8 +350,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
Then("We should get a 201 ")
response.code should equal(201)
response.body.extract[PostConsentResponseJson].consentId should not be (empty)
response.body.extract[PostConsentResponseJson].consentStatus should be (ConsentStatus.received.toString)
val jsonResponse = response.body.extract[PostConsentResponseJson]
jsonResponse.consentId should not be (empty)
jsonResponse.consentStatus should be (ConsentStatus.received.toString)
}
}
@ -484,7 +573,7 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
scenario("Authentication User, only mocked data, just test succeed", BerlinGroupV1_3, updateConsentsPsuDataTransactionAuthorisation) {
val requestStartConsentAuthorisation = (V1_3_BG / "consents"/"consentId" /"authorisations"/ "AUTHORISATIONID" ).PUT <@ (user1)
val responseStartConsentAuthorisation = makePutRequest(requestStartConsentAuthorisation, """{"scaAuthenticationData":""}""")
responseStartConsentAuthorisation.code should be (400)
responseStartConsentAuthorisation.code should be (403)
}

View File

@ -2,20 +2,21 @@ package code.api.berlin.group.v1_3
import code.api.Constant
import code.api.Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID
import code.api.berlin.group.ConstantsBG
import code.api.util.APIUtil.OAuth._
import code.api.util.APIUtil.OAuth.{Consumer, Token}
import code.api.v3_0_0.ViewJsonV300
import code.api.v4_0_0.{PostAccountAccessJsonV400, PostViewJsonV400}
import code.setup.ServerSetupWithTestData
import code.views.Views
import com.openbankproject.commons.util.ApiVersion
import dispatch.Req
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
trait BerlinGroupServerSetupV1_3 extends ServerSetupWithTestData {
val berlinGroupVersion1: String = ConstantsBG.berlinGroupVersion1.apiShortVersion
object BerlinGroupV1_3 extends Tag("BerlinGroup_v1_3")
val V1_3_BG = baseRequest / ApiVersion.berlinGroupV13.urlPrefix / "v1.3"
val V1_3_BG = baseRequest / ConstantsBG.berlinGroupVersion1.urlPrefix / ConstantsBG.berlinGroupVersion1.apiShortVersion
def v4_0_0_Request: Req = baseRequest / "obp" / "v4.0.0"
override def beforeEach() = {

View File

@ -30,6 +30,7 @@ package code.util
import code.api.Constant.SYSTEM_OWNER_VIEW_ID
import code.api.UKOpenBanking.v2_0_0.{APIMethods_UKOpenBanking_200, OBP_UKOpenBanking_200}
import code.api.UKOpenBanking.v3_1_0.{APIMethods_AccountAccessApi, OBP_UKOpenBanking_310}
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.OBP_BERLIN_GROUP_1_3
import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi
import code.api.util.APIUtil.OBPEndpoint
@ -42,6 +43,8 @@ import code.views.system.ViewDefinition
import com.openbankproject.commons.util.ApiVersion
class APIUtilHeavyTest extends V400ServerSetup with PropsReset {
val bgVersion = ConstantsBG.berlinGroupVersion1.apiShortVersion
feature("test APIUtil.versionIsAllowed method") {
//This mean, we are only disabled the v4.0.0, all other versions should be enabled
@ -101,70 +104,72 @@ class APIUtilHeavyTest extends V400ServerSetup with PropsReset {
feature("test APIUtil.getAllowedEndpoints method") {
val obpEndpointsV400: List[OBPEndpoint] = OBPAPI4_0_0.endpointsOf4_0_0.toList
val obpAllResourceDocsV400 = Implementations4_0_0.resourceDocs
scenario(s"Test the APIUtil.getAllowedEndpoints method") {
val obpEndpointsV400: List[OBPEndpoint] = OBPAPI4_0_0.endpointsOf4_0_0.toList
val obpAllResourceDocsV400 = Implementations4_0_0.resourceDocs
val allowedEndpoints: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(obpEndpointsV400, obpAllResourceDocsV400).toList
val allowedEndpoints: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(obpEndpointsV400, obpAllResourceDocsV400).toList
val allowedOperationIds = allowedEndpoints.map(_.operationId)
val allowedOperationIds = allowedEndpoints.map(_.operationId)
allowedOperationIds contains("OBPv4.0.0-getLogoutLink") should be (true)
allowedOperationIds contains("OBPv4.0.0-getLogoutLink") should be (true)
setPropsValues(
"api_disabled_endpoints" -> "[OBPv4.0.0-getLogoutLink,OBPv4.0.0-getMapperDatabaseInfo,OBPv4.0.0-callsLimit,OBPv4.0.0-getBanks,OBPv4.0.0-ibanChecker]",
"api_enabled_endpoints" -> "[]"
)
val allowedEndpoints2: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(obpEndpointsV400, obpAllResourceDocsV400).toList
setPropsValues(
"api_disabled_endpoints" -> "[OBPv4.0.0-getLogoutLink,OBPv4.0.0-getMapperDatabaseInfo,OBPv4.0.0-callsLimit,OBPv4.0.0-getBanks,OBPv4.0.0-ibanChecker]",
"api_enabled_endpoints" -> "[]"
)
val allowedEndpoints2: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(obpEndpointsV400, obpAllResourceDocsV400).toList
val allowedOperationIds2 = allowedEndpoints2.map(_.operationId)
val allowedOperationIds2 = allowedEndpoints2.map(_.operationId)
allowedOperationIds2 contains("OBPv4.0.0-getLogoutLink") should be (false)
allowedOperationIds2 contains("OBPv4.0.0-getMapperDatabaseInfo") should be (false)
allowedOperationIds2 contains("OBPv4.0.0-callsLimit") should be (false)
allowedOperationIds2 contains("OBPv4.0.0-getLogoutLink") should be (false)
allowedOperationIds2 contains("OBPv4.0.0-getMapperDatabaseInfo") should be (false)
allowedOperationIds2 contains("OBPv4.0.0-callsLimit") should be (false)
val bgResourceDocsV13 = APIMethods_AccountInformationServiceAISApi.resourceDocs
val bgEndpointsV13 = APIMethods_AccountInformationServiceAISApi.endpoints
val bgResourceDocsV13 = APIMethods_AccountInformationServiceAISApi.resourceDocs
val bgEndpointsV13 = APIMethods_AccountInformationServiceAISApi.endpoints
setPropsValues(
"api_disabled_endpoints" -> "[BGv1.3-createConsent,BGv1.3-deleteConsent]",
"api_enabled_endpoints" -> "[]"
)
val allowedEndpoints3: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(bgEndpointsV13, bgResourceDocsV13).toList
val allowedOperationIds3 = allowedEndpoints3.map(_.operationId)
allowedOperationIds3 contains("BGv1.3-getCardAccountTransactionList") should be (true)
allowedOperationIds3 contains("BGv1.3-createConsent") should be (false)
allowedOperationIds3 contains("BGv1.3-deleteConsent") should be (false)
setPropsValues(
"api_disabled_endpoints" -> s"[BG${bgVersion}-createConsent,BG${bgVersion}-deleteConsent]",
"api_enabled_endpoints" -> "[]"
)
val ukResourceDocsV31 = APIMethods_AccountAccessApi.resourceDocs
val ukEndpointsV31 = APIMethods_AccountAccessApi.endpoints
val allowedEndpoints3: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(bgEndpointsV13, bgResourceDocsV13).toList
val allowedOperationIds3 = allowedEndpoints3.map(_.operationId)
setPropsValues(
"api_disabled_endpoints" -> "[UKv3.1-createAccountAccessConsents,UKv3.1-deleteConsent]",
"api_enabled_endpoints" -> "[]"
)
allowedOperationIds3 contains(s"BG${bgVersion}-getCardAccountTransactionList") should be (true)
allowedOperationIds3 contains(s"BG${bgVersion}-createConsent") should be (false)
allowedOperationIds3 contains(s"BG${bgVersion}-deleteConsent") should be (false)
val allowedEndpoints4: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(ukEndpointsV31, ukResourceDocsV31).toList
val allowedOperationIds4 = allowedEndpoints4.map(_.operationId)
val ukResourceDocsV31 = APIMethods_AccountAccessApi.resourceDocs
val ukEndpointsV31 = APIMethods_AccountAccessApi.endpoints
allowedOperationIds4 contains("UKv3.1-getAccountAccessConsentsConsentId") should be (true)
allowedOperationIds4 contains("UKv3.1-createAccountAccessConsents") should be (false)
allowedOperationIds4 contains("UKv3.1-deleteConsent") should be (false)
setPropsValues(
"api_disabled_endpoints" -> "[UKv3.1-createAccountAccessConsents,UKv3.1-deleteConsent]",
"api_enabled_endpoints" -> "[]"
)
setPropsValues(
"api_disabled_endpoints" -> "[]",
"api_enabled_endpoints" -> "[UKv3.1-createAccountAccessConsents]"
)
val allowedEndpoints4: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(ukEndpointsV31, ukResourceDocsV31).toList
val allowedOperationIds4 = allowedEndpoints4.map(_.operationId)
val allowedEndpoints5: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(ukEndpointsV31, ukResourceDocsV31).toList
val allowedOperationIds5 = allowedEndpoints5.map(_.operationId)
allowedOperationIds4 contains("UKv3.1-getAccountAccessConsentsConsentId") should be (true)
allowedOperationIds4 contains("UKv3.1-createAccountAccessConsents") should be (false)
allowedOperationIds4 contains("UKv3.1-deleteConsent") should be (false)
allowedOperationIds5.length should be (1)
allowedOperationIds5 contains("UKv3.1-createAccountAccessConsents") should be (true)
allowedOperationIds5 contains("UKv3.1-deleteConsent") should be (false)
setPropsValues(
"api_disabled_endpoints" -> "[]",
"api_enabled_endpoints" -> "[UKv3.1-createAccountAccessConsents]"
)
val allowedEndpoints5: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(ukEndpointsV31, ukResourceDocsV31).toList
val allowedOperationIds5 = allowedEndpoints5.map(_.operationId)
allowedOperationIds5.length should be (1)
allowedOperationIds5 contains("UKv3.1-createAccountAccessConsents") should be (true)
allowedOperationIds5 contains("UKv3.1-deleteConsent") should be (false)
}
}
feature("test APIUtil.getPermissionPairFromViewDefinition method") {