Skip to content

Commit

Permalink
Merge pull request #2387 from constantine2nd/develop
Browse files Browse the repository at this point in the history
Fixed test
  • Loading branch information
simonredfern authored May 24, 2024
2 parents f134b86 + 2376b1d commit e61268e
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package code.api.builder.AccountInformationServiceAISApi

import java.text.SimpleDateFormat

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.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _}
Expand All @@ -12,6 +11,7 @@ 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.{APIUtil, ApiTag, CallContext, Consent, ExampleValue, NewStyle}
import code.bankconnectors.Connector
import code.consent.{ConsentStatus, Consents}
Expand Down Expand Up @@ -367,7 +367,7 @@ The account-id is constant at least throughout the lifecycle of a given consent.
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext)
(accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
(accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext))
}
Expand Down Expand Up @@ -482,7 +482,7 @@ This account-id then can be retrieved by the
_ <- passesPsd2Aisp(callContext)
(account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext)
_ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext)
(accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
(accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext)
} yield {
(JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext))
}
Expand Down
10 changes: 10 additions & 0 deletions obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ import code.api.v2_1_0.OBPAPI2_1_0.Implementations2_1_0
import code.api.v2_2_0.OBPAPI2_2_0.Implementations2_2_0
import code.etag.MappedETag
import code.users.Users
import code.views.system.AccountAccess
import net.liftweb.mapper.By

import scala.collection.mutable
Expand Down Expand Up @@ -4925,4 +4926,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
else
UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})"


def intersectAccountAccessAndView(accountAccesses: List[AccountAccess], views: List[View]): List[BankIdAccountId] = {
val intersectedViewIds = accountAccesses.map(item => item.view_id.get)
.intersect(views.map(item => item.viewId.value)).distinct // Join view definition and account access via view_id
accountAccesses
.filter(i => intersectedViewIds.contains(i.view_id.get))
.map(item => BankIdAccountId(BankId(item.bank_id.get), AccountId(item.account_id.get)))
.distinct // List pairs (bank_id, account_id)
}
}
18 changes: 8 additions & 10 deletions obp-api/src/main/scala/code/api/util/NewStyle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,14 @@ object NewStyle extends MdcLoggable{
}
}
}

def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[AccountsBalances] = {
Connector.connector.vend.getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
def getAccountListThroughView(user : User, viewId: ViewId, callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = {
val viewIds = List(viewId)
Views.views.vend.getPrivateBankAccountsFuture(user, viewIds) map { i =>
if(i.isEmpty) {
(unboxFullOrFail(Empty, callContext, NoViewReadAccountsBerlinGroup , 403), callContext)
} else {
(i, callContext )
}
}
}

Expand All @@ -385,12 +389,6 @@ object NewStyle extends MdcLoggable{
}
}

def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = {
Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
}
}

def getAccountRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) : OBPReturnType[BankAccountRouting] = {
Future(Connector.connector.vend.getAccountRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext])) map { i =>
unboxFullOrFail(i, callContext,s"$AccountRoutingNotFound Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package code.api.util.newstyle

import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail}
import code.api.util.ErrorMessages.InvalidConnectorResponseForGetBankAccounts
import code.api.util.{APIUtil, CallContext}
import code.bankconnectors.Connector
import code.views.Views
import com.openbankproject.commons.model.{AccountBalances, AccountsBalances, BankId, BankIdAccountId, User, ViewId}

import scala.concurrent.Future

object BalanceNewStyle {

import com.openbankproject.commons.ExecutionContext.Implicits.global

def getAccountAccessAtBankThroughView(user: User,
bankId: BankId,
viewId: ViewId,
callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = {
Future {
val (views, accountAccesses) = Views.views.vend.getAccountAccessAtBankThroughView(user, bankId, viewId)
// Filter views which can read the balance
val canSeeBankAccountBalanceViews = views.filter(_.canSeeBankAccountBalance)
// Filter accounts the user has permission to see balances and remove duplicates
val allowedAccounts = APIUtil.intersectAccountAccessAndView(accountAccesses, canSeeBankAccountBalanceViews)
allowedAccounts
} map {
(_, callContext)
}
}

def getAccountAccessAtBank(user: User,
bankId: BankId,
callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = {
Future {
val (views, accountAccesses) = Views.views.vend.privateViewsUserCanAccessAtBank(user, bankId)
// Filter views which can read the balance
val canSeeBankAccountBalanceViews = views.filter(_.canSeeBankAccountBalance)
// Filter accounts the user has permission to see balances and remove duplicates
val allowedAccounts = APIUtil.intersectAccountAccessAndView(accountAccesses, canSeeBankAccountBalanceViews)
allowedAccounts
} map {
(_, callContext)
}
}

def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = {
Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
}
}

def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[AccountsBalances] = {
Connector.connector.vend.getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i =>
(unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2)
}
}


}
3 changes: 2 additions & 1 deletion obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import code.api.util.ExampleValue._
import code.api.util.FutureUtil.EndpointContext
import code.api.util.NewStyle.HttpCode
import code.api.util._
import code.api.util.newstyle.BalanceNewStyle
import code.api.v1_2_1.{JSONFactory, RateLimiting}
import code.api.v2_0_0.CreateMeetingJson
import code.api.v2_1_0._
Expand Down Expand Up @@ -5935,7 +5936,7 @@ trait APIMethods310 {
(Full(u), callContext) <- authenticatedAccess(cc)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId)
(accountsBalances, callContext)<- NewStyle.function.getBankAccountsBalances(availablePrivateAccounts, callContext)
(accountsBalances, callContext)<- BalanceNewStyle.getBankAccountsBalances(availablePrivateAccounts, callContext)
} yield{
(createBalancesJson(accountsBalances), HttpCode.`200`(callContext))
}
Expand Down
25 changes: 14 additions & 11 deletions obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import code.api.util._
import code.api.util.migration.Migration
import code.api.util.newstyle.AttributeDefinition._
import code.api.util.newstyle.Consumer._
import code.api.util.newstyle.UserCustomerLinkNewStyle
import code.api.util.newstyle.{BalanceNewStyle, UserCustomerLinkNewStyle}
import code.api.util.newstyle.UserCustomerLinkNewStyle.getUserCustomerLinks
import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON}
import code.api.v1_4_0.JSONFactory1_4_0
Expand Down Expand Up @@ -3346,9 +3346,9 @@ trait APIMethods400 extends MdcLoggable {
}

staticResourceDocs += ResourceDoc(
getBankAccountsBalances,
getBankAccountsBalancesForCurrentUser,
implementedInApiVersion,
nameOf(getBankAccountsBalances),
nameOf(getBankAccountsBalancesForCurrentUser),
"GET",
"/banks/BANK_ID/balances",
"Get Accounts Balances",
Expand All @@ -3359,14 +3359,14 @@ trait APIMethods400 extends MdcLoggable {
apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil
)

lazy val getBankAccountsBalances : OBPEndpoint = {
lazy val getBankAccountsBalancesForCurrentUser : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "balances" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- SS.user
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId)
(accountsBalances, callContext)<- NewStyle.function.getBankAccountsBalances(availablePrivateAccounts, callContext)
} yield{
(allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext)
(accountsBalances, callContext)<- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext)
} yield {
(createBalancesJson(accountsBalances), HttpCode.`200`(callContext))
}
}
Expand All @@ -3391,10 +3391,13 @@ trait APIMethods400 extends MdcLoggable {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- SS.user
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId)
bankIdAcconutId <- NewStyle.function.tryons(s"$CannotFindAccountAccess AccountId(${accountId.value})", 400, cc.callContext) {availablePrivateAccounts.find(_.accountId==accountId).get}
(accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(bankIdAcconutId, callContext)
} yield{
(allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext)
msg = s"$CannotFindAccountAccess AccountId(${accountId.value})"
bankIdAccountId <- NewStyle.function.tryons(msg, 400, cc.callContext) {
allowedAccounts.find(_.accountId==accountId).get
}
(accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(bankIdAccountId, callContext)
} yield {
(createAccountBalancesJson(accountBalances), HttpCode.`200`(callContext))
}
}
Expand Down
69 changes: 66 additions & 3 deletions obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import code.api.util.JwtUtil.{getSignedPayloadAsJson, verifyJwt}
import code.api.util.NewStyle.HttpCode
import code.api.util.X509.{getCommonName, getEmailAddress, getOrganization}
import code.api.util._
import code.api.util.newstyle.BalanceNewStyle
import code.api.util.newstyle.Consumer.createConsumerNewStyle
import code.api.util.newstyle.RegulatedEntityNewStyle.{createRegulatedEntityNewStyle, deleteRegulatedEntityNewStyle, getRegulatedEntitiesNewStyle, getRegulatedEntityByEntityIdNewStyle}
import code.api.v2_1_0.{ConsumerRedirectUrlJSON, JSONFactory210}
import code.api.v3_0_0.JSONFactory300
import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson
import code.api.v3_1_0.ConsentJsonV310
import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson
import code.api.v4_0_0.JSONFactory400.createAccountBalancesJson
import code.api.v4_0_0.JSONFactory400.{createAccountBalancesJson, createBalancesJson}
import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400}
import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.atmattribute.AtmAttribute
Expand Down Expand Up @@ -2181,17 +2182,79 @@ trait APIMethods510 {
(Full(u), callContext) <- SS.user
bankIdAccountId = BankIdAccountId(bankId, accountId)
view <- NewStyle.function.checkViewAccessAndReturnView(viewId, bankIdAccountId, Full(u), callContext)
// Note we do one explicit check here rather than use moderated account because this provide an explicit message
failMsg = ViewDoesNotPermitAccess + " You need the permission canSeeBankAccountBalance."
_ <- Helper.booleanToFuture(failMsg, 403, cc = callContext) {
view.canSeeBankAccountBalance
}
(accountBalances, callContext) <- NewStyle.function.getBankAccountBalances(bankIdAccountId, callContext)
} yield{
(accountBalances, callContext) <- BalanceNewStyle.getBankAccountBalances(bankIdAccountId, callContext)
} yield {
(createAccountBalancesJson(accountBalances), HttpCode.`200`(callContext))
}
}
}

staticResourceDocs += ResourceDoc(
getBankAccountsBalances,
implementedInApiVersion,
nameOf(getBankAccountsBalances),
"GET",
"/banks/BANK_ID/balances",
"Get Account Balances by BANK_ID",
"""Get the Balances for the Account specified by BANK_ID.""",
EmptyBody,
accountBalancesV400Json,
List(
$UserNotLoggedIn,
$BankNotFound,
UnknownError
),
apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil
)

lazy val getBankAccountsBalances : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "balances" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- SS.user
(allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext)
(accountsBalances, callContext) <- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext)
} yield {
(createBalancesJson(accountsBalances), HttpCode.`200`(callContext))
}
}
}

staticResourceDocs += ResourceDoc(
getBankAccountsBalancesThroughView,
implementedInApiVersion,
nameOf(getBankAccountsBalancesThroughView),
"GET",
"/banks/BANK_ID/views/VIEW_ID/balances",
"Get Account Balances by BANK_ID",
"""Get the Balances for the Account specified by BANK_ID.""",
EmptyBody,
accountBalancesV400Json,
List(
$UserNotLoggedIn,
$BankNotFound,
UnknownError
),
apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil
)

lazy val getBankAccountsBalancesThroughView : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "views" :: ViewId(viewId) :: "balances" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- SS.user
(allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBankThroughView(u, bankId, viewId, callContext)
(accountsBalances, callContext) <- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext)
} yield {
(createBalancesJson(accountsBalances), HttpCode.`200`(callContext))
}
}
}


}
Expand Down
3 changes: 3 additions & 0 deletions obp-api/src/main/scala/code/remotedata/RemotedataViews.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ object RemotedataViews extends ObpActorInit with Views {
def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess]) = getValueFromFuture(
(actor ? cc.privateViewsUserCanAccessAtBank(user, bankId)).mapTo[(List[View], List[AccountAccess])]
)
def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): ((List[View], List[AccountAccess])) = getValueFromFuture(
(actor ? cc.privateViewsUserCanAccessAtBankThroughView(user, bankId, viewId)).mapTo[(List[View], List[AccountAccess])]
)

def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId): List[View] = getValueFromFuture(
(actor ? cc.privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId)).mapTo[List[View]]
Expand Down
11 changes: 11 additions & 0 deletions obp-api/src/main/scala/code/views/MapperViews.scala
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,17 @@ object MapperViews extends Views with MdcLoggable {
})
PrivateViewsUserCanAccessCommon(accountAccess)
}
def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): (List[View], List[AccountAccess]) ={
val accountAccess = AccountAccess.findAll(
By(AccountAccess.user_fk, user.userPrimaryKey.value),
By(AccountAccess.bank_id, bankId.value),
By(AccountAccess.view_id, viewId.value)
).filter(accountAccess => {
val view = getViewFromAccountAccess(accountAccess)
view.isDefined && view.map(_.isPrivate) == Full(true)
})
PrivateViewsUserCanAccessCommon(accountAccess)
}

private def PrivateViewsUserCanAccessCommon(accountAccess: List[AccountAccess]): (List[ViewDefinition], List[AccountAccess]) = {
val listOfTuples: List[(AccountAccess, Box[ViewDefinition])] = accountAccess.map(
Expand Down
2 changes: 2 additions & 0 deletions obp-api/src/main/scala/code/views/Views.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ trait Views {
def privateViewsUserCanAccess(user: User): (List[View], List[AccountAccess])
def privateViewsUserCanAccess(user: User, viewIds: List[ViewId]): (List[View], List[AccountAccess])
def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess])
def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): (List[View], List[AccountAccess])
def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId) : List[View]

//the following return list[BankIdAccountId], just use the list[View] method, the View object contains enough data for it.
Expand Down Expand Up @@ -150,6 +151,7 @@ class RemotedataViewsCaseClasses {
case class privateViewsUserCanAccess(user: User)
case class privateViewsUserCanAccessViaViewId(user: User, viewIds: List[ViewId])
case class privateViewsUserCanAccessAtBank(user: User, bankId: BankId)
case class privateViewsUserCanAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId)
case class privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId)
case class getAllFirehoseAccounts(bank: Bank, user : User)
case class publicViews()
Expand Down
Binary file modified obp-api/src/test/resources/frozen_type_meta_data
Binary file not shown.
Loading

0 comments on commit e61268e

Please sign in to comment.