Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/VRP consent #2416

Merged
merged 32 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cf387bd
bugfix/added the missing fields for obp consent creation
hongwei1 Jul 18, 2024
9ebe0e3
bugfix/added vrp logic to create consent
hongwei1 Jul 22, 2024
f60d7a8
test/fixed the failed test
hongwei1 Jul 22, 2024
1d3547f
Merge remote-tracking branch 'refs/remotes/hongwei/develop' into hong…
hongwei1 Jul 22, 2024
4c6f3bb
bugfix/added vrp logic to create consent -step2
hongwei1 Jul 22, 2024
2fb0101
refactor/added log for createBerlinGroupConsentJWT
hongwei1 Jul 23, 2024
461f16c
bugfix/added the callContext to createBerlinGroupConsentJWT method
hongwei1 Jul 24, 2024
00fac70
feature/VRP add the tests
hongwei1 Jul 25, 2024
7394a59
feature/VRP added new view permission can_add_transaction_request_to_…
hongwei1 Jul 25, 2024
d84a596
feature/VRP added account access to the user
hongwei1 Jul 25, 2024
67eafac
feature/VRP added views to consent
hongwei1 Jul 25, 2024
a5ebcb5
feature/VRP add new permission `canAddTransactionRequestToBeneficiary_`
hongwei1 Jul 26, 2024
c2465d6
feature/VRP set the canAddTransactionRequestToBeneficiary_ for owner …
hongwei1 Jul 26, 2024
9d590cc
bugfix/tweaked the view permission for canGetCounterparty
hongwei1 Jul 26, 2024
65c4f64
feature/VRP add consent_type to obp internal
hongwei1 Jul 26, 2024
d7cca29
refactor/typo
hongwei1 Jul 26, 2024
7929273
feature/VRP add helperInfo object
hongwei1 Jul 26, 2024
4e67fdb
refactor/typo
hongwei1 Jul 26, 2024
c5ae53c
refactor/tweaked error message
hongwei1 Jul 26, 2024
4b45608
refactor/tweaked createConsentByConsentRequestId endpoint response body
hongwei1 Jul 29, 2024
65142f5
feature/VRP fixed the failed test
hongwei1 Jul 29, 2024
a625fc9
feature/added consenter and outboundAdapterConsenterInfo
hongwei1 Jul 30, 2024
3050998
refactor/typo
hongwei1 Jul 31, 2024
5dd05a6
feature/added outboundAdapterConsenterInfo
hongwei1 Jul 31, 2024
c904a81
refactor/added log for consenter
hongwei1 Jul 31, 2024
5327443
bugfix/added callContext to applyConsentRules method
hongwei1 Jul 31, 2024
f3e7a16
bugfix/tweaked BGv1.3 view error messages
hongwei1 Aug 2, 2024
78c7f3d
refactor/typo
hongwei1 Aug 5, 2024
25b163a
refactor/tweaked the challenge logic for authorisationId
hongwei1 Aug 6, 2024
ef2e13e
bugfix/fixed the SigningBasket paymentIds and consentIds mapping
hongwei1 Aug 6, 2024
35d0315
Merge remote-tracking branch 'Hongwei/develop' into develop
hongwei1 Aug 6, 2024
43bb59e
feature/added new method getChallengesByBasketId to StoredProcedureCo…
hongwei1 Aug 7, 2024
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
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
Loading