Skip to content

Commit

Permalink
OBG-1234 Allow fintech pass consent (#1263)
Browse files Browse the repository at this point in the history
* OBG-1234 Allow fintech pass consent

* OBG-1234 X-Create-Consent-If-None header as String instead of Object

* OBG-1234 add example in api, consent hardcoded in consent-ui

* OBG-1234 fixed hardcoded consent object

* OBG-1234 fix mapper, add tests

* OBG-1234 fix ui-tests

* OBG-1234 make consent optional in fintech-ui

* OBG-1234 fix consent-ui behavior when fintech doesn't sends consnet header

* OBG-1234 add custom consent field in fintech-ui

* OBG-1234 add e2e test with consent from fintech
  • Loading branch information
Maxim Grischenko committed Jul 22, 2021
1 parent 403e12f commit d4c4405
Show file tree
Hide file tree
Showing 44 changed files with 811 additions and 139 deletions.
4 changes: 4 additions & 0 deletions consent-ui/src/app/ais/common/consent-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class ConsentUtil {
sessionService.setConsentObject(authorizationId, consentObj);
}

public static isEmptyObject(obj: Object) {
return (obj && (Object.keys(obj).length === 0));
}

private static initializeConsentObject(): AisConsentToGrant {
const aisConsent = new AisConsentToGrant();
// FIXME: These fields MUST be initialized by FinTech through API and user can only adjust it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ApiHeaders } from '../../../../api/api.headers';
import { AuthConsentState } from '../../../common/dto/auth-state';
import { ConsentAuth, AuthStateConsentAuthorizationService } from '../../../../api';
import ActionEnum = ConsentAuth.ActionEnum;
import { ConsentUtil } from "../../../common/consent-util";

@Component({
selector: 'consent-app-consent-initiate',
Expand Down Expand Up @@ -56,6 +57,9 @@ export class ConsentInitiateComponent implements OnInit {
this.sessionService.setFintechName(authorizationId, (res.body as ConsentAuth).fintechName);

this.sessionService.setRedirectCode(authorizationId, res.headers.get(ApiHeaders.X_XSRF_TOKEN));
if (!ConsentUtil.isEmptyObject(res.body.consent)) {
this.sessionService.setConsentObject(authorizationId, res.body);
}
this.navigate(authorizationId, res.body);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ paths:
- $ref: "#/components/parameters/Fintech-Redirect-URL-OK"
- $ref: "#/components/parameters/Fintech-Redirect-URL-NOK"
- $ref: "#/components/parameters/LoARetrievalInformation"
- $ref: "#/components/parameters/X-Create-Consent-If-None"
#query
- $ref: "#/components/parameters/withBalance"
- $ref: "#/components/parameters/online"
Expand Down Expand Up @@ -287,21 +288,21 @@ paths:
#path
- $ref: "#/components/parameters/bank-id"
- $ref: "#/components/parameters/account-id"
#query
- $ref: "#/components/parameters/dateFrom"
- $ref: "#/components/parameters/dateTo"
- $ref: "#/components/parameters/entryReferenceFrom"
- $ref: "#/components/parameters/bookingStatus"
- $ref: "#/components/parameters/deltaList"
- $ref: "#/components/parameters/online"

#header
- $ref: "#/components/parameters/X-Request-ID"
- $ref: "#/components/parameters/X-XSRF-TOKEN"
- $ref: "#/components/parameters/X-Psu-Authentication-Required"
- $ref: "#/components/parameters/Fintech-Redirect-URL-OK"
- $ref: "#/components/parameters/Fintech-Redirect-URL-NOK"
- $ref: "#/components/parameters/LoTRetrievalInformation"
- $ref: "#/components/parameters/X-Create-Consent-If-None"
#query
- $ref: "#/components/parameters/dateFrom"
- $ref: "#/components/parameters/dateTo"
- $ref: "#/components/parameters/entryReferenceFrom"
- $ref: "#/components/parameters/bookingStatus"
- $ref: "#/components/parameters/deltaList"
- $ref: "#/components/parameters/online"

security:
- sessionCookie: []
Expand Down Expand Up @@ -606,6 +607,15 @@ components:
- "FROM_TPP_WITH_AVAILABLE_CONSENT"
- "FROM_TPP_WITH_NEW_CONSENT"

X-Create-Consent-If-None:
name: X-Create-Consent-If-None
required: false
in: header
schema:
type: string
description: json representation of AisConsentRequest object
example: {"access":{"allPsd2":"ALL_ACCOUNTS_WITH_BALANCES"},"frequencyPerDay":4,"validUntil":"2021-06-18","recurringIndicator":true,"combinedServiceIndicator":false}

X-Request-ID:
name: X-Request-ID
in: header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,37 @@ public class FinTechAccountInformationImpl implements FinTechAccountInformationA
@Override
public ResponseEntity<AccountList> aisAccountsGET(String bankId, UUID xRequestID, String xsrfToken,
String fintechRedirectURLOK, String fintechRedirectURLNOK,
String loARetrievalInformation,
Boolean xPsuAuthenticationRequired, Boolean withBalance,
Boolean online) {
String loARetrievalInformation, Boolean xPsuAuthenticationRequired,
String createConsentIfNone, Boolean withBalance, Boolean online) {
if (!sessionLogicService.isSessionAuthorized()) {
log.warn("aisAccountsGET failed: user is not authorized!");
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}

SessionEntity sessionEntity = sessionLogicService.getSession();
return sessionLogicService.addSessionMaxAgeToHeader(accountService.listAccounts(sessionEntity,
fintechRedirectURLOK, fintechRedirectURLNOK, bankId, LoARetrievalInformation.valueOf(loARetrievalInformation), withBalance, xPsuAuthenticationRequired, online));
return sessionLogicService.addSessionMaxAgeToHeader(
accountService.listAccounts(sessionEntity, fintechRedirectURLOK, fintechRedirectURLNOK,
bankId, LoARetrievalInformation.valueOf(loARetrievalInformation),
createConsentIfNone, withBalance, xPsuAuthenticationRequired, online));
}

@Override
public ResponseEntity<TransactionsResponse> aisTransactionsGET(String bankId, String accountId, UUID xRequestID,
String xsrfToken, String fintechRedirectURLOK, String fintechRedirectURLNOK,
String loTRetrievalInformation,
LocalDate dateFrom, LocalDate dateTo,
String entryReferenceFrom, String bookingStatus, Boolean deltaList,
Boolean online, Boolean xPsuAuthenticationRequired) {
Boolean xPsuAuthenticationRequired, String createConsentIfNone,
LocalDate dateFrom, LocalDate dateTo, String entryReferenceFrom, String bookingStatus,
Boolean deltaList, Boolean online) {
if (!sessionLogicService.isSessionAuthorized()) {
log.warn("aisTransactionsGET failed: user is not authorized!");
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
SessionEntity sessionEntity = sessionLogicService.getSession();
return sessionLogicService.addSessionMaxAgeToHeader(
transactionService.listTransactions(sessionEntity, fintechRedirectURLOK, fintechRedirectURLNOK,
bankId, accountId, dateFrom, dateTo, entryReferenceFrom, bookingStatus, deltaList, LoTRetrievalInformation.valueOf(loTRetrievalInformation), xPsuAuthenticationRequired, online));
transactionService.listTransactions(sessionEntity, fintechRedirectURLOK, fintechRedirectURLNOK,
bankId, accountId, createConsentIfNone, dateFrom, dateTo,
entryReferenceFrom, bookingStatus, deltaList,
LoTRetrievalInformation.valueOf(loTRetrievalInformation), xPsuAuthenticationRequired, online));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
import de.adorsys.opba.fintech.impl.tppclients.AisErrorDecoder;
import de.adorsys.opba.fintech.impl.tppclients.ConsentType;
import de.adorsys.opba.fintech.impl.tppclients.TppAisClient;
import de.adorsys.opba.tpp.banksearch.api.model.generated.BankProfileResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;
import java.util.UUID;

Expand All @@ -45,13 +46,14 @@ public class AccountService {

public ResponseEntity listAccounts(SessionEntity sessionEntity,
String fintechOkUrl, String fintechNOKUrl,
String bankId, LoARetrievalInformation loARetrievalInformation, boolean withBalance, Boolean psuAuthenticationRequired, Boolean online) {
String bankId, LoARetrievalInformation loARetrievalInformation, String createConsentIfNone,
boolean withBalance, Boolean psuAuthenticationRequired, Boolean online) {

log.info("List of accounts {} with balance {}", loARetrievalInformation, withBalance);
final String fintechRedirectCode = UUID.randomUUID().toString();

try {
ResponseEntity accounts = readOpbaResponse(bankId, sessionEntity, fintechRedirectCode, loARetrievalInformation, withBalance, psuAuthenticationRequired, online);
ResponseEntity accounts = readOpbaResponse(bankId, sessionEntity, fintechRedirectCode, loARetrievalInformation, createConsentIfNone, withBalance, psuAuthenticationRequired, online);

switch (accounts.getStatusCode()) {
case OK:
Expand All @@ -68,14 +70,14 @@ public ResponseEntity listAccounts(SessionEntity sessionEntity,
} catch (ConsentException consentException) {
HttpHeaders headers = new HttpHeaders();
headers.add(AisErrorDecoder.X_ERROR_CODE, consentException.getXErrorCode());
return new ResponseEntity(headers, HttpStatus.valueOf(consentException.getHttpResponseCode()));
return new ResponseEntity<>(headers, HttpStatus.valueOf(consentException.getHttpResponseCode()));
}
}


@SneakyThrows
private ResponseEntity readOpbaResponse(String bankID, SessionEntity sessionEntity, String redirectCode,
LoARetrievalInformation loARetrievalInformation, boolean withBalance,
Boolean psuAuthenticationRequired, Boolean online) {
LoARetrievalInformation loARetrievalInformation, String createConsentIfNone,
boolean withBalance, Boolean psuAuthenticationRequired, Boolean online) {
UUID xRequestId = UUID.fromString(restRequestContext.getRequestId());
Optional<ConsentEntity> optionalConsent = Optional.empty();
if (loARetrievalInformation.equals(LoARetrievalInformation.FROM_TPP_WITH_AVAILABLE_CONSENT)) {
Expand All @@ -88,21 +90,20 @@ private ResponseEntity readOpbaResponse(String bankID, SessionEntity sessionEnti
optionalConsent.get().getUserEntity().getLoginUserName(),
optionalConsent.get().getBankId(),
optionalConsent.get().getCreationTime());
return consentAvailable(bankID, sessionEntity, redirectCode, xRequestId, optionalConsent, withBalance, psuAuthenticationRequired, online);
return consentAvailable(bankID, sessionEntity, redirectCode, xRequestId, optionalConsent, createConsentIfNone, withBalance, psuAuthenticationRequired, online);
}

BankProfileResponse bankProfile = searchService.getBankProfileById(bankID).getBody();
if (null != bankProfile.getBankProfileDescriptor().getConsentSupportByService()
&& "true".equals(bankProfile.getBankProfileDescriptor().getConsentSupportByService().get(Actions.LIST_ACCOUNTS.name()))) {
Map<String, String> consentSupportByService = searchService.getBankProfileById(bankID).getBody().getBankProfileDescriptor().getConsentSupportByService();
if (null != consentSupportByService && "true".equals(consentSupportByService.get(Actions.LIST_ACCOUNTS.name()))) {
log.info("LoA no valid ais consent for user {} bank {} available", sessionEntity.getUserEntity().getLoginUserName(), bankID);
return consentNotYetAvailable(bankID, sessionEntity, redirectCode, xRequestId, psuAuthenticationRequired, optionalConsent, withBalance, online);
return consentNotYetAvailable(bankID, sessionEntity, redirectCode, xRequestId, psuAuthenticationRequired, optionalConsent, withBalance, online, createConsentIfNone);
}

return consentAvailable(bankID, sessionEntity, redirectCode, xRequestId, optionalConsent, withBalance, psuAuthenticationRequired, online);
return consentAvailable(bankID, sessionEntity, redirectCode, xRequestId, optionalConsent, createConsentIfNone, withBalance, psuAuthenticationRequired, online);
}

private ResponseEntity consentAvailable(String bankID, SessionEntity sessionEntity, String redirectCode,
UUID xRequestId, Optional<ConsentEntity> optionalConsent, boolean withBalance,
UUID xRequestId, Optional<ConsentEntity> optionalConsent, String createConsentIfNone, boolean withBalance,
Boolean psuAuthenticationRequired, Boolean online) {
log.info("do LOA for bank {} {} consent", bankID, optionalConsent.isPresent() ? "with" : "without");
UUID serviceSessionID = optionalConsent.map(ConsentEntity::getTppServiceSessionId).orElse(null);
Expand All @@ -118,13 +119,14 @@ private ResponseEntity consentAvailable(String bankID, SessionEntity sessionEnti
bankID,
psuAuthenticationRequired,
serviceSessionID,
createConsentIfNone,
withBalance,
online);
}

private ResponseEntity consentNotYetAvailable(String bankID, SessionEntity sessionEntity, String redirectCode, UUID xRequestId,
Boolean psuAuthenticationRequired, Optional<ConsentEntity> optionalConsent,
boolean withBalance, Boolean online) {
boolean withBalance, Boolean online, String createConsentIfNone) {
log.info("do LOT (instead of loa) for bank {} {} consent", bankID, optionalConsent.isPresent() ? "with" : "without");
UUID serviceSessionID = optionalConsent.map(ConsentEntity::getTppServiceSessionId).orElse(null);
var response = tppAisClient.getTransactionsWithoutAccountId(
Expand All @@ -139,6 +141,7 @@ private ResponseEntity consentNotYetAvailable(String bankID, SessionEntity sessi
bankID,
psuAuthenticationRequired,
serviceSessionID,
createConsentIfNone,
null,
null,
null,
Expand All @@ -157,6 +160,7 @@ private ResponseEntity consentNotYetAvailable(String bankID, SessionEntity sessi
redirectCode,
UUID.randomUUID(),
optionalConsent,
createConsentIfNone,
withBalance,
psuAuthenticationRequired,
online
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.adorsys.opba.fintech.impl.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.adorsys.opba.fintech.impl.config.FintechUiConfig;
import de.adorsys.opba.fintech.impl.controller.utils.LoTRetrievalInformation;
import de.adorsys.opba.fintech.impl.controller.utils.RestRequestContext;
Expand All @@ -13,6 +14,7 @@
import de.adorsys.opba.fintech.impl.tppclients.TppAisClient;
import de.adorsys.opba.tpp.ais.api.model.generated.TransactionsResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -39,10 +41,12 @@ public class TransactionService {
private final RedirectHandlerService redirectHandlerService;
private final ConsentRepository consentRepository;
private final HandleAcceptedService handleAcceptedService;
private final ObjectMapper mapper;

@SuppressWarnings("checkstyle:MethodLength") // FIXME - It is just too many lines of text
@SneakyThrows
public ResponseEntity listTransactions(SessionEntity sessionEntity, String fintechOkUrl, String fintechNOkUrl, String bankId,
String accountId, LocalDate dateFrom, LocalDate dateTo, String entryReferenceFrom,
String accountId, String createConsentIfNone, LocalDate dateFrom, LocalDate dateTo, String entryReferenceFrom,
String bookingStatus, Boolean deltaList, LoTRetrievalInformation loTRetrievalInformation,
Boolean psuAuthenticationRequired, Boolean online) {
log.info("LoT {}", loTRetrievalInformation);
Expand Down Expand Up @@ -71,6 +75,7 @@ public ResponseEntity listTransactions(SessionEntity sessionEntity, String finte
bankId,
psuAuthenticationRequired,
optionalConsent.map(ConsentEntity::getTppServiceSessionId).orElse(null),
createConsentIfNone,
dateFrom,
dateTo,
entryReferenceFrom,
Expand Down
Loading

0 comments on commit d4c4405

Please sign in to comment.