From 3b7f1e4231914342709642d862a7b787e51bfca9 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 12 Jun 2023 17:00:33 +0800 Subject: [PATCH 1/6] refactor/move createCustomRandomView method to the test scope --- .../code/remotedata/RemotedataViews.scala | 4 - .../remotedata/RemotedataViewsActor.scala | 4 - .../main/scala/code/views/MapperViews.scala | 107 ---------------- obp-api/src/main/scala/code/views/Views.scala | 6 - ...onnectorSetupWithStandardPermissions.scala | 114 +++++++++++++++++- 5 files changed, 111 insertions(+), 124 deletions(-) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala b/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala index e8999f00d..12e155d93 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala @@ -144,10 +144,6 @@ object RemotedataViews extends ObpActorInit with Views { (actor ? cc.getOrCreatePublicPublicView(bankId, accountId, description)).mapTo[Box[View]] ) - def createCustomRandomView(bankId: BankId, accountId: AccountId) : Box[View] = getValueFromFuture( - (actor ? cc.createRandomView(bankId, accountId)).mapTo[Box[View]] - ) - // For tests def bulkDeleteAllPermissionsAndViews(): Boolean = getValueFromFuture( (actor ? cc.bulkDeleteAllPermissionsAndViews()).mapTo[Boolean] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataViewsActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataViewsActor.scala index a731b5d74..da5a0562e 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataViewsActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataViewsActor.scala @@ -134,10 +134,6 @@ class RemotedataViewsActor extends Actor with ObpActorHelper with MdcLoggable { logger.debug("getOrCreatePublicPublicView(" + bankId +", "+ accountId +", "+ description +")") sender ! (mapper.getOrCreateCustomPublicView(bankId, accountId, description)) - case cc.createRandomView(bankId, accountId) => - logger.debug("createRandomView(" + bankId +", "+ accountId +")") - sender ! (mapper.createCustomRandomView(bankId, accountId)) - case cc.getOwners(view) => logger.debug("getOwners(" + view +")") sender ! (mapper.getOwners(view)) diff --git a/obp-api/src/main/scala/code/views/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index c665393f8..06f209507 100644 --- a/obp-api/src/main/scala/code/views/MapperViews.scala +++ b/obp-api/src/main/scala/code/views/MapperViews.scala @@ -662,113 +662,6 @@ object MapperViews extends Views with MdcLoggable { } } - /** - * This is only for scala tests. - */ - def createCustomRandomView(bankId: BankId, accountId: AccountId) : Box[View] = { - //we set the length is to 40, try to be difficult for scala tests create the same viewName. - val viewName = "_" + randomString(40) - val viewId = MapperViews.createViewIdByName(viewName) - val description = randomString(40) - - if (!checkCustomViewIdOrName(viewName)) { - return Failure(InvalidCustomViewFormat) - } - - getExistingCustomView(bankId, accountId, viewId) match { - case Empty => { - tryo{ViewDefinition.create. - isSystem_(false). - isFirehose_(false). - name_(viewName). - metadataView_(SYSTEM_OWNER_VIEW_ID). - description_(description). - view_id(viewId). - isPublic_(false). - bank_id(bankId.value). - account_id(accountId.value). - usePrivateAliasIfOneExists_(false). - usePublicAliasIfOneExists_(false). - hideOtherAccountMetadataIfAlias_(false). - canSeeTransactionThisBankAccount_(true). - canSeeTransactionOtherBankAccount_(true). - canSeeTransactionMetadata_(true). - canSeeTransactionDescription_(true). - canSeeTransactionAmount_(true). - canSeeTransactionType_(true). - canSeeTransactionCurrency_(true). - canSeeTransactionStartDate_(true). - canSeeTransactionFinishDate_(true). - canSeeTransactionBalance_(true). - canSeeComments_(true). - canSeeOwnerComment_(true). - canSeeTags_(true). - canSeeImages_(true). - canSeeBankAccountOwners_(true). - canSeeBankAccountType_(true). - canSeeBankAccountBalance_(true). - canSeeBankAccountCurrency_(true). - canSeeBankAccountLabel_(true). - canSeeBankAccountNationalIdentifier_(true). - canSeeBankAccountSwift_bic_(true). - canSeeBankAccountIban_(true). - canSeeBankAccountNumber_(true). - canSeeBankAccountBankName_(true). - canSeeBankAccountBankPermalink_(true). - canSeeOtherAccountNationalIdentifier_(true). - canSeeOtherAccountSWIFT_BIC_(true). - canSeeOtherAccountIBAN_(true). - canSeeOtherAccountBankName_(true). - canSeeOtherAccountNumber_(true). - canSeeOtherAccountMetadata_(true). - canSeeOtherAccountKind_(true). - canSeeMoreInfo_(true). - canSeeUrl_(true). - canSeeImageUrl_(true). - canSeeOpenCorporatesUrl_(true). - canSeeCorporateLocation_(true). - canSeePhysicalLocation_(true). - canSeePublicAlias_(true). - canSeePrivateAlias_(true). - canAddMoreInfo_(true). - canAddURL_(true). - canAddImageURL_(true). - canAddOpenCorporatesUrl_(true). - canAddCorporateLocation_(true). - canAddPhysicalLocation_(true). - canAddPublicAlias_(true). - canAddPrivateAlias_(true). - canDeleteCorporateLocation_(true). - canDeletePhysicalLocation_(true). - canEditOwnerComment_(true). - canAddComment_(true). - canDeleteComment_(true). - canAddTag_(true). - canDeleteTag_(true). - canAddImage_(true). - canDeleteImage_(true). - canAddWhereTag_(true). - canSeeWhereTag_(true). - canDeleteWhereTag_(true). - canSeeBankRoutingScheme_(true). //added following in V300 - canSeeBankRoutingAddress_(true). - canSeeBankAccountRoutingScheme_(true). - canSeeBankAccountRoutingAddress_(true). - canSeeOtherBankRoutingScheme_(true). - canSeeOtherBankRoutingAddress_(true). - canSeeOtherAccountRoutingScheme_(true). - canSeeOtherAccountRoutingAddress_(true). - canAddTransactionRequestToOwnAccount_(false). //added following two for payments - canAddTransactionRequestToAnyAccount_(false). - canSeeBankAccountCreditLimit_(true). - saveMe} - } - case Full(v) => Full(v) - case Failure(msg, t, c) => Failure(msg, t, c) - case ParamFailure(x, y, z, q) => ParamFailure(x, y, z, q) - } - } - def createDefaultSystemView(name: String): Box[View] = { createAndSaveSystemView(name) } diff --git a/obp-api/src/main/scala/code/views/Views.scala b/obp-api/src/main/scala/code/views/Views.scala index 8049ec776..4abd2c425 100644 --- a/obp-api/src/main/scala/code/views/Views.scala +++ b/obp-api/src/main/scala/code/views/Views.scala @@ -113,11 +113,6 @@ trait Views { def getOrCreateSystemView(viewId: String) : Box[View] def getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, description: String) : Box[View] - /** - * this is only used for the scala test - */ - def createCustomRandomView(bankId: BankId, accountId: AccountId) : Box[View] - def getOwners(view: View): Set[User] def removeAllPermissions(bankId: BankId, accountId: AccountId) : Boolean @@ -167,7 +162,6 @@ class RemotedataViewsCaseClasses { case class getOrCreateSystemViewFromCbs(viewId: String) case class getOrCreateSystemView(viewId: String) case class getOrCreatePublicPublicView(bankId: BankId, accountId: AccountId, description: String) - case class createRandomView(bankId: BankId, accountId: AccountId) case class getOwners(view: View) diff --git a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala index a3805d5f4..5141e4cfd 100644 --- a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala +++ b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala @@ -3,11 +3,15 @@ package code.setup import bootstrap.liftweb.ToSchemify import code.accountholders.AccountHolders import code.api.Constant.{CUSTOM_PUBLIC_VIEW_ID, SYSTEM_OWNER_VIEW_ID} +import code.api.util.APIUtil.checkCustomViewIdOrName import code.api.util.ErrorMessages._ import code.model._ import code.model.dataAccess._ -import code.views.Views +import code.views.MapperViews.getExistingCustomView +import code.views.system.ViewDefinition +import code.views.{MapperViews, Views} import com.openbankproject.commons.model._ +import net.liftweb.common.{Failure, Full, ParamFailure} import net.liftweb.mapper.MetaMapper import net.liftweb.util.Helpers._ @@ -32,8 +36,112 @@ trait TestConnectorSetupWithStandardPermissions extends TestConnectorSetup { Views.views.vend.getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, CUSTOM_PUBLIC_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox) } - protected def createCustomRandomView(bankId: BankId, accountId: AccountId) : View = { - Views.views.vend.createCustomRandomView(bankId, accountId).openOrThrowException(attemptedToOpenAnEmptyBox) + def createCustomRandomView(bankId: BankId, accountId: AccountId) : View = { + { + //we set the length is to 40, try to be difficult for scala tests create the same viewName. + val viewName = "_" + randomString(40) + val viewId = MapperViews.createViewIdByName(viewName) + val description = randomString(40) + + if (!checkCustomViewIdOrName(viewName)) { + throw new RuntimeException(InvalidCustomViewFormat) + } + + getExistingCustomView(bankId, accountId, viewId) match { + case net.liftweb.common.Empty => { + tryo { + ViewDefinition.create. + isSystem_(false). + isFirehose_(false). + name_(viewName). + metadataView_(SYSTEM_OWNER_VIEW_ID). + description_(description). + view_id(viewId). + isPublic_(false). + bank_id(bankId.value). + account_id(accountId.value). + usePrivateAliasIfOneExists_(false). + usePublicAliasIfOneExists_(false). + hideOtherAccountMetadataIfAlias_(false). + canSeeTransactionThisBankAccount_(true). + canSeeTransactionOtherBankAccount_(true). + canSeeTransactionMetadata_(true). + canSeeTransactionDescription_(true). + canSeeTransactionAmount_(true). + canSeeTransactionType_(true). + canSeeTransactionCurrency_(true). + canSeeTransactionStartDate_(true). + canSeeTransactionFinishDate_(true). + canSeeTransactionBalance_(true). + canSeeComments_(true). + canSeeOwnerComment_(true). + canSeeTags_(true). + canSeeImages_(true). + canSeeBankAccountOwners_(true). + canSeeBankAccountType_(true). + canSeeBankAccountBalance_(true). + canSeeBankAccountCurrency_(true). + canSeeBankAccountLabel_(true). + canSeeBankAccountNationalIdentifier_(true). + canSeeBankAccountSwift_bic_(true). + canSeeBankAccountIban_(true). + canSeeBankAccountNumber_(true). + canSeeBankAccountBankName_(true). + canSeeBankAccountBankPermalink_(true). + canSeeOtherAccountNationalIdentifier_(true). + canSeeOtherAccountSWIFT_BIC_(true). + canSeeOtherAccountIBAN_(true). + canSeeOtherAccountBankName_(true). + canSeeOtherAccountNumber_(true). + canSeeOtherAccountMetadata_(true). + canSeeOtherAccountKind_(true). + canSeeMoreInfo_(true). + canSeeUrl_(true). + canSeeImageUrl_(true). + canSeeOpenCorporatesUrl_(true). + canSeeCorporateLocation_(true). + canSeePhysicalLocation_(true). + canSeePublicAlias_(true). + canSeePrivateAlias_(true). + canAddMoreInfo_(true). + canAddURL_(true). + canAddImageURL_(true). + canAddOpenCorporatesUrl_(true). + canAddCorporateLocation_(true). + canAddPhysicalLocation_(true). + canAddPublicAlias_(true). + canAddPrivateAlias_(true). + canDeleteCorporateLocation_(true). + canDeletePhysicalLocation_(true). + canEditOwnerComment_(true). + canAddComment_(true). + canDeleteComment_(true). + canAddTag_(true). + canDeleteTag_(true). + canAddImage_(true). + canDeleteImage_(true). + canAddWhereTag_(true). + canSeeWhereTag_(true). + canDeleteWhereTag_(true). + canSeeBankRoutingScheme_(true). //added following in V300 + canSeeBankRoutingAddress_(true). + canSeeBankAccountRoutingScheme_(true). + canSeeBankAccountRoutingAddress_(true). + canSeeOtherBankRoutingScheme_(true). + canSeeOtherBankRoutingAddress_(true). + canSeeOtherAccountRoutingScheme_(true). + canSeeOtherAccountRoutingAddress_(true). + canAddTransactionRequestToOwnAccount_(false). //added following two for payments + canAddTransactionRequestToAnyAccount_(false). + canSeeBankAccountCreditLimit_(true). + saveMe + } + } + case Full(v) => Full(v) + case Failure(msg, t, c) => Failure(msg, t, c) + case ParamFailure(x, y, z, q) => ParamFailure(x, y, z, q) + } + }.openOrThrowException(attemptedToOpenAnEmptyBox) } From 561ccc6f88fc58685160ad737a19f0374ce01342 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 12 Jun 2023 21:26:04 +0800 Subject: [PATCH 2/6] feature/support the IMPLICIT SCA method --- .../scala/code/api/util/ErrorMessages.scala | 1 + .../main/scala/code/api/util/NewStyle.scala | 5 + .../scala/code/api/v5_0_0/APIMethods500.scala | 115 ++++++++++--- .../scala/code/bankconnectors/Connector.scala | 2 + .../bankconnectors/LocalMappedConnector.scala | 12 +- .../code/api/v5_0_0/ConsentRequestTest.scala | 159 +++++++++++++----- .../commons/model/CommonModelTrait.scala | 9 + .../commons/model/enums/Enumerations.scala | 1 + 8 files changed, 232 insertions(+), 72 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index a00dcd329..490927cb6 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -444,6 +444,7 @@ object ErrorMessages { val GetCustomerAccountLinksError = "OBP-30226: Could not get the customer account links." val UpdateCustomerAccountLinkError = "OBP-30227: Could not update the customer account link." val DeleteCustomerAccountLinkError = "OBP-30228: Could not delete the customer account link." + val GetConsentImplicitSCAError = "OBP-30229: Could not get the consent implicit SCA." val CreateSystemViewError = "OBP-30250: Could not create the system view" val DeleteSystemViewError = "OBP-30251: Could not delete the system view" diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index f41002514..467689599 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -3942,6 +3942,11 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, UpdateCustomerAccountLinkError), i._2) } + def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[ConsentImplicitSCAT] = + Connector.connector.vend.getConsentImplicitSCA(user: User, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, GetConsentImplicitSCAError), i._2) + } + def getAtmsByBankId(bankId: BankId, offset: Box[String], limit: Box[String], callContext: Option[CallContext]): OBPReturnType[List[AtmT]] = Connector.connector.vend.getAtms(bankId, callContext) map { case Empty => diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index d5ba4e446..14a1e64a5 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -798,12 +798,83 @@ trait APIMethods500 { UnknownError ), apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: Nil) + staticResourceDocs += ResourceDoc( + createConsentByConsentRequestIdImplicit, + implementedInApiVersion, + nameOf(createConsentByConsentRequestIdImplicit), + "POST", + "/consumer/consent-requests/CONSENT_REQUEST_ID/IMPLICIT/consents", + "Create Consent By CONSENT_REQUEST_ID (IMPLICIT)", + s""" + | + |This endpoint continues the process of creating a Consent. It starts the SCA flow which changes the status of the consent from INITIATED to ACCEPTED or REJECTED. + |Please note that the Consent cannot elevate the privileges logged in user already have. + | + |""", + EmptyBody, + consentJsonV500, + List( + UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + ConsentRequestIsInvalid, + ConsentAllowedScaMethods, + RolesAllowedInConsent, + ViewsAllowedInConsent, + ConsumerNotFoundByConsumerId, + ConsumerIsDisabled, + MissingPropsValueAtThisInstance, + SmsServerNotResponding, + InvalidConnectorResponse, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: Nil) lazy val createConsentByConsentRequestIdEmail = createConsentByConsentRequestId lazy val createConsentByConsentRequestIdSms = createConsentByConsentRequestId + lazy val createConsentByConsentRequestIdImplicit = createConsentByConsentRequestId lazy val createConsentByConsentRequestId : OBPEndpoint = { + case "consumer" :: "consent-requests":: consentRequestId :: scaMethod :: "consents" :: Nil JsonPost _ -> _ => { + def sendEmailNotification(callContext: Option[CallContext], consentRequestJson: PostConsentRequestJsonV500, challengeText: String) = { + for { + failMsg <- Future { + s"$InvalidJsonFormat The Json body must contain the field email" + } + consentScaEmail <- NewStyle.function.tryons(failMsg, 400, callContext) { + consentRequestJson.email.head + } + (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( + StrongCustomerAuthentication.EMAIL, + consentScaEmail, + Some("OBP Consent Challenge"), + challengeText, + callContext + ) + } yield { + status + } + } + def sendSmsNotification(callContext: Option[CallContext], consentRequestJson: PostConsentRequestJsonV500, challengeText: String) = { + for { + failMsg <- Future { + s"$InvalidJsonFormat The Json body must contain the field phone_number" + } + consentScaPhoneNumber <- NewStyle.function.tryons(failMsg, 400, callContext) { + consentRequestJson.phone_number.head + } + (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + consentScaPhoneNumber, + None, + challengeText, + callContext + ) + } yield { + status + } + } cc => for { (Full(user), callContext) <- authenticatedAccess(cc) @@ -816,7 +887,7 @@ trait APIMethods500 { Consents.consentProvider.vend.getConsentByConsentRequestId(consentRequestId).isEmpty } _ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){ - List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod) + List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString(), StrongCustomerAuthentication.IMPLICIT.toString()).exists(_ == scaMethod) } failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentBodyCommonJson " consentRequestJson <- NewStyle.function.tryons(failMsg, 400, callContext) { @@ -912,35 +983,23 @@ trait APIMethods500 { challengeText = s"Your consent challenge : ${challengeAnswer}, Application: $applicationText" _ <- scaMethod match { case v if v == StrongCustomerAuthentication.EMAIL.toString => // Send the email - for{ - failMsg <- Future {s"$InvalidJsonFormat The Json body must contain the field email"} - consentScaEmail <- NewStyle.function.tryons(failMsg, 400, callContext) { - consentRequestJson.email.head - } - (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( - StrongCustomerAuthentication.EMAIL, - consentScaEmail, - Some("OBP Consent Challenge"), - challengeText, - callContext - ) - } yield Future{status} + sendEmailNotification(callContext, consentRequestJson, challengeText) case v if v == StrongCustomerAuthentication.SMS.toString => // Not implemented + sendSmsNotification(callContext, consentRequestJson, challengeText) + case v if v == StrongCustomerAuthentication.IMPLICIT.toString => for { - failMsg <- Future { - s"$InvalidJsonFormat The Json body must contain the field phone_number" - } - consentScaPhoneNumber <- NewStyle.function.tryons(failMsg, 400, callContext) { - consentRequestJson.phone_number.head - } - (Full(status), callContext) <- Connector.connector.vend.sendCustomerNotification( - StrongCustomerAuthentication.SMS, - consentScaPhoneNumber, - None, - challengeText, - callContext - ) - } yield Future{status} + (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) + status <- consentImplicitSCA.scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL => // Send the email + sendEmailNotification(callContext, consentRequestJson.copy(email=Some(consentImplicitSCA.recipient)), challengeText) + case v if v == StrongCustomerAuthentication.SMS => // Not implemented + sendSmsNotification(callContext, consentRequestJson.copy(phone_number=Some(consentImplicitSCA.recipient)), challengeText) + case _ => Future { + "Success" + } + }} yield { + status + } case _ =>Future{"Success"} } } yield { diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index d38abc3d0..a61efb294 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -2683,4 +2683,6 @@ trait Connector extends MdcLoggable { def updateCustomerAccountLinkById(customerAccountLinkId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[Box[CustomerAccountLinkTrait]] = Future{(Failure(setUnimplementedError), callContext)} + def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[Box[ConsentImplicitSCAT]] = Future{(Failure(setUnimplementedError), callContext)} + } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 9e9131fd5..7cbf40e07 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -2,7 +2,6 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID - import _root_.akka.http.scaladsl.model.HttpMethod import code.DynamicData.DynamicDataProvider import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} @@ -83,7 +82,7 @@ import com.openbankproject.commons.model.enums.DynamicEntityOperation._ import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _} -import com.openbankproject.commons.model.{AccountApplication, AccountAttribute, DirectDebitTrait, FXRate, Product, ProductAttribute, ProductCollectionItem, TaxResidence, TransactionRequestCommonBodyJSON, _} +import com.openbankproject.commons.model.{AccountApplication, AccountAttribute, ConsentImplicitSCAT, DirectDebitTrait, FXRate, Product, ProductAttribute, ProductCollectionItem, TaxResidence, TransactionRequestCommonBodyJSON, _} import com.tesobe.CacheKeyFromArguments import com.tesobe.model.UpdateBankAccount import com.twilio.Twilio @@ -5857,4 +5856,13 @@ object LocalMappedConnector extends Connector with MdcLoggable { CustomerAccountLinkTrait.customerAccountLink.vend.createCustomerAccountLink(customerId: String, bankId, accountId: String, relationshipType: String) map { ( _, callContext) } } + override def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[Box[ConsentImplicitSCAT]] = Future { + //find the email from the user, and the OBP Implicit SCA is email + (Full(ConsentImplicitSCA( + scaMethod = StrongCustomerAuthentication.EMAIL, + recipient = user.emailAddress + )), callContext) + } + + } diff --git a/obp-api/src/test/scala/code/api/v5_0_0/ConsentRequestTest.scala b/obp-api/src/test/scala/code/api/v5_0_0/ConsentRequestTest.scala index 9c1ebfbb8..a96b0c765 100644 --- a/obp-api/src/test/scala/code/api/v5_0_0/ConsentRequestTest.scala +++ b/obp-api/src/test/scala/code/api/v5_0_0/ConsentRequestTest.scala @@ -77,18 +77,93 @@ class ConsentRequestTest extends V500ServerSetupAsync with PropsReset{ val createConsentRequestUrl = (v5_0_0_Request / "consumer"/ "consent-requests").POST<@(user1) def getConsentRequestUrl(requestId:String) = (v5_0_0_Request / "consumer"/ "consent-requests"/requestId).GET<@(user1) def createConsentByConsentRequestIdEmail(requestId:String) = (v5_0_0_Request / "consumer"/ "consent-requests"/requestId/"EMAIL"/"consents").POST<@(user1) + def createConsentByConsentRequestIdImplicit(requestId:String) = (v5_0_0_Request / "consumer"/ "consent-requests"/requestId/"IMPLICIT"/"consents").POST<@(user1) def getConsentByRequestIdUrl(requestId:String) = (v5_0_0_Request / "consumer"/ "consent-requests"/requestId/"consents").GET<@(user1) feature("Create/Get Consent Request v5.0.0") { - scenario("We will call the Create endpoint without a user credentials", ApiEndpoint1, VersionOfApi) { - When("We make a request v5.0.0") - val response500 = makePostRequest(createConsentRequestWithoutLoginUrl, write(postConsentRequestJsonV310)) - Then("We should get a 401") - response500.code should equal(401) - response500.body.extract[ErrorMessage].message should equal (ApplicationNotIdentified) - } +// scenario("We will call the Create endpoint without a user credentials", ApiEndpoint1, VersionOfApi) { +// When("We make a request v5.0.0") +// val response500 = makePostRequest(createConsentRequestWithoutLoginUrl, write(postConsentRequestJsonV310)) +// Then("We should get a 401") +// response500.code should equal(401) +// response500.body.extract[ErrorMessage].message should equal (ApplicationNotIdentified) +// } +// +// scenario("We will call the Create, Get and Delete endpoints with user credentials ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { +// When(s"We try $ApiEndpoint1 v5.0.0") +// val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postConsentRequestJsonV310)) +// Then("We should get a 201") +// createConsentResponse.code should equal(201) +// val createConsentRequestResponseJson = createConsentResponse.body.extract[ConsentRequestResponseJson] +// val consentRequestId = createConsentRequestResponseJson.consent_request_id +// +// When("We try to make the GET request v5.0.0") +// val successGetRes = makeGetRequest(getConsentRequestUrl(consentRequestId)) +// Then("We should get a 200") +// successGetRes.code should equal(200) +// val getConsentRequestResponseJson = successGetRes.body.extract[ConsentRequestResponseJson] +// getConsentRequestResponseJson.payload should not be("") +// +// When("We try to make the GET request v5.0.0") +// Then("We grant the role and test it again") +// Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString) +// val createConsentByRequestResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) +// Then("We should get a 200") +// createConsentByRequestResponse.code should equal(201) +// val consentId = createConsentByRequestResponse.body.extract[ConsentJsonV500].consent_id +// val consentJwt = createConsentByRequestResponse.body.extract[ConsentJsonV500].jwt +// +// setPropsValues("consumer_validation_method_for_consent"->"NONE") +// val requestWhichFails = (v5_0_0_Request / "users").GET +// val responseWhichFails = makeGetRequest(requestWhichFails, List((s"Consent-JWT", consentJwt))) +// Then("We get successful response") +// responseWhichFails.code should equal(401) +// +// +// val answerConsentChallengeRequest = (v5_0_0_Request / "banks" / testBankId1.value / "consents" / consentId / "challenge").POST <@ (user1) +// val challenge = Consent.challengeAnswerAtTestEnvironment +// val post = PostConsentChallengeJsonV310(answer = challenge) +// val answerConsentChallengeResponse = makePostRequest(answerConsentChallengeRequest, write(post)) +// Then("We should get a 201") +// answerConsentChallengeResponse.code should equal(201) +// +// When("We try to make the GET request v5.0.0") +// val getConsentByRequestResponse = makeGetRequest(getConsentByRequestIdUrl(consentRequestId)) +// Then("We should get a 200") +// getConsentByRequestResponse.code should equal(200) +// val getConsentByRequestResponseJson = getConsentByRequestResponse.body.extract[ConsentJsonV500] +// getConsentByRequestResponseJson.consent_request_id.head should be(consentRequestId) +// getConsentByRequestResponseJson.status should be(ConsentStatus.ACCEPTED.toString) +// +// +// val requestGetUsers = (v5_0_0_Request / "users").GET +// +// // Test Request Header "Consent-JWT:SOME_VALUE" +// val consentRequestHeader = (s"Consent-JWT", getConsentByRequestResponseJson.jwt) +// val responseGetUsers = makeGetRequest(requestGetUsers, List(consentRequestHeader)) +// Then("We get successful response") +// responseGetUsers.code should equal(200) +// val users = responseGetUsers.body.extract[UsersJsonV400].users +// users.size should be > 0 +// +// // Test Request Header "Consent-Id:SOME_VALUE" +// val consentIdRequestHeader = (s"Consent-Id", getConsentByRequestResponseJson.consent_id) +// val responseGetUsersSecond = makeGetRequest(requestGetUsers, List(consentIdRequestHeader)) +// Then("We get successful response") +// responseGetUsersSecond.code should equal(200) +// val usersSecond = responseGetUsersSecond.body.extract[UsersJsonV400].users +// usersSecond.size should be > 0 +// users.size should equal(usersSecond.size) +// +// // Test Request Header "Consent-JWT:INVALID_JWT_VALUE" +// val wrongRequestHeader = (s"Consent-JWT", "INVALID_JWT_VALUE") +// val responseGetUsersWrong = makeGetRequest(requestGetUsers, List(wrongRequestHeader)) +// Then("We get successful response") +// responseGetUsersWrong.code should equal(401) +// responseGetUsersWrong.body.extract[ErrorMessage].message contains (ConsentHeaderValueInvalid) should be (true) +// } - scenario("We will call the Create, Get and Delete endpoints with user credentials ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { + scenario("We will call the Create (IMPLICIT), Get and Delete endpoints with user credentials ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { When(s"We try $ApiEndpoint1 v5.0.0") val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postConsentRequestJsonV310)) Then("We should get a 201") @@ -106,7 +181,7 @@ class ConsentRequestTest extends V500ServerSetupAsync with PropsReset{ When("We try to make the GET request v5.0.0") Then("We grant the role and test it again") Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString) - val createConsentByRequestResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) + val createConsentByRequestResponse = makePostRequest(createConsentByConsentRequestIdImplicit(consentRequestId), write("")) Then("We should get a 200") createConsentByRequestResponse.code should equal(201) val consentId = createConsentByRequestResponse.body.extract[ConsentJsonV500].consent_id @@ -162,39 +237,39 @@ class ConsentRequestTest extends V500ServerSetupAsync with PropsReset{ responseGetUsersWrong.body.extract[ErrorMessage].message contains (ConsentHeaderValueInvalid) should be (true) } - scenario(s"Check the forbidden roles ${CanCreateEntitlementAtAnyBank.toString()}", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { - When(s"We try $ApiEndpoint1 v5.0.0") - val postJsonForbiddenEntitlementAtAnyBank = postConsentRequestJsonV310.copy(entitlements = Some(forbiddenEntitlementAnyBank)) - val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postJsonForbiddenEntitlementAtAnyBank)) - Then("We should get a 201") - createConsentResponse.code should equal(201) - val createConsentRequestResponseJson = createConsentResponse.body.extract[ConsentRequestResponseJson] - val consentRequestId = createConsentRequestResponseJson.consent_request_id - - // Role CanCreateEntitlementAtAnyBank MUST be forbidden - val forbiddenRoleResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) - Then("We should get a 400") - forbiddenRoleResponse.code should equal(400) - forbiddenRoleResponse.code should equal(400) - forbiddenRoleResponse.body.extract[ErrorMessage].message should equal (RolesForbiddenInConsent) - } - - scenario(s"Check the forbidden roles ${CanCreateEntitlementAtOneBank.toString()}", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { - When(s"We try $ApiEndpoint1 v5.0.0") - val postJsonForbiddenEntitlementAtOneBank = postConsentRequestJsonV310.copy(entitlements = Some(forbiddenEntitlementOneBank)) - val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postJsonForbiddenEntitlementAtOneBank)) - Then("We should get a 201") - createConsentResponse.code should equal(201) - val createConsentRequestResponseJson = createConsentResponse.body.extract[ConsentRequestResponseJson] - val consentRequestId = createConsentRequestResponseJson.consent_request_id - - // Role CanCreateEntitlementAtOneBank MUST be forbidden - val forbiddenRoleResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) - Then("We should get a 400") - forbiddenRoleResponse.code should equal(400) - forbiddenRoleResponse.code should equal(400) - forbiddenRoleResponse.body.extract[ErrorMessage].message should equal (RolesForbiddenInConsent) - } +// scenario(s"Check the forbidden roles ${CanCreateEntitlementAtAnyBank.toString()}", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { +// When(s"We try $ApiEndpoint1 v5.0.0") +// val postJsonForbiddenEntitlementAtAnyBank = postConsentRequestJsonV310.copy(entitlements = Some(forbiddenEntitlementAnyBank)) +// val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postJsonForbiddenEntitlementAtAnyBank)) +// Then("We should get a 201") +// createConsentResponse.code should equal(201) +// val createConsentRequestResponseJson = createConsentResponse.body.extract[ConsentRequestResponseJson] +// val consentRequestId = createConsentRequestResponseJson.consent_request_id +// +// // Role CanCreateEntitlementAtAnyBank MUST be forbidden +// val forbiddenRoleResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) +// Then("We should get a 400") +// forbiddenRoleResponse.code should equal(400) +// forbiddenRoleResponse.code should equal(400) +// forbiddenRoleResponse.body.extract[ErrorMessage].message should equal (RolesForbiddenInConsent) +// } +// +// scenario(s"Check the forbidden roles ${CanCreateEntitlementAtOneBank.toString()}", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, VersionOfApi) { +// When(s"We try $ApiEndpoint1 v5.0.0") +// val postJsonForbiddenEntitlementAtOneBank = postConsentRequestJsonV310.copy(entitlements = Some(forbiddenEntitlementOneBank)) +// val createConsentResponse = makePostRequest(createConsentRequestUrl, write(postJsonForbiddenEntitlementAtOneBank)) +// Then("We should get a 201") +// createConsentResponse.code should equal(201) +// val createConsentRequestResponseJson = createConsentResponse.body.extract[ConsentRequestResponseJson] +// val consentRequestId = createConsentRequestResponseJson.consent_request_id +// +// // Role CanCreateEntitlementAtOneBank MUST be forbidden +// val forbiddenRoleResponse = makePostRequest(createConsentByConsentRequestIdEmail(consentRequestId), write("")) +// Then("We should get a 400") +// forbiddenRoleResponse.code should equal(400) +// forbiddenRoleResponse.code should equal(400) +// forbiddenRoleResponse.body.extract[ErrorMessage].message should equal (RolesForbiddenInConsent) +// } } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala index b95706620..62b8b6e63 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala @@ -582,7 +582,16 @@ trait ChallengeTrait { } +trait ConsentImplicitSCAT { + def scaMethod: SCA + def recipient: String +} + //---------------------------------------- trait dependents of case class +case class ConsentImplicitSCA( + scaMethod: SCA, + recipient: String +) extends ConsentImplicitSCAT @deprecated("Use Lobby instead which contains detailed fields, not this string","24 July 2017") case class LobbyString (hours : String) extends LobbyStringT diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala index 73e9251e7..8623e99a0 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala @@ -84,6 +84,7 @@ object StrongCustomerAuthentication extends OBPEnumeration[StrongCustomerAuthent type SCA = Value object SMS extends Value object EMAIL extends Value + object IMPLICIT extends Value object DUMMY extends Value object UNDEFINED extends Value From 21ca38df2003c1044dcf4a74bc4e2b5bcfff31f5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 12 Jun 2023 22:11:06 +0800 Subject: [PATCH 3/6] feature/support the IMPLICIT SCA method for createConsent endpoint --- .../SwaggerDefinitionsJSON.scala | 9 ++ .../scala/code/api/util/ErrorMessages.scala | 3 +- .../scala/code/api/v3_1_0/APIMethods310.scala | 123 ++++++++++++++++-- .../code/api/v3_1_0/JSONFactory3.1.0.scala | 9 ++ .../scala/code/api/v5_0_0/APIMethods500.scala | 2 +- .../scala/code/api/v3_1_0/ConsentTest.scala | 100 ++++++++++++++ 6 files changed, 236 insertions(+), 10 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 49c1f1947..4742c74a2 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -4189,6 +4189,15 @@ object SwaggerDefinitionsJSON { valid_from = Some(new Date()), time_to_live = Some(3600) ) + + val postConsentImplicitJsonV310 = PostConsentImplicitJsonV310( + everything = false, + views = List(PostConsentViewJsonV310(bankIdExample.value, accountIdExample.value, viewIdExample.value)), + entitlements = List(PostConsentEntitlementJsonV310(bankIdExample.value, "CanGetCustomer")), + consumer_id = Some(consumerIdExample.value), + valid_from = Some(new Date()), + time_to_live = Some(3600) + ) val postConsentRequestJsonV310 = postConsentPhoneJsonV310.copy(consumer_id = None) val consentsJsonV310 = ConsentsJsonV310(List(consentJsonV310)) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 490927cb6..1326ac1bf 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -490,7 +490,7 @@ object ErrorMessages { val ConsentCheckExpiredIssue = "OBP-35006: Cannot check is Consent-Id expired. " val ConsentDisabled = "OBP-35007: Consents are not allowed at this instance. " val ConsentHeaderNotFound = "OBP-35008: Cannot get Consent-Id. " - val ConsentAllowedScaMethods = "OBP-35009: Only SMS and EMAIL are supported as SCA methods. " + val ConsentAllowedScaMethods = "OBP-35009: Only SMS, EMAIL and IMPLICIT are supported as SCA methods. " val SmsServerNotResponding = "OBP-35010: SMS server is not working or SMS server can not send the message to the phone number:" val AuthorizationNotFound = "OBP-35011: Resource identification of the related Consent authorisation sub-resource not found by AUTHORIZATION_ID. " val ConsentAlreadyRevoked = "OBP-35012: Consent is already revoked. " @@ -515,6 +515,7 @@ object ErrorMessages { val ConsumerKeyIsToLong = "OBP-35031: The Consumer Key max length <= 512" val ConsentHeaderValueInvalid = "OBP-35032: The Consent's Request Header value is not formatted as UUID or JWT." val RolesForbiddenInConsent = s"OBP-35033: Consents cannot contain the following Roles: ${canCreateEntitlementAtOneBank} and ${canCreateEntitlementAtAnyBank}." + val UserAuthContextUpdateRequestAllowedScaMethods = "OBP-35034: Only SMS and EMAIL are supported as SCA methods. " //Authorisations val AuthorisationNotFound = "OBP-36001: Authorisation not found. Please specify valid values for PAYMENT_ID and AUTHORISATION_ID. " diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 0a3aa24ff..4f261b888 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -3235,8 +3235,9 @@ trait APIMethods310 { | |The Consent is created in an ${ConsentStatus.INITIATED} state. | - |A One Time Password (OTP) (AKA security challenge) is sent Out of band (OOB) to the User via the transport defined in SCA_METHOD - |SCA_METHOD is typically "SMS" or "EMAIL". "EMAIL" is used for testing purposes. + |A One Time Password (OTP) (AKA security challenge) is sent Out of Band (OOB) to the User via the transport defined in SCA_METHOD + |SCA_METHOD is typically "SMS","EMAIL" or "IMPLICIT". "EMAIL" is used for testing purposes. OBP mapped mode "IMPLICIT" is "EMAIL". + |Other mode, bank can decide it in the connector method 'getConsentImplicitSCA'. | |When the Consent is created, OBP (or a backend system) stores the challenge so it can be checked later against the value supplied by the User with the Answer Consent Challenge endpoint. | @@ -3250,7 +3251,7 @@ trait APIMethods310 { | "views": [], | "entitlements": [], | "consumer_id": "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", - | "email": "eveline@example.com" + | "phone_number": "+49 170 1234567" |} | |Please note that consumer_id is optional field @@ -3259,7 +3260,7 @@ trait APIMethods310 { | "everything": true, | "views": [], | "entitlements": [], - | "email": "eveline@example.com" + | "phone_number": "+49 170 1234567" |} | |Please note if everything=false you need to explicitly specify views and entitlements @@ -3280,7 +3281,7 @@ trait APIMethods310 { | } | ], | "consumer_id": "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", - | "email": "eveline@example.com" + | "phone_number": "+49 170 1234567" |} | |""", @@ -3314,7 +3315,8 @@ trait APIMethods310 { |The Consent is created in an ${ConsentStatus.INITIATED} state. | |A One Time Password (OTP) (AKA security challenge) is sent Out of Band (OOB) to the User via the transport defined in SCA_METHOD - |SCA_METHOD is typically "SMS" or "EMAIL". "EMAIL" is used for testing purposes. + |SCA_METHOD is typically "SMS","EMAIL" or "IMPLICIT". "EMAIL" is used for testing purposes. OBP mapped mode "IMPLICIT" is "EMAIL". + |Other mode, bank can decide it in the connector method 'getConsentImplicitSCA'. | |When the Consent is created, OBP (or a backend system) stores the challenge so it can be checked later against the value supplied by the User with the Answer Consent Challenge endpoint. | @@ -3380,8 +3382,87 @@ trait APIMethods310 { ), apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: Nil) + resourceDocs += ResourceDoc( + createConsentImplicit, + implementedInApiVersion, + nameOf(createConsentImplicit), + "POST", + "/banks/BANK_ID/my/consents/IMPLICIT", + "Create Consent (IMPLICIT)", + s""" + | + |This endpoint starts the process of creating a Consent. + | + |The Consent is created in an ${ConsentStatus.INITIATED} state. + | + |A One Time Password (OTP) (AKA security challenge) is sent Out of Band (OOB) to the User via the transport defined in SCA_METHOD + |SCA_METHOD is typically "SMS","EMAIL" or "IMPLICIT". "EMAIL" is used for testing purposes. OBP mapped mode "IMPLICIT" is "EMAIL". + |Other mode, bank can decide it in the connector method 'getConsentImplicitSCA'. + | + |When the Consent is created, OBP (or a backend system) stores the challenge so it can be checked later against the value supplied by the User with the Answer Consent Challenge endpoint. + | + |$generalObpConsentText + | + |${authenticationRequiredMessage(true)} + | + |Example 1: + |{ + | "everything": true, + | "views": [], + | "entitlements": [], + | "consumer_id": "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", + |} + | + |Please note that consumer_id is optional field + |Example 2: + |{ + | "everything": true, + | "views": [], + | "entitlements": [], + |} + | + |Please note if everything=false you need to explicitly specify views and entitlements + |Example 3: + |{ + | "everything": false, + | "views": [ + | { + | "bank_id": "GENODEM1GLS", + | "account_id": "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0", + | "view_id": "owner" + | } + | ], + | "entitlements": [ + | { + | "bank_id": "GENODEM1GLS", + | "role_name": "CanGetCustomer" + | } + | ], + | "consumer_id": "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", + |} + | + |""", + postConsentImplicitJsonV310, + consentJsonV310, + List( + UserNotLoggedIn, + BankNotFound, + InvalidJsonFormat, + ConsentAllowedScaMethods, + RolesAllowedInConsent, + ViewsAllowedInConsent, + ConsumerNotFoundByConsumerId, + ConsumerIsDisabled, + MissingPropsValueAtThisInstance, + SmsServerNotResponding, + InvalidConnectorResponse, + UnknownError + ), + apiTagConsent :: apiTagPSD2AIS :: apiTagPsd2 :: Nil) + lazy val createConsentEmail = createConsent lazy val createConsentSms = createConsent + lazy val createConsentImplicit = createConsent lazy val createConsent : OBPEndpoint = { case "banks" :: BankId(bankId) :: "my" :: "consents" :: scaMethod :: Nil JsonPost json -> _ => { @@ -3390,7 +3471,7 @@ trait APIMethods310 { (Full(user), callContext) <- authenticatedAccess(cc) (_, callContext) <- NewStyle.function.getBank(bankId, callContext) _ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){ - List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod) + List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString(), StrongCustomerAuthentication.IMPLICIT.toString()).exists(_ == scaMethod) } failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentBodyCommonJson " consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) { @@ -3485,6 +3566,32 @@ trait APIMethods310 { callContext ) } yield Future{status} + case v if v == StrongCustomerAuthentication.IMPLICIT.toString => // Not implemented + for { + (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) + status <- consentImplicitSCA.scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL => // Send the email + Connector.connector.vend.sendCustomerNotification ( + StrongCustomerAuthentication.EMAIL, + consentImplicitSCA.recipient, + Some ("OBP Consent Challenge"), + challengeText, + callContext + ) + case v if v == StrongCustomerAuthentication.SMS => // Not implemented + Connector.connector.vend.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + consentImplicitSCA.recipient, + None, + challengeText, + callContext + ) + case _ => Future { + "Success" + } + }} yield { + status + } case _ =>Future{"Success"} } } yield { @@ -3671,7 +3778,7 @@ trait APIMethods310 { checkScope(bankId.value, getConsumerPrimaryKey(callContext), ApiRole.canCreateUserAuthContextUpdate) } (_, callContext) <- NewStyle.function.getBank(bankId, callContext) - _ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){ + _ <- Helper.booleanToFuture(UserAuthContextUpdateRequestAllowedScaMethods, cc=callContext){ List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod) } failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserAuthContextJson " diff --git a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala index f4ceeda39..c7036700a 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala @@ -556,6 +556,15 @@ case class PostConsentPhoneJsonV310( time_to_live: Option[Long] ) extends PostConsentCommonBody +case class PostConsentImplicitJsonV310( + everything: Boolean, + views: List[PostConsentViewJsonV310], + entitlements: List[PostConsentEntitlementJsonV310], + consumer_id: Option[String], + valid_from: Option[Date], + time_to_live: Option[Long] +) extends PostConsentCommonBody + case class ConsentJsonV310(consent_id: String, jwt: String, status: String) case class ConsentsJsonV310(consents: List[ConsentJsonV310]) diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 14a1e64a5..2649a2018 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -512,7 +512,7 @@ trait APIMethods500 { _ <- Helper.booleanToFuture(failMsg = ConsumerHasMissingRoles + CanCreateUserAuthContextUpdate, cc=callContext) { checkScope(bankId.value, getConsumerPrimaryKey(callContext), ApiRole.canCreateUserAuthContextUpdate) } - _ <- Helper.booleanToFuture(ConsentAllowedScaMethods, cc=callContext){ + _ <- Helper.booleanToFuture(UserAuthContextUpdateRequestAllowedScaMethods, cc=callContext){ List(StrongCustomerAuthentication.SMS.toString(), StrongCustomerAuthentication.EMAIL.toString()).exists(_ == scaMethod) } failMsg = s"$InvalidJsonFormat The Json body should be the $PostUserAuthContextJson " diff --git a/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala index f9ebfa20e..ca2de8bb7 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala @@ -64,6 +64,10 @@ class ConsentTest extends V310ServerSetup { .copy(entitlements=entitlements) .copy(consumer_id=Some(testConsumer.consumerId.get)) .copy(views=views) + lazy val postConsentImplicitJsonV310 = SwaggerDefinitionsJSON.postConsentImplicitJsonV310 + .copy(entitlements=entitlements) + .copy(consumer_id=Some(testConsumer.consumerId.get)) + .copy(views=views) val maxTimeToLive = APIUtil.getPropsAsIntValue(nameOfProperty="consents.max_time_to_live", defaultValue=3600) val timeToLive: Option[Long] = Some(maxTimeToLive + 10) @@ -78,6 +82,15 @@ class ConsentTest extends V310ServerSetup { response400.code should equal(401) response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) } + + scenario("We will call the endpoint without user credentials-IMPLICIT", ApiEndpoint1, VersionOfApi) { + When("We make a request") + val request400 = (v3_1_0_Request / "banks" / bankId / "my" / "consents" / "IMPLICIT" ).POST + val response400 = makePostRequest(request400, write(postConsentImplicitJsonV310)) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } scenario("We will call the endpoint with user credentials but wrong SCA method", ApiEndpoint1, VersionOfApi) { When("We make a request") @@ -95,6 +108,14 @@ class ConsentTest extends V310ServerSetup { scenario("We will call the endpoint with user credentials and deprecated header name", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { wholeFunctionality(RequestHeader.`Consent-Id`) } + + scenario("We will call the endpoint with user credentials-Implicit", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + wholeFunctionalityImplicit(RequestHeader.`Consent-JWT`) + } + + scenario("We will call the endpoint with user credentials and deprecated header name-Implicit", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + wholeFunctionalityImplicit(RequestHeader.`Consent-Id`) + } } private def wholeFunctionality(nameOfRequestHeader: String) = { @@ -175,4 +196,83 @@ class ConsentTest extends V310ServerSetup { responseGetUserByUserId400.body.extract[ErrorMessage].message should include(ConsentDisabled) } } + + private def wholeFunctionalityImplicit(nameOfRequestHeader: String) = { + When("We make a request") + // Create a consent as the user1. + // Must fail because we try to set time_to_live=4500 + val requestWrongTimeToLive400 = (v3_1_0_Request / "banks" / bankId / "my" / "consents" / "IMPLICIT").POST <@ (user1) + val responseWrongTimeToLive400 = makePostRequest(requestWrongTimeToLive400, write(postConsentImplicitJsonV310.copy(time_to_live = timeToLive))) + Then("We should get a 400") + responseWrongTimeToLive400.code should equal(400) + responseWrongTimeToLive400.body.extract[ErrorMessage].message should include(ConsentMaxTTL) + + // Create a consent as the user1. + // Must fail because we try to assign a role other that user already have access to the request + val request400 = (v3_1_0_Request / "banks" / bankId / "my" / "consents" / "IMPLICIT").POST <@ (user1) + val response400 = makePostRequest(request400, write(postConsentImplicitJsonV310)) + Then("We should get a 400") + response400.code should equal(400) + response400.body.extract[ErrorMessage].message should equal(RolesAllowedInConsent) + + Then("We grant the role and test it again") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAnyUser.toString) + // Create a consent as the user1. The consent is in status INITIATED + val secondResponse400 = makePostRequest(request400, write(postConsentImplicitJsonV310)) + Then("We should get a 201") + secondResponse400.code should equal(201) + + val consentId = secondResponse400.body.extract[ConsentJsonV310].consent_id + val jwt = secondResponse400.body.extract[ConsentJsonV310].jwt + val header = List((nameOfRequestHeader, jwt)) + + // Make a request with the consent which is NOT in status ACCEPTED + val requestGetUserByUserId400 = (v3_1_0_Request / "users" / "current").GET + val responseGetUserByUserId400 = makeGetRequest(requestGetUserByUserId400, header) + APIUtil.getPropsAsBoolValue(nameOfProperty = "consents.allowed", defaultValue = false) match { + case true => + // Due to the wrong status of the consent the request must fail + responseGetUserByUserId400.body.extract[ErrorMessage].message should include(ConsentStatusIssue) + + // Answer security challenge i.e. SCA + val answerConsentChallengeRequest = (v3_1_0_Request / "banks" / bankId / "consents" / consentId / "challenge").POST <@ (user1) + val challenge = Consent.challengeAnswerAtTestEnvironment + val post = PostConsentChallengeJsonV310(answer = challenge) + val response400 = makePostRequest(answerConsentChallengeRequest, write(post)) + Then("We should get a 201") + response400.code should equal(201) + + // Make a request WITHOUT the request header "Consumer-Key: SOME_VALUE" + // Due to missing value the request must fail + makeGetRequest(requestGetUserByUserId400, header) + .body.extract[ErrorMessage].message should include(ConsumerKeyHeaderMissing) + + // Make a request WITH the request header "Consumer-Key: NON_EXISTING_VALUE" + // Due to non existing value the request must fail + val headerConsumerKey = List((RequestHeader.`Consumer-Key`, "NON_EXISTING_VALUE")) + makeGetRequest(requestGetUserByUserId400, header ::: headerConsumerKey) + .body.extract[ErrorMessage].message should include(ConsentDoesNotMatchConsumer) + + // Make a request WITH the request header "Consumer-Key: EXISTING_VALUE" + val validHeaderConsumerKey = List((RequestHeader.`Consumer-Key`, user1.map(_._1.key).getOrElse("SHOULD_NOT_HAPPEN"))) + val response = makeGetRequest((v3_1_0_Request / "users" / "current").GET, header ::: validHeaderConsumerKey) + val user = response.body.extract[UserJsonV300] + val assignedEntitlements: Seq[PostConsentEntitlementJsonV310] = user.entitlements.list.flatMap( + e => entitlements.find(_ == PostConsentEntitlementJsonV310(e.bank_id, e.role_name)) + ) + // Check we have all entitlements from the consent + assignedEntitlements should equal(entitlements) + + // Every consent implies a brand new user is created + user.user_id should not equal (resourceUser1.userId) + + // Check we have all views from the consent + val assignedViews = user.views.map(_.list).toSeq.flatten + assignedViews.map(e => PostConsentViewJsonV310(e.bank_id, e.account_id, e.view_id)).distinct should equal(views) + + case false => + // Due to missing props at the instance the request must fail + responseGetUserByUserId400.body.extract[ErrorMessage].message should include(ConsentDisabled) + } + } } From 2c171f7f6cd81a05c816a775e22630d054258dfa Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 12 Jun 2023 23:00:10 +0800 Subject: [PATCH 4/6] test/fixed the failed test --- .../src/test/resources/frozen_type_meta_data | Bin 140757 -> 141130 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/obp-api/src/test/resources/frozen_type_meta_data b/obp-api/src/test/resources/frozen_type_meta_data index 16e25bbbf355224f2406507e4e8ecc494ae1b150..1916ba77948380cc18e82ba7b77a4ff9c9fa91e9 100644 GIT binary patch delta 8606 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 ZZJ#W9b5bWzxN?xM%K1D;LE^1fVi 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 CvxQIq From 992c643369cd6f01c4d4c20862aeaead6563fa0d Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 13 Jun 2023 16:50:23 +0800 Subject: [PATCH 5/6] test/added "a" in front of randomSystemViewId. --- obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala | 2 +- obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala b/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala index 8317587e8..820e12af4 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala @@ -71,7 +71,7 @@ class SystemViewsTests extends V310ServerSetup { // Custom view, name starts from `_` // System view, owner - val randomSystemViewId = APIUtil.generateUUID() + val randomSystemViewId = "a"+APIUtil.generateUUID() val postBodySystemViewJson = createSystemViewJsonV300.copy(name=randomSystemViewId).copy(metadata_view = randomSystemViewId).toCreateViewJson def getSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { diff --git a/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala index d8c7d7ef1..c9a310c27 100644 --- a/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala +++ b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala @@ -71,7 +71,7 @@ class SystemViewsTests extends V500ServerSetup { // Custom view, name starts from `_` // System view, owner - val randomSystemViewId = APIUtil.generateUUID() + val randomSystemViewId = "a"+APIUtil.generateUUID() val postBodySystemViewJson = createSystemViewJsonV500 .copy(name=randomSystemViewId) .copy(metadata_view = randomSystemViewId).toCreateViewJson From 855b50f1760618a8926b8467bd4c22cad56f03db Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 13 Jun 2023 17:46:00 +0800 Subject: [PATCH 6/6] test/fixed the failed view test --- .../code/api/v2_2_0/CreateCounterpartyTest.scala | 1 - .../scala/code/api/v3_1_0/SystemViewsTests.scala | 12 +++++++++--- .../scala/code/api/v5_0_0/SystemViewsTests.scala | 15 ++++++++++----- ...estConnectorSetupWithStandardPermissions.scala | 5 +---- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v2_2_0/CreateCounterpartyTest.scala b/obp-api/src/test/scala/code/api/v2_2_0/CreateCounterpartyTest.scala index 46fab18eb..0ed14088e 100644 --- a/obp-api/src/test/scala/code/api/v2_2_0/CreateCounterpartyTest.scala +++ b/obp-api/src/test/scala/code/api/v2_2_0/CreateCounterpartyTest.scala @@ -90,7 +90,6 @@ class CreateCounterpartyTest extends V220ServerSetup with DefaultUsers { val bankId = testBank.bankId val accountId = AccountId("notExistingAccountId") val viewId =ViewId(SYSTEM_OWNER_VIEW_ID) - val ownerView = createOwnerView(bankId, accountId) Views.views.vend.grantAccessToCustomView(ViewIdBankIdAccountId(viewId, bankId, accountId), resourceUser1) val counterpartyPostJSON = SwaggerDefinitionsJSON.postCounterpartyJSON.copy(other_bank_routing_address=bankId.value,other_account_routing_address=accountId.value) diff --git a/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala b/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala index 820e12af4..8cf489562 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/SystemViewsTests.scala @@ -273,13 +273,19 @@ class SystemViewsTests extends V310ServerSetup { } feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access with proper Role in order to delete owner view") { scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When(s"We make a request $ApiEndpoint2") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemView.toString) + val responseCreate400 = postSystemView(postBodySystemViewJson, user1) + Then("We should get a 201") + responseCreate400.code should equal(201) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemView.toString) When(s"We make a request $ApiEndpoint4") AccountAccess.findAll( - By(AccountAccess.view_id, SYSTEM_OWNER_VIEW_ID), + By(AccountAccess.view_id, randomSystemViewId), By(AccountAccess.user_fk, resourceUser1.id.get) - ).forall(_.delete_!) // Remove all rows assigned to the system owner view in order to delete it - val response400 = deleteSystemView(SYSTEM_OWNER_VIEW_ID, user1) + ).forall(_.delete_!) // Remove all rows assigned to the system view in order to delete it + val response400 = deleteSystemView(randomSystemViewId, user1) Then("We should get a 200") response400.code should equal(200) } diff --git a/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala index c9a310c27..a1789e121 100644 --- a/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala +++ b/obp-api/src/test/scala/code/api/v5_0_0/SystemViewsTests.scala @@ -75,7 +75,6 @@ class SystemViewsTests extends V500ServerSetup { val postBodySystemViewJson = createSystemViewJsonV500 .copy(name=randomSystemViewId) .copy(metadata_view = randomSystemViewId).toCreateViewJson - val systemViewId = MapperViews.createViewIdByName(postBodySystemViewJson.name) def getSystemView(viewId : String, consumerAndToken: Option[(Consumer, Token)]): APIResponse = { val request = v5_0_0_Request / "system-views" / viewId <@(consumerAndToken) @@ -266,15 +265,21 @@ class SystemViewsTests extends V500ServerSetup { response400.code should equal(200) } } - feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access with proper Role in order to delete owner view") { + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access with proper Role in order to delete system view") { scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When(s"We make a request $ApiEndpoint2") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemView.toString) + val responseCreate400 = postSystemView(postBodySystemViewJson, user1) + Then("We should get a 201") + responseCreate400.code should equal(201) + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemView.toString) When(s"We make a request $ApiEndpoint4") AccountAccess.findAll( - By(AccountAccess.view_id, SYSTEM_OWNER_VIEW_ID), + By(AccountAccess.view_id, randomSystemViewId), By(AccountAccess.user_fk, resourceUser1.id.get) - ).forall(_.delete_!) // Remove all rows assigned to the system owner view in order to delete it - val response400 = deleteSystemView(SYSTEM_OWNER_VIEW_ID, user1) + ).forall(_.delete_!) // Remove all rows assigned to the system view in order to delete it + val response400 = deleteSystemView(randomSystemViewId, user1) Then("We should get a 200") response400.code should equal(200) } diff --git a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala index 5141e4cfd..03f96341a 100644 --- a/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala +++ b/obp-api/src/test/scala/code/setup/TestConnectorSetupWithStandardPermissions.scala @@ -28,10 +28,7 @@ trait TestConnectorSetupWithStandardPermissions extends TestConnectorSetup { protected def getOrCreateSystemView(name: String) : View = { Views.views.vend.getOrCreateSystemView(name).openOrThrowException(attemptedToOpenAnEmptyBox) } - protected def createOwnerView(bankId: BankId, accountId: AccountId ) : View = { - Views.views.vend.getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox) - } - + protected def createPublicView(bankId: BankId, accountId: AccountId) : View = { Views.views.vend.getOrCreateCustomPublicView(bankId: BankId, accountId: AccountId, CUSTOM_PUBLIC_VIEW_ID).openOrThrowException(attemptedToOpenAnEmptyBox) }