diff --git a/obp-api/src/main/scala/code/transactionChallenge/ChallengeProvider.scala b/obp-api/src/main/scala/code/transactionChallenge/ChallengeProvider.scala index 53af3e711..6b3191452 100644 --- a/obp-api/src/main/scala/code/transactionChallenge/ChallengeProvider.scala +++ b/obp-api/src/main/scala/code/transactionChallenge/ChallengeProvider.scala @@ -20,6 +20,10 @@ trait ChallengeProvider { basketId: Option[String], // Note: basketId, consentId and transactionRequestId are exclusive here. authenticationMethodId: Option[String], challengeType: String, + // PSD2 Dynamic Linking fields + challengePurpose: Option[String] = None, // Human-readable description shown to user + challengeContextHash: Option[String] = None, // SHA-256 hash of critical transaction fields + challengeContextStructure: Option[String] = None // Comma-separated list of field names in hash ): Box[ChallengeTrait] def getChallenge(challengeId: String): Box[ChallengeTrait] diff --git a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala index 654af20e9..b9fb7e154 100644 --- a/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala +++ b/obp-api/src/main/scala/code/transactionChallenge/MappedChallengeProvider.scala @@ -28,8 +28,12 @@ object MappedChallengeProvider extends ChallengeProvider { consentId: Option[String], // Note: consentId and transactionRequestId and basketId are exclusive here. basketId: Option[String], // Note: consentId and transactionRequestId and basketId are exclusive here. authenticationMethodId: Option[String], - challengeType: String, - ): Box[ChallengeTrait] = + challengeType: String, + // PSD2 Dynamic Linking fields + challengePurpose: Option[String] = None, + challengeContextHash: Option[String] = None, + challengeContextStructure: Option[String] = None + ): Box[ChallengeTrait] = tryo ( MappedExpectedChallengeAnswer .create @@ -44,6 +48,10 @@ object MappedChallengeProvider extends ChallengeProvider { .ConsentId(consentId.getOrElse("")) .BasketId(basketId.getOrElse("")) .AuthenticationMethodId(expectedUserId) + // PSD2 Dynamic Linking + .ChallengePurpose(challengePurpose.getOrElse("")) + .ChallengeContextHash(challengeContextHash.getOrElse("")) + .ChallengeContextStructure(challengeContextStructure.getOrElse("")) .saveMe() ) diff --git a/obp-api/src/main/scala/code/transactionChallenge/MappedExpectedChallengeAnswer.scala b/obp-api/src/main/scala/code/transactionChallenge/MappedExpectedChallengeAnswer.scala index 38bbf97db..0e27f284a 100644 --- a/obp-api/src/main/scala/code/transactionChallenge/MappedExpectedChallengeAnswer.scala +++ b/obp-api/src/main/scala/code/transactionChallenge/MappedExpectedChallengeAnswer.scala @@ -29,6 +29,11 @@ class MappedExpectedChallengeAnswer extends ChallengeTrait with LongKeyedMapper[ override def defaultValue = 0 } + // PSD2 Dynamic Linking fields + object ChallengePurpose extends MappedString(this, 2000) + object ChallengeContextHash extends MappedString(this, 64) + object ChallengeContextStructure extends MappedString(this, 500) + override def challengeId: String = ChallengeId.get override def challengeType: String = ChallengeType.get override def transactionRequestId: String = TransactionRequestId.get @@ -42,6 +47,11 @@ class MappedExpectedChallengeAnswer extends ChallengeTrait with LongKeyedMapper[ override def scaStatus: Option[SCAStatus] = Option(StrongCustomerAuthenticationStatus.withName(ScaStatus.get)) override def authenticationMethodId: Option[String] = Option(AuthenticationMethodId.get) override def attemptCounter: Int = AttemptCounter.get + + // PSD2 Dynamic Linking + override def challengePurpose: Option[String] = Option(ChallengePurpose.get).filter(_.nonEmpty) + override def challengeContextHash: Option[String] = Option(ChallengeContextHash.get).filter(_.nonEmpty) + override def challengeContextStructure: Option[String] = Option(ChallengeContextStructure.get).filter(_.nonEmpty) } object MappedExpectedChallengeAnswer extends MappedExpectedChallengeAnswer with LongKeyedMetaMapper[MappedExpectedChallengeAnswer] { 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 c89720376..1b7a69b88 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 @@ -647,8 +647,16 @@ trait ChallengeTrait { def scaMethod: Option[SCA] def scaStatus: Option[SCAStatus] def authenticationMethodId: Option[String] - + def attemptCounter: Int + + // PSD2 Dynamic Linking support - these fields ensure the authentication is linked to the transaction details + // challenge_purpose: Human-readable description of what is being authorized (shown to user in SMS/email) + def challengePurpose: Option[String] + // challenge_context_hash: SHA-256 hash of critical transaction fields for tamper detection + def challengeContextHash: Option[String] + // challenge_context_structure: Comma-separated list of field names included in the hash + def challengeContextStructure: Option[String] }