diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index da4f06a894..f13752e987 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -1160,12 +1160,18 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER ## Berlin Group Create Consent Frequency per Day Upper Limit #berlin_group_frequency_per_day_upper_limit = 4 +## Berlin Group Create Consent ASPSP-SCA-Approach response header value +#berlin_group_aspsp_sca_approach = redirect + # Support multiple brands on one instance. Note this needs checking on a clustered environment #brands_enabled=false # Support removing the app type checkbox during consumer registration #consumer_registration.display_app_type=true +# Default logo URL during of consumer +#consumer_default_logo_url= + # if set this props, we can automatically grant the Entitlements required to use all the Dynamic Endpoint roles belonging # to the bank_ids (Spaces) the User has access to via their validated email domain. Entitlements are generated /refreshed # both following manual login and Direct Login token generation (POST). diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 287327ac84..2bea4e96c2 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -1,6 +1,5 @@ package code.api.builder.AccountInformationServiceAISApi -import java.text.SimpleDateFormat import code.api.APIFailureNewStyle import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID} import code.api.berlin.group.ConstantsBG @@ -13,7 +12,7 @@ import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ import code.api.util.NewStyle.HttpCode import code.api.util.newstyle.BalanceNewStyle -import code.api.util.{APIUtil, ApiTag, CallContext, Consent, ExampleValue, NewStyle} +import code.api.util._ import code.bankconnectors.Connector import code.consent.{ConsentStatus, Consents} import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} @@ -24,16 +23,14 @@ import code.views.Views import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model._ -import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthentication, StrongCustomerAuthenticationStatus, SuppliedAnswerType} -import com.openbankproject.commons.util.ApiVersion +import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} +import net.liftweb import net.liftweb.common.{Empty, Full} import net.liftweb.http.js.JE.JsRaw import net.liftweb.http.rest.RestHelper -import net.liftweb import net.liftweb.json import net.liftweb.json._ -import scala.collection.immutable.Nil import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future @@ -160,11 +157,28 @@ recurringIndicator: consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) { json.extract[PostConsentJson] } - _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessIsEmpty, cc=callContext) { - consentJson.access.accounts.isDefined || - consentJson.access.balances.isDefined || - consentJson.access.transactions.isDefined + + _ <- if (consentJson.access.availableAccounts.isDefined) { + for { + _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessAvailableAccounts, cc = callContext) { + consentJson.access.availableAccounts.contains("allAccounts") + } + _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessRecurringIndicator, cc = callContext) { + !consentJson.recurringIndicator + } + _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessFrequencyPerDay, cc = callContext) { + consentJson.frequencyPerDay == 1 + } + } yield Full(()) + } else { + Helper.booleanToFuture( + failMsg = BerlinGroupConsentAccessIsEmpty, cc = callContext) { + consentJson.access.accounts.isDefined || + consentJson.access.balances.isDefined || + consentJson.access.transactions.isDefined + } } + upperLimit = APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4) _ <- Helper.booleanToFuture(failMsg = FrequencyPerDayError, cc=callContext) { consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit @@ -246,11 +260,16 @@ recurringIndicator: case "consents" :: consentId :: Nil JsonDelete _ => { cc => for { - (Full(user), callContext) <- authenticatedAccess(cc) + (_, callContext) <- applicationAccess(cc) _ <- passesPsd2Aisp(callContext) - _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { unboxFullOrFail(_, callContext, ConsentNotFound, 403) } + consumerIdFromConsent = consent.mConsumerId.get + consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") + _ <- Helper.booleanToFuture(failMsg = s"$ConsentNotFound $consumerIdFromConsent != $consumerIdFromCurrentCall", failCode = 403, cc = cc.callContext) { + consumerIdFromConsent == consumerIdFromCurrentCall + } _ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map { i => connectorEmptyResponse(i, callContext) } diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/BgSpecValidation.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/BgSpecValidation.scala index e480710f7b..082c2fcd54 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/BgSpecValidation.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/BgSpecValidation.scala @@ -34,7 +34,7 @@ object BgSpecValidation { if (date.isBefore(today)) { Left(s"$InvalidDateFormat The `validUntil` date ($dateStr) cannot be in the past!") - } else if (date.isAfter(MaxValidDays)) { + } else if (date.isEqual(MaxValidDays) || date.isAfter(MaxValidDays)) { Left(s"$InvalidDateFormat The `validUntil` date ($dateStr) exceeds the maximum allowed period of 180 days (until $MaxValidDays).") } else { Right(date) // Valid date diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala index 279925c985..35eaab593a 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala @@ -47,7 +47,7 @@ object OBP_BERLIN_GROUP_1_3_Alias extends OBPRestHelper with MdcLoggable with Sc override val allResourceDocs: ArrayBuffer[ResourceDoc] = if(berlinGroupV13AliasPath.nonEmpty){ OBP_BERLIN_GROUP_1_3.allResourceDocs.map(resourceDoc => resourceDoc.copy( - implementedInApiVersion = apiVersion, + implementedInApiVersion = apiVersion.copy(apiStandard = resourceDoc.implementedInApiVersion.apiStandard), )) } else ArrayBuffer.empty[ResourceDoc] diff --git a/obp-api/src/main/scala/code/api/constant/constant.scala b/obp-api/src/main/scala/code/api/constant/constant.scala index 976a6eb206..da60249f84 100644 --- a/obp-api/src/main/scala/code/api/constant/constant.scala +++ b/obp-api/src/main/scala/code/api/constant/constant.scala @@ -175,6 +175,7 @@ object RequestHeader { final lazy val `If-Modified-Since` = "If-Modified-Since" } object ResponseHeader { + final lazy val `ASPSP-SCA-Approach` = "ASPSP-SCA-Approach" // Berlin Group final lazy val `Correlation-Id` = "Correlation-Id" final lazy val `WWW-Authenticate` = "WWW-Authenticate" final lazy val ETag = "ETag" 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 572472e2d6..8f2ac4bd60 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -449,8 +449,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ private def getHeadersNewStyle(cc: Option[CallContextLight]) = { CustomResponseHeaders( - getGatewayLoginHeader(cc).list ::: - getRateLimitHeadersNewStyle(cc).list ::: + getGatewayLoginHeader(cc).list ::: + getRequestHeadersBerlinGroup(cc).list ::: + getRateLimitHeadersNewStyle(cc).list ::: getPaginationHeadersNewStyle(cc).list ::: getRequestHeadersToMirror(cc).list ::: getRequestHeadersToEcho(cc).list @@ -556,6 +557,18 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ CustomResponseHeaders(Nil) } } + + def getRequestHeadersBerlinGroup(callContext: Option[CallContextLight]): CustomResponseHeaders = { + val aspspScaApproach = getPropsValue("berlin_group_aspsp_sca_approach", defaultValue = "redirect") + callContext match { + case Some(cc) if cc.url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix) && cc.url.endsWith("/consents") => + CustomResponseHeaders(List( + (ResponseHeader.`ASPSP-SCA-Approach`, aspspScaApproach) + )) + case _ => + CustomResponseHeaders(Nil) + } + } /** * * @param jwt is a JWT value extracted from GatewayLogin Authorization Header. diff --git a/obp-api/src/main/scala/code/api/util/BerlinGroupError.scala b/obp-api/src/main/scala/code/api/util/BerlinGroupError.scala index 738ebd758e..196375a37f 100644 --- a/obp-api/src/main/scala/code/api/util/BerlinGroupError.scala +++ b/obp-api/src/main/scala/code/api/util/BerlinGroupError.scala @@ -90,6 +90,9 @@ object BerlinGroupError { case "400" if message.contains("OBP-20252") => "FORMAT_ERROR" case "400" if message.contains("OBP-20251") => "FORMAT_ERROR" case "400" if message.contains("OBP-20088") => "FORMAT_ERROR" + case "400" if message.contains("OBP-20089") => "FORMAT_ERROR" + case "400" if message.contains("OBP-20090") => "FORMAT_ERROR" + case "400" if message.contains("OBP-20091") => "FORMAT_ERROR" case "429" if message.contains("OBP-10018") => "ACCESS_EXCEEDED" case _ => code diff --git a/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala b/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala index 6163107451..73338e414d 100644 --- a/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala +++ b/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala @@ -301,7 +301,8 @@ object BerlinGroupSigning extends MdcLoggable { developerEmail = extractedEmail, redirectURL = None, createdByUserId = None, - certificate = None + certificate = None, + logoUrl = APIUtil.getPropsValue("consumer_default_logo_url") ) // Set or update certificate diff --git a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala index 295d96b330..2b7b9eb5f1 100644 --- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala +++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala @@ -1,5 +1,6 @@ package code.api.util +import code.accountholders.AccountHolders import code.api.berlin.group.ConstantsBG import java.text.SimpleDateFormat @@ -20,6 +21,7 @@ import code.consumer.Consumers import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} import code.entitlement.Entitlement import code.model.Consumer +import code.model.dataAccess.BankAccountRouting import code.scheduler.ConsentScheduler.logger import code.users.Users import code.util.Helper.MdcLoggable @@ -873,6 +875,66 @@ object Consent extends MdcLoggable { } } } + def updateViewsOfBerlinGroupConsentJWT(user: User, + consent: MappedConsent, + callContext: Option[CallContext]): Future[Box[MappedConsent]] = { + implicit val dateFormats = CustomJsonFormats.formats + val payloadToUpdate: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken) // Payload as JSON string + .map(net.liftweb.json.parse(_).extract[ConsentJWT]) // Extract case class + + val availableAccountsUserIbans: List[String] = payloadToUpdate match { + case Full(consentJwt) => + val availableAccountsUserIbans: List[String] = + if (consentJwt.access.map(_.availableAccounts.contains("allAccounts")).isDefined) { + // Get all accounts held by the current user + val userAccounts: List[BankIdAccountId] = + AccountHolders.accountHolders.vend.getAccountsHeldByUser(user, Some(null)).toList + userAccounts.flatMap { acc => + BankAccountRouting.find( + By(BankAccountRouting.BankId, acc.bankId.value), + By(BankAccountRouting.AccountId, acc.accountId.value), + By(BankAccountRouting.AccountRoutingScheme, "IBAN") + ).map(_.AccountRoutingAddress.get) + } + } else { + val emptyList: List[String] = Nil + emptyList + } + availableAccountsUserIbans + case _ => + val emptyList: List[String] = Nil + emptyList + } + + + // 1. Add access + val availableAccounts: List[Future[ConsentView]] = availableAccountsUserIbans.distinct map { iban => + Connector.connector.vend.getBankAccountByIban(iban, callContext) map { bankAccount => + logger.debug(s"createBerlinGroupConsentJWT.accounts.bankAccount: $bankAccount") + val error = s"${InvalidConnectorResponse} IBAN: ${iban} ${handleBox(bankAccount._1)}" + ConsentView( + bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""), + account_id = bankAccount._1.map(_.accountId.value).openOrThrowException(error), + view_id = Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, + None + ) + } + } + + Future.sequence(availableAccounts) map { views => + if(views.isEmpty) { + Empty + } else { + val updatedPayload = payloadToUpdate.map(i => + i.copy(views = views) // Update the field "views" + ) + val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload)) + val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson) + val jwt = CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret) + Consents.consentProvider.vend.setJsonWebToken(consent.consentId, jwt) + } + } + } def updateUserIdOfBerlinGroupConsentJWT(createdByUserId: String, consent: MappedConsent, 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 937764aa26..27c61a20f0 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -244,7 +244,10 @@ object ErrorMessages { s"OBP-20087: The current source view.can_revoke_access_to_custom_views is false." val BerlinGroupConsentAccessIsEmpty = s"OBP-20088: An access must be requested." - + val BerlinGroupConsentAccessRecurringIndicator = s"OBP-20089: Recurring indicator must be false when availableAccounts is used." + val BerlinGroupConsentAccessFrequencyPerDay = s"OBP-20090: Frequency per day must be 1 when availableAccounts is used." + val BerlinGroupConsentAccessAvailableAccounts = s"OBP-20091: availableAccounts must be exactly 'allAccounts'." + val UserNotSuperAdminOrMissRole = "OBP-20101: Current User is not super admin or is missing entitlements:" val CannotGetOrCreateUser = "OBP-20102: Cannot get or create user." val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider." diff --git a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala index ac927d726a..dc4098f920 100644 --- a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala +++ b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala @@ -71,7 +71,9 @@ trait ConsumersProvider { developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], - certificate: Option[String] = None): Box[Consumer] + certificate: Option[String] = None, + logoUrl: Option[String] = None + ): Box[Consumer] def populateMissingUUIDs(): Boolean } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/model/OAuth.scala b/obp-api/src/main/scala/code/model/OAuth.scala index 3e8645befc..bf23e57199 100644 --- a/obp-api/src/main/scala/code/model/OAuth.scala +++ b/obp-api/src/main/scala/code/model/OAuth.scala @@ -389,7 +389,9 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable { developerEmail: Option[String], redirectURL: Option[String], createdByUserId: Option[String], - certificate: Option[String]): Box[Consumer] = { + certificate: Option[String], + logoUrl: Option[String], + ): Box[Consumer] = { val consumer: Box[Consumer] = // 1st try to find via UUID issued by OBP-API back end @@ -473,6 +475,10 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable { case Some(v) => c.clientCertificate(v) case None => } + logoUrl match { + case Some(v) => c.logoUrl(v) + case None => + } consumerId match { case Some(v) => c.consumerId(v) case None => diff --git a/obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala b/obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala index 00722bba43..e55a343c23 100644 --- a/obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala +++ b/obp-api/src/main/scala/code/snippet/BerlinGroupConsent.scala @@ -171,6 +171,19 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 // Select all IBANs selectedAccountsIbansValue.set(userIbans) + var canReadAccountsIbansAvailableAccounts: List[String] = List() + if(json.access.availableAccounts.contains("allAccounts")) { // + /* + Access is requested via: + "access": + { + "availableAccounts": "allAccounts" + } + */ + accessAccountsDefinedVar.set(true) + canReadAccountsIbansAvailableAccounts = userIbans.toList + } + // Determine which IBANs the user can access for accounts, balances, and transactions val canReadAccountsIbans: List[String] = json.access.accounts match { case Some(accounts) if accounts.isEmpty => // Access is requested via "accounts": [] @@ -226,7 +239,7 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 } // all Selected IBANs - val ibansFromGetConsentResponseJson = (canReadAccountsIbans ::: canReadBalancesIbans ::: canReadTransactionsIbans).distinct + val ibansFromGetConsentResponseJson = (canReadAccountsIbansAvailableAccounts ::: canReadAccountsIbans ::: canReadBalancesIbans ::: canReadTransactionsIbans).distinct /** * Generates toggle switches for IBAN lists. @@ -388,7 +401,9 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 Consents.consentProvider.vend.getConsentByConsentId(consentId) match { case Full(consent) if otpValue.is == consent.challenge => updateConsentUser(consent) - Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected) + updateConsentJwt(consent) map { i => + Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected) + } S.redirectTo( s"$redirectUriValue?CONSENT_ID=${consentId}" ) @@ -406,7 +421,9 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 Consents.consentProvider.vend.getConsentByConsentId(consentId) match { case Full(consent) if otpValue.is == consent.challenge => updateConsentUser(consent) - Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid) + updateConsentJwt(consent) map { i => + Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid) + } S.redirectTo( s"/confirm-bg-consent-request-redirect-uri?CONSENT_ID=${consentId}" ) @@ -421,6 +438,10 @@ class BerlinGroupConsent extends MdcLoggable with RestHelper with APIMethods510 val jwt = Consent.updateUserIdOfBerlinGroupConsentJWT(loggedInUser.userId, consent, None).openOrThrowException(ErrorMessages.InvalidConnectorResponse) Consents.consentProvider.vend.setJsonWebToken(consent.consentId, jwt) } + private def updateConsentJwt(consent: MappedConsent) = { + val loggedInUser = AuthUser.currentUser.flatMap(_.user.foreign).openOrThrowException(ErrorMessages.UserNotLoggedIn) + Consent.updateViewsOfBerlinGroupConsentJWT(loggedInUser, consent, None) + } private def getTppRedirectUri() = { val consentId = ObpS.param("CONSENT_ID") openOr ("") diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala index ddf244c088..eb5ff33bac 100644 --- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala +++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala @@ -258,6 +258,68 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit } } + feature(s"BG v1.3 - $createConsent - postJsonBodyAvailableAccounts") { + lazy val postJsonBody = PostConsentJson( + access = ConsentAccessJson( + accounts = None, + balances = None, + transactions = None, + availableAccounts = Some("allAccounts"), + allPsd2 = None + ), + recurringIndicator = false, + validUntil = getNextMonthDate(), + frequencyPerDay = 1, + combinedServiceIndicator = Some(false) + ) + val postJsonBodyWrong1 = postJsonBody.copy( + access = postJsonBody.access.copy( + availableAccounts = Some("wrong") + ) + ) + val postJsonBodyWrong2 = postJsonBody.copy( + frequencyPerDay = 2 + ) + val postJsonBodyWrong3 = postJsonBody.copy( + recurringIndicator = true + ) + + scenario("Authentication User, test failed due to availableAccounts wrong value", BerlinGroupV1_3, createConsent) { + val requestPost = (V1_3_BG / "consents" ).POST <@ (user1) + val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong1)) + + Then("We should get a 400") + response.code should equal(400) + response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessAvailableAccounts) + } + scenario("Authentication User, test failed due to frequency per day", BerlinGroupV1_3, createConsent) { + val requestPost = (V1_3_BG / "consents" ).POST <@ (user1) + val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong2)) + + Then("We should get a 400") + response.code should equal(400) + response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessFrequencyPerDay) + } + scenario("Authentication User, test failed due to recurringIndicator = true", BerlinGroupV1_3, createConsent) { + val requestPost = (V1_3_BG / "consents" ).POST <@ (user1) + val response: APIResponse = makePostRequest(requestPost, write(postJsonBodyWrong3)) + + Then("We should get a 400") + response.code should equal(400) + response.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(BerlinGroupConsentAccessRecurringIndicator) + } + scenario("Authentication User, test succeed", BerlinGroupV1_3, createConsent) { + val requestPost = (V1_3_BG / "consents" ).POST <@ (user1) + val response: APIResponse = makePostRequest(requestPost, write(postJsonBody)) + + Then("We should get a 201 ") + response.code should equal(201) + val jsonResponse = response.body.extract[PostConsentResponseJson] + jsonResponse.consentId should not be (empty) + jsonResponse.consentStatus should be (ConsentStatus.received.toString) + } + } + feature(s"BG v1.3 - $createConsent") { scenario("Authentication User, test succeed", BerlinGroupV1_3, createConsent) { val testBankId = testAccountId1