Merge pull request #2296 from hongwei1/develop

feature/use Twilio as OBP OTP library
This commit is contained in:
Simon Redfern 2023-10-19 11:03:44 +02:00 committed by GitHub
commit 66ff69184c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 173 additions and 143 deletions

View File

@ -887,9 +887,10 @@ database_messages_scheduler_interval=3600
# ---------------------------------------------------------
# -- SCA (Strong Customer Authentication) -------
# For now, OBP-API use `Twilio` server as the SMS provider. Please check `Twilio` website, and get the api key and value there.
# sca_phone_api_key = oXAjqAJ6rvCunpzN
# sca_phone_api_secret =oXAjqAJ6rvCunpzN123sdf
# For now, OBP-API use `Twilio` server as the SMS provider. Please check `Twilio` website, and get the api key, value and phone number there.
# sca_phone_api_key = ACobpb72ab850501b5obp8dobp9dobp111
# sca_phone_api_secret =7afobpdacobpd427obpff87a22obp222
# sca_phone_api_id =MGcobp8575119887f10b62a2461obpb333
#
# -- PSD2 Certificates --------------------------

View File

@ -70,7 +70,6 @@ import code.customer.{MappedCustomer, MappedCustomerMessage}
import code.customeraccountlinks.CustomerAccountLink
import code.customeraddress.MappedCustomerAddress
import code.customerattribute.MappedCustomerAttribute
import code.database.authorisation.Authorisation
import code.directdebit.DirectDebit
import code.dynamicEntity.DynamicEntity
import code.dynamicMessageDoc.DynamicMessageDoc
@ -1057,7 +1056,6 @@ object ToSchemify {
MethodRouting,
EndpointMapping,
WebUiProps,
Authorisation,
DynamicEntity,
DynamicData,
DynamicEndpoint,

View File

@ -7,7 +7,6 @@ import code.api.util.APIUtil._
import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil}
import code.bankconnectors.Connector
import code.consent.ConsentTrait
import code.database.authorisation.Authorisation
import code.model.ModeratedTransaction
import com.openbankproject.commons.model.enums.AccountRoutingScheme
import com.openbankproject.commons.model.{BankAccount, TransactionRequest, User, _}

View File

@ -493,6 +493,11 @@ trait APIMethods220 {
bank <- tryo{ json.extract[BankJSONV220] } ?~! ErrorMessages.InvalidJsonFormat
_ <- Helper.booleanToBox(
bank.id.length > 5,s"$InvalidJsonFormat Min length of BANK_ID should be 5 characters.")
checkShortStringValue = APIUtil.checkShortString(bank.id)
_ <- Helper.booleanToBox(checkShortStringValue == SILENCE_IS_GOLDEN, s"$checkShortStringValue.")
_ <- Helper.booleanToBox(
!`checkIfContains::::` (bank.id), s"$InvalidJsonFormat BANK_ID can not contain `::::` characters")
u <- cc.user ?~!ErrorMessages.UserNotLoggedIn

View File

@ -72,7 +72,7 @@ import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{app
import code.usercustomerlinks.UserCustomerLink
import code.userlocks.UserLocksProvider
import code.users.Users
import code.util.Helper.{ObpS, booleanToFuture}
import code.util.Helper.{ObpS, SILENCE_IS_GOLDEN, booleanToFuture}
import code.util.{Helper, JsonSchemaUtil}
import code.validation.JsonValidation
import code.views.Views
@ -4039,6 +4039,12 @@ trait APIMethods400 {
cc.callContext.map(_.consumer.isDefined == true).isDefined
}
checkShortStringValue = APIUtil.checkShortString(bank.id)
_ <- Helper.booleanToFuture(failMsg = s"$checkShortStringValue.", cc=cc.callContext) {
checkShortStringValue==SILENCE_IS_GOLDEN
}
_ <- Helper.booleanToFuture(failMsg = s"$InvalidJsonFormat Min length of BANK_ID should be greater than 3 characters.", cc=cc.callContext) {
bank.id.length > 3
}

View File

@ -26,7 +26,7 @@ import code.model._
import code.model.dataAccess.BankAccountCreation
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _}
import code.util.Helper
import code.util.Helper.booleanToFuture
import code.util.Helper.{SILENCE_IS_GOLDEN, booleanToFuture}
import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
@ -183,6 +183,13 @@ trait APIMethods500 {
postJson <- NewStyle.function.tryons(failMsg, 400, cc.callContext) {
json.extract[PostBankJson500]
}
//if postJson.id is empty, just return SILENCE_IS_GOLDEN, and will pass the guard.
checkShortStringValue = APIUtil.checkShortString(postJson.id.getOrElse(SILENCE_IS_GOLDEN))
_ <- Helper.booleanToFuture(failMsg = s"$checkShortStringValue.", cc = cc.callContext) {
checkShortStringValue == SILENCE_IS_GOLDEN
}
_ <- Helper.booleanToFuture(failMsg = ErrorMessages.InvalidConsumerCredentials, cc=cc.callContext) {
cc.callContext.map(_.consumer.isDefined == true).isDefined
}

View File

@ -34,7 +34,6 @@ import code.customer._
import code.customeraccountlinks.CustomerAccountLinkTrait
import code.customeraddress.CustomerAddressX
import code.customerattribute.CustomerAttributeX
import code.database.authorisation.Authorisations
import code.directdebit.DirectDebits
import code.endpointTag.{EndpointTag, EndpointTagT}
import code.fx.fx.TTL
@ -334,14 +333,16 @@ object LocalMappedConnector extends Connector with MdcLoggable {
challengeId.toList
}
Authorisations.authorisationProvider.vend.createAuthorization(
transactionRequestId.getOrElse(""),
consentId.getOrElse(""),
AuthenticationType.SMS_OTP.toString,
"",
ScaStatus.received.toString,
"12345" // TODO Implement SMS sending
)
//We use obp MappedExpectedChallengeAnswer instead of Authorisations now.
// please also check Challenges.ChallengeProvider.vend.saveChallenge
// Authorisations.authorisationProvider.vend.createAuthorization(
// transactionRequestId.getOrElse(""),
// consentId.getOrElse(""),
// AuthenticationType.SMS_OTP.toString,
// "",
// ScaStatus.received.toString,
// "12345" // TODO Implement SMS sending
// )
(Full(challenges.flatten), callContext)
}
@ -396,12 +397,25 @@ object LocalMappedConnector extends Connector with MdcLoggable {
for {
smsProviderApiKey <- APIUtil.getPropsValue("sca_phone_api_key") ?~! s"$MissingPropsValueAtThisInstance sca_phone_api_key"
smsProviderApiSecret <- APIUtil.getPropsValue("sca_phone_api_secret") ?~! s"$MissingPropsValueAtThisInstance sca_phone_api_secret"
client = Twilio.init(smsProviderApiKey, smsProviderApiSecret)
scaPhoneApiId <- APIUtil.getPropsValue("sca_phone_api_id") ?~! s"$MissingPropsValueAtThisInstance sca_phone_api_id"
client = Twilio.init(smsProviderApiKey, smsProviderApiSecret) //TODO, move this to other place, we only need to init it once.
phoneNumber = tuple._2
messageText = s"Your consent challenge : ${challengeAnswer}";
message: Box[Message] = tryo(Message.creator(new PhoneNumber(phoneNumber), new PhoneNumber(phoneNumber), messageText).create())
failMsg = s"$SmsServerNotResponding: $phoneNumber. Or Please to use EMAIL first. ${message.map(_.getErrorMessage).getOrElse("")}"
_ <- Helper.booleanToBox(message.forall(_.getErrorMessage.isEmpty), failMsg)
message: Message <- tryo {Message.creator(
new PhoneNumber(phoneNumber),
scaPhoneApiId,
messageText).create()}
isSuccess <- tryo {message.getErrorMessage == null}
_ = logger.debug(s"createChallengeInternal.send message to $phoneNumber, detail is $message")
failMsg = if (message.getErrorMessage ==null)
s"$SmsServerNotResponding: $phoneNumber. Or Please to use EMAIL first. ${message.getErrorMessage}"
else
s"$SmsServerNotResponding: $phoneNumber. Or Please to use EMAIL first."
_ <- Helper.booleanToBox(isSuccess, failMsg)
} yield true
}
val errorMessage = sendingResult.filter(_.isInstanceOf[Failure]).map(_.asInstanceOf[Failure].msg)

View File

@ -1,25 +1,25 @@
package code.database.authorisation
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object Authorisations extends SimpleInjector {
val authorisationProvider = new Inject(buildOne _) {}
def buildOne: AuthorisationProvider = MappedAuthorisationProvider
}
trait AuthorisationProvider {
def getAuthorizationByAuthorizationId(authorizationId: String): Box[Authorisation]
def getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String): Box[Authorisation]
def getAuthorizationByPaymentId(paymentId: String): Box[List[Authorisation]]
def getAuthorizationByConsentId(consentId: String): Box[List[Authorisation]]
def createAuthorization(paymentId: String,
consentId: String,
authenticationType: String,
authenticationMethodId: String,
scaStatus: String,
challengeData: String
): Box[Authorisation]
def checkAnswer(paymentId: String, authorizationId: String, challengeData: String): Box[Authorisation]
}
//package code.database.authorisation
//
//import net.liftweb.common.Box
//import net.liftweb.util.SimpleInjector
//
//
//object Authorisations extends SimpleInjector {
// val authorisationProvider = new Inject(buildOne _) {}
// def buildOne: AuthorisationProvider = MappedAuthorisationProvider
//}
//
//trait AuthorisationProvider {
// def getAuthorizationByAuthorizationId(authorizationId: String): Box[Authorisation]
// def getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String): Box[Authorisation]
// def getAuthorizationByPaymentId(paymentId: String): Box[List[Authorisation]]
// def getAuthorizationByConsentId(consentId: String): Box[List[Authorisation]]
// def createAuthorization(paymentId: String,
// consentId: String,
// authenticationType: String,
// authenticationMethodId: String,
// scaStatus: String,
// challengeData: String
// ): Box[Authorisation]
// def checkAnswer(paymentId: String, authorizationId: String, challengeData: String): Box[Authorisation]
//}

View File

@ -1,97 +1,97 @@
package code.database.authorisation
import code.api.BerlinGroup.ScaStatus
import code.api.util.ErrorMessages
import code.consent.{ConsentStatus, MappedConsent}
import code.util.MappedUUID
import net.liftweb.common.{Box, Empty, Failure, Full}
import net.liftweb.mapper.{BaseIndex, By, CreatedUpdated, IdPK, LongKeyedMapper, LongKeyedMetaMapper, MappedString, UniqueIndex}
import net.liftweb.util.Helpers.tryo
class Authorisation extends LongKeyedMapper[Authorisation] with IdPK with CreatedUpdated {
def getSingleton = Authorisation
// Enum: received, psuIdentified, psuAuthenticated, scaMethodSelected, started, finalised, failed, exempted
object ScaStatus extends MappedString(this, 20)
object AuthorisationId extends MappedUUID(this)
object PaymentId extends MappedUUID(this)
object ConsentId extends MappedUUID(this)
// Enum: SMS_OTP, CHIP_OTP, PHOTO_OTP, PUSH_OTP
object AuthenticationType extends MappedString(this, 10)
object AuthenticationMethodId extends MappedString(this, 35)
object ChallengeData extends MappedString(this, 1024)
def scaStatus: String = ScaStatus.get
def authorisationId: String = AuthorisationId.get
def paymentId: String = PaymentId.get
def consentId: String = ConsentId.get
def authenticationType: String = AuthenticationType.get
def authenticationMethodId: String = AuthenticationMethodId.get
def challengeData: String = ChallengeData.get
}
object Authorisation extends Authorisation with LongKeyedMetaMapper[Authorisation] {
override def dbIndexes: List[BaseIndex[Authorisation]] = UniqueIndex(AuthorisationId) :: super.dbIndexes
}
object MappedAuthorisationProvider extends AuthorisationProvider {
override def getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String): Box[Authorisation] = {
val result: Box[Authorisation] = Authorisation.find(
By(Authorisation.PaymentId, paymentId),
By(Authorisation.AuthorisationId, authorizationId)
)
result
}
override def getAuthorizationByAuthorizationId(authorizationId: String): Box[Authorisation] = {
val result: Box[Authorisation] = Authorisation.find(
By(Authorisation.AuthorisationId, authorizationId)
)
result
}
override def getAuthorizationByPaymentId(paymentId: String): Box[List[Authorisation]] = {
tryo(Authorisation.findAll(By(Authorisation.PaymentId, paymentId)))
}
override def getAuthorizationByConsentId(consentId: String): Box[List[Authorisation]] = {
tryo(Authorisation.findAll(By(Authorisation.ConsentId, consentId)))
}
def createAuthorization(paymentId: String,
consentId: String,
authenticationType: String,
authenticationMethodId: String,
scaStatus: String,
challengeData: String
): Box[Authorisation] = tryo {
Authorisation
.create
.PaymentId(paymentId)
.ConsentId(consentId)
.AuthenticationType(authenticationType)
.AuthenticationMethodId(authenticationMethodId)
.ChallengeData(challengeData)
.ScaStatus(scaStatus).saveMe()
}
def checkAnswer(paymentId: String, authorizationId: String, challengeData: String): Box[Authorisation] =
getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String) match {
case Full(authorisation) =>
authorisation.scaStatus match {
case value if value == ScaStatus.received.toString =>
val status = if (authorisation.challengeData == challengeData) ScaStatus.finalised.toString else ScaStatus.failed.toString
tryo(authorisation.ScaStatus(status).saveMe())
case _ => //make sure, only `received` can be processed, all others are invalid .
Failure(s"${ErrorMessages.InvalidAuthorisationStatus}.It should be `received`, but now it is `${authorisation.scaStatus}`")
}
case Empty =>
Empty ?~! s"${ErrorMessages.AuthorisationNotFound} Current PAYMENT_ID($paymentId) and AUTHORISATION_ID ($authorizationId),"
case Failure(msg, _, _) =>
Failure(msg)
case _ =>
Failure(ErrorMessages.UnknownError)
}
}
//package code.database.authorisation
//
//import code.api.BerlinGroup.ScaStatus
//import code.api.util.ErrorMessages
//import code.consent.{ConsentStatus, MappedConsent}
//import code.util.MappedUUID
//import net.liftweb.common.{Box, Empty, Failure, Full}
//import net.liftweb.mapper.{BaseIndex, By, CreatedUpdated, IdPK, LongKeyedMapper, LongKeyedMetaMapper, MappedString, UniqueIndex}
//import net.liftweb.util.Helpers.tryo
//
//
//class Authorisation extends LongKeyedMapper[Authorisation] with IdPK with CreatedUpdated {
// def getSingleton = Authorisation
// // Enum: received, psuIdentified, psuAuthenticated, scaMethodSelected, started, finalised, failed, exempted
// object ScaStatus extends MappedString(this, 20)
// object AuthorisationId extends MappedUUID(this)
// object PaymentId extends MappedUUID(this)
// object ConsentId extends MappedUUID(this)
// // Enum: SMS_OTP, CHIP_OTP, PHOTO_OTP, PUSH_OTP
// object AuthenticationType extends MappedString(this, 10)
// object AuthenticationMethodId extends MappedString(this, 35)
// object ChallengeData extends MappedString(this, 1024)
//
// def scaStatus: String = ScaStatus.get
// def authorisationId: String = AuthorisationId.get
// def paymentId: String = PaymentId.get
// def consentId: String = ConsentId.get
// def authenticationType: String = AuthenticationType.get
// def authenticationMethodId: String = AuthenticationMethodId.get
// def challengeData: String = ChallengeData.get
//}
//
//object Authorisation extends Authorisation with LongKeyedMetaMapper[Authorisation] {
// override def dbIndexes: List[BaseIndex[Authorisation]] = UniqueIndex(AuthorisationId) :: super.dbIndexes
//}
//
//object MappedAuthorisationProvider extends AuthorisationProvider {
// override def getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String): Box[Authorisation] = {
// val result: Box[Authorisation] = Authorisation.find(
// By(Authorisation.PaymentId, paymentId),
// By(Authorisation.AuthorisationId, authorizationId)
// )
// result
// }
// override def getAuthorizationByAuthorizationId(authorizationId: String): Box[Authorisation] = {
// val result: Box[Authorisation] = Authorisation.find(
// By(Authorisation.AuthorisationId, authorizationId)
// )
// result
// }
//
// override def getAuthorizationByPaymentId(paymentId: String): Box[List[Authorisation]] = {
// tryo(Authorisation.findAll(By(Authorisation.PaymentId, paymentId)))
// }
// override def getAuthorizationByConsentId(consentId: String): Box[List[Authorisation]] = {
// tryo(Authorisation.findAll(By(Authorisation.ConsentId, consentId)))
// }
//
// def createAuthorization(paymentId: String,
// consentId: String,
// authenticationType: String,
// authenticationMethodId: String,
// scaStatus: String,
// challengeData: String
// ): Box[Authorisation] = tryo {
// Authorisation
// .create
// .PaymentId(paymentId)
// .ConsentId(consentId)
// .AuthenticationType(authenticationType)
// .AuthenticationMethodId(authenticationMethodId)
// .ChallengeData(challengeData)
// .ScaStatus(scaStatus).saveMe()
// }
//
// def checkAnswer(paymentId: String, authorizationId: String, challengeData: String): Box[Authorisation] =
// getAuthorizationByAuthorizationId(paymentId: String, authorizationId: String) match {
// case Full(authorisation) =>
// authorisation.scaStatus match {
// case value if value == ScaStatus.received.toString =>
// val status = if (authorisation.challengeData == challengeData) ScaStatus.finalised.toString else ScaStatus.failed.toString
// tryo(authorisation.ScaStatus(status).saveMe())
// case _ => //make sure, only `received` can be processed, all others are invalid .
// Failure(s"${ErrorMessages.InvalidAuthorisationStatus}.It should be `received`, but now it is `${authorisation.scaStatus}`")
// }
// case Empty =>
// Empty ?~! s"${ErrorMessages.AuthorisationNotFound} Current PAYMENT_ID($paymentId) and AUTHORISATION_ID ($authorizationId),"
// case Failure(msg, _, _) =>
// Failure(msg)
// case _ =>
// Failure(ErrorMessages.UnknownError)
// }
//}
//
//
//
//