feature/support the IMPLICIT SCA method

This commit is contained in:
hongwei 2023-06-12 21:26:04 +08:00
parent 3b7f1e4231
commit 561ccc6f88
8 changed files with 232 additions and 72 deletions

View File

@ -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"

View File

@ -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 =>

View File

@ -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 {

View File

@ -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)}
}

View File

@ -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)
}
}

View File

@ -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)
// }
}

View File

@ -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

View File

@ -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