Skip to content

Commit

Permalink
36: VRP payments (#470)
Browse files Browse the repository at this point in the history
- Bumped latest dependencies
- Few fixes to support VRP payments
Issue: OpenBankingToolkit/openbanking-toolkit#36
  • Loading branch information
jsanhc authored Dec 20, 2021
1 parent ef3db5f commit edd48bf
Show file tree
Hide file tree
Showing 12 changed files with 1,350 additions and 638 deletions.
1,808 changes: 1,233 additions & 575 deletions CHANGELOG.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ public class OpenBankingHttpHeaders {
* Issued by OBIE and corresponds to the Organization Id of the ASPSP in the Open Banking Directory.
*/
public static final String X_FAPI_FINANCIAL_ID = "x-fapi-financial-id";

/**
* Contains a flag to identify if the request is in mode test to adapt the behaviour for test purposes.
*/
public static final String X_OB_MODE_TEST = "x-ob-mode-test";
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public enum FRExternalPermissionsCode {
READTRANSACTIONSBASIC("ReadTransactionsBasic"),
READTRANSACTIONSCREDITS("ReadTransactionsCredits"),
READTRANSACTIONSDEBITS("ReadTransactionsDebits"),
READTRANSACTIONSDETAIL("ReadTransactionsDetail");
READTRANSACTIONSDETAIL("ReadTransactionsDetail"),
READCUSTOMERINFOCONSENT("ReadCustomerInfoPSU");

private String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
package com.forgerock.openbanking.aspsp.rs.api.payment.v3_1_8.vrp;

import com.forgerock.openbanking.aspsp.rs.wrappper.RSEndpointWrapperService;
import com.forgerock.openbanking.common.constants.OpenBankingHttpHeaders;
import com.forgerock.openbanking.common.model.version.OBVersion;
import com.forgerock.openbanking.common.services.store.RsStoreGateway;
import com.forgerock.openbanking.exceptions.OBErrorResponseException;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -29,6 +31,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import uk.org.openbanking.datamodel.vrp.OBDomesticVRPConsentRequest;
import uk.org.openbanking.datamodel.vrp.OBDomesticVRPConsentResponse;
import uk.org.openbanking.datamodel.vrp.OBVRPFundsConfirmationRequest;
Expand Down Expand Up @@ -59,6 +62,7 @@ public ResponseEntity domesticVrpConsentsPost(
.authorization(authorization)
.xFapiFinancialId(xFapiInteractionId)
.principal(principal)
.obVersion(OBVersion.v3_1_8)
.filters(f -> {
f.verifyIdempotencyKeyLength(xIdempotencyKey);
f.verifyJwsDetachedSignature(xJwsSignature, request);
Expand All @@ -85,6 +89,7 @@ public ResponseEntity domesticVrpConsentsGet(
.authorization(authorization)
.xFapiFinancialId(xFapiInteractionId)
.principal(principal)
.obVersion(OBVersion.v3_1_8)
.execute(
(String tppId) -> {
HttpHeaders additionalHttpHeaders = new HttpHeaders();
Expand All @@ -102,6 +107,7 @@ public ResponseEntity domesticVrpConsentsDelete(
.authorization(authorization)
.xFapiFinancialId(xFapiInteractionId)
.principal(principal)
.obVersion(OBVersion.v3_1_8)
.execute(
(String tppId) -> {
HttpHeaders additionalHttpHeaders = new HttpHeaders();
Expand All @@ -127,11 +133,13 @@ public ResponseEntity domesticVrpConsentsFundsConfirmation(
"' path parameter does not match with the consent ID '" +
obVRPFundsConfirmationRequest.getData().getConsentId() + "' requested to confirm the funds.");
}
log.debug("(domesticVrpConsentsFundsConfirmation) Request mode test: '{}'", StringUtils.hasLength(request.getHeader(OpenBankingHttpHeaders.X_OB_MODE_TEST)));
return rsEndpointWrapperService.vrpPaymentEndpoint()
.authorization(authorization)
.xFapiFinancialId(xFapiInteractionId)
.principal(principal)
.isFundsConfirmationRequest(true)
.isAuthorizationCodeGrantType(true)
.obVersion(OBVersion.v3_1_8)
.filters(f -> {
f.verifyJwsDetachedSignature(xJwsSignature, request);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
*/
package com.forgerock.openbanking.aspsp.rs.api.payment.v3_1_8.vrp;

import com.forgerock.openbanking.api.annotations.OBGroupName;
import com.forgerock.openbanking.api.annotations.OBReference;
import com.forgerock.openbanking.api.annotations.OpenBankingAPI;
import com.forgerock.openbanking.exceptions.OBErrorResponseException;
import io.swagger.annotations.*;
import org.springframework.http.ResponseEntity;
Expand All @@ -42,6 +45,11 @@
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2021-11-17T13:54:56.728Z[Europe/London]")
@Validated
@Api(value = "domestic-vrps", description = "the domestic-vrps API")
@OpenBankingAPI(
obVersion = "3.1.8",
obGroupName = OBGroupName.PISP,
obReference = OBReference.DOMESTIC_VRP_PAYMENTS
)
@RequestMapping(value = "/open-banking/v3.1.8/pisp")
public interface DomesticVrpsApi {

Expand Down Expand Up @@ -80,6 +88,9 @@ public interface DomesticVrpsApi {
@ApiResponse(code = 415, message = "Unsupported Media Type"),
@ApiResponse(code = 429, message = "Too Many Requests"),
@ApiResponse(code = 500, message = "Internal Server Error", response = OBErrorResponse1.class)})
@OpenBankingAPI(
obReference = OBReference.GET_DOMESTIC_VRP_PAYMENT
)
@RequestMapping(
value = "/domestic-vrps/{DomesticVRPId}",
produces = {"application/json; charset=utf-8", "application/json", "application/jose+jwe"},
Expand Down Expand Up @@ -144,6 +155,9 @@ ResponseEntity<OBDomesticVRPResponse> domesticVrpGet(
@ApiResponse(code = 415, message = "Unsupported Media Type"),
@ApiResponse(code = 429, message = "Too Many Requests"),
@ApiResponse(code = 500, message = "Internal Server Error", response = OBErrorResponse1.class)})
@OpenBankingAPI(
obReference = OBReference.GET_DOMESTIC_VRP_PAYMENT_DETAILS
)
@RequestMapping(
value = "/domestic-vrps/{DomesticVRPId}/payment-details",
produces = {"application/json; charset=utf-8", "application/json", "application/jose+jwe"},
Expand Down Expand Up @@ -211,6 +225,9 @@ ResponseEntity<OBDomesticVRPDetails> domesticVrpPaymentDetailsGet(
@ApiResponse(code = 415, message = "Unsupported Media Type"),
@ApiResponse(code = 429, message = "Too Many Requests"),
@ApiResponse(code = 500, message = "Internal Server Error", response = OBErrorResponse1.class)})
@OpenBankingAPI(
obReference = OBReference.CREATE_DOMESTIC_VRP_PAYMENT
)
@RequestMapping(
value = "/domestic-vrps",
produces = {"application/json; charset=utf-8", "application/json", "application/jose+jwe"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.forgerock.openbanking.aspsp.rs.wrappper.RSEndpointWrapperService;
import com.forgerock.openbanking.aspsp.rs.wrappper.endpoints.DomesticVrpPaymentsEndpointWrapper;
import com.forgerock.openbanking.common.model.openbanking.persistence.vrp.FRDomesticVRPConsent;
import com.forgerock.openbanking.common.model.version.OBVersion;
import com.forgerock.openbanking.common.services.store.RsStoreGateway;
import com.forgerock.openbanking.common.services.store.vrp.DomesticVrpPaymentConsentService;
import com.forgerock.openbanking.exceptions.OBErrorResponseException;
Expand Down Expand Up @@ -69,6 +70,7 @@ public ResponseEntity<OBDomesticVRPResponse> domesticVrpGet(
.authorization(authorization)
.xFapiFinancialId(rsEndpointWrapperService.getRsConfiguration().financialId)
.principal(principal)
.obVersion(OBVersion.v3_1_8)
.execute(
(String tppId) -> {
HttpHeaders additionalHttpHeaders = new HttpHeaders();
Expand All @@ -86,6 +88,7 @@ public ResponseEntity<OBDomesticVRPDetails> domesticVrpPaymentDetailsGet(
.authorization(authorization)
.xFapiFinancialId(rsEndpointWrapperService.getRsConfiguration().financialId)
.principal(principal)
.obVersion(OBVersion.v3_1_8)
.execute(
(String tppId) -> {
HttpHeaders additionalHttpHeaders = new HttpHeaders();
Expand Down Expand Up @@ -130,9 +133,11 @@ public ResponseEntity<OBDomesticVRPResponse> domesticVrpPost(
FRDomesticVRPConsent consent = vrpPaymentConsentService.getVrpPaymentConsent(consentId);
DomesticVrpPaymentsEndpointWrapper vrpPaymentsEndpointWrapper = rsEndpointWrapperService.vrpPaymentEndpoint();
vrpPaymentsEndpointWrapper.authorization(authorization);
vrpPaymentsEndpointWrapper.obVersion(OBVersion.v3_1_8);
vrpPaymentsEndpointWrapper.xFapiFinancialId(rsEndpointWrapperService.getRsConfiguration().financialId);
vrpPaymentsEndpointWrapper.principal(principal);
vrpPaymentsEndpointWrapper.payment(consent);
vrpPaymentsEndpointWrapper.isAuthorizationCodeGrantType(true);
vrpPaymentsEndpointWrapper.filters(f -> {
f.verifyJwsDetachedSignature(xJwsSignature, request);
f.validateRisk(obDomesticVRPRequest.getRisk());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ public void verifyDetachedJws(String detachedJws, OBVersion obVersion, HttpServl

// obVersion is only set from 3.1.3 onwards
if ((obVersion == null || obVersion.isBeforeVersion(v3_1_4)) && isBase64Encoded(detachedJws)) {
log.warn("Invalid detached signature {}", detachedJws, "b64 claim header not set to false in version: " + obVersion);
log.warn("Invalid detached signature {}, {}", detachedJws, "b64 claim header not set to false in version: " + obVersion);
throw new OBErrorException(OBRIErrorType.DETACHED_JWS_INVALID, detachedJws, "b64 claim header not set to false");
}
if (obVersion != null && obVersion.isAfterVersion(v3_1_3) && isB64ClaimHeaderPresent(detachedJws)) {
log.warn("Invalid detached signature {}", detachedJws, "b64 claim header must not be present in version: " + obVersion);
log.warn("Invalid detached signature {}, {}", detachedJws, "b64 claim header must not be present in version: " + obVersion);
throw new OBErrorException(OBRIErrorType.DETACHED_JWS_INVALID, detachedJws, "b64 claim header must not be present");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,30 @@ public class DomesticVrpPaymentsEndpointWrapper extends RSEndpointWrapper<Domest

private FRDomesticVRPConsent consent;
private final OBRisk1Validator riskValidator;
private boolean isFundsConfirmationRequest;
private boolean isAuthorizationCodeGrantType;
private boolean isModeTest;

public DomesticVrpPaymentsEndpointWrapper(RSEndpointWrapperService RSEndpointWrapperService,
TppStoreService tppStoreService,
OBRisk1Validator riskValidator) {
super(RSEndpointWrapperService, tppStoreService);
this.riskValidator = riskValidator;
this.isAuthorizationCodeGrantType = false;
this.isModeTest = false;
}

public DomesticVrpPaymentsEndpointWrapper payment(FRDomesticVRPConsent consent) {
this.consent = consent;
return this;
}

public DomesticVrpPaymentsEndpointWrapper isFundsConfirmationRequest(boolean isFundsConfirmationRequest) {
this.isFundsConfirmationRequest = isFundsConfirmationRequest;
public DomesticVrpPaymentsEndpointWrapper isAuthorizationCodeGrantType(boolean isAuthorizationCodeGrantType) {
this.isAuthorizationCodeGrantType = isAuthorizationCodeGrantType;
return this;
}

public DomesticVrpPaymentsEndpointWrapper isModeTest(boolean isModeTest) {
this.isModeTest = isModeTest;
return this;
}

Expand All @@ -78,7 +86,7 @@ protected ResponseEntity run(DomesticVrpPaymentRestEndpointContent main) throws
protected void applyFilters() throws OBErrorException {
List grantTypes = Arrays.asList(OIDCConstants.GrantType.CLIENT_CREDENTIAL);
// the grant type for funds confirmation endpoint is different than the others payment endpoints
if (isFundsConfirmationRequest) {
if (isAuthorizationCodeGrantType) {
grantTypes = Arrays.asList(
OIDCConstants.GrantType.AUTHORIZATION_CODE
);
Expand Down Expand Up @@ -121,7 +129,7 @@ public void checkRequestAndConsentRiskMatch(OBDomesticVRPRequest request, FRDome
public void checkCreditorAccountIsInInstructionIfNotInConsent(OBDomesticVRPRequest vrpRequest,
FRDomesticVRPConsent frConsent) throws OBErrorException {
if (frConsent.getVrpDetails().getData().getInitiation().getCreditorAccount() == null) {
if (vrpRequest.getData().getInitiation().getCreditorAccount() == null) {
if (vrpRequest.getData().getInstruction().getCreditorAccount() == null) {
throw new OBErrorException(OBRIErrorType.REQUEST_VRP_CREDITOR_ACCOUNT_NOT_SPECIFIED);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ public void setUp() {
}

@Test
public void createVrpPaymentConsent() throws Exception {
public void createVrpPayment() throws Exception {
// Given
String jws = jws("payments", OIDCConstants.GrantType.CLIENT_CREDENTIAL);
String jws = jws("payments", OIDCConstants.GrantType.AUTHORIZATION_CODE);
springSecForTest.mockAuthCollector.mockAuthorities(OBRIRole.ROLE_PISP);
given(amResourceServerService.verifyAccessToken("Bearer " + jws)).willReturn(SignedJWT.parse(jws));
FRDomesticVRPConsent frDomesticVRPConsent = aValidFRDomesticVRPConsent(
Expand Down Expand Up @@ -149,9 +149,9 @@ public void createVrpPaymentConsent() throws Exception {
}

@Test
public void createVrpPaymentConsentInitiationNotMatch() throws Exception {
public void createVrpPaymentInitiationNotMatch() throws Exception {
// Given
String jws = jws("payments", OIDCConstants.GrantType.CLIENT_CREDENTIAL);
String jws = jws("payments", OIDCConstants.GrantType.AUTHORIZATION_CODE);
springSecForTest.mockAuthCollector.mockAuthorities(OBRIRole.ROLE_PISP);
given(amResourceServerService.verifyAccessToken("Bearer " + jws)).willReturn(SignedJWT.parse(jws));
FRDomesticVRPConsent frDomesticVRPConsent = aValidFRDomesticVRPConsent(
Expand Down Expand Up @@ -188,9 +188,9 @@ public void createVrpPaymentConsentInitiationNotMatch() throws Exception {
}

@Test
public void createVrpPaymentConsentRiskNotMatch() throws Exception {
public void createVrpPaymentRiskNotMatch() throws Exception {
// Given
String jws = jws("payments", OIDCConstants.GrantType.CLIENT_CREDENTIAL);
String jws = jws("payments", OIDCConstants.GrantType.AUTHORIZATION_CODE);
springSecForTest.mockAuthCollector.mockAuthorities(OBRIRole.ROLE_PISP);
given(amResourceServerService.verifyAccessToken("Bearer " + jws)).willReturn(SignedJWT.parse(jws));
FRDomesticVRPConsent frDomesticVRPConsent = aValidFRDomesticVRPConsent(
Expand Down Expand Up @@ -227,9 +227,9 @@ public void createVrpPaymentConsentRiskNotMatch() throws Exception {
}

@Test
public void createVrpPaymentConsentCreditorAccountNotProvided() throws Exception {
public void createVrpPaymentInstructedCreditorAccountNotProvided() throws Exception {
// Given
String jws = jws("payments", OIDCConstants.GrantType.CLIENT_CREDENTIAL);
String jws = jws("payments", OIDCConstants.GrantType.AUTHORIZATION_CODE);
springSecForTest.mockAuthCollector.mockAuthorities(OBRIRole.ROLE_PISP);
given(amResourceServerService.verifyAccessToken("Bearer " + jws)).willReturn(SignedJWT.parse(jws));
FRDomesticVRPConsent frDomesticVRPConsent = aValidFRDomesticVRPConsent(
Expand All @@ -242,7 +242,7 @@ public void createVrpPaymentConsentCreditorAccountNotProvided() throws Exception
OBDomesticVRPRequest request = buildAValidOBDomesticVRPRequest(consentResponse);
frDomesticVRPConsent.getVrpDetails().getData().getInitiation().setCreditorAccount(null);
consentResponse.getData().getInitiation().setCreditorAccount(null);
request.getData().getInitiation().setCreditorAccount(null);
request.getData().getInstruction().setCreditorAccount(null);

given(vrpPaymentConsentService.getVrpPaymentConsent(request.getData().getConsentId())).willReturn(frDomesticVRPConsent);

Expand All @@ -260,17 +260,17 @@ public void createVrpPaymentConsentCreditorAccountNotProvided() throws Exception
assertThat(response).isNotNull();
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
assertThat(response.getParsingError().get().getOriginalBody()).contains(
"{\"ErrorCode\":\"UK.OBIE.Resource.ConsentMismatch\""
"{\"ErrorCode\":\"UK.OBIE.Field.Invalid\""
);
assertThat(response.getParsingError().get().getOriginalBody()).contains(
"Creditor account must be specified in the VRP request when not provided in the consent"
"data.instruction.creditorAccount"
);
}

@Test
public void createVrpPaymentConsentBreachLimitationMaxAmount() throws Exception {
public void createVrpPaymentBreachLimitationMaxAmount() throws Exception {
// Given
String jws = jws("payments", OIDCConstants.GrantType.CLIENT_CREDENTIAL);
String jws = jws("payments", OIDCConstants.GrantType.AUTHORIZATION_CODE);
springSecForTest.mockAuthCollector.mockAuthorities(OBRIRole.ROLE_PISP);
given(amResourceServerService.verifyAccessToken("Bearer " + jws)).willReturn(SignedJWT.parse(jws));
FRDomesticVRPConsent frDomesticVRPConsent = aValidFRDomesticVRPConsent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public void fail_checkCreditorAccountIsInInstructionIfNotInConsent() throws OBEr
// Create the request data
OBDomesticVRPRequest vrpRequest = OBDomesticVRPRequestTestDataFactory.aValidOBDomesticVRPRequest();
vrpRequest.getData().getInitiation().setCreditorAccount(null);
vrpRequest.getData().getInstruction().setCreditorAccount(null);

// Create an FR Consent with slightly differing initiation data
FRDomesticVRPConsent frConsent = FRVrpTestDataFactory.aValidFRDomesticVRPConsent();
Expand All @@ -190,4 +191,4 @@ public void fail_checkCreditorAccountIsInInstructionIfNotInConsent() throws OBEr

}

}
}
Loading

0 comments on commit edd48bf

Please sign in to comment.