From ad68f054fb7ad5285807413944c70b0ea2e2e287 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 24 Jan 2025 22:10:59 +0800 Subject: [PATCH] feature/added props skip_consent_sca_for_consumer_id_pairs --- .../main/scala/code/api/util/APIUtil.scala | 22 +++ .../scala/code/api/v3_1_0/APIMethods310.scala | 137 +++++++++------- .../scala/code/api/v5_0_0/APIMethods500.scala | 69 +++++--- .../scala/code/api/v5_1_0/APIMethods510.scala | 150 ++++++++++-------- 4 files changed, 228 insertions(+), 150 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 96134b20a..e394d1a78 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -601,6 +601,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ domain: String, bank_ids: List[String] ) + //This is used for get the value from props `skip_consent_sca_for_consumer_id_pairs` + case class ConsumerIdPair( + grantor_consumer_id: String, + grantee_consumer_id: String + ) case class EmailDomainToEntitlementMapping( domain: String, @@ -4729,6 +4734,23 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ APIUtil.getPropsValue("email_domain_to_space_mappings").map(extractor).getOrElse(Nil) } + val skipConsentScaForConsumerIdPairs: List[ConsumerIdPair] = { + def extractor(str: String) = try { + val consumerIdPair = json.parse(str).extract[List[ConsumerIdPair]] + //The props value can be parsed to JNothing. + if(str.nonEmpty && consumerIdPair == Nil) + throw new RuntimeException("props [skip_consent_sca_for_consumer_id_pairs] parse -> extract to Nil!") + else + consumerIdPair + } catch { + case e: Throwable => // error handling, found wrong props value as early as possible. + this.logger.error(s"props [skip_consent_sca_for_consumer_id_pairs] value is invalid, it should be the class($ConsumerIdPair) json format, current value is $str ." ); + throw e; + } + + APIUtil.getPropsValue("skip_consent_sca_for_consumer_id_pairs").map(extractor).getOrElse(Nil) + } + val emailDomainToEntitlementMappings: List[EmailDomainToEntitlementMapping] = { def extractor(str: String) = try { val emailDomainToEntitlementMappings = json.parse(str).extract[List[EmailDomainToEntitlementMapping]] 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 e23e96eea..ef3b22944 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 @@ -28,7 +28,7 @@ import code.api.v3_0_0.JSONFactory300.createAdapterInfoJson import code.api.v3_1_0.JSONFactory310._ import code.bankconnectors.rest.RestConnector_vMar2019 import code.bankconnectors.{Connector, LocalMappedConnector} -import code.consent.{ConsentRequests, ConsentStatus, Consents} +import code.consent.{ConsentRequests, ConsentStatus, Consents, MappedConsent} import code.consumer.Consumers import code.context.UserAuthContextUpdateProvider import code.entitlement.Entitlement @@ -57,6 +57,7 @@ import net.liftweb.http.provider.HTTPParam import net.liftweb.http.rest.RestHelper import net.liftweb.json._ import net.liftweb.util.Helpers.tryo +import net.liftweb.mapper.By import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} import net.liftweb.util.{Helpers, Mailer, Props, StringHelpers} import org.apache.commons.lang3.{StringUtils, Validate} @@ -3586,66 +3587,82 @@ trait APIMethods310 { _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { i => connectorEmptyResponse(i, callContext) } - 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 should be the $PostConsentEmailJsonV310"} - postConsentEmailJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[PostConsentEmailJsonV310] - } - (status, callContext) <- NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.EMAIL, - postConsentEmailJson.email, - Some("OBP Consent Challenge"), - challengeText, - callContext - ) - } yield Future{status} - case v if v == StrongCustomerAuthentication.SMS.toString => - for { - failMsg <- Future { - s"$InvalidJsonFormat The Json body should be the $PostConsentPhoneJsonV310" - } - postConsentPhoneJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[PostConsentPhoneJsonV310] - } - phoneNumber = postConsentPhoneJson.phone_number - (status, callContext) <- NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.SMS, - phoneNumber, - None, - challengeText, - callContext - ) - } yield Future{status} - case v if v == StrongCustomerAuthentication.IMPLICIT.toString => - for { - (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) - status <- consentImplicitSCA.scaMethod match { - case v if v == StrongCustomerAuthentication.EMAIL => // Send the email - NewStyle.function.sendCustomerNotification ( - StrongCustomerAuthentication.EMAIL, - consentImplicitSCA.recipient, - Some ("OBP Consent Challenge"), - challengeText, - callContext - ) - case v if v == StrongCustomerAuthentication.SMS => - NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.SMS, - consentImplicitSCA.recipient, - None, - challengeText, - callContext - ) - case _ => Future { - "Success" - } - }} yield { - status + //we need to check `skip_consent_sca_for_consumer_id_pairs` props, to see if we really need the SCA flow. + //this is from callContext + grantorConsumerId = callContext.map(_.consumer.toOption.map(_.consumerId.get)).flatten.getOrElse("Unknown") + //this is from json body + granteeConsumerId = consentJson.consumer_id.getOrElse("Unknown") + + shouldSkipConsentScaForConsumerIdPair = APIUtil.skipConsentScaForConsumerIdPairs.contains( + APIUtil.ConsumerIdPair( + grantorConsumerId, + granteeConsumerId + )) + mappedConsent <- if (shouldSkipConsentScaForConsumerIdPair) { + Future{ + MappedConsent.find(By(MappedConsent.mConsentId, createdConsent.consentId)).map(_.mStatus(ConsentStatus.ACCEPTED.toString).saveMe()).head } - case _ =>Future{"Success"} + } else { + val 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 should be the $PostConsentEmailJsonV310"} + postConsentEmailJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[PostConsentEmailJsonV310] + } + (status, callContext) <- NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.EMAIL, + postConsentEmailJson.email, + Some("OBP Consent Challenge"), + challengeText, + callContext + ) + } yield createdConsent + case v if v == StrongCustomerAuthentication.SMS.toString => + for { + failMsg <- Future { + s"$InvalidJsonFormat The Json body should be the $PostConsentPhoneJsonV310" + } + postConsentPhoneJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[PostConsentPhoneJsonV310] + } + phoneNumber = postConsentPhoneJson.phone_number + (status, callContext) <- NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + phoneNumber, + None, + challengeText, + callContext + ) + } yield createdConsent + case v if v == StrongCustomerAuthentication.IMPLICIT.toString => + for { + (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) + status <- consentImplicitSCA.scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL => // Send the email + NewStyle.function.sendCustomerNotification ( + StrongCustomerAuthentication.EMAIL, + consentImplicitSCA.recipient, + Some ("OBP Consent Challenge"), + challengeText, + callContext + ) + case v if v == StrongCustomerAuthentication.SMS => + NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + consentImplicitSCA.recipient, + None, + challengeText, + callContext + ) + case _ => Future { + "Success" + } + }} yield { + createdConsent + } + case _ =>Future{createdConsent}} } } yield { (ConsentJsonV310(createdConsent.consentId, consentJWT, createdConsent.status), HttpCode.`201`(callContext)) 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 5c9894d1e..51ff0b6f7 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 @@ -18,7 +18,7 @@ import code.api.v4_0_0.{JSONFactory400, PostCounterpartyJson400} import code.api.v5_0_0.JSONFactory500.{createPhysicalCardJson, createViewJsonV500, createViewsIdsJsonV500, createViewsJsonV500} import code.api.v5_1_0.{CreateCustomViewJson, PostCounterpartyLimitV510, PostVRPConsentRequestJsonV510} import code.bankconnectors.Connector -import code.consent.{ConsentRequests, Consents} +import code.consent.{ConsentRequests, ConsentStatus, Consents, MappedConsent} import code.consumer.Consumers import code.entitlement.Entitlement import code.metadata.counterparties.MappedCounterparty @@ -40,6 +40,7 @@ import net.liftweb.json import net.liftweb.json.{Extraction, compactRender, prettyRender} import net.liftweb.util.Helpers.tryo import net.liftweb.util.{Helpers, Props, StringHelpers} +import net.liftweb.mapper.By import java.util.UUID import java.util.concurrent.ThreadLocalRandom @@ -1202,34 +1203,54 @@ trait APIMethods500 { _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { i => connectorEmptyResponse(i, callContext) } - challengeText = s"Your consent challenge : ${challengeAnswer}, Application: $applicationText" - _ <- scaMethod match { - case v if v == StrongCustomerAuthentication.EMAIL.toString => // Send the email - sendEmailNotification(callContext, consentRequestJson, challengeText) - case v if v == StrongCustomerAuthentication.SMS.toString => - sendSmsNotification(callContext, consentRequestJson, challengeText) - case v if v == StrongCustomerAuthentication.IMPLICIT.toString => - for { - (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 => - sendSmsNotification(callContext, consentRequestJson.copy(phone_number=Some(consentImplicitSCA.recipient)), challengeText) - case _ => Future { - "Success" - } - }} yield { - status + //we need to check `skip_consent_sca_for_consumer_id_pairs` props, to see if we really need the SCA flow. + //this is from callContext + grantorConsumerId = callContext.map(_.consumer.toOption.map(_.consumerId.get)).flatten.getOrElse("Unknown") + //this is from json body + granteeConsumerId = postConsentBodyCommonJson.consumer_id.getOrElse("Unknown") + + shouldSkipConsentScaForConsumerIdPair = APIUtil.skipConsentScaForConsumerIdPairs.contains( + APIUtil.ConsumerIdPair( + grantorConsumerId, + granteeConsumerId + )) + mappedConsent <- if (shouldSkipConsentScaForConsumerIdPair) { + Future{ + MappedConsent.find(By(MappedConsent.mConsentId, createdConsent.consentId)).map(_.mStatus(ConsentStatus.ACCEPTED.toString).saveMe()).head + } + } else { + val challengeText = s"Your consent challenge : ${challengeAnswer}, Application: $applicationText" + scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL.toString => // Send the email + sendEmailNotification(callContext, consentRequestJson, challengeText) + case v if v == StrongCustomerAuthentication.SMS.toString => + sendSmsNotification(callContext, consentRequestJson, challengeText) + case v if v == StrongCustomerAuthentication.IMPLICIT.toString => + for { + (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 => + sendSmsNotification(callContext, consentRequestJson.copy(phone_number = Some(consentImplicitSCA.recipient)), challengeText) + case _ => Future { + "Success" + } + }} yield { + status + } + case _ => Future { + "Success" } - case _ =>Future{"Success"} + } + Future{createdConsent} } } yield { (ConsentJsonV500( - createdConsent.consentId, + mappedConsent.consentId, consentJWT, - createdConsent.status, - Some(createdConsent.consentRequestId), + mappedConsent.status, + Some(mappedConsent.consentRequestId), if (isVRPConsentRequest) Some(ConsentAccountAccessJson(bankId.value, accountId.value, viewId.value, HelperInfoJson(List(counterpartyId.value)))) else None ), HttpCode.`201`(callContext)) } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index cc86dfa46..f70cfb458 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -2050,77 +2050,95 @@ trait APIMethods510 { _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { i => connectorEmptyResponse(i, callContext) } - 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 should be the $PostConsentEmailJsonV310" - } - postConsentEmailJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[PostConsentEmailJsonV310] - } - (status, callContext) <- NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.EMAIL, - postConsentEmailJson.email, - Some("OBP Consent Challenge"), - challengeText, - callContext - ) - } yield Future { - status - } - case v if v == StrongCustomerAuthentication.SMS.toString => - for { - failMsg <- Future { - s"$InvalidJsonFormat The Json body should be the $PostConsentPhoneJsonV310" - } - postConsentPhoneJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[PostConsentPhoneJsonV310] - } - phoneNumber = postConsentPhoneJson.phone_number - (status, callContext) <- NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.SMS, - phoneNumber, - None, - challengeText, - callContext - ) - } yield Future { - status - } - case v if v == StrongCustomerAuthentication.IMPLICIT.toString => - for { - (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) - status <- consentImplicitSCA.scaMethod match { - case v if v == StrongCustomerAuthentication.EMAIL => // Send the email - NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.EMAIL, - consentImplicitSCA.recipient, - Some("OBP Consent Challenge"), - challengeText, - callContext - ) - case v if v == StrongCustomerAuthentication.SMS => - NewStyle.function.sendCustomerNotification( - StrongCustomerAuthentication.SMS, - consentImplicitSCA.recipient, - None, - challengeText, - callContext - ) - case _ => Future { - "Success" + + //we need to check `skip_consent_sca_for_consumer_id_pairs` props, to see if we really need the SCA flow. + //this is from callContext + grantorConsumerId = callContext.map(_.consumer.toOption.map(_.consumerId.get)).flatten.getOrElse("Unknown") + //this is from json body + granteeConsumerId = consentJson.consumer_id.getOrElse("Unknown") + + shouldSkipConsentScaForConsumerIdPair = APIUtil.skipConsentScaForConsumerIdPairs.contains( + APIUtil.ConsumerIdPair( + grantorConsumerId, + granteeConsumerId + )) + mappedConsent <- if (shouldSkipConsentScaForConsumerIdPair) { + Future{ + MappedConsent.find(By(MappedConsent.mConsentId, createdConsent.consentId)).map(_.mStatus(ConsentStatus.ACCEPTED.toString).saveMe()).head + } + } else { + val 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 should be the $PostConsentEmailJsonV310" } - }} yield { - status + postConsentEmailJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[PostConsentEmailJsonV310] + } + (status, callContext) <- NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.EMAIL, + postConsentEmailJson.email, + Some("OBP Consent Challenge"), + challengeText, + callContext + ) + } yield { + createdConsent + } + case v if v == StrongCustomerAuthentication.SMS.toString => + for { + failMsg <- Future { + s"$InvalidJsonFormat The Json body should be the $PostConsentPhoneJsonV310" + } + postConsentPhoneJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[PostConsentPhoneJsonV310] + } + phoneNumber = postConsentPhoneJson.phone_number + (status, callContext) <- NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + phoneNumber, + None, + challengeText, + callContext + ) + } yield { + createdConsent + } + case v if v == StrongCustomerAuthentication.IMPLICIT.toString => + for { + (consentImplicitSCA, callContext) <- NewStyle.function.getConsentImplicitSCA(user, callContext) + status <- consentImplicitSCA.scaMethod match { + case v if v == StrongCustomerAuthentication.EMAIL => // Send the email + NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.EMAIL, + consentImplicitSCA.recipient, + Some("OBP Consent Challenge"), + challengeText, + callContext + ) + case v if v == StrongCustomerAuthentication.SMS => + NewStyle.function.sendCustomerNotification( + StrongCustomerAuthentication.SMS, + consentImplicitSCA.recipient, + None, + challengeText, + callContext + ) + case _ => Future { + "Success" + } + }} yield { + createdConsent + } + case _ => Future { + createdConsent } - case _ => Future { - "Success" } } } yield { - (ConsentJsonV310(createdConsent.consentId, consentJWT, createdConsent.status), HttpCode.`201`(callContext)) + (ConsentJsonV310(mappedConsent.consentId, consentJWT, mappedConsent.status), HttpCode.`201`(callContext)) } } }