Skip to content

Commit

Permalink
Merge pull request #2416 from hongwei1/develop
Browse files Browse the repository at this point in the history
Feature/VRP consent
  • Loading branch information
simonredfern committed Aug 13, 2024
2 parents 166c325 + 43bb59e commit 6f75607
Show file tree
Hide file tree
Showing 45 changed files with 505 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,44 @@ object MessageDocsSwaggerDefinitions
emailAddress = emailExample.value,
name = usernameExample.value
)))))))

val outboundAdapterConsenterInfo = OutboundAdapterAuthInfo(
userId = Some(userIdExample.value),
username = Some(usernameExample.value),
linkedCustomers = Some(List(BasicLinkedCustomer(customerIdExample.value,customerNumberExample.value,legalNameExample.value))),
userAuthContext = Some(List(BasicUserAuthContext(keyExample.value,valueExample.value))), //be set by obp from some endpoints.
authViews = Some(List(AuthView(
view = ViewBasic(
id = viewIdExample.value,
name = viewNameExample.value,
description = viewDescriptionExample.value,
),
account = AccountBasic(
id = accountIdExample.value,
accountRoutings =List(AccountRouting(
scheme = accountRoutingSchemeExample.value,
address = accountRoutingAddressExample.value
)),
customerOwners = List(InternalBasicCustomer(
bankId = bankIdExample.value,
customerId = customerIdExample.value,
customerNumber = customerNumberExample.value,
legalName = legalNameExample.value,
dateOfBirth=parseDate(dateOfBirthExample.value).getOrElse(sys.error("dateOfBirthExample.value is not validate date format.")),
)),
userOwners = List(InternalBasicUser(
userId = userIdExample.value,
emailAddress = emailExample.value,
name = usernameExample.value
)))))))

val outboundAdapterCallContext = OutboundAdapterCallContext(
correlationIdExample.value,
Some(sessionIdExample.value),
Some(consumerIdExample.value),
generalContext = Some(List(BasicGeneralContext(keyExample.value,valueExample.value))),
Some(outboundAdapterAuthInfo)
Some(outboundAdapterAuthInfo),
Some(outboundAdapterConsenterInfo)
)

val inboundAdapterCallContext = InboundAdapterCallContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5469,13 +5469,14 @@ object SwaggerDefinitionsJSON {
)

val consentRequestToAccountJson = ConsentRequestToAccountJson (
counterparty_name = counterpartyNameExample.value,
bank_routing = bankRoutingJsonV121,
account_routing = accountRoutingJsonV121,
branch_routing = branchRoutingJsonV141,
limit = postCounterpartyLimitV510
)

val postConsentRequestJsonV510 = PostConsentRequestJsonV510(
val postVRPConsentRequestJsonV510 = PostVRPConsentRequestJsonV510(
from_account = consentRequestFromAccountJson,
to_account = consentRequestToAccountJson,
email = Some(emailExample.value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ object APIMethods_AccountInformationServiceAISApi extends RestHelper {
Future {
Helper.booleanToBox(u.hasViewAccess(BankIdAccountId(account.bankId, account.accountId), viewId, callContext))
} map {
unboxFullOrFail(_, callContext, NoViewReadAccountsBerlinGroup + " userId : " + u.userId + ". account : " + account.accountId, 403)
unboxFullOrFail(_, callContext, s"$NoViewReadAccountsBerlinGroup ${viewId.value} userId : ${u.userId}. account : ${account.accountId}", 403)
}
}

Expand Down Expand Up @@ -186,7 +186,8 @@ As a last option, an ASPSP might in addition accept a command with access rights
createdConsent.secret,
createdConsent.consentId,
callContext.flatMap(_.consumer).map(_.consumerId.get),
Some(validUntil)
Some(validUntil),
callContext
)
_ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map {
i => connectorEmptyResponse(i, callContext)
Expand Down Expand Up @@ -1255,15 +1256,12 @@ Maybe in a later version the access path will change.
updateJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
jsonPut.extract[TransactionAuthorisation]
}
(challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext)
_ <- NewStyle.function.tryons(s"$AuthorisationNotFound Current AUTHORISATION_ID($authorisationId)", 400, callContext) {
challenges.filter(_.challengeId == authorisationId).size == 1
}
(_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext)
(challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4(
ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE,
None,
Some(consentId),
challenges.filter(_.challengeId == authorisationId).head.challengeId,
authorisationId,
updateJson.scaAuthenticationData,
SuppliedAnswerType.PLAIN_TEXT_VALUE,
callContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ This applies in the following scenarios:
None,
callContext
)
//NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly.
//NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it returns the 1st challenge properly.
challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) {
challenges.head
}
Expand Down Expand Up @@ -1447,7 +1447,7 @@ There are the following request types on this access path:
)

lazy val updatePaymentPsuDataTransactionAuthorisation : OBPEndpoint = {
case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => {
case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => {
cc =>
for {
(Full(u), callContext) <- authenticatedAccess(cc)
Expand All @@ -1469,11 +1469,12 @@ There are the following request types on this access path:
_ <- Helper.booleanToFuture(failMsg= CannotUpdatePSUData, cc=callContext) {
existingTransactionRequest.status == TransactionRequestStatus.INITIATED.toString
}
(_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext)
(challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4(
ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE,
Some(paymentId),
None,
authorisationid,
authorisationId,
transactionAuthorisationJson.scaAuthenticationData,
SuppliedAnswerType.PLAIN_TEXT_VALUE,
callContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The resource identifications of these transactions are contained in the payload
jsonPost.extract[PostSigningBasketJsonV13]
}
_ <- booleanToFuture(failMsg, cc = callContext) {
// One of them MUST be defined. Otherwise post json is treated as empty one.
// One of them MUST be defined. Otherwise, post json is treated as empty one.
!(jsonPost.extract[PostSigningBasketJsonV13].paymentIds.isEmpty &&
jsonPost.extract[PostSigningBasketJsonV13].consentIds.isEmpty)
}
Expand Down
6 changes: 4 additions & 2 deletions obp-api/src/main/scala/code/api/util/APIUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3649,11 +3649,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{

lazy val canAddTransactionRequestToAnyAccount = view.map(_.canAddTransactionRequestToAnyAccount).getOrElse(false)

lazy val canAddTransactionRequestToBeneficiary = view.map(_.canAddTransactionRequestToBeneficiary).getOrElse(false)
//1st check the admin level role/entitlement `canCreateAnyTransactionRequest`
if (hasCanCreateAnyTransactionRequestRole) {
Full(true)
//2rd: check if the user have the view access and the view has the `canAddTransactionRequestToAnyAccount` permission
} else if (canAddTransactionRequestToAnyAccount) {
} else if (canAddTransactionRequestToAnyAccount) { //2rd: check if the user have the view access and the view has the `canAddTransactionRequestToAnyAccount` permission
Full(true)
} else if (canAddTransactionRequestToBeneficiary) { //3erd: check if the user have the view access and the view has the `canAddTransactionRequestToBeneficiary` permission
Full(true)
} else {
Empty
Expand Down
10 changes: 9 additions & 1 deletion obp-api/src/main/scala/code/api/util/ApiSession.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ case class CallContext(
dauthResponseHeader: Option[String] = None,
spelling: Option[String] = None,
user: Box[User] = Empty,
consenter: Box[User] = Empty,
consumer: Box[Consumer] = Empty,
ipAddress: String = "",
resourceDocument: Option[ResourceDoc] = None,
Expand Down Expand Up @@ -96,7 +97,14 @@ case class CallContext(
username = username,
linkedCustomers = likedCustomersBasic,
userAuthContext = basicUserAuthContexts,
if (authViews.isEmpty) None else Some(authViews)))
if (authViews.isEmpty) None else Some(authViews))),
outboundAdapterConsenterInfo =
if (this.consenter.isDefined){
Some(OutboundAdapterAuthInfo(
username = this.consenter.toOption.map(_.name)))//TODO, here we may added more field to the consenter, at the moment only username is useful
}else{
None
}
)
}}.openOr(OutboundAdapterCallContext( //For anonymousAccess endpoints, there are no user info
this.correlationId,
Expand Down
20 changes: 13 additions & 7 deletions obp-api/src/main/scala/code/api/util/ConsentUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ object Consent extends MdcLoggable {
private def applyBerlinGroupConsentRulesCommon(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = {
implicit val dateFormats = CustomJsonFormats.formats

def applyConsentRules(consent: ConsentJWT): Future[(Box[User], Option[CallContext])] = {
def applyConsentRules(consent: ConsentJWT, callContext: CallContext): Future[(Box[User], Option[CallContext])] = {
val cc = callContext
// 1. Get or Create a User
getOrCreateUser(consent.sub, consent.iss, Some(consent.toConsent().consentId), None, None) map {
Expand Down Expand Up @@ -507,7 +507,9 @@ object Consent extends MdcLoggable {
case Full(storedConsent) =>
// Set Consumer into Call Context
val consumer = getCurrentConsumerViaMtls(callContext)
val updatedCallContext = callContext.copy(consumer = consumer)
val user = Users.users.vend.getUserByUserId(storedConsent.userId)
logger.debug(s"applyBerlinGroupConsentRulesCommon.storedConsent.user : $user")
val updatedCallContext = callContext.copy(consumer = consumer).copy(consenter = user)
// This function MUST be called only once per call. I.e. it's date dependent
val (canBeUsed, currentCounterState) = checkFrequencyPerDay(storedConsent)
if(canBeUsed) {
Expand All @@ -524,7 +526,7 @@ object Consent extends MdcLoggable {
// Update MappedConsent.usesSoFarTodayCounter field
val consentUpdatedBox = Consents.consentProvider.vend.updateBerlinGroupConsent(consentId, currentCounterState + 1)
logger.debug(s"applyBerlinGroupConsentRulesCommon.consentUpdatedBox: $consentUpdatedBox")
applyConsentRules(consent)
applyConsentRules(consent, updatedCallContext)
case failure@Failure(_, _, _) => // Handled errors
Future(failure, Some(updatedCallContext))
case _ => // Unexpected errors
Expand Down Expand Up @@ -666,7 +668,8 @@ object Consent extends MdcLoggable {
secret: String,
consentId: String,
consumerId: Option[String],
validUntil: Option[Date]): Future[String] = {
validUntil: Option[Date],
callContext: Option[CallContext]): Future[String] = {

val currentTimeInSeconds = System.currentTimeMillis / 1000
val validUntilTimeInSeconds = validUntil match {
Expand All @@ -682,7 +685,8 @@ object Consent extends MdcLoggable {

// 1. Add access
val accounts: List[Future[ConsentView]] = consent.access.accounts.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), None) map { bankAccount =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.accounts.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
Expand All @@ -691,7 +695,8 @@ object Consent extends MdcLoggable {
}
}
val balances: List[Future[ConsentView]] = consent.access.balances.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), None) map { bankAccount =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.balances.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
Expand All @@ -700,7 +705,8 @@ object Consent extends MdcLoggable {
}
}
val transactions: List[Future[ConsentView]] = consent.access.transactions.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), None) map { bankAccount =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.transactions.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
Expand Down
6 changes: 4 additions & 2 deletions obp-api/src/main/scala/code/api/util/ErrorMessages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ object ErrorMessages {
val UserNotFoundByUserId = "OBP-20057: User not found by userId."
val ConsumerIsDisabled = "OBP-20058: Consumer is disabled."
val CouldNotGetUserLockStatus = "OBP-20059: Could not get the lock status of the user."
val NoViewReadAccountsBerlinGroup = s"OBP-20060: User does not have access to the view $SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID."
val NoViewReadAccountsBerlinGroup = s"OBP-20060: User does not have access to the view:"
val FrequencyPerDayError = "OBP-20062: Frequency per day must be greater than 0."
val FrequencyPerDayMustBeOneError = "OBP-20063: Frequency per day must be equal to 1 in case of one-off access."

Expand Down Expand Up @@ -507,6 +507,7 @@ object ErrorMessages {
val CounterpartyLimitAlreadyExists = "OBP-30264: Counterparty limit already exists. Please specify a different value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID."
val DeleteCounterpartyLimitError = "OBP-30265: Could not delete the counterparty limit."
val CustomViewAlreadyExistsError = "OBP-30266: The custom view is already exists."
val UserDoesNotHavePermission = "OBP-30267: The user does not have the permission:"

val TaxResidenceNotFound = "OBP-30300: Tax Residence not found by TAX_RESIDENCE_ID. "
val CustomerAddressNotFound = "OBP-30310: Customer's Address not found by CUSTOMER_ADDRESS_ID. "
Expand Down Expand Up @@ -599,7 +600,8 @@ object ErrorMessages {
"because the login user doesn't have access to the view of the from account " +
"or the consumer doesn't have the access to the view of the from account " +
s"or the login user does not have the `${CanCreateAnyTransactionRequest.toString()}` role " +
s"or the view does not have the permission ${StringHelpers.snakify(ViewDefinition.canAddTransactionRequestToAnyAccount_.dbColumnName).dropRight(1)}."
s"or the view does not have the permission can_add_transaction_request_to_any_account " +
s"or the view does not have the permission can_add_transaction_request_to_beneficiary."
val InvalidTransactionRequestCurrency = "OBP-40003: Transaction Request Currency must be the same as From Account Currency."
val InvalidTransactionRequestId = "OBP-40004: Transaction Request Id not found."
val InsufficientAuthorisationToCreateTransactionType = "OBP-40005: Insufficient authorisation to Create Transaction Type offered by the bank. The Request could not be created because you don't have access to CanCreateTransactionType."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ object Migration extends MdcLoggable {
alterMappedCustomerAttribute(startedBeforeSchemifier)
dropMappedBadLoginAttemptIndex()
alterMetricColumnUrlLength()
populateViewDefinitionCanAddTransactionRequestToBeneficiary()
}

private def dummyScript(): Boolean = {
Expand Down Expand Up @@ -128,6 +129,13 @@ object Migration extends MdcLoggable {
runOnce(name) {
TableViewDefinition.populate(name)
}
}

private def populateViewDefinitionCanAddTransactionRequestToBeneficiary(): Boolean = {
val name = nameOf(populateViewDefinitionCanAddTransactionRequestToBeneficiary)
runOnce(name) {
MigrationOfViewDefinitionCanAddTransactionRequestToBeneficiary.populateTheField(name)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ object TableViewDefinition {
.canSeeOtherAccountRoutingAddress_(view.canSeeOtherAccountRoutingAddress)
.canAddTransactionRequestToOwnAccount_(view.canAddTransactionRequestToOwnAccount)
.canAddTransactionRequestToAnyAccount_(view.canAddTransactionRequestToAnyAccount)
.canAddTransactionRequestToBeneficiary_(view.canAddTransactionRequestToBeneficiary)
.save
}
val isSuccessful = insertedRows.forall(_ == true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package code.api.util.migration

import code.api.Constant.SYSTEM_OWNER_VIEW_ID

import java.time.format.DateTimeFormatter
import java.time.{ZoneId, ZonedDateTime}
import code.api.util.APIUtil
import code.api.util.migration.Migration.{DbFunction, saveLog}
import code.model.Consumer
import code.views.system.ViewDefinition

object MigrationOfViewDefinitionCanAddTransactionRequestToBeneficiary {

val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1)
val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")

def populateTheField(name: String): Boolean = {
DbFunction.tableExists(ViewDefinition) match {
case true =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
var isSuccessful = false

val view = ViewDefinition.findSystemView(SYSTEM_OWNER_VIEW_ID).map(_.canAddTransactionRequestToBeneficiary_(true).saveMe())


val endDate = System.currentTimeMillis()
val comment: String =
s"""set $SYSTEM_OWNER_VIEW_ID.canAddTransactionRequestToBeneficiary_ to {true}""".stripMargin
val value = view.map(_.canAddTransactionRequestToBeneficiary_.get).getOrElse(false)
isSuccessful = value
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful

case false =>
val startDate = System.currentTimeMillis()
val commitId: String = APIUtil.gitCommit
val isSuccessful = false
val endDate = System.currentTimeMillis()
val comment: String =
s"""${Consumer._dbTableNameLC} table does not exist""".stripMargin
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
isSuccessful
}
}
}
Loading

0 comments on commit 6f75607

Please sign in to comment.