Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ jwt.use.ssl=false
# Bypass TPP signature validation
# bypass_tpp_signature_validation = false

## Reject Berlin Group consents with status "received" after a defined time (in seconds)
## Reject Berlin Group TRANSACTIONS with status "received" after a defined time (in seconds)
# berlin_group_outdated_transactions_time_in_seconds = 300
# berlin_group_outdated_transactions_interval_in_seconds =

## Reject Berlin Group CONSENTS with status "received" after a defined time (in seconds)
# berlin_group_outdated_consents_time_in_seconds = 300
# berlin_group_outdated_consents_interval_in_seconds =

Expand Down Expand Up @@ -795,8 +799,14 @@ display_internal_errors=false
# - redirection_with_dedicated_start_of_authorization
# - embedded
# - decoupled
#
# In case that "psu_authentication_method = redirection" you must define
# Please note that in case that redirect_url_value contains special word PLACEHOLDER it will be replaced with actual ID
# http://127.0.0.1:8080/confirm-bg-consent-request?CONSENT_ID=PLACEHOLDER
# psu_authentication_method_sca_redirect_url = redirect_url_value
#
# Please note that in case that redirect_url_value contains special word PLACEHOLDER it will be replaced with actual ID
# http://127.0.0.1:8080/confirm-bg-consent-request?PAYMENT_ID=PLACEHOLDER
# psu_make_payment_sca_redirect_url = redirect_url_value
# -------------------------------------------------------------- Authentication methods --

Expand Down
4 changes: 3 additions & 1 deletion obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ import code.productfee.ProductFee
import code.products.MappedProduct
import code.ratelimiting.RateLimiting
import code.regulatedentities.MappedRegulatedEntity
import code.scheduler.{ConsentScheduler, DataBaseCleanerScheduler, DatabaseDriverScheduler, JobScheduler, MetricsArchiveScheduler}
import code.scheduler.{ConsentScheduler, DataBaseCleanerScheduler, DatabaseDriverScheduler, JobScheduler, MetricsArchiveScheduler, TransactionScheduler}
import code.scope.{MappedScope, MappedUserScope}
import code.signingbaskets.{MappedSigningBasket, MappedSigningBasketConsent, MappedSigningBasketPayment}
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
Expand Down Expand Up @@ -137,6 +137,7 @@ import code.regulatedentities.attribute.RegulatedEntityAttribute
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.Functions.Implicits._
import com.openbankproject.commons.util.{ApiVersion, Functions}

import javax.mail.internet.MimeMessage
import net.liftweb.common._
import net.liftweb.db.{DB, DBLogEntry}
Expand Down Expand Up @@ -730,6 +731,7 @@ class Boot extends MdcLoggable {
case _ => // Do not start it
}
ConsentScheduler.startAll()
TransactionScheduler.startAll()


APIUtil.getPropsAsBoolValue("enable_metrics_scheduler", true) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ respectively the OAuth2 access token.
//The card contains the account object, it mean the card account.
(_, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext)
(accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext)
(canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext)
(canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext)
//also see `getAccountList` endpoint
bankAccountsFiltered = accounts.filter(bankAccount =>
bankAccount.attributes.toList.flatten.find(attribute=>
Expand All @@ -456,7 +458,14 @@ respectively the OAuth2 access token.
attribute.value.equalsIgnoreCase("card")
).isDefined)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson(bankAccountsFiltered, u), callContext)
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson(
bankAccountsFiltered,
canReadBalancesAccounts,
canReadTransactionsAccounts,
u
),
callContext
)
}
}
}
Expand Down Expand Up @@ -986,9 +995,19 @@ Give detailed information about the addressed account together with balance info
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext)
(canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext)
(canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson(account, u), callContext)
(
JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson(
account,
canReadBalancesAccounts,
canReadTransactionsAccounts,
u
),
callContext
)
}
}
}
Expand Down Expand Up @@ -1040,9 +1059,11 @@ respectively the OAuth2 access token.
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext)
(canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext)
(canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, u), callContext)
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, canReadBalancesAccounts, canReadTransactionsAccounts, u), callContext)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
)

case class CoreAccountLinksJsonV13(
balances: LinkHrefJson,
balances: Option[LinkHrefJson] = None,
transactions: Option[LinkHrefJson] = None // These links are only supported, when the corresponding consent has been already granted.
)

case class CoreAccountBalancesJson(
balanceAmount:AmountOfMoneyV13 = AmountOfMoneyV13("EUR","123"),
balanceType: String = "closingBooked",
lastChangeDateTime: String = "2019-01-28T06:26:52.185Z",
referenceDate: String = "2020-07-02",
lastCommittedTransaction: String = "string",
case class CoreAccountBalanceJson(
balanceAmount:AmountOfMoneyV13,// = AmountOfMoneyV13("EUR","123"),
balanceType: String //= "closingBooked",
// lastChangeDateTime: String = "2019-01-28T06:26:52.185Z",
// referenceDate: String = "2020-07-02",
// lastCommittedTransaction: String = "string",
)
case class CoreAccountJsonV13(
resourceId: String,
Expand All @@ -70,16 +70,16 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
// linkedAccounts: String ="string",
// usage: String ="PRIV",
// details: String ="",
// balances: CoreAccountBalancesJson,// We put this under the _links, not need to show it here.
balances: Option[List[CoreAccountBalanceJson]] = None,
_links: CoreAccountLinksJsonV13,
)

case class CoreAccountsJsonV13(accounts: List[CoreAccountJsonV13])
case class CoreCardAccountsJsonV13(cardAccounts: List[CoreAccountJsonV13])

case class AccountDetailsLinksJsonV13(
balances: LinkHrefJson,
transactions: LinkHrefJson
balances: Option[LinkHrefJson],
transactions: Option[LinkHrefJson]
)

case class AccountJsonV13(
Expand Down Expand Up @@ -315,9 +315,13 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
val (iBan: String, bBan: String) = getIbanAndBban(x)
val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}"
val balanceRef = LinkHrefJson(s"/$commonPath/balances")
val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(x.accountId.value)
val transactionRef = LinkHrefJson(s"/$commonPath/transactions")
val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(x.accountId.value)

val accountBalances = Some(List(CoreAccountBalanceJson(
balanceAmount = AmountOfMoneyV13(x.currency, x.balance.toString),
balanceType = "closingBooked"
)))

CoreAccountJsonV13(
resourceId = x.accountId.value,
Expand All @@ -327,19 +331,33 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
name = x.name,
cashAccountType = x.accountType,
product = x.accountType,
balances = accountBalances,
_links = CoreAccountLinksJsonV13(
balances = balanceRef,
balances = if(canReadBalances) Some(balanceRef) else None,
transactions = if(canReadTransactions) Some(transactionRef) else None,
)
)
}
)
}

def createCardAccountListJson(bankAccounts: List[BankAccount], user: User): CoreCardAccountsJsonV13 = {
def createCardAccountListJson(bankAccounts: List[BankAccount],
canReadBalancesAccounts: List[BankIdAccountId],
canReadTransactionsAccounts: List[BankIdAccountId],
user: User): CoreCardAccountsJsonV13 = {
CoreCardAccountsJsonV13(bankAccounts.map {
x =>
val (iBan: String, bBan: String) = getIbanAndBban(x)
val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}"
val balanceRef = LinkHrefJson(s"/$commonPath/balances")
val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(x.accountId.value)
val transactionRef = LinkHrefJson(s"/$commonPath/transactions")
val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(x.accountId.value)

val accountBalances = Some(List(CoreAccountBalanceJson(
balanceAmount = AmountOfMoneyV13(x.currency, x.balance.toString),
balanceType = "closingBooked"
)))

CoreAccountJsonV13(
resourceId = x.accountId.value,
Expand All @@ -349,19 +367,34 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
name = x.name,
cashAccountType = x.accountType,
product = x.accountType,
_links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances"))
balances = accountBalances,
_links = CoreAccountLinksJsonV13(
balances = if (canReadBalances) Some(balanceRef) else None,
transactions = if (canReadTransactions) Some(transactionRef) else None,
)
)
}
)
}

def createCardAccountDetailsJson(bankAccount: BankAccount, user: User): CardAccountDetailsJsonV13 = {
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount: BankAccount, user: User)
def createCardAccountDetailsJson(bankAccount: BankAccount,
canReadBalancesAccounts: List[BankIdAccountId],
canReadTransactionsAccounts: List[BankIdAccountId],
user: User): CardAccountDetailsJsonV13 = {
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, user)
CardAccountDetailsJsonV13(accountDetailsJsonV13.account)
}

def createAccountDetailsJson(bankAccount: BankAccount, user: User): AccountDetailsJsonV13 = {
def createAccountDetailsJson(bankAccount: BankAccount,
canReadBalancesAccounts: List[BankIdAccountId],
canReadTransactionsAccounts: List[BankIdAccountId],
user: User): AccountDetailsJsonV13 = {
val (iBan: String, bBan: String) = getIbanAndBban(bankAccount)
val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}"
val balanceRef = LinkHrefJson(s"/$commonPath/balances")
val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(bankAccount.accountId.value)
val transactionRef = LinkHrefJson(s"/$commonPath/transactions")
val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(bankAccount.accountId.value)
val account = AccountJsonV13(
resourceId = bankAccount.accountId.value,
iban = iBan,
Expand All @@ -370,8 +403,8 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
cashAccountType = bankAccount.accountType,
product = bankAccount.accountType,
_links = AccountDetailsLinksJsonV13(
LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}/balances"),
LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}/transactions")
balances = if (canReadBalances) Some(balanceRef) else None,
transactions = if (canReadTransactions) Some(transactionRef) else None,
)
)
AccountDetailsJsonV13(account)
Expand Down Expand Up @@ -644,8 +677,13 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
// Remark: This code may be
//map OBP transactionRequestId to BerlinGroup PaymentId
val paymentId = transactionRequest.id.value
val scaRedirectUrl = getPropsValue("psu_make_payment_sca_redirect_url")
val scaRedirectUrlPattern = getPropsValue("psu_make_payment_sca_redirect_url")
.openOr(MissingPropsValueAtThisInstance + "psu_make_payment_sca_redirect_url")
val scaRedirectUrl =
if (scaRedirectUrlPattern.contains("PLACEHOLDER"))
scaRedirectUrlPattern.replace("PLACEHOLDER", paymentId)
else
s"$scaRedirectUrlPattern/${paymentId}"
InitiatePaymentResponseJson(
transactionStatus = mapTransactionStatus(transactionRequest.status),
paymentId = paymentId,
Expand Down
47 changes: 25 additions & 22 deletions obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ object BerlinGroupSigning extends MdcLoggable {
*/
def verifySignedRequest(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = {
def checkRequestIsSigned(requestHeaders: List[HTTPParam]): Boolean = {
requestHeaders.exists(_.name == RequestHeader.`TPP-Signature-Certificate`) &&
requestHeaders.exists(_.name == RequestHeader.Signature) &&
requestHeaders.exists(_.name == RequestHeader.Digest)
requestHeaders.exists(_.name.toLowerCase() == RequestHeader.`TPP-Signature-Certificate`.toLowerCase()) &&
requestHeaders.exists(_.name.toLowerCase() == RequestHeader.Signature.toLowerCase()) &&
requestHeaders.exists(_.name.toLowerCase() == RequestHeader.Digest.toLowerCase())
}
checkRequestIsSigned(forwardResult._2.map(_.requestHeaders).getOrElse(Nil)) match {
case false =>
Expand All @@ -161,26 +161,29 @@ object BerlinGroupSigning extends MdcLoggable {
X509.validateCertificate(certificate) match {
case Full(true) => // PEM certificate is ok
val digest = generateDigest(body.getOrElse(""))

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"
} else {
s"$h: ${getHeaderValue(h, requestHeaders)}"
if(digest == getHeaderValue(RequestHeader.Digest, requestHeaders)) { // 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"
} else {
s"$h: ${getHeaderValue(h, requestHeaders)}"
}
)
val signingString = headers.mkString("\n")
val isVerified = verifySignature(signingString, signature, certificate.getPublicKey)
val isValidated = CertificateVerifier.validateCertificate(certificate)
val bypassValidation = APIUtil.getPropsAsBoolValue("bypass_tpp_signature_validation", defaultValue = false)
(isVerified, isValidated) match {
case (true, true) => forwardResult
case (true, false) if bypassValidation => forwardResult
case (true, false) => (Failure(ErrorMessages.X509PublicKeyCannotBeValidated), forwardResult._2)
case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
}
)
val signingString = headers.mkString("\n")
val isVerified = verifySignature(signingString, signature, certificate.getPublicKey)
val isValidated = CertificateVerifier.validateCertificate(certificate)
val bypassValidation = APIUtil.getPropsAsBoolValue("bypass_tpp_signature_validation", defaultValue = false)
(isVerified, isValidated) match {
case (true, true) => forwardResult
case (true, false) if bypassValidation => forwardResult
case (true, false) => (Failure(ErrorMessages.X509PublicKeyCannotBeValidated), forwardResult._2)
case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
} else { // The two DIGEST hashes do NOT match, the integrity of the request body is NOT confirmed.
(Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2)
}
case Failure(msg, t, c) => (Failure(msg, t, c), forwardResult._2) // PEM certificate is not valid
case _ => (Failure(ErrorMessages.X509GeneralError), forwardResult._2) // PEM certificate cannot be validated
Expand Down
Loading