Merge pull request #2596 from constantine2nd/develop

Add query param withBalance at BG endpoint Read Account Details
This commit is contained in:
Simon Redfern 2025-08-15 13:52:38 +02:00 committed by GitHub
commit 0481dd353b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 86 additions and 12 deletions

View File

@ -1036,17 +1036,27 @@ Give detailed information about the addressed account together with balance info
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) {
val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance")
if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean)
}
_ <- 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)
(accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances(
AccountId(accountId),
callContext
)
} yield {
(
JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson(
account,
canReadBalancesAccounts,
canReadTransactionsAccounts,
withBalanceParam,
accountBalances,
u
),
callContext
@ -1105,8 +1115,23 @@ respectively the OAuth2 access token.
(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)
withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) {
val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance")
if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean)
}
(accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances(
AccountId(accountId),
callContext
)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, canReadBalancesAccounts, canReadTransactionsAccounts, u), callContext)
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(
account,
canReadBalancesAccounts,
canReadTransactionsAccounts,
withBalanceParam,
accountBalances,
u
), callContext)
}
}
}

View File

@ -93,6 +93,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
product: String,
cashAccountType: String,
name: Option[String],
balances: Option[List[CoreAccountBalanceJson]] = None,
_links: AccountDetailsLinksJsonV13,
)
@ -407,14 +408,18 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
def createCardAccountDetailsJson(bankAccount: BankAccount,
canReadBalancesAccounts: List[BankIdAccountId],
canReadTransactionsAccounts: List[BankIdAccountId],
withBalanceParam: Option[Boolean],
balances: List[BankAccountBalanceTrait],
user: User): CardAccountDetailsJsonV13 = {
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, user)
val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, withBalanceParam, balances, user)
CardAccountDetailsJsonV13(accountDetailsJsonV13.account)
}
def createAccountDetailsJson(bankAccount: BankAccount,
canReadBalancesAccounts: List[BankIdAccountId],
canReadTransactionsAccounts: List[BankIdAccountId],
withBalanceParam: Option[Boolean],
balances: List[BankAccountBalanceTrait],
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}"
@ -423,7 +428,15 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
val transactionRef = LinkHrefJson(s"/$commonPath/transactions")
val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(bankAccount.accountId.value)
val cashAccountType = bankAccount.attributes.getOrElse(Nil).filter(_.name== "cashAccountType").map(_.value).headOption.getOrElse("")
val accountBalances = if (withBalanceParam.contains(true)) {
Some(balances.filter(_.accountId.equals(bankAccount.accountId)).flatMap(balance => (List(CoreAccountBalanceJson(
balanceAmount = AmountOfMoneyV13(bankAccount.currency, balance.balanceAmount.toString()),
balanceType = balance.balanceType,
lastChangeDateTime = balance.lastChangeDateTime.map(APIUtil.DateWithMsAndTimeZoneOffset.format(_))
)))))
} else {
None
}
val account = AccountJsonV13(
resourceId = bankAccount.accountId.value,
@ -432,6 +445,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{
name = if(APIUtil.getPropsAsBoolValue("BG_v1312_show_account_name", defaultValue = true)) Some(bankAccount.name) else None,
cashAccountType = cashAccountType,
product = bankAccount.accountType,
balances = if(canReadBalances) accountBalances else None,
_links = AccountDetailsLinksJsonV13(
balances = if (canReadBalances) Some(balanceRef) else None,
transactions = if (canReadTransactions) Some(transactionRef) else None,

View File

@ -1147,6 +1147,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case "iss" => Full(OBPIss(values.head))
case "consent_id" => Full(OBPConsentId(values.head))
case "user_id" => Full(OBPUserId(values.head))
case "provider_provider_id" => Full(ProviderProviderId(values.head))
case "bank_id" => Full(OBPBankId(values.head))
case "account_id" => Full(OBPAccountId(values.head))
case "url" => Full(OBPUrl(values.head))
@ -1198,6 +1199,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
iss <- getHttpParamValuesByName(httpParams,"iss")
consentId <- getHttpParamValuesByName(httpParams,"consent_id")
userId <- getHttpParamValuesByName(httpParams, "user_id")
providerProviderId <- getHttpParamValuesByName(httpParams, "provider_provider_id")
bankId <- getHttpParamValuesByName(httpParams, "bank_id")
accountId <- getHttpParamValuesByName(httpParams, "account_id")
url <- getHttpParamValuesByName(httpParams, "url")
@ -1231,9 +1233,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
*/
//val sortBy = json.header("obp_sort_by")
val ordering = OBPOrdering(None, sortDirection)
//This guarantee the order
//This guarantee the order
List(limit, offset, ordering, sortBy, fromDate, toDate,
anon, status, consumerId, azp, iss, consentId, userId, url, appName, implementedByPartialFunction, implementedInVersion,
anon, status, consumerId, azp, iss, consentId, userId, providerProviderId, url, appName, implementedByPartialFunction, implementedInVersion,
verb, correlationId, duration, excludeAppNames, excludeUrlPattern, excludeImplementedByPartialfunctions,
includeAppNames, includeUrlPattern, includeImplementedByPartialfunctions,
connectorName,functionName, bankId, accountId, customerId, lockedStatus, deletedStatus
@ -1276,6 +1278,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val azp = getHttpRequestUrlParam(httpRequestUrl,"azp")
val consentId = getHttpRequestUrlParam(httpRequestUrl,"consent_id")
val userId = getHttpRequestUrlParam(httpRequestUrl, "user_id")
val providerProviderId = getHttpRequestUrlParam(httpRequestUrl, "provider_provider_id")
val bankId = getHttpRequestUrlParam(httpRequestUrl, "bank_id")
val accountId = getHttpRequestUrlParam(httpRequestUrl, "account_id")
val url = getHttpRequestUrlParam(httpRequestUrl, "url")
@ -1305,7 +1308,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
Full(List(
HTTPParam("sort_by",sortBy), HTTPParam("sort_direction",sortDirection), HTTPParam("from_date",fromDate), HTTPParam("to_date", toDate), HTTPParam("limit",limit), HTTPParam("offset",offset),
HTTPParam("anon", anon), HTTPParam("status", status), HTTPParam("consumer_id", consumerId), HTTPParam("azp", azp), HTTPParam("iss", iss), HTTPParam("consent_id", consentId), HTTPParam("user_id", userId), HTTPParam("url", url), HTTPParam("app_name", appName),
HTTPParam("anon", anon), HTTPParam("status", status), HTTPParam("consumer_id", consumerId), HTTPParam("azp", azp), HTTPParam("iss", iss), HTTPParam("consent_id", consentId), HTTPParam("user_id", userId), HTTPParam("provider_provider_id", providerProviderId), HTTPParam("url", url), HTTPParam("app_name", appName),
HTTPParam("implemented_by_partial_function",implementedByPartialFunction), HTTPParam("implemented_in_version",implementedInVersion), HTTPParam("verb", verb),
HTTPParam("correlation_id", correlationId), HTTPParam("duration", duration), HTTPParam("exclude_app_names", excludeAppNames),
HTTPParam("exclude_url_patterns", excludeUrlPattern),HTTPParam("exclude_implemented_by_partial_functions", excludeImplementedByPartialfunctions),

View File

@ -31,6 +31,7 @@ case class OBPAzp(value: String) extends OBPQueryParam
case class OBPIss(value: String) extends OBPQueryParam
case class OBPConsentId(value: String) extends OBPQueryParam
case class OBPUserId(value: String) extends OBPQueryParam
case class ProviderProviderId(value: String) extends OBPQueryParam
case class OBPStatus(value: String) extends OBPQueryParam
case class OBPBankId(value: String) extends OBPQueryParam
case class OBPAccountId(value: String) extends OBPQueryParam

View File

@ -1707,6 +1707,10 @@ trait APIMethods510 {
|
|7 bank_id (ignore if omitted)
|
|8 provider_provider_id (ignore if omitted)
|provider and provider_id values are separated by pipe char
|eg: provider_provider_id=http%3A%2F%2Flocalhost%3A7070%2Frealms%2Fmaster|7837ee9c-3446-4d8c-9b90-301a52b4851d
|
|eg:/management/consents?consumer_id=78&limit=10&offset=10
|
""".stripMargin,

View File

@ -46,12 +46,13 @@ import code.consent.MappedConsent
import code.metrics.APIMetric
import code.model.Consumer
import code.users.{UserAttribute, Users}
import code.util.Helper.MdcLoggable
import code.views.system.{AccountAccess, ViewDefinition, ViewPermission}
import com.openbankproject.commons.model._
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.{Box, Full}
import net.liftweb.json
import net.liftweb.json.{JString, JValue, parse, parseOpt}
import net.liftweb.json.{JString, JValue, MappingException, parse, parseOpt}
import java.text.SimpleDateFormat
import java.util.Date
@ -676,7 +677,7 @@ case class ViewPermissionJson(
extra_data: Option[List[String]]
)
object JSONFactory510 extends CustomJsonFormats {
object JSONFactory510 extends CustomJsonFormats with MdcLoggable {
def createTransactionRequestJson(tr : TransactionRequest, transactionRequestAttributes: List[TransactionRequestAttributeTrait] ) : TransactionRequestJsonV510 = {
TransactionRequestJsonV510(
@ -980,7 +981,16 @@ object JSONFactory510 extends CustomJsonFormats {
def createConsentsJsonV510(consents: List[MappedConsent]): ConsentsJsonV510 = {
ConsentsJsonV510(
consents.map { c =>
val jwtPayload = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT]).toOption
val jwtPayload = JwtUtil
.getSignedPayloadAsJson(c.jsonWebToken)
.flatMap { payload =>
Try(parse(payload).extract[ConsentJWT]).recover {
case e: MappingException =>
logger.warn(s"Invalid JWT payload: ${e.getMessage}")
null
}.toOption
}.toOption
AllConsentJsonV510(
consent_reference_id = c.consentReferenceId,
consumer_id = c.consumerId,

View File

@ -1,17 +1,20 @@
package code.consent
import java.util.Date
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPBankId, OBPConsentId, OBPConsumerId, OBPLimit, OBPOffset, OBPQueryParam, OBPSortBy, OBPStatus, OBPUserId, SecureRandomUtil}
import code.api.util.{APIUtil, Consent, ErrorMessages, OBPBankId, OBPConsentId, OBPConsumerId, OBPLimit, OBPOffset, OBPQueryParam, OBPSortBy, OBPStatus, OBPUserId, ProviderProviderId, SecureRandomUtil}
import code.consent.ConsentStatus.ConsentStatus
import code.model.Consumer
import code.model.dataAccess.ResourceUser
import code.util.MappedUUID
import com.openbankproject.commons.model.User
import com.openbankproject.commons.util.ApiStandards
import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.mapper.{MappedString, _}
import net.liftweb.mapper._
import net.liftweb.util.Helpers.{now, tryo}
import org.mindrot.jbcrypt.BCrypt
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import scala.collection.immutable.List
object MappedConsentProvider extends ConsentProvider {
@ -71,6 +74,20 @@ object MappedConsentProvider extends ConsentProvider {
// The optional variables:
val consumerId = queryParams.collectFirst { case OBPConsumerId(value) => By(MappedConsent.mConsumerId, value) }
val consentId = queryParams.collectFirst { case OBPConsentId(value) => By(MappedConsent.mConsentId, value) }
val providerProviderId: Option[Cmp[MappedConsent, String]] = queryParams.collectFirst {
case ProviderProviderId(value) =>
val (provider, providerId) = value.split("\\|") match { // split by literal '|'
case Array(a, b) => (a, b)
case _ => ("", "") // fallback if format is unexpected
}
ResourceUser.findAll(By(ResourceUser.provider_, provider), By(ResourceUser.providerId, providerId)) match {
case x :: Nil => // exactly one
Some(By(MappedConsent.mUserId, x.userId))
case _ =>
None
}
}.flatten
val userId = queryParams.collectFirst { case OBPUserId(value) => By(MappedConsent.mUserId, value) }
val status = queryParams.collectFirst {
case OBPStatus(value) =>
@ -96,7 +113,7 @@ object MappedConsentProvider extends ConsentProvider {
offset.toSeq,
limit.toSeq,
status.toSeq,
userId.toSeq,
userId.orElse(providerProviderId).toSeq,
consentId.toSeq,
consumerId.toSeq
).flatten