feature/Add endpoint updateConsentPayloadByConsentId v5.1.0

This commit is contained in:
Marko Milić 2024-12-13 16:54:05 +01:00
parent 751c16890e
commit d79f076556
5 changed files with 172 additions and 4 deletions

View File

@ -992,6 +992,10 @@ object ApiRole extends MdcLoggable{
lazy val canUpdateConsentStatusAtOneBank = CanUpdateConsentStatusAtOneBank()
case class CanUpdateConsentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateConsentStatusAtAnyBank = CanUpdateConsentStatusAtAnyBank()
case class CanUpdateConsentPayloadAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canUpdateConsentPayloadAtOneBank = CanUpdateConsentPayloadAtOneBank()
case class CanUpdateConsentPayloadAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateConsentPayloadAtAnyBank = CanUpdateConsentPayloadAtAnyBank()
case class CanRevokeConsentAtBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canRevokeConsentAtBank = CanRevokeConsentAtBank()
case class CanGetConsentsAtOneBank(requiresBankId: Boolean = true) extends ApiRole

View File

@ -2,7 +2,6 @@ package code.api.util
import java.text.SimpleDateFormat
import java.util.{Date, UUID}
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ConsentAccessJson, PostConsentJson}
import code.api.util.ApiRole.{canCreateEntitlementAtAnyBank, canCreateEntitlementAtOneBank}
import code.api.v3_1_0.{PostConsentBodyCommonJson, PostConsentEntitlementJsonV310, PostConsentViewJsonV310}
@ -741,6 +740,57 @@ object Consent extends MdcLoggable {
}
}
}
def updateBerlinGroupConsentJWT(access: ConsentAccessJson,
consent: MappedConsent,
callContext: Option[CallContext]): Future[Box[String]] = {
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
// 1. Add access
val accounts: List[Future[ConsentView]] = access.accounts.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.accounts.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
view_id = Constant.SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID
)
}
}
val balances: List[Future[ConsentView]] = access.balances.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.balances.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
view_id = Constant.SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID
)
}
}
val transactions: List[Future[ConsentView]] = access.transactions.getOrElse(Nil) map { account =>
Connector.connector.vend.getBankAccountByIban(account.iban.getOrElse(""), callContext) map { bankAccount =>
logger.debug(s"createBerlinGroupConsentJWT.transactions.bankAccount: $bankAccount")
ConsentView(
bank_id = bankAccount._1.map(_.bankId.value).getOrElse(""),
account_id = bankAccount._1.map(_.accountId.value).getOrElse(""),
view_id = Constant.SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID
)
}
}
Future.sequence(accounts ::: balances ::: transactions) map { views =>
if(views.isEmpty) {
Empty
} else {
val updatedPayload = payloadToUpdate.map(i => i.copy(views = views))
val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload))
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
Full(CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret))
}
}
}
def createUKConsentJWT(
user: Option[User],

View File

@ -3,6 +3,7 @@ package code.api.v5_1_0
import code.api.Constant
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ConsentAccessAccountsJson, ConsentAccessJson}
import code.api.util.APIUtil._
import code.api.util.ApiRole._
import code.api.util.ApiTag._
@ -28,7 +29,7 @@ import code.api.v5_0_0.JSONFactory500
import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createConsentsJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson}
import code.atmattribute.AtmAttribute
import code.bankconnectors.Connector
import code.consent.{ConsentRequests, ConsentStatus, Consents}
import code.consent.{ConsentRequests, ConsentStatus, Consents, MappedConsent}
import code.consumer.Consumers
import code.loginattempts.LoginAttempt
import code.metrics.APIMetrics
@ -55,6 +56,7 @@ import net.liftweb.mapper.By
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.{Helpers, StringHelpers}
import java.text.SimpleDateFormat
import java.time.{LocalDate, ZoneId}
import java.util.Date
import scala.collection.immutable.{List, Nil}
@ -1347,6 +1349,83 @@ trait APIMethods510 {
}
}
staticResourceDocs += ResourceDoc(
updateConsentPayloadByConsentId,
implementedInApiVersion,
nameOf(updateConsentPayloadByConsentId),
"PUT",
"/management/banks/BANK_ID/consents/CONSENT_ID/payload",
"Update Consent Payload by CONSENT_ID",
s"""
|
|This endpoint is used to update the Payload of Consent.
|
|${authenticationRequiredMessage(true)}
|
|""",
PutConsentPayloadJsonV510(
access = ConsentAccessJson(
accounts = Option(List(ConsentAccessAccountsJson(
iban = Some(ExampleValue.ibanExample.value),
bban = None,
pan = None,
maskedPan = None,
msisdn = None,
currency = None,
)))
)
),
ConsentChallengeJsonV310(
consent_id = "9d429899-24f5-42c8-8565-943ffa6a7945",
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJlbnRpdGxlbWVudHMiOltdLCJjcmVhdGVkQnlVc2VySWQiOiJhYjY1MzlhOS1iMTA1LTQ0ODktYTg4My0wYWQ4ZDZjNjE2NTciLCJzdWIiOiIyMWUxYzhjYy1mOTE4LTRlYWMtYjhlMy01ZTVlZWM2YjNiNGIiLCJhdWQiOiJlanpuazUwNWQxMzJyeW9tbmhieDFxbXRvaHVyYnNiYjBraWphanNrIiwibmJmIjoxNTUzNTU0ODk5LCJpc3MiOiJodHRwczpcL1wvd3d3Lm9wZW5iYW5rcHJvamVjdC5jb20iLCJleHAiOjE1NTM1NTg0OTksImlhdCI6MTU1MzU1NDg5OSwianRpIjoiMDlmODhkNWYtZWNlNi00Mzk4LThlOTktNjYxMWZhMWNkYmQ1Iiwidmlld3MiOlt7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAxIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifSx7ImFjY291bnRfaWQiOiJtYXJrb19wcml2aXRlXzAyIiwiYmFua19pZCI6ImdoLjI5LnVrLngiLCJ2aWV3X2lkIjoib3duZXIifV19.8cc7cBEf2NyQvJoukBCmDLT7LXYcuzTcSYLqSpbxLp4",
status = "AUTHORISED"
),
List(
$UserNotLoggedIn,
$BankNotFound,
InvalidJsonFormat,
ConsentNotFound,
InvalidConnectorResponse,
UnknownError
),
apiTagConsent :: apiTagPSD2AIS :: Nil,
Some(List(canUpdateConsentPayloadAtOneBank, canUpdateConsentPayloadAtAnyBank))
)
lazy val updateConsentPayloadByConsentId: OBPEndpoint = {
case "management" :: "banks" :: BankId(bankId) :: "consents" :: consentId :: "payload" :: Nil JsonPut json -> _ => {
cc =>
implicit val ec = EndpointContext(Some(cc))
for {
consentJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PutConsentPayloadJsonV510 ", 400, cc.callContext) {
json.extract[PutConsentPayloadJsonV510]
}
consent: MappedConsent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
unboxFullOrFail(_, cc.callContext, s"$ConsentNotFound ($consentId)", 404)
}
consentJWT <- Consent.updateBerlinGroupConsentJWT(
consentJson.access,
consent,
cc.callContext
) map {
i => connectorEmptyResponse(i, cc.callContext)
}
updatedConsent <- Future(Consents.consentProvider.vend.setJsonWebToken(consent.consentId, consentJWT)) map {
i => connectorEmptyResponse(i, cc.callContext)
}
} yield {
(
ConsentJsonV310(
updatedConsent.consentId,
updatedConsent.jsonWebToken,
updatedConsent.status
),
HttpCode.`200`(cc.callContext)
)
}
}
}
staticResourceDocs += ResourceDoc(
getMyConsents,

View File

@ -27,6 +27,7 @@
package code.api.v5_1_0
import code.api.Constant
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.ConsentAccessJson
import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, gitCommit, stringOrNull}
import code.api.util._
import code.api.v1_2_1.BankRoutingJsonV121
@ -134,6 +135,8 @@ case class ConsentInfoJsonV510(consent_id: String,
)
case class ConsentsInfoJsonV510(consents: List[ConsentInfoJsonV510])
case class PutConsentPayloadJsonV510(access: ConsentAccessJson)
case class AllConsentJsonV510(consent_reference_id: String,
consumer_id: String,
created_by_user_id: String,

View File

@ -28,7 +28,7 @@ package code.api.v5_1_0
import code.api.Constant
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.{canUpdateConsentStatusAtOneBank, _}
import code.api.util.ApiRole._
import code.api.util.Consent
import code.api.util.ErrorMessages._
import code.api.v3_1_0.{PostConsentChallengeJsonV310, PostConsentEntitlementJsonV310}
@ -68,6 +68,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
object ApiEndpoint8 extends Tag(nameOf(Implementations5_1_0.getMyConsents))
object ApiEndpoint9 extends Tag(nameOf(Implementations5_1_0.getConsentsAtBank))
object UpdateConsentStatusByUser extends Tag(nameOf(Implementations5_1_0.updateConsentStatusByUser))
object UpdateConsentPayloadByConsentId extends Tag(nameOf(Implementations5_1_0.updateConsentPayloadByConsentId))
lazy val entitlements = List(PostConsentEntitlementJsonV310("", CanGetAnyUser.toString()))
lazy val bankId = testBankId1.value
@ -93,6 +94,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
def getMyConsents(consentId: String) = (v5_1_0_Request / "banks" / bankId / "my" / "consents").GET
def getConsentsAtBAnk(consentId: String) = (v5_1_0_Request / "management"/ "consents" / "banks" / bankId).GET
def updateConsentStatusByUser(consentId: String) = (v5_1_0_Request / "management" / "banks" / bankId / "consents" / consentId).PUT
def updateConsentPayloadByConsent(consentId: String) = (v5_1_0_Request / "management" / "banks" / bankId / "consents" / consentId / "payload").PUT
feature(s"test $ApiEndpoint6 version $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint6, VersionOfApi) {
@ -155,7 +157,7 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
feature(s"test $UpdateConsentStatusByUser version $VersionOfApi - Unauthenticated access") {
scenario("We will call the endpoint without user credentials", UpdateConsentStatusByUser, VersionOfApi) {
When(s"We make a request $UpdateConsentStatusByUser")
val response510 = makeGetRequest(getConsentsAtBAnk("whatever"))
val response510 = makePutRequest(updateConsentStatusByUser("whatever"), write(consentStatus))
Then("We should get a 401")
response510.code should equal(401)
response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
@ -180,6 +182,36 @@ class ConsentsTest extends V510ServerSetup with PropsReset{
response510.body.extract[ErrorMessage].message should startWith(ConsentNotFound)
}
}
feature(s"test $UpdateConsentPayloadByConsentId version $VersionOfApi - Unauthenticated access") {
scenario("We will call the endpoint without user credentials", UpdateConsentPayloadByConsentId, VersionOfApi) {
When(s"We make a request $UpdateConsentPayloadByConsentId")
val response510 = makePutRequest(updateConsentPayloadByConsent("whatever"), write(consentStatus))
Then("We should get a 401")
response510.code should equal(401)
response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $UpdateConsentPayloadByConsentId version $VersionOfApi - Authenticated access") {
scenario("We will call the endpoint with user credentials", UpdateConsentPayloadByConsentId, VersionOfApi) {
When(s"We make a request $UpdateConsentPayloadByConsentId")
val response510 = makePutRequest(updateConsentPayloadByConsent("whatever") <@ user1, write(consentStatus))
Then("We should get a 403")
response510.code should equal(403)
response510.body.extract[ErrorMessage].message contains (UserHasMissingRoles + s"$CanUpdateConsentPayloadAtOneBank or $CanUpdateConsentPayloadAtAnyBank") should be(true)
}
}
feature(s"test $UpdateConsentPayloadByConsentId version $VersionOfApi - Authenticated access with Role $CanUpdateConsentStatusAtAnyBank") {
scenario("We will call the endpoint with user credentials", UpdateConsentPayloadByConsentId, VersionOfApi) {
When(s"We make a request $UpdateConsentPayloadByConsentId")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateConsentPayloadAtAnyBank.toString)
val response510 = makePutRequest(updateConsentPayloadByConsent("whatever") <@ user1, write(consentStatus))
Then("We should get a 404")
response510.code should equal(404)
response510.body.extract[ErrorMessage].message should startWith(ConsentNotFound)
}
}
feature(s"Create/Use/Revoke Consent $VersionOfApi") {
scenario("We will call the Create, Get and Delete endpoints with user credentials ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, ApiEndpoint5, ApiEndpoint6, ApiEndpoint7, VersionOfApi) {