From 8cbbe6a0e3960b1da8ee68ba3d5162e9e67f6463 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 1 Oct 2024 09:53:02 +0530 Subject: [PATCH 01/15] Adding consent enforcement implementation --- .../repository/components/lib/jjwt-0.12.6.jar | Bin 0 -> 2341 bytes .../constant/FinancialServicesConstants.java | 5 + .../pom.xml | 4 + .../consent/ConsentEnforcementExecutor.java | 317 ++++++++++++++++++ .../gateway/internal/GatewayDataHolder.java | 55 +++ pom.xml | 6 + 6 files changed, 387 insertions(+) create mode 100644 financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/lib/jjwt-0.12.6.jar create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/lib/jjwt-0.12.6.jar b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/lib/jjwt-0.12.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..da40c1ea9a665d41e0f36531ca382f9f9b326490 GIT binary patch literal 2341 zcmWIWW@h1HVBlb2=t;X8!GHuffoxyb5Jz24KR5jVpfVAlG7hk^M^)T*8yFcF?lUtm z07VhXd>#Ef-CTo1^nBf>2A}S`Wgv3b{gi#o%GVbBUW)goCWf#ziQ zjmCn1e_1I`p4oJ(OJ~oH^Tp>aZyKo1yLjV)^)E!oqqR$>hK3Si6wAA-u z#+IzY{I8s%g15el^-eI0n4@{3>F=Z)S!ZkWmv3FNA;4zF1kFwlp`)8Md@oh}GkjYm zGF`%E<0ri%{H8|Td*Z+COsZ_$kf^`nff4_{6VDW`oK3yIFHM9iTif~Y2ZfxAZ#>tX z{&3?~a79V}|DF~9s$TLYe!s%1?dO>BY2Pm=IsR*ZL`rhX51qUBaEDNe&b!whUy6-< zW3Cj=F-$g}Wxe%6ZfE*O8{f;BM*fRVTI3n;$dH*G<6X18x$Wf46M>R7k9XuoAF8VD zSjWF`&lBGnYvttTZ|irwbhA7oZqdo?z?*WHHcaZT?zk*6(=kKq-IDgrd9Su_ad=$K zaE9S}S@8Ll<5Qy%`F{z|^ZXqx9udHY8<=bheL)}eoIk5JE_#3@V9{m))|`~C~B zko&)RK{1RyA@C!|aBgB*Y92T_U{@@UsW>xVFRM5|uRJxWBtILkW);Gkv$D!dkW!-t zFwvIkcQn=jQ=%&{#cC62LP376UPW%s(a`((x6K6VzQ-3{{gNiOLeNV&X z>tv6d+hixYrDWPmk0owF(|Q;FzFoeg{)D5gj&#%+eYW@etn13Ze-zmtH=T7&fb_yA zsRid2F$65r5;oe;BeyJnmp$A1$$o!Ax8G*`A-pbR>qVvbh#ehzN;_)j>?b9bSanEZsl%IZxf)I2Li8rhHk28#|lc*L|`yTh%rx-$9FetD~0^&l%Uo zhSwt6+)hrbPOTD%S;o=3fxG^q9`9$*d+#T$nEABjxk%u}%(@Dm2p_XpALIWD%Fwwpa_C~MB%_kBmVh82H( zwfUTYgp<3Sq+TC!3UvF@W6l@)Dst}wqxC$+T#tC~UR}+9vAtuhub9f*zR5u`t9ZKS zUJ;(J!Z-hB*^Vv|h1^H{_a*R;8zPs+Qzd3%>c5+m;-*eSJ<-ZhlaQ zs=ndQrM`@N<#-IAzhqlB%Ud}nZO1V#PsvGZw#OV_*q*O!Y_d{ps&vRE?lXKDY~`#) zm(pTW?<~44v#XR_Mex$;z|2^o=qd}9@;9hmUln7nlHUAgavuXe??s4BVlmc5)Q9s234ys^@J&cB8!I(*T`_nmfs z_&aaQR@tRF)|aIIFP-7X)fN6I|BJ}HU7xx?7F5495#%@Da@+9Y)-8*USlp5OufNV) zh_SU+ZP(hF`}P^QKHew$g;B%IcE5Ri9AB=sS+FT%P+m@>{)+Z5Gbgcyui0Ar=lCw2 z!aq62Kh`gqf8>|<1iMW?*k8~YQ`*(aT^9Ik`aa#6E+N5KqEp2TM-R2b4lY?B29!A*pNU& p1Uq`NK^R=ch7oA!{suc0WH2HT1$eUp^)WE810f&KX^iY39stW@dq@BP literal 0 HcmV?d00001 diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java index ccc7707d..cb33e13b 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java @@ -86,5 +86,10 @@ public class FinancialServicesConstants { public static final String REQUEST_ROUTER = "Gateway.RequestRouter"; public static final String GATEWAY_CACHE_EXPIRY = "Gateway.Cache.GatewayCache.CacheAccessExpiry"; public static final String GATEWAY_CACHE_MODIFIED_EXPIRY = "Gateway.Cache.GatewayCache.CacheModifiedExpiry"; + public static final String CONSENT_VALIDATION_ENDPOINT = "Gateway.ConsentValidationEndpoint"; + public static final String KEYSTORE_LOCATION_TAG = "Security.InternalKeyStore.Location"; + public static final String KEYSTORE_PASSWORD_TAG = "Security.InternalKeyStore.Password"; + public static final String SIGNING_ALIAS_TAG = "Security.InternalKeyStore.KeyAlias"; + public static final String SIGNING_KEY_PASSWORD = "Security.InternalKeyStore.KeyPassword"; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml index 86ea23cf..42bbebf7 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml @@ -78,6 +78,10 @@ mockito-testng test + + io.jsonwebtoken + jjwt + diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java new file mode 100644 index 00000000..bfa40267 --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.impl.consent; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.jsonwebtoken.Jwts; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.json.JSONObject; +import org.wso2.financial.services.accelerator.common.constant.FinancialServicesConstants; +import org.wso2.financial.services.accelerator.common.constant.FinancialServicesErrorCodes; +import org.wso2.financial.services.accelerator.common.exception.FinancialServicesException; +import org.wso2.financial.services.accelerator.common.util.Generated; +import org.wso2.financial.services.accelerator.gateway.executor.core.FinancialServicesGatewayExecutor; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSExecutorError; +import org.wso2.financial.services.accelerator.gateway.internal.GatewayDataHolder; +import org.wso2.financial.services.accelerator.gateway.util.GatewayConstants; +import org.wso2.financial.services.accelerator.gateway.util.GatewayUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Consent Enforcement executor. + */ +public class ConsentEnforcementExecutor implements FinancialServicesGatewayExecutor { + + protected static final String ERROR_TITLE = "Consent Enforcement Error"; + protected static final String HEADERS_TAG = "headers"; + protected static final String BODY_TAG = "body"; + protected static final String CONTEXT_TAG = "context"; + protected static final String RESOURCE_TAG = "resource"; + protected static final String ELECTED_RESOURCE_TAG = "electedResource"; + protected static final String HTTP_METHOD = "httpMethod"; + protected static final String CONSENT_ID_TAG = "consentId"; + protected static final String USER_ID_TAG = "userId"; + protected static final String CLIENT_ID_TAG = "clientId"; + protected static final String RESOURCE_PARAMS = "resourceParams"; + private static final Log log = LogFactory.getLog(ConsentEnforcementExecutor.class); + private static final GatewayDataHolder dataHolder = GatewayDataHolder.getInstance(); + private static final String INFO_HEADER_TAG = "Account-Request-Information"; + private static final String IS_VALID = "isValid"; + private static final String ERROR_CODE = "errorCode"; + private static final String ERROR_MESSAGE = "errorMessage"; + private static final String HTTP_CODE = "httpCode"; + private static final String MODIFIED_PAYLOAD = "modifiedPayload"; + private static final String CONSENT_INFO = "consentInformation"; + private static volatile String consentValidationEndpoint; + private static volatile Key key; + + /** + * Method to handle request. + * + * @param fsapiRequestContext FS request context object + */ + @Generated(message = "Unit testable components are covered") + @Override + public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { + + } + + /** + * Method to handle post request. + * + * @param fsapiRequestContext FS request context object + */ + @Override + public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { + // Consent ID is required for consent enforcement. If the consent ID is null, we are assume this is a + // pre-consent creation call. Therefore consent enforcement is not required. + if (fsapiRequestContext.isError() || fsapiRequestContext.getConsentId() == null) { + return; + } + + Map requestHeaders = fsapiRequestContext.getMsgInfo().getHeaders(); + Map additionalParams = new HashMap<>(); + additionalParams.put(ELECTED_RESOURCE_TAG, fsapiRequestContext.getMsgInfo().getElectedResource()); + additionalParams.put(CONSENT_ID_TAG, fsapiRequestContext.getConsentId()); + additionalParams.put(USER_ID_TAG, fsapiRequestContext.getApiRequestInfo().getUsername()); + additionalParams.put(CLIENT_ID_TAG, fsapiRequestContext.getApiRequestInfo().getConsumerKey()); + additionalParams.put(RESOURCE_PARAMS, getResourceParamMap(fsapiRequestContext)); + + JSONObject validationRequest; + if (StringUtils.isNotBlank(fsapiRequestContext.getModifiedPayload())) { + validationRequest = createValidationRequestPayload(requestHeaders, + fsapiRequestContext.getModifiedPayload(), additionalParams); + } else { + validationRequest = createValidationRequestPayload(requestHeaders, + fsapiRequestContext.getRequestPayload(), additionalParams); + } + String enforcementJWTPayload = generateJWT(validationRequest.toString()); + JSONObject jsonResponse; + try { + String response = invokeConsentValidationService(enforcementJWTPayload); + jsonResponse = new JSONObject(response); + } catch (IOException | FinancialServicesException e) { + handleError(fsapiRequestContext, FinancialServicesErrorCodes.CONSENT_VALIDATION_REQUEST_FAILURE, + e.getMessage(), FinancialServicesErrorCodes.SERVER_ERROR_CODE); + return; + } + + boolean isValid = (boolean) jsonResponse.get(IS_VALID); + if (!isValid) { + String errorCode = jsonResponse.get(ERROR_CODE).toString(); + String errorMessage = jsonResponse.get(ERROR_MESSAGE).toString(); + String httpCode = jsonResponse.get(HTTP_CODE).toString(); + fsapiRequestContext.setError(true); + handleError(fsapiRequestContext, errorCode, errorMessage, httpCode); + return; + } else if (!jsonResponse.isNull(MODIFIED_PAYLOAD)) { + Object modifiedPayloadObj = jsonResponse.get(MODIFIED_PAYLOAD); + if (modifiedPayloadObj != null) { + fsapiRequestContext.setModifiedPayload(modifiedPayloadObj.toString()); + } + } else if (!jsonResponse.isNull(CONSENT_INFO)) { + Object consentInformationObj = jsonResponse.get(CONSENT_INFO); + if (consentInformationObj != null) { + requestHeaders.put(INFO_HEADER_TAG, consentInformationObj.toString()); + fsapiRequestContext.setAddedHeaders(requestHeaders); + } + } + } + + /** + * Method to handle response. + * + * @param fsapiResponseContext FS response context object + */ + @Override + public void preProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + } + + /** + * Method to handle post response. + * + * @param fsapiResponseContext FS response context object + */ + @Override + public void postProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + } + + private static String getValidationEndpoint() { + + if (consentValidationEndpoint == null) { + synchronized (ConsentEnforcementExecutor.class) { + if (consentValidationEndpoint == null) { + consentValidationEndpoint = dataHolder + .getFinancialServicesConfigurationService().getConfigurations() + .get(FinancialServicesConstants.CONSENT_VALIDATION_ENDPOINT).toString(); + } + } + } + return consentValidationEndpoint; + + } + + /** + * Method to obtain signing key. + * + * @return Key as an Object. + */ + @SuppressFBWarnings("PATH_TRAVERSAL_IN") + // Suppressed content - dataHolder.getKeyStoreLocation() + // Suppression reason - False Positive : Keystore location is obtained from deployment.toml. So it can be marked + // as a trusted filepath + // Suppressed warning count - 1 + protected static Key getJWTSigningKey() { + + if (key == null) { + synchronized (ConsentEnforcementExecutor.class) { + if (key == null) { + try (FileInputStream is = new FileInputStream(dataHolder.getKeyStoreLocation())) { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(is, dataHolder.getKeyStorePassword()); + key = keystore.getKey(dataHolder.getKeyAlias(), dataHolder.getKeyPassword().toCharArray()); + } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException + | UnrecoverableKeyException e) { + log.error("Error occurred while retrieving private key from keystore ", e); + } + } + } + } + return key; + } + + /** + * Method to generate JWT. + * @param payload Payload to be signed + * @return Signed JWT + */ + protected String generateJWT(String payload) { + + return Jwts.builder() + .content(payload) + .signWith(getJWTSigningKey()) + .compact(); + } + + /** + * Method to invoke consent validation service when the JWT payload is provided. + * + * @param enforcementJWTPayload JWT Payload + * @return Response as a String + * @throws IOException When failed to invoke the validation endpoint or failed to parse the response. + */ + @Generated(message = "Ignoring from unit tests since this method require calling external component to function") + private String invokeConsentValidationService(String enforcementJWTPayload) throws IOException, + FinancialServicesException { + + HttpPost httpPost = new HttpPost(getValidationEndpoint()); + StringEntity params; + params = new StringEntity(enforcementJWTPayload); + httpPost.setEntity(params); + httpPost.setHeader(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JWT_CONTENT_TYPE); + String userName = GatewayUtils.getAPIMgtConfig(GatewayConstants.API_KEY_VALIDATOR_USERNAME); + String password = GatewayUtils.getAPIMgtConfig(GatewayConstants.API_KEY_VALIDATOR_PASSWORD); + httpPost.setHeader(GatewayConstants.AUTH_HEADER, GatewayUtils.getBasicAuthHeader(userName, password)); + HttpResponse response = GatewayDataHolder.getHttpClient().execute(httpPost); + InputStream in = response.getEntity().getContent(); + return IOUtils.toString(in, String.valueOf(StandardCharsets.UTF_8)); + } + + /** + * Method to handle errors. + * + * @param fsapiRequestContext API Context + * @param errorCode Error Code + * @param errorMessage Error Message + * @param httpCode HTTP status code ( in 4XX range) + */ + protected void handleError(FSAPIRequestContext fsapiRequestContext, String errorCode, String errorMessage, + String httpCode) { + + fsapiRequestContext.setError(true); + ArrayList errors = fsapiRequestContext.getErrors(); + errors.add(new FSExecutorError(errorCode, ERROR_TITLE, errorMessage, httpCode)); + fsapiRequestContext.setErrors(errors); + fsapiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, httpCode); + } + + /** + * Method to create validation payload. + * + * @param requestHeaders Request headers of original request + * @param requestPayload Request payload of original request + * @return JSON Object with added attributes. + */ + protected JSONObject createValidationRequestPayload(Map requestHeaders, String requestPayload, + Map additionalParams) { + + JSONObject validationRequest = new JSONObject(); + JSONObject headers = new JSONObject(); + requestHeaders.forEach(headers::put); + validationRequest.put(HEADERS_TAG, headers); + /*requestContextDTO.getMsgInfo().getPayloadHandler().consumeAsString() method sets the request payload as a + null string, hence adding string null check to the validation*/ + if (requestPayload != null && !requestPayload.isEmpty() && !requestPayload.equals("null")) { + //This assumes all input payloads are in Content-Type : Application/JSON + validationRequest.put(BODY_TAG, new JSONObject(requestPayload)); + } + additionalParams.forEach(validationRequest::put); + return validationRequest; + } + + /** + * Method to construct resource parameter map to invoke the validation service. + * + * @param fsapiRequestContext FS request context object + * @return A Map containing resource path(ex: /aisp/accounts/{AccountId}?queryParam=urlEncodedQueryParamValue), + * http method and context(ex: /open-banking/v3.1/aisp) + */ + private Map getResourceParamMap(FSAPIRequestContext fsapiRequestContext) { + + Map resourceMap = new HashMap<>(); + resourceMap.put(RESOURCE_TAG, fsapiRequestContext.getMsgInfo().getResource()); + resourceMap.put(HTTP_METHOD, fsapiRequestContext.getMsgInfo().getHttpMethod()); + resourceMap.put(CONTEXT_TAG, fsapiRequestContext.getApiRequestInfo().getContext()); + + return resourceMap; + } +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/internal/GatewayDataHolder.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/internal/GatewayDataHolder.java index e3b7b1b4..fdd17b93 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/internal/GatewayDataHolder.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/internal/GatewayDataHolder.java @@ -20,6 +20,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService; +import org.wso2.carbon.base.ServerConfiguration; import org.wso2.financial.services.accelerator.common.config.FinancialServicesConfigurationService; import org.wso2.financial.services.accelerator.common.constant.FinancialServicesConstants; import org.wso2.financial.services.accelerator.common.exception.FinancialServicesException; @@ -28,6 +29,7 @@ import org.wso2.financial.services.accelerator.gateway.cache.GatewayCache; import org.wso2.financial.services.accelerator.gateway.executor.core.AbstractRequestRouter; +import java.util.Arrays; import java.util.Map; /** @@ -43,6 +45,10 @@ public class GatewayDataHolder { private int gatewayCacheModifiedExpiry; private APIManagerConfigurationService apiManagerConfigurationService; private AbstractRequestRouter requestRouter; + private String keyStoreLocation; + private char[] keyStorePassword; + private String keyAlias; + private String keyPassword; private GatewayDataHolder() { @@ -135,6 +141,55 @@ public void setGatewayCacheModifiedExpiry(String expTime) { this.gatewayCacheModifiedExpiry = expTime == null ? 60 : Integer.parseInt(expTime); } + public String getKeyStoreLocation() { + + return keyStoreLocation == null ? ServerConfiguration.getInstance() + .getFirstProperty(FinancialServicesConstants.KEYSTORE_LOCATION_TAG) : keyStoreLocation; + } + + public void setKeyStoreLocation(String keyStoreLocation) { + + this.keyStoreLocation = keyStoreLocation; + } + + public char[] getKeyStorePassword() { + + if (this.keyStorePassword == null) { + this.keyStorePassword = ServerConfiguration.getInstance() + .getFirstProperty(FinancialServicesConstants.KEYSTORE_PASSWORD_TAG).toCharArray(); + } + return Arrays.copyOf(this.keyStorePassword, this.keyStorePassword.length); + } + + public void setKeyStorePassword(char[] keyStorePassword) { + + if (keyStorePassword != null) { + this.keyStorePassword = Arrays.copyOf(keyStorePassword, keyStorePassword.length); + } + } + + public String getKeyAlias() { + + return keyAlias == null ? ServerConfiguration.getInstance() + .getFirstProperty(FinancialServicesConstants.SIGNING_ALIAS_TAG) : keyAlias; + } + + public void setKeyAlias(String keyAlias) { + + this.keyAlias = keyAlias; + } + + public String getKeyPassword() { + + return keyPassword == null ? ServerConfiguration.getInstance() + .getFirstProperty(FinancialServicesConstants.SIGNING_KEY_PASSWORD) : keyPassword; + } + + public void setKeyPassword(String keyPassword) { + + this.keyPassword = keyPassword; + } + public void setApiManagerConfiguration(APIManagerConfigurationService apiManagerConfigurationService) { this.apiManagerConfigurationService = apiManagerConfigurationService; diff --git a/pom.xml b/pom.xml index 9b0ef11f..b0ed7db8 100644 --- a/pom.xml +++ b/pom.xml @@ -443,6 +443,11 @@ swagger-parser ${swagger.parser.version} + + io.jsonwebtoken + jjwt + ${jjwt.version} + org.wso2.carbon @@ -593,6 +598,7 @@ 9.0.11 2.0.1.Final 2.0.24 + 0.12.6 7.0.75 [7.0.75, 8.0.0) From b8ce7b604eedd089e20031b3ab9a1bac06ae07a1 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Mon, 7 Oct 2024 12:14:46 +0530 Subject: [PATCH 02/15] Gateway implementation --- .../conf/templates/repository/conf/financial-services.xml.j2 | 2 +- .../apis/Accounts/accounts-dynamic-endpoint-insequence.xml | 4 ++-- pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 index ccaf14d6..27afbeb0 100644 --- a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 +++ b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 @@ -39,7 +39,7 @@ org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter {% endif %} - {% for type in financial_services.gateway.openbanking_gateway_executors.type %} + {% for type in financial_services.gateway.gateway_executors.type %} <{{type.name}}> {% for executor in type.executors %} diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml index 54919e9f..7d466883 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml @@ -23,11 +23,11 @@ - + -

+
diff --git a/pom.xml b/pom.xml index b0ed7db8..8dc38d2d 100644 --- a/pom.xml +++ b/pom.xml @@ -605,8 +605,8 @@ 7.0.26 [6.13.0, 7.0.62) 2.5.10 - 9.29.120 - [9.29.120, 9.29.121) + 9.30.10 + [9.29.120, 9.30.100) 0.8.6 5.3.1 From 1f7c31d5ac249b8bda41d1b06957f09fbee59262 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 15 Oct 2024 11:33:26 +0530 Subject: [PATCH 03/15] Adding gateway executor implementation --- .../components/dropins/jjwt-0.9.1.jar | Bin 0 -> 113618 bytes .../repository/components/lib/jjwt-0.12.6.jar | Bin 2341 -> 0 bytes .../repository/conf/financial-services.xml | 3 + .../repository/conf/financial-services.xml.j2 | 7 + .../accelerators/fs-apim/pom.xml | 5 +- .../apis/Accounts/account-info-swagger.yaml | 11188 ++-------------- .../accounts-dynamic-endpoint-insequence.xml | 4 +- .../pom.xml | 1 + .../common/ConsentExtensionConstants.java | 1 + .../manage/utils/ConsentManageUtils.java | 4 +- .../impl/DefaultConsentValidator.java | 29 +- .../pom.xml | 17 +- .../consent/ConsentEnforcementExecutor.java | 12 +- .../impl/consent/TestEnforcementExecutor.java | 186 + .../resources/test-validation-response.json | 12 + .../src/test/resources/testng.xml | 1 + .../src/test/resources/wso2carbon.jks | Bin 0 -> 98874 bytes pom.xml | 10 +- 18 files changed, 1081 insertions(+), 10399 deletions(-) create mode 100644 financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/dropins/jjwt-0.9.1.jar delete mode 100644 financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/lib/jjwt-0.12.6.jar create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/wso2carbon.jks diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/dropins/jjwt-0.9.1.jar b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/components/dropins/jjwt-0.9.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..905c80e9f4bd64d6b0c958bf0e56644b4423e1ed GIT binary patch literal 113618 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

bnXpV5iuewH{Y4i@(tM_~Dd$Dsl)?$`@F^+S}H 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 diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/conf/financial-services.xml b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/conf/financial-services.xml index b40fab63..fd0074fe 100644 --- a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/conf/financial-services.xml +++ b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/conf/financial-services.xml @@ -42,4 +42,7 @@ 2000 1500 + + consent_id + diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 index 27afbeb0..0d663fa2 100644 --- a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 +++ b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 @@ -76,4 +76,11 @@ {{financial_services.http_connection_pool.max_connections_per_route}} {% endif %} + + {% if financial_services.identity.consent_id_claim_name is defined %} + {{financial_services.identity.consent_id_claim_name}} + {% else %} + consent_id + {% endif %} + diff --git a/financial-services-accelerator/accelerators/fs-apim/pom.xml b/financial-services-accelerator/accelerators/fs-apim/pom.xml index 47dc76e1..3efb3a28 100644 --- a/financial-services-accelerator/accelerators/fs-apim/pom.xml +++ b/financial-services-accelerator/accelerators/fs-apim/pom.xml @@ -47,14 +47,11 @@ - ${project.basedir}/carbon-home/repository/components/lib + ${project.basedir}/carbon-home/repository/components/dropins **/jjwt-0.9.1.jar - - ${project.basedir}/carbon-home/repository/components/dropins - ${project.basedir}/carbon-home/repository/deployment/server/webapps diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml index 0d8b166b..45f61d34 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml @@ -17,14 +17,12 @@ schemes: - https consumes: - application/json; charset=utf-8 - - application/jose+jwe produces: - application/json; charset=utf-8 - - application/jose+jwe security: - default: [] paths: - /account-access-consents: + '/account-access-consents': post: tags: - Account Access @@ -140,6 +138,42 @@ paths: x-auth-type: Application x-throttling-tier: Unlimited x-scope: accounts + '/accounts': + get: + tags: + - Accounts + summary: Get Accounts + operationId: GetAccounts + parameters: + - $ref: '#/parameters/x-fapi-auth-date' + - $ref: '#/parameters/x-fapi-customer-ip-address' + - $ref: '#/parameters/x-fapi-interaction-id' + - $ref: '#/parameters/Authorization' + responses: + '200': + $ref: '#/responses/200AccountsRead' + '400': + $ref: '#/responses/400Error' + '401': + $ref: '#/responses/401Error' + '403': + $ref: '#/responses/403Error' + '405': + $ref: '#/responses/405Error' + '406': + $ref: '#/responses/406Error' + '429': + $ref: '#/responses/429Error' + '500': + $ref: '#/responses/500Error' + security: + - PSUOAuth2Security: + - accounts + - default: + - accounts + x-auth-type: Application User + x-throttling-tier: Unlimited + x-scope: accounts '/accounts/{AccountId}': get: tags: @@ -228,8 +262,6 @@ paths: - $ref: '#/parameters/x-fapi-customer-ip-address' - $ref: '#/parameters/x-fapi-interaction-id' - $ref: '#/parameters/Authorization' - - $ref: '#/parameters/FromBookingDateTimeParam' - - $ref: '#/parameters/ToBookingDateTimeParam' responses: '200': $ref: '#/responses/200AccountsAccountIdTransactionsRead' @@ -258,54 +290,6 @@ paths: x-throttling-tier: Unlimited x-scope: accounts parameters: - FromBookingDateTimeParam: - in: query - name: fromBookingDateTime - type: string - format: date-time - description: >- - The UTC ISO 8601 Date Time to filter transactions FROM - - NB Time component is optional - set to 00:00:00 for just Date. - - If the Date Time contains a timezone, the ASPSP must ignore the timezone - component. - ToBookingDateTimeParam: - in: query - name: toBookingDateTime - type: string - format: date-time - description: >- - The UTC ISO 8601 Date Time to filter transactions TO - - NB Time component is optional - set to 00:00:00 for just Date. - - If the Date Time contains a timezone, the ASPSP must ignore the timezone - component. - FromStatementDateTimeParam: - in: query - name: fromStatementDateTime - type: string - format: date-time - description: >- - The UTC ISO 8601 Date Time to filter statements FROM - - NB Time component is optional - set to 00:00:00 for just Date. - - If the Date Time contains a timezone, the ASPSP must ignore the timezone - component. - ToStatementDateTimeParam: - in: query - name: toStatementDateTime - type: string - format: date-time - description: >- - The UTC ISO 8601 Date Time to filter statements TO - - NB Time component is optional - set to 00:00:00 for just Date. - - If the Date Time contains a timezone, the ASPSP must ignore the timezone - component. OBReadConsent1Param: name: OBReadConsent1Param in: body @@ -313,36 +297,12 @@ parameters: required: true schema: $ref: '#/definitions/OBReadConsent1' - ConsentId: - name: ConsentId - in: path - description: ConsentId - required: true - type: string - AccountId: - name: AccountId - in: path - description: AccountId - required: true - type: string - StatementId: - name: StatementId - in: path - description: StatementId - required: true - type: string Authorization: in: header name: Authorization type: string required: true description: 'An Authorisation Token as per https://tools.ietf.org/html/rfc6750' - x-customer-user-agent: - in: header - name: x-customer-user-agent - type: string - description: Indicates the user-agent that the PSU is using. - required: false x-fapi-customer-ip-address: in: header name: x-fapi-customer-ip-address @@ -365,28 +325,24 @@ parameters: ^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} (GMT|UTC)$ + ConsentId: + name: ConsentId + in: path + description: ConsentId + required: true + type: string + AccountId: + name: AccountId + in: path + description: AccountId + required: true + type: string x-fapi-interaction-id: in: header name: x-fapi-interaction-id type: string required: false description: An RFC4122 UID used as a correlation id. - x-idempotency-key: - name: x-idempotency-key - in: header - description: | - Every request will be processed only once per x-idempotency-key. The - Idempotency Key will be valid for 24 hours. - required: true - type: string - pattern: ^(?!\s)(.*)(\S)$ - maxLength: 40 - x-jws-signature: - in: header - name: x-jws-signature - type: string - required: true - description: A detached JWS signature of the body of the payload. responses: 201AccountAccessConsentsCreated: description: Account Access Consents Created @@ -417,7 +373,7 @@ responses: type: string description: An RFC4122 UID used as a correlation id. schema: - $ref: '#/definitions/OBReadAccount4' + $ref: '#/definitions/OBReadAccount5' 200AccountsAccountIdRead: description: Accounts Read headers: @@ -434,14 +390,6 @@ responses: description: An RFC4122 UID used as a correlation id. schema: $ref: '#/definitions/OBReadBalance1' - 200BalancesRead: - description: Balances Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadBalance1' 200AccountsAccountIdTransactionsRead: description: Transactions Read headers: @@ -450,174 +398,6 @@ responses: description: An RFC4122 UID used as a correlation id. schema: $ref: '#/definitions/OBReadTransaction5' - 200TransactionsRead: - description: Transactions Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadTransaction5' - 200AccountsAccountIdBeneficiariesRead: - description: Beneficiaries Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadBeneficiary3' - 200BeneficiariesRead: - description: Beneficiaries Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadBeneficiary3' - 200AccountsAccountIdDirectDebitsRead: - description: Direct Debits Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadDirectDebit1' - 200DirectDebitsRead: - description: Direct Debits Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadDirectDebit1' - 200AccountsAccountIdStandingOrdersRead: - description: Standing Orders Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadStandingOrder5' - 200StandingOrdersRead: - description: Standing Orders Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadStandingOrder5' - 200AccountsAccountIdProductRead: - description: Products Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadProduct2' - 200ProductsRead: - description: Products Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadProduct2' - 200AccountsAccountIdOffersRead: - description: Offers Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadOffer1' - 200OffersRead: - description: Offers Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadOffer1' - 200AccountsAccountIdPartiesRead: - description: Parties Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadParty3' - 200AccountsAccountIdPartyRead: - description: Parties Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadParty2' - 200PartyRead: - description: Parties Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadParty2' - 200AccountsAccountIdScheduledPaymentsRead: - description: Scheduled Payments Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadScheduledPayment2' - 200ScheduledPaymentsRead: - description: Scheduled Payments Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadScheduledPayment2' - 200AccountsAccountIdStatementsRead: - description: Statements Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadStatement2' - 200AccountsAccountIdStatementsStatementIdRead: - description: Statements Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadStatement2' - 200AccountsAccountIdStatementsStatementIdFileRead: - description: Statements Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/File' - 200AccountsAccountIdStatementsStatementIdTransactionsRead: - description: Transactions Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadTransaction5' - 200StatementsRead: - description: Statements Read - headers: - x-fapi-interaction-id: - type: string - description: An RFC4122 UID used as a correlation id. - schema: - $ref: '#/definitions/OBReadStatement2' 400Error: description: Bad request headers: @@ -712,301 +492,178 @@ securityDefinitions: x-scopes-bindings: accounts: Internal/subscriber definitions: - AccountId: - description: >- - A unique and immutable identifier used to identify the account resource. - This identifier has no meaning to the account owner. - type: string - minLength: 1 - maxLength: 40 - ActiveOrHistoricCurrencyCode_0: - description: >- - Identification of the currency in which the account is held. - - Usage: Currency should only be used in case one and the same account - number covers several currencies - - and the initiating party needs to identify which currency needs to be used - for settlement on the account. - type: string - pattern: '^[A-Z]{3,3}$' - ActiveOrHistoricCurrencyCode_1: - description: >- - A code allocated to a currency by a Maintenance Agency under an - international identification scheme, as described in the latest edition of - the international standard ISO 4217 "Codes for the representation of - currencies and funds". - type: string - pattern: '^[A-Z]{3,3}$' - AddressLine: - description: >- - Information that locates and identifies a specific address for a - transaction entry, that is presented in free format text. - type: string - minLength: 1 - maxLength: 70 - BeneficiaryId: - description: >- - A unique and immutable identifier used to identify the beneficiary - resource. This identifier has no meaning to the account owner. - type: string - minLength: 1 - maxLength: 40 - BookingDateTime: - description: >- - Date and time when a transaction entry is posted to an account on the - account servicer's books. - - Usage: Booking date is the expected booking date, unless the status is - booked, in which case it is the actual booking date.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - BuildingNumber: - description: Number that identifies the position of a building on a street. - type: string - minLength: 1 - maxLength: 16 - CountryCode: - description: 'Nation with its own government, occupying a particular territory.' - type: string - pattern: '^[A-Z]{2,2}$' - CountrySubDivision: - description: 'Identifies a subdivision of a country eg, state, region, county.' - type: string - minLength: 1 - maxLength: 35 - CreationDateTime: - description: >- - Date and time at which the resource was created.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - DateTime: - description: >- - Date and time associated with the date time type.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - Description_0: - description: Specifies the description of the account type. - type: string - minLength: 1 - maxLength: 35 - Description_1: - description: Description that may be available for the statement fee. - type: string - minLength: 1 - maxLength: 128 - Description_2: - description: Description that may be available for the statement interest. - type: string - minLength: 1 - maxLength: 128 - Description_3: - description: Description to describe the purpose of the code - type: string - minLength: 1 - maxLength: 350 - EmailAddress: - description: Address for electronic mail (e-mail). - type: string - minLength: 1 - maxLength: 256 - EndDateTime: - description: >- - Date and time at which the statement period ends.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - File: + OBReadConsent1: type: object - properties: {} - FinalPaymentDateTime: - description: >- - The date on which the final payment for a Standing Order schedule will be - made.All dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - FirstPaymentDateTime: - description: >- - The date on which the first payment for a Standing Order schedule will be - made.All dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - Frequency_0: - description: >- - Individual Definitions: - - EvryDay - Every day - - EvryWorkgDay - Every working day - - IntrvlWkDay - An interval specified in weeks (01 to 09), and the day - within the week (01 to 07) - - WkInMnthDay - A monthly interval, specifying the week of the month (01 to - 05) and day within the week (01 to 07) - - IntrvlMnthDay - An interval specified in months (between 01 to 06, 12, - 24), specifying the day within the month (-5 to -1, 1 to 31) - - QtrDay - Quarterly (either ENGLISH, SCOTTISH, or RECEIVED) - - ENGLISH = Paid on the 25th March, 24th June, 29th September and 25th - December. + required: + - Data + - Risk + properties: + Data: + $ref: '#/definitions/OBReadData1' + Risk: + $ref: '#/definitions/OBRisk2' + OBReadConsentResponse1: + type: object + required: + - Data + - Risk + properties: + Data: + type: object + required: + - ConsentId + - CreationDateTime + - StatusUpdateDateTime + - Permissions + properties: + ConsentId: + description: >- + Unique identification as assigned to identify the account access + consent resource. + type: string + minLength: 1 + maxLength: 128 + CreationDateTime: + $ref: '#/definitions/CreationDateTime' + Status: + $ref: '#/definitions/OBExternalRequestStatus1Code' + StatusUpdateDateTime: + $ref: '#/definitions/StatusUpdateDateTime' + Permissions: + type: array + items: + $ref: '#/definitions/OBExternalPermissions1Code' + minItems: 1 + ExpirationDateTime: + description: >- + Specified date and time the permissions will expire. - SCOTTISH = Paid on the 2nd February, 15th May, 1st August and 11th - November. + If this is not populated, the permissions will be open ended.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - RECEIVED = Paid on the 20th March, 19th June, 24th September and 20th - December. + All date-time fields in responses must include the timezone. An + example is below: - Individual Patterns: + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + TransactionFromDateTime: + description: >- + Specified start date and time for the transaction query period. - EvryDay (ScheduleCode) + If this is not populated, the start date will be open ended, and + data will be returned from the earliest available transaction.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - EvryWorkgDay (ScheduleCode) + All date-time fields in responses must include the timezone. An + example is below: - IntrvlWkDay:IntervalInWeeks:DayInWeek (ScheduleCode + IntervalInWeeks + - DayInWeek) + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + TransactionToDateTime: + description: >- + Specified end date and time for the transaction query period. - WkInMnthDay:WeekInMonth:DayInWeek (ScheduleCode + WeekInMonth + DayInWeek) + If this is not populated, the end date will be open ended, and + data will be returned to the latest available transaction.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - IntrvlMnthDay:IntervalInMonths:DayInMonth (ScheduleCode + IntervalInMonths - + DayInMonth) + All date-time fields in responses must include the timezone. An + example is below: - QtrDay: + either (ENGLISH, SCOTTISH or RECEIVED) ScheduleCode + QuarterDay + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + Risk: + $ref: '#/definitions/OBRisk2' + Links: + $ref: '#/definitions/Links' + Meta: + $ref: '#/definitions/Meta' + OBReadData1: + type: object + required: + - Permissions + properties: + Permissions: + type: array + items: + $ref: '#/definitions/OBExternalPermissions1Code' + minItems: 1 + description: >- + Specifies the Open Banking account access data types. This is a list + of the data clusters being consented by the PSU, and requested for + authorisation with the ASPSP. + ExpirationDateTime: + description: >- + Specified date and time the permissions will expire. - The regular expression for this element combines five smaller versions for - each permitted pattern. To aid legibility - the components are presented - individually here: + If this is not populated, the permissions will be open ended.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - EvryDay + All date-time fields in responses must include the timezone. An + example is below: - EvryWorkgDay + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + TransactionFromDateTime: + description: >- + Specified start date and time for the transaction query period. - IntrvlWkDay:0[1-9]:0[1-7] + If this is not populated, the start date will be open ended, and + data will be returned from the earliest available transaction.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - WkInMnthDay:0[1-5]:0[1-7] + All date-time fields in responses must include the timezone. An + example is below: - IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]) + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + TransactionToDateTime: + description: >- + Specified end date and time for the transaction query period. - QtrDay:(ENGLISH|SCOTTISH|RECEIVED) + If this is not populated, the end date will be open ended, and + data will be returned to the latest available transaction.All + dates in the JSON payloads are represented in ISO 8601 date-time + format. - Full Regular Expression: + All date-time fields in responses must include the timezone. An + example is below: - ^(EvryDay)$|^(EvryWorkgDay)$|^(IntrvlWkDay:0[1-9]:0[1-7])$|^(WkInMnthDay:0[1-5]:0[1-7])$|^(IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]))$|^(QtrDay:(ENGLISH|SCOTTISH|RECEIVED))$ - type: string - pattern: >- - ^(EvryDay)$|^(EvryWorkgDay)$|^(IntrvlWkDay:0[1-9]:0[1-7])$|^(WkInMnthDay:0[1-5]:0[1-7])$|^(IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]))$|^(QtrDay:(ENGLISH|SCOTTISH|RECEIVED))$ - Frequency_1: + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + OBRisk2: description: >- - Individual Definitions: - - EvryDay - Every day - - EvryWorkgDay - Every working day - - IntrvlWkDay - An interval specified in weeks (01 to 09), and the day - within the week (01 to 07) - - WkInMnthDay - A monthly interval, specifying the week of the month (01 to - 05) and day within the week (01 to 07) - - IntrvlMnthDay - An interval specified in months (between 01 to 06, 12, - 24), specifying the day within the month (-5 to -1, 1 to 31) - - QtrDay - Quarterly (either ENGLISH, SCOTTISH, or RECEIVED) - - ENGLISH = Paid on the 25th March, 24th June, 29th September and 25th - December. - - SCOTTISH = Paid on the 2nd February, 15th May, 1st August and 11th - November. - - RECEIVED = Paid on the 20th March, 19th June, 24th September and 20th - December. - - Individual Patterns: - - EvryDay (ScheduleCode) - - EvryWorkgDay (ScheduleCode) - - IntrvlWkDay:IntervalInWeeks:DayInWeek (ScheduleCode + IntervalInWeeks + - DayInWeek) - - WkInMnthDay:WeekInMonth:DayInWeek (ScheduleCode + WeekInMonth + DayInWeek) - - IntrvlMnthDay:IntervalInMonths:DayInMonth (ScheduleCode + IntervalInMonths - + DayInMonth) - - QtrDay: + either (ENGLISH, SCOTTISH or RECEIVED) ScheduleCode + QuarterDay - - The regular expression for this element combines five smaller versions for - each permitted pattern. To aid legibility - the components are presented - individually here: - - EvryDay - - EvryWorkgDay - - IntrvlWkDay:0[1-9]:0[1-7] - - WkInMnthDay:0[1-5]:0[1-7] - - IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]) - - QtrDay:(ENGLISH|SCOTTISH|RECEIVED) - - Full Regular Expression: - - ^(EvryDay)$|^(EvryWorkgDay)$|^(IntrvlWkDay:0[1-9]:0[1-7])$|^(WkInMnthDay:0[1-5]:0[1-7])$|^(IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]))$|^(QtrDay:(ENGLISH|SCOTTISH|RECEIVED))$ - type: string - pattern: >- - ^(EvryDay)$|^(EvryWorkgDay)$|^(IntrvlDay:((0[2-9])|([1-2][0-9])|3[0-1]))$|^(IntrvlWkDay:0[1-9]:0[1-7])$|^(WkInMnthDay:0[1-5]:0[1-7])$|^(IntrvlMnthDay:(0[1-6]|12|24):(-0[1-5]|0[1-9]|[12][0-9]|3[01]))$|^(QtrDay:(ENGLISH|SCOTTISH|RECEIVED))$ - FullLegalName: - description: Specifies a character string with a maximum length of 350 characters. + The Risk section is sent by the initiating party to the ASPSP. It is used + to specify additional details for risk scoring for Account Info. + type: object + properties: {} + additionalProperties: false + OBExternalPermissions1Code: + description: >- + Specifies the Open Banking account access data types. This is a + list of the data clusters being consented by the PSU, and + requested for authorisation with the ASPSP. type: string - minLength: 1 - maxLength: 350 - ISODateTime: + enum: + - ReadAccountsBasic + - ReadAccountsDetail + - ReadBalances + - ReadTransactionsDetail + CreationDateTime: description: >- - All dates in the JSON payloads are represented in ISO 8601 date-time - format. + Date and time at which the resource was created.All dates in the JSON + payloads are represented in ISO 8601 date-time format. All date-time fields in responses must include the timezone. An example is below: @@ -1014,94 +671,10 @@ definitions: 2017-04-05T10:43:07+00:00 type: string format: date-time - Identification_0: + StatusUpdateDateTime: description: >- - Identification assigned by an institution to identify an account. This - identification is known by the account owner. - type: string - minLength: 1 - maxLength: 256 - Identification_1: - description: Unique and unambiguous identification of the servicing institution. - type: string - minLength: 1 - maxLength: 35 - Identification_2: - description: >- - Unique and unambiguous identification of a financial institution or a - branch of a financial institution. - type: string - minLength: 1 - maxLength: 35 - Links: - type: object - description: Links relevant to the payload - properties: - Self: - type: string - format: uri - First: - type: string - format: uri - Prev: - type: string - format: uri - Next: - type: string - format: uri - Last: - type: string - format: uri - additionalProperties: false - required: - - Self - Meta: - title: MetaData - type: object - description: Meta Data relevant to the payload - properties: - TotalPages: - type: integer - format: int32 - FirstAvailableDateTime: - $ref: '#/definitions/ISODateTime' - LastAvailableDateTime: - $ref: '#/definitions/ISODateTime' - additionalProperties: false - Name_0: - description: >- - The account name is the name or names of the account owner(s) represented - at an account level, as displayed by the ASPSP's online channels. - - Note, the account name is not the product name or the nickname of the - account. - type: string - minLength: 1 - maxLength: 70 - Name_1: - description: >- - Name by which a party is known and which is usually used to identify that - party. - type: string - minLength: 1 - maxLength: 70 - Name_2: - description: >- - Name by which an agent is known and which is usually used to identify that - agent. - type: string - minLength: 1 - maxLength: 140 - Name_3: - description: Long name associated with the code - type: string - minLength: 1 - maxLength: 70 - NextPaymentDateTime: - description: >- - The date on which the next payment for a Standing Order schedule will be - made.All dates in the JSON payloads are represented in ISO 8601 date-time - format. + Date and time at which the resource status was updated.All dates in the + JSON payloads are represented in ISO 8601 date-time format. All date-time fields in responses must include the timezone. An example is below: @@ -1109,133 +682,47 @@ definitions: 2017-04-05T10:43:07+00:00 type: string format: date-time - Nickname: - description: >- - The nickname of the account, assigned by the account owner in order to - provide an additional means of identification of the account. + OBExternalRequestStatus1Code: + description: Specifies the status of consent resource in code form. type: string - minLength: 1 - maxLength: 70 - Number_0: - description: >- - Indicates whether the advertised overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. if it�s part of a government - scheme, or whether the rate may vary dependent on the applicant�s - circumstances. - type: integer - Number_1: - description: >- - fee/charges are captured dependent on the number of occurrences rather - than capped at a particular amount - type: integer - OBAccount3: - type: object - description: >- - Unambiguous identification of the account to which credit and debit - entries are made. - required: - - AccountId - - Currency - - AccountType - - AccountSubType - properties: - AccountId: - $ref: '#/definitions/AccountId' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' - AccountType: - $ref: '#/definitions/OBExternalAccountType1Code' - AccountSubType: - $ref: '#/definitions/OBExternalAccountSubType1Code' - Description: - $ref: '#/definitions/Description_0' - Nickname: - $ref: '#/definitions/Nickname' - Account: - type: array - items: - type: object - description: Provides the details to identify an account. - required: - - SchemeName - - Identification - properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' - Identification: - $ref: '#/definitions/Identification_0' - Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - Servicer: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_0' - OBAccount3Basic: + enum: + - Authorised + - AwaitingAuthorisation + - Rejected + - Revoked + OBReadAccount4: type: object - description: >- - Unambiguous identification of the account to which credit and debit - entries are made. required: - - AccountId - - Currency - - AccountType - - AccountSubType + - Data properties: - AccountId: - $ref: '#/definitions/AccountId' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' - AccountType: - $ref: '#/definitions/OBExternalAccountType1Code' - AccountSubType: - $ref: '#/definitions/OBExternalAccountSubType1Code' - Description: - $ref: '#/definitions/Description_0' - Nickname: - $ref: '#/definitions/Nickname' - OBAccount3Detail: + Data: + type: object + properties: + Account: + type: array + items: + $ref: '#/definitions/OBAccount4' + Links: + $ref: '#/definitions/Links' + Meta: + $ref: '#/definitions/Meta' + OBReadAccount5: type: object - description: >- - Unambiguous identification of the account to which credit and debit - entries are made. required: - - AccountId - - Currency - - AccountType - - AccountSubType - - Account + - Data properties: - AccountId: - $ref: '#/definitions/AccountId' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' - AccountType: - $ref: '#/definitions/OBExternalAccountType1Code' - AccountSubType: - $ref: '#/definitions/OBExternalAccountSubType1Code' - Description: - $ref: '#/definitions/Description_0' - Nickname: - $ref: '#/definitions/Nickname' - Account: - type: array - items: - type: object - description: Provides the details to identify an account. - required: - - SchemeName - - Identification - properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' - Identification: - $ref: '#/definitions/Identification_0' - Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - Servicer: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_0' + Data: + type: object + properties: + Account: + type: array + items: + $ref: '#/definitions/OBAccount4' + Links: + $ref: '#/definitions/Links' + Meta: + $ref: '#/definitions/Meta' + additionalProperties: false OBAccount4: type: object description: >- @@ -1254,13 +741,13 @@ definitions: StatusUpdateDateTime: $ref: '#/definitions/StatusUpdateDateTime' Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' AccountType: $ref: '#/definitions/OBExternalAccountType1Code' AccountSubType: $ref: '#/definitions/OBExternalAccountSubType1Code' Description: - $ref: '#/definitions/Description_0' + $ref: '#/definitions/Description' Nickname: $ref: '#/definitions/Nickname' Account: @@ -1275,232 +762,310 @@ definitions: SchemeName: $ref: '#/definitions/OBExternalAccountIdentification4Code' Identification: - $ref: '#/definitions/Identification_0' + $ref: '#/definitions/Identification' Name: - $ref: '#/definitions/Name_0' + $ref: '#/definitions/Name' SecondaryIdentification: $ref: '#/definitions/SecondaryIdentification' Servicer: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_0' - OBAccount4Basic: - type: object - description: >- - Unambiguous identification of the account to which credit and debit - entries are made. - required: - - AccountId - - Currency - - AccountType - - AccountSubType - properties: - AccountId: - $ref: '#/definitions/AccountId' - Status: - $ref: '#/definitions/OBAccountStatus1Code' - StatusUpdateDateTime: - $ref: '#/definitions/StatusUpdateDateTime' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' - AccountType: - $ref: '#/definitions/OBExternalAccountType1Code' - AccountSubType: - $ref: '#/definitions/OBExternalAccountSubType1Code' - Description: - $ref: '#/definitions/Description_0' - Nickname: - $ref: '#/definitions/Nickname' - OBAccount4Detail: + $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5' + OBReadBalance1: type: object - description: >- - Unambiguous identification of the account to which credit and debit - entries are made. required: - - AccountId - - Currency - - AccountType - - AccountSubType - - Account + - Data properties: - AccountId: - $ref: '#/definitions/AccountId' - Status: - $ref: '#/definitions/OBAccountStatus1Code' - StatusUpdateDateTime: - $ref: '#/definitions/StatusUpdateDateTime' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_0' - AccountType: - $ref: '#/definitions/OBExternalAccountType1Code' - AccountSubType: - $ref: '#/definitions/OBExternalAccountSubType1Code' - Description: - $ref: '#/definitions/Description_0' - Nickname: - $ref: '#/definitions/Nickname' - Account: - type: array - items: - type: object - description: Provides the details to identify an account. - required: - - SchemeName - - Identification - properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' - Identification: - $ref: '#/definitions/Identification_0' - Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - Servicer: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_0' - OBAccountStatus1Code: - description: Specifies the status of account resource in code form. - type: string - enum: - - Deleted - - Disabled - - Enabled - - Pending - - ProForma - OBActiveCurrencyAndAmount_SimpleType: - description: >- - A number of monetary units specified in an active currency where the unit - of currency is explicit and compliant with ISO 4217. - type: string - pattern: '^\d{1,13}\.\d{1,5}$' - OBActiveOrHistoricCurrencyAndAmount_0: - type: object - required: - - Amount - - Currency - description: The amount of the first Standing Order - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_1: - type: object - required: - - Amount - - Currency - description: The amount of the next Standing Order. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_2: - type: object - required: - - Amount - - Currency - description: The amount of the final Standing Order - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_3: - type: object - required: - - Amount - - Currency - description: Amount of money associated with the statement benefit type. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_4: - type: object - required: - - Amount - - Currency - description: Amount of money associated with the statement fee type. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_5: - type: object - required: - - Amount - - Currency - description: Amount of money associated with the statement interest amount type. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_6: + Data: + type: object + required: + - Balance + properties: + Balance: + type: array + items: + type: object + description: Set of elements used to define the balance details. + required: + - AccountId + - CreditDebitIndicator + - Type + - DateTime + - Amount + properties: + AccountId: + $ref: '#/definitions/AccountId' + CreditDebitIndicator: + $ref: '#/definitions/OBCreditDebitCode' + Type: + $ref: '#/definitions/OBBalanceType1Code' + DateTime: + description: >- + Indicates the date (and time) of the balance.All dates in + the JSON payloads are represented in ISO 8601 date-time + format. + + All date-time fields in responses must include the timezone. + An example is below: + + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + Amount: + type: object + required: + - Amount + - Currency + description: Amount of money of the cash balance. + properties: + Amount: + $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' + Currency: + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' + CreditLine: + type: array + items: + type: object + description: >- + Set of elements used to provide details on the credit + line. + required: + - Included + properties: + Included: + description: >- + Indicates whether or not the credit line is included + in the balance of the account. + + Usage: If not present, credit line is not included in + the balance amount of the account. + type: boolean + Type: + description: 'Limit type, in a coded form.' + type: string + enum: + - Available + - Credit + - Emergency + - Pre-Agreed + - Temporary + Amount: + type: object + required: + - Amount + - Currency + description: Amount of money of the credit line. + properties: + Amount: + $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' + Currency: + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' + minItems: 1 + Links: + $ref: '#/definitions/Links' + Meta: + $ref: '#/definitions/Meta' + OBReadTransaction5: type: object required: - - Amount - - Currency - description: Amount of money associated with the amount type. + - Data properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_7: + Data: + $ref: '#/definitions/OBReadDataTransaction5' + Links: + $ref: '#/definitions/Links' + Meta: + $ref: '#/definitions/Meta' + OBReadDataTransaction5: type: object - required: - - Amount - - Currency - description: Amount of money in the cash transaction entry. properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_8: + Transaction: + type: array + items: + $ref: '#/definitions/OBTransaction5' + OBTransaction5: type: object + description: Provides further details on an entry in the report. required: + - AccountId + - CreditDebitIndicator + - Status + - BookingDateTime - Amount - - Currency - description: Transaction charges to be paid by the charge bearer. properties: + AccountId: + $ref: '#/definitions/AccountId' + TransactionId: + $ref: '#/definitions/TransactionId' + TransactionReference: + $ref: '#/definitions/TransactionReference' + StatementReference: + type: array + items: + $ref: '#/definitions/StatementReference' + CreditDebitIndicator: + $ref: '#/definitions/OBCreditDebitCode' + Status: + $ref: '#/definitions/OBEntryStatus1Code' + BookingDateTime: + $ref: '#/definitions/BookingDateTime' + ValueDateTime: + $ref: '#/definitions/ValueDateTime' + TransactionInformation: + $ref: '#/definitions/TransactionInformation' + AddressLine: + $ref: '#/definitions/AddressLine' Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBActiveOrHistoricCurrencyAndAmount_9: - type: object - required: - - Amount - - Currency + $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount' + ChargeAmount: + $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount' + CurrencyExchange: + $ref: '#/definitions/OBCurrencyExchange5' + BankTransactionCode: + $ref: '#/definitions/OBBankTransactionCodeStructure1' + ProprietaryBankTransactionCode: + $ref: '#/definitions/ProprietaryBankTransactionCodeStructure1' + Balance: + $ref: '#/definitions/OBTransactionCashBalance' + MerchantDetails: + $ref: '#/definitions/OBMerchantDetails1' + CreditorAgent: + $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6' + CreditorAccount: + $ref: '#/definitions/OBCashAccount6' + DebtorAgent: + $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6' + DebtorAccount: + $ref: '#/definitions/OBCashAccount6' + CardInstrument: + $ref: '#/definitions/OBTransactionCardInstrument1' + SupplementaryData: + $ref: '#/definitions/OBSupplementaryData1' + AccountId: + description: >- + A unique and immutable identifier used to identify the account resource. + This identifier has no meaning to the account owner. + type: string + minLength: 1 + maxLength: 40 + OBAccountStatus1Code: + description: Specifies the status of account resource in code form. + type: string + enum: + - Deleted + - Disabled + - Enabled + - Pending + - ProForma + ActiveOrHistoricCurrencyCode: description: >- - Amount of money to be moved between the debtor and creditor, before - deduction of charges, expressed in the currency as ordered by the - initiating party. + Identification of the currency in which the account is held. - Usage: This amount has to be transported unchanged through the transaction - chain. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBAddressTypeCode: - description: Identifies the nature of the postal address. + Usage: Currency should only be used in case one and the same account + number covers several currencies + + and the initiating party needs to identify which currency needs to be used + for settlement on the account. + type: string + pattern: '^[A-Z]{3,3}$' + OBExternalAccountType1Code: + description: Specifies the type of account (personal or business). type: string enum: - Business - - Correspondence - - DeliveryTo - - MailTo - - POBox - - Postal - - Residential - - Statement - OBBalanceType1Code: - description: 'Balance type, in a coded form.' + - Personal + OBExternalAccountSubType1Code: + description: Specifies the sub type of account (product family group). + type: string + enum: + - ChargeCard + - CreditCard + - CurrentAccount + - EMoney + - Loan + - Mortgage + - PrePaidCard + - Savings + Description: + description: Specifies the description of the account type. + type: string + minLength: 1 + maxLength: 35 + Nickname: + description: >- + The nickname of the account, assigned by the account owner in order to + provide an additional means of identification of the account. + type: string + minLength: 1 + maxLength: 70 + OBExternalAccountIdentification4Code: + description: >- + Name of the identification scheme, in a coded form as published in an + external list. + type: string + x-namespaced-enum: + - UK.OBIE.BBAN + - UK.OBIE.IBAN + - UK.OBIE.PAN + - UK.OBIE.Paym + - UK.OBIE.SortCodeAccountNumber + Identification: + description: >- + Identification assigned by an institution to identify an account. This + identification is known by the account owner. + type: string + minLength: 1 + maxLength: 256 + Name: + description: >- + The account name is the name or names of the account owner(s) represented + at an account level, as displayed by the ASPSP's online channels. + + Note, the account name is not the product name or the nickname of the + account. + type: string + minLength: 1 + maxLength: 70 + SecondaryIdentification: + description: >- + This is secondary identification of the account, as assigned by the + account servicing institution. + + This can be used by building societies to additionally identify accounts + with a roll number (in addition to a sort code and account number + combination). + type: string + minLength: 1 + maxLength: 34 + OBBranchAndFinancialInstitutionIdentification5: + type: object + required: + - SchemeName + - Identification + description: >- + Party that manages the account on behalf of the account owner, that is + manages the registration and booking of entries on the account, calculates + balances on the account and provides information about the account. + properties: + SchemeName: + $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' + Identification: + $ref: '#/definitions/Identification' + OBExternalFinancialInstitutionIdentification4Code: + description: >- + Name of the identification scheme, in a coded form as published in an + external list. + type: string + x-namespaced-enum: + - UK.OBIE.BICFI + OBCreditDebitCode: + description: |- + Indicates whether the balance is a credit or a debit balance. + Usage: A zero balance is considered to be a credit balance. + type: string + enum: + - Credit + - Debit + OBBalanceType1Code: + description: 'Balance type, in a coded form.' type: string enum: - ClosingAvailable @@ -1516,6 +1081,167 @@ definitions: - OpeningBooked - OpeningCleared - PreviouslyClosedBooked + OBActiveCurrencyAndAmount_SimpleType: + description: >- + A number of monetary units specified in an active currency where the unit + of currency is explicit and compliant with ISO 4217. + type: string + pattern: '^\d{1,13}\.\d{1,5}$' + TransactionId: + description: >- + Unique identifier for the transaction within an servicing institution. + This identifier is both unique and immutable. + type: string + minLength: 1 + maxLength: 210 + TransactionInformation: + description: |- + Further details of the transaction. + This is the transaction narrative, which is unstructured text. + type: string + minLength: 1 + maxLength: 500 + TransactionReference: + description: >- + Unique reference for the transaction. This reference is optionally + populated, and may as an example be the FPID in the Faster Payments + context. + type: string + minLength: 1 + maxLength: 35 + StatementReference: + description: >- + Unique reference for the statement. This reference may be optionally + populated if available. + type: string + minLength: 1 + maxLength: 35 + OBEntryStatus1Code: + description: Status of a transaction entry on the books of the account servicer. + type: string + enum: + - Booked + - Pending + BookingDateTime: + description: >- + Date and time when a transaction entry is posted to an account on the + account servicer's books. + + Usage: Booking date is the expected booking date, unless the status is + booked, in which case it is the actual booking date.All dates in the JSON + payloads are represented in ISO 8601 date-time format. + + All date-time fields in responses must include the timezone. An example is + below: + + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + ValueDateTime: + description: >- + Date and time at which assets become available to the account owner in + case of a credit entry, or cease to be available to the account owner in + case of a debit transaction entry. + + Usage: If transaction entry status is pending and value date is present, + then the value date refers to an expected/requested value date. + + For transaction entries subject to availability/float and for which + availability information is provided, the value date must not be used. In + this case the availability component identifies the number of availability + days.All dates in the JSON payloads are represented in ISO 8601 date-time + format. + + All date-time fields in responses must include the timezone. An example is + below: + + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + AddressLine: + description: >- + Information that locates and identifies a specific address for a + transaction entry, that is presented in free format text. + type: string + minLength: 1 + maxLength: 70 + OBActiveOrHistoricCurrencyAndAmount: + type: object + required: + - Amount + - Currency + description: Amount of money in the cash transaction entry. + properties: + Amount: + $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' + Currency: + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' + OBCurrencyExchange5: + type: object + required: + - SourceCurrency + - ExchangeRate + description: Set of elements used to provide details on the currency exchange. + properties: + SourceCurrency: + description: >- + Currency from which an amount is to be converted in a currency + conversion. + type: string + pattern: '^[A-Z]{3,3}$' + TargetCurrency: + description: >- + Currency into which an amount is to be converted in a currency + conversion. + type: string + pattern: '^[A-Z]{3,3}$' + UnitCurrency: + description: >- + Currency in which the rate of exchange is expressed in a currency + exchange. In the example 1GBP = xxxCUR, the unit currency is GBP. + type: string + pattern: '^[A-Z]{3,3}$' + ExchangeRate: + description: >- + Factor used to convert an amount from one currency into another. This + reflects the price at which one currency was bought with another + currency. + + Usage: ExchangeRate expresses the ratio between UnitCurrency and + QuotedCurrency (ExchangeRate = UnitCurrency/QuotedCurrency). + type: number + ContractIdentification: + description: >- + Unique identification to unambiguously identify the foreign exchange + contract. + type: string + minLength: 1 + maxLength: 35 + QuotationDate: + description: >- + Date and time at which an exchange rate is quoted.All dates in the + JSON payloads are represented in ISO 8601 date-time format. + + All date-time fields in responses must include the timezone. An + example is below: + + 2017-04-05T10:43:07+00:00 + type: string + format: date-time + InstructedAmount: + type: object + required: + - Amount + - Currency + description: >- + Amount of money to be moved between the debtor and creditor, before + deduction of charges, expressed in the currency as ordered by the + initiating party. + properties: + Amount: + $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' + Currency: + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' OBBankTransactionCodeStructure1: type: object required: @@ -1531,324 +1257,183 @@ definitions: SubCode: description: Specifies the sub-product family within a specific family. type: string - OBBeneficiary3: + ProprietaryBankTransactionCodeStructure1: type: object + required: + - Code + description: Set of elements to fully identify a proprietary bank transaction code. properties: - AccountId: - $ref: '#/definitions/AccountId' - BeneficiaryId: - $ref: '#/definitions/BeneficiaryId' - Reference: - $ref: '#/definitions/Reference' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_2' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_1' - OBBeneficiary3Basic: - type: object - properties: - AccountId: - $ref: '#/definitions/AccountId' - BeneficiaryId: - $ref: '#/definitions/BeneficiaryId' - Reference: - $ref: '#/definitions/Reference' - OBBeneficiary3Detail: - type: object - properties: - AccountId: - $ref: '#/definitions/AccountId' - BeneficiaryId: - $ref: '#/definitions/BeneficiaryId' - Reference: - $ref: '#/definitions/Reference' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_2' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_1' - required: - - CreditorAccount - OBBranchAndFinancialInstitutionIdentification5_0: + Code: + description: >- + Proprietary bank transaction code to identify the underlying + transaction. + type: string + minLength: 1 + maxLength: 35 + Issuer: + description: Identification of the issuer of the proprietary bank transaction code. + type: string + minLength: 1 + maxLength: 35 + OBMerchantDetails1: type: object - required: - - SchemeName - - Identification - description: >- - Party that manages the account on behalf of the account owner, that is - manages the registration and booking of entries on the account, calculates - balances on the account and provides information about the account. + description: Details of the merchant involved in the transaction. properties: - SchemeName: - $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' - Identification: - $ref: '#/definitions/Identification_1' - OBBranchAndFinancialInstitutionIdentification5_1: + MerchantName: + description: Name by which the merchant is known. + type: string + minLength: 1 + maxLength: 350 + MerchantCategoryCode: + description: >- + Category code conform to ISO 18245, related to the type of services or + goods the merchant provides for the transaction. + type: string + minLength: 3 + maxLength: 4 + OBTransactionCashBalance: type: object required: - - SchemeName - - Identification + - CreditDebitIndicator + - Type + - Amount description: >- - Party that manages the account on behalf of the account owner, that is - manages the registration and booking of entries on the account, calculates - balances on the account and provides information about the account. - - This is the servicer of the beneficiary account. + Set of elements used to define the balance as a numerical representation + of the net increases and decreases in an account after a transaction entry + is applied to the account. properties: - SchemeName: - $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' - Identification: - $ref: '#/definitions/Identification_1' - OBBranchAndFinancialInstitutionIdentification6_0: + CreditDebitIndicator: + $ref: '#/definitions/OBCreditDebitCode' + Type: + $ref: '#/definitions/OBBalanceType1Code' + Amount: + type: object + required: + - Amount + - Currency + description: >- + Amount of money of the cash balance after a transaction entry is + applied to the account.. + properties: + Amount: + $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' + Currency: + $ref: '#/definitions/ActiveOrHistoricCurrencyCode' + OBBranchAndFinancialInstitutionIdentification6: type: object description: Financial institution servicing an account for the creditor. properties: SchemeName: $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' Identification: - $ref: '#/definitions/Identification_2' - Name: - $ref: '#/definitions/Name_2' - PostalAddress: - $ref: '#/definitions/OBPostalAddress6' - OBBranchAndFinancialInstitutionIdentification6_1: - type: object - description: Financial institution servicing an account for the debtor. - properties: - SchemeName: - $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' - Identification: - $ref: '#/definitions/Identification_2' + $ref: '#/definitions/Identification' Name: - $ref: '#/definitions/Name_2' + $ref: '#/definitions/Name' PostalAddress: - $ref: '#/definitions/OBPostalAddress6' - OBBranchAndFinancialInstitutionIdentification6_2: + type: object + OBCashAccount6: type: object description: >- - Party that manages the account on behalf of the account owner, that is - manages the registration and booking of entries on the account, calculates - balances on the account and provides information about the account. - - This is the servicer of the beneficiary account. - properties: - SchemeName: - $ref: '#/definitions/OBExternalFinancialInstitutionIdentification4Code' - Identification: - $ref: '#/definitions/Identification_1' - Name: - $ref: '#/definitions/Name_2' - PostalAddress: - $ref: '#/definitions/OBPostalAddress6' - OBCashAccount5_0: - type: object - required: - - SchemeName - - Identification - description: Provides the details to identify the beneficiary account. + Unambiguous identification of the account of the creditor, in the case of + a debit transaction. properties: SchemeName: $ref: '#/definitions/OBExternalAccountIdentification4Code' Identification: - description: Beneficiary account identification. - type: string - minLength: 1 - maxLength: 256 + $ref: '#/definitions/Identification' Name: - $ref: '#/definitions/Name_0' + $ref: '#/definitions/Name' SecondaryIdentification: $ref: '#/definitions/SecondaryIdentification' - OBCashAccount5_1: + OBTransactionCardInstrument1: type: object required: - - SchemeName - - Identification - description: Provides the details to identify the beneficiary account. + - CardSchemeName + description: Set of elements to describe the card instrument used in the transaction. properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' - Identification: - $ref: '#/definitions/Identification_0' + CardSchemeName: + description: Name of the card scheme. + type: string + enum: + - AmericanExpress + - Diners + - Discover + - MasterCard + - VISA + AuthorisationType: + description: The card authorisation type. + type: string + enum: + - ConsumerDevice + - Contactless + - None + - PIN Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - OBCashAccount6_0: - type: object - description: >- - Unambiguous identification of the account of the creditor, in the case of - a debit transaction. - properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' + description: Name of the cardholder using the card instrument. + type: string + minLength: 1 + maxLength: 70 Identification: - $ref: '#/definitions/Identification_0' - Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - OBCashAccount6_1: + description: >- + Identification assigned by an institution to identify the card + instrument used in the transaction. This identification is known by + the account owner, and may be masked. + type: string + minLength: 1 + maxLength: 34 + OBSupplementaryData1: type: object + properties: {} + additionalProperties: true description: >- - Unambiguous identification of the account of the debtor, in the case of a - crebit transaction. - properties: - SchemeName: - $ref: '#/definitions/OBExternalAccountIdentification4Code' - Identification: - $ref: '#/definitions/Identification_0' - Name: - $ref: '#/definitions/Name_0' - SecondaryIdentification: - $ref: '#/definitions/SecondaryIdentification' - OBCreditDebitCode_0: - description: |- - Indicates whether the amount is a credit or a debit. - Usage: A zero amount is considered to be a credit amount. - type: string - enum: - - Credit - - Debit - OBCreditDebitCode_1: - description: Indicates whether the transaction is a credit or a debit entry. - type: string - enum: - - Credit - - Debit - OBCreditDebitCode_2: - description: |- - Indicates whether the balance is a credit or a debit balance. - Usage: A zero balance is considered to be a credit balance. - type: string - enum: - - Credit - - Debit - OBCurrencyExchange5: + Additional information that can not be captured in the structured fields + and/or any other specific block. + Links: type: object - required: - - SourceCurrency - - ExchangeRate - description: Set of elements used to provide details on the currency exchange. + description: Links relevant to the payload properties: - SourceCurrency: - description: >- - Currency from which an amount is to be converted in a currency - conversion. + Self: type: string - pattern: '^[A-Z]{3,3}$' - TargetCurrency: - description: >- - Currency into which an amount is to be converted in a currency - conversion. + format: uri + First: type: string - pattern: '^[A-Z]{3,3}$' - UnitCurrency: - description: >- - Currency in which the rate of exchange is expressed in a currency - exchange. In the example 1GBP = xxxCUR, the unit currency is GBP. + format: uri + Prev: type: string - pattern: '^[A-Z]{3,3}$' - ExchangeRate: - description: >- - Factor used to convert an amount from one currency into another. This - reflects the price at which one currency was bought with another - currency. - - Usage: ExchangeRate expresses the ratio between UnitCurrency and - QuotedCurrency (ExchangeRate = UnitCurrency/QuotedCurrency). - type: number - ContractIdentification: - description: >- - Unique identification to unambiguously identify the foreign exchange - contract. + format: uri + Next: type: string - minLength: 1 - maxLength: 35 - QuotationDate: + format: uri + Last: + type: string + format: uri + additionalProperties: false + required: + - Self + Meta: + title: MetaData + type: object + description: Meta Data relevant to the payload + properties: + TotalPages: + type: integer + format: int32 + additionalProperties: false + OBError1: + type: object + properties: + ErrorCode: + description: 'Low level textual error code, e.g., UK.OBIE.Field.Missing' + type: string + Message: description: >- - Date and time at which an exchange rate is quoted.All dates in the - JSON payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An - example is below: + A description of the error that occurred. e.g., 'A mandatory field + isn't supplied' or 'RequestedExecutionDateTime must be in future' - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - InstructedAmount: - type: object - required: - - Amount - - Currency - description: >- - Amount of money to be moved between the debtor and creditor, before - deduction of charges, expressed in the currency as ordered by the - initiating party. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OBEntryStatus1Code: - description: Status of a transaction entry on the books of the account servicer. - type: string - enum: - - Booked - - Pending - OBError1: - type: object - properties: - ErrorCode: - description: 'Low level textual error code, e.g., UK.OBIE.Field.Missing' - type: string - x-namespaced-enum: - - UK.OBIE.Field.Expected - - UK.OBIE.Field.Invalid - - UK.OBIE.Field.InvalidDate - - UK.OBIE.Field.Missing - - UK.OBIE.Field.Unexpected - - UK.OBIE.Header.Invalid - - UK.OBIE.Header.Missing - - UK.OBIE.Reauthenticate - - UK.OBIE.Resource.ConsentMismatch - - UK.OBIE.Resource.InvalidConsentStatus - - UK.OBIE.Resource.InvalidFormat - - UK.OBIE.Resource.NotFound - - UK.OBIE.Rules.AfterCutOffDateTime - - UK.OBIE.Rules.DuplicateReference - - UK.OBIE.Signature.Invalid - - UK.OBIE.Signature.InvalidClaim - - UK.OBIE.Signature.Malformed - - UK.OBIE.Signature.Missing - - UK.OBIE.Signature.MissingClaim - - UK.OBIE.Signature.Unexpected - - UK.OBIE.UnexpectedError - - UK.OBIE.Unsupported.AccountIdentifier - - UK.OBIE.Unsupported.AccountSecondaryIdentifier - - UK.OBIE.Unsupported.Currency - - UK.OBIE.Unsupported.Frequency - - UK.OBIE.Unsupported.LocalInstrument - - UK.OBIE.Unsupported.Scheme - Message: - description: >- - A description of the error that occurred. e.g., 'A mandatory field - isn't supplied' or 'RequestedExecutionDateTime must be in future' - - OBIE doesn't standardise this field - type: string - minLength: 1 - maxLength: 500 - Path: - description: >- - Recommended but optional reference to the JSON Path of the field with - error, e.g., Data.Initiation.InstructedAmount.Currency + OBIE doesn't standardise this field type: string minLength: 1 maxLength: 500 - Url: - description: >- - URL to help remediate the problem, or provide more information, or to - API Reference, or help etc - type: string required: - ErrorCode - Message @@ -1889,9147 +1474,6 @@ definitions: - Message - Errors additionalProperties: false - OBExternalAccountIdentification4Code: - description: >- - Name of the identification scheme, in a coded form as published in an - external list. - type: string - x-namespaced-enum: - - UK.OBIE.BBAN - - UK.OBIE.IBAN - - UK.OBIE.PAN - - UK.OBIE.Paym - - UK.OBIE.SortCodeAccountNumber - OBExternalAccountRole1Code: - description: A party�s role with respect to the related account. - type: string - x-namespaced-enum: - - UK.OBIE.Administrator - - UK.OBIE.Beneficiary - - UK.OBIE.CustodianForMinor - - UK.OBIE.Granter - - UK.OBIE.LegalGuardian - - UK.OBIE.OtherParty - - UK.OBIE.PowerOfAttorney - - UK.OBIE.Principal - - UK.OBIE.Protector - - UK.OBIE.RegisteredShareholderName - - UK.OBIE.SecondaryOwner - - UK.OBIE.SeniorManagingOfficial - - UK.OBIE.Settlor - - UK.OBIE.SuccessorOnDeath - OBExternalAccountSubType1Code: - description: Specifies the sub type of account (product family group). - type: string - enum: - - ChargeCard - - CreditCard - - CurrentAccount - - EMoney - - Loan - - Mortgage - - PrePaidCard - - Savings - OBExternalAccountType1Code: - description: Specifies the type of account (personal or business). - type: string - enum: - - Business - - Personal - OBExternalFinancialInstitutionIdentification4Code: - description: >- - Name of the identification scheme, in a coded form as published in an - external list. - type: string - x-namespaced-enum: - - UK.OBIE.BICFI - OBExternalLegalStructureType1Code: - description: Legal standing of the party. - type: string - x-namespaced-enum: - - UK.OBIE.CIC - - UK.OBIE.CIO - - UK.OBIE.Charity - - UK.OBIE.CoOp - - UK.OBIE.GeneralPartnership - - UK.OBIE.Individual - - UK.OBIE.LimitedLiabilityPartnership - - UK.OBIE.LimitedPartnership - - UK.OBIE.PrivateLimitedCompany - - UK.OBIE.PublicLimitedCompany - - UK.OBIE.ScottishLimitedPartnership - - UK.OBIE.Sole - OBExternalPartyType1Code: - description: 'Party type, in a coded form.' - type: string - enum: - - Delegate - - Joint - - Sole - OBExternalScheduleType1Code: - description: Specifies the scheduled payment date type requested - type: string - enum: - - Arrival - - Execution - OBExternalStandingOrderStatus1Code: - description: Specifies the status of the standing order in code form. - type: string - enum: - - Active - - Inactive - OBExternalStatementAmountType1Code: - description: 'Amount type, in a coded form.' - type: string - x-namespaced-enum: - - UK.OBIE.ArrearsClosingBalance - - UK.OBIE.AvailableBalance - - UK.OBIE.AverageBalanceWhenInCredit - - UK.OBIE.AverageBalanceWhenInDebit - - UK.OBIE.AverageDailyBalance - - UK.OBIE.BalanceTransferClosingBalance - - UK.OBIE.CashClosingBalance - - UK.OBIE.ClosingBalance - - UK.OBIE.CreditLimit - - UK.OBIE.CurrentPayment - - UK.OBIE.DirectDebitPaymentDue - - UK.OBIE.FSCSInsurance - - UK.OBIE.MinimumPaymentDue - - UK.OBIE.PendingTransactionsBalance - - UK.OBIE.PreviousClosingBalance - - UK.OBIE.PreviousPayment - - UK.OBIE.PurchaseClosingBalance - - UK.OBIE.StartingBalance - - UK.OBIE.TotalAdjustments - - UK.OBIE.TotalCashAdvances - - UK.OBIE.TotalCharges - - UK.OBIE.TotalCredits - - UK.OBIE.TotalDebits - - UK.OBIE.TotalPurchases - OBExternalStatementBenefitType1Code: - description: 'Benefit type, in a coded form.' - type: string - x-namespaced-enum: - - UK.OBIE.Cashback - - UK.OBIE.Insurance - - UK.OBIE.TravelDiscount - - UK.OBIE.TravelInsurance - OBExternalStatementDateTimeType1Code: - description: 'Date time type, in a coded form.' - type: string - x-namespaced-enum: - - UK.OBIE.BalanceTransferPromoEnd - - UK.OBIE.DirectDebitDue - - UK.OBIE.LastPayment - - UK.OBIE.LastStatement - - UK.OBIE.NextStatement - - UK.OBIE.PaymentDue - - UK.OBIE.PurchasePromoEnd - - UK.OBIE.StatementAvailable - OBExternalStatementFeeFrequency1Code: - description: How frequently the fee is applied to the Account. - type: string - x-namespaced-enum: - - UK.OBIE.ChargingPeriod - - UK.OBIE.PerTransactionAmount - - UK.OBIE.PerTransactionPercentage - - UK.OBIE.Quarterly - - UK.OBIE.StatementMonthly - - UK.OBIE.Weekly - OBExternalStatementFeeRateType1Code: - description: Description that may be available for the statement fee rate type. - type: string - x-namespaced-enum: - - UK.OBIE.AER - - UK.OBIE.EAR - OBExternalStatementFeeType1Code: - description: 'Fee type, in a coded form.' - type: string - x-namespaced-enum: - - UK.OBIE.Annual - - UK.OBIE.BalanceTransfer - - UK.OBIE.CashAdvance - - UK.OBIE.CashTransaction - - UK.OBIE.ForeignCashTransaction - - UK.OBIE.ForeignTransaction - - UK.OBIE.Gambling - - UK.OBIE.LatePayment - - UK.OBIE.MoneyTransfer - - UK.OBIE.Monthly - - UK.OBIE.Overlimit - - UK.OBIE.PostalOrder - - UK.OBIE.PrizeEntry - - UK.OBIE.StatementCopy - - UK.OBIE.Total - OBExternalStatementInterestFrequency1Code: - description: Specifies the statement fee type requested - type: string - x-namespaced-enum: - - UK.OBIE.Daily - - UK.OBIE.HalfYearly - - UK.OBIE.Monthly - - UK.OBIE.PerStatementDate - - UK.OBIE.Quarterly - - UK.OBIE.Weekly - - UK.OBIE.Yearly - OBExternalStatementInterestRateType1Code: - description: Description that may be available for the statement Interest rate type. - type: string - x-namespaced-enum: - - UK.OBIE.BOEBaseRate - - UK.OBIE.FixedRate - - UK.OBIE.Gross - - UK.OBIE.LoanProviderBaseRate - - UK.OBIE.Net - OBExternalStatementInterestType1Code: - description: 'Interest amount type, in a coded form.' - type: string - x-namespaced-enum: - - UK.OBIE.BalanceTransfer - - UK.OBIE.Cash - - UK.OBIE.EstimatedNext - - UK.OBIE.Purchase - - UK.OBIE.Total - OBExternalStatementRateType1Code: - description: Rate associated with the statement rate type. - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - OBExternalStatementType1Code: - description: 'Statement type, in a coded form.' - type: string - enum: - - AccountClosure - - AccountOpening - - Annual - - Interim - - RegularPeriodic - OBExternalStatementValueType1Code: - description: Value associated with the statement value type. - type: string - x-namespaced-enum: - - UK.OBIE.AirMilesPoints - - UK.OBIE.AirMilesPointsBalance - - UK.OBIE.Credits - - UK.OBIE.Debits - - UK.OBIE.HotelPoints - - UK.OBIE.HotelPointsBalance - - UK.OBIE.RetailShoppingPoints - - UK.OBIE.RetailShoppingPointsBalance - OBMerchantDetails1: - type: object - description: Details of the merchant involved in the transaction. - properties: - MerchantName: - description: Name by which the merchant is known. - type: string - minLength: 1 - maxLength: 350 - MerchantCategoryCode: - description: >- - Category code conform to ISO 18245, related to the type of services or - goods the merchant provides for the transaction. - type: string - minLength: 3 - maxLength: 4 - OBParty2: - type: object - required: - - PartyId - properties: - PartyId: - $ref: '#/definitions/PartyId' - PartyNumber: - $ref: '#/definitions/PartyNumber' - PartyType: - $ref: '#/definitions/OBExternalPartyType1Code' - Name: - $ref: '#/definitions/Name_1' - FullLegalName: - $ref: '#/definitions/FullLegalName' - LegalStructure: - $ref: '#/definitions/OBExternalLegalStructureType1Code' - BeneficialOwnership: - type: boolean - AccountRole: - $ref: '#/definitions/OBExternalAccountRole1Code' - EmailAddress: - $ref: '#/definitions/EmailAddress' - Phone: - $ref: '#/definitions/PhoneNumber_0' - Mobile: - $ref: '#/definitions/PhoneNumber_1' - Relationships: - $ref: '#/definitions/OBPartyRelationships1' - Address: - type: array - items: - type: object - description: Postal address of a party. - required: - - Country - properties: - AddressType: - $ref: '#/definitions/OBAddressTypeCode' - AddressLine: - type: array - items: - description: >- - Information that locates and identifies a specific address, as - defined by postal services, that is presented in free format - text. - type: string - minLength: 1 - maxLength: 70 - minItems: 0 - maxItems: 5 - StreetName: - $ref: '#/definitions/StreetName' - BuildingNumber: - $ref: '#/definitions/BuildingNumber' - PostCode: - $ref: '#/definitions/PostCode' - TownName: - $ref: '#/definitions/TownName' - CountrySubDivision: - $ref: '#/definitions/CountrySubDivision' - Country: - $ref: '#/definitions/CountryCode' - OBPartyRelationships1: - type: object - description: The Party's relationships with other resources. - properties: - Account: - type: object - required: - - Related - - Id - description: Relationship to the Account resource. - properties: - Related: - description: Absolute URI to the related resource. - type: string - format: uri - Id: - description: >- - Unique identification as assigned by the ASPSP to uniquely - identify the related resource. - type: string - minLength: 1 - maxLength: 40 - OBPostalAddress6: - type: object - description: >- - Information that locates and identifies a specific address, as defined by - postal services. - properties: - AddressType: - $ref: '#/definitions/OBAddressTypeCode' - Department: - description: Identification of a division of a large organisation or building. - type: string - minLength: 1 - maxLength: 70 - SubDepartment: - description: Identification of a sub-division of a large organisation or building. - type: string - minLength: 1 - maxLength: 70 - StreetName: - $ref: '#/definitions/StreetName' - BuildingNumber: - $ref: '#/definitions/BuildingNumber' - PostCode: - $ref: '#/definitions/PostCode' - TownName: - $ref: '#/definitions/TownName' - CountrySubDivision: - description: 'Identifies a subdivision of a country such as state, region, county.' - type: string - minLength: 1 - maxLength: 35 - Country: - description: Nation with its own government. - type: string - pattern: '^[A-Z]{2,2}$' - AddressLine: - type: array - items: - description: >- - Information that locates and identifies a specific address, as - defined by postal services, presented in free format text. - type: string - minLength: 1 - maxLength: 70 - minItems: 0 - maxItems: 7 - OBRate1_0: - description: >- - Rate charged for Statement Fee (where it is charged in terms of a rate - rather than an amount) - type: number - OBRate1_1: - description: >- - field representing a percentage (e.g. 0.05 represents 5% and 0.9525 - represents 95.25%). Note the number of decimal places may vary. - type: number - OBReadAccount3: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Account: - type: array - items: - $ref: '#/definitions/OBAccount3' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadAccount4: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Account: - type: array - items: - $ref: '#/definitions/OBAccount4' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadBalance1: - type: object - required: - - Data - properties: - Data: - type: object - required: - - Balance - properties: - Balance: - type: array - items: - type: object - description: Set of elements used to define the balance details. - required: - - AccountId - - CreditDebitIndicator - - Type - - DateTime - - Amount - properties: - AccountId: - $ref: '#/definitions/AccountId' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_2' - Type: - $ref: '#/definitions/OBBalanceType1Code' - DateTime: - description: >- - Indicates the date (and time) of the balance.All dates in - the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. - An example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - Amount: - type: object - required: - - Amount - - Currency - description: Amount of money of the cash balance. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - CreditLine: - type: array - items: - type: object - description: >- - Set of elements used to provide details on the credit - line. - required: - - Included - properties: - Included: - description: >- - Indicates whether or not the credit line is included - in the balance of the account. - - Usage: If not present, credit line is not included in - the balance amount of the account. - type: boolean - Type: - description: 'Limit type, in a coded form.' - type: string - enum: - - Available - - Credit - - Emergency - - Pre-Agreed - - Temporary - Amount: - type: object - required: - - Amount - - Currency - description: Amount of money of the credit line. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - minItems: 1 - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadBeneficiary3: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Beneficiary: - type: array - items: - $ref: '#/definitions/OBBeneficiary3' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadConsent1: - type: object - required: - - Data - - Risk - properties: - Data: - $ref: '#/definitions/OBReadData1' - Risk: - $ref: '#/definitions/OBRisk2' - OBReadConsentResponse1: - type: object - required: - - Data - - Risk - properties: - Data: - type: object - required: - - ConsentId - - CreationDateTime - - StatusUpdateDateTime - - Permissions - properties: - ConsentId: - description: >- - Unique identification as assigned to identify the account access - consent resource. - type: string - minLength: 1 - maxLength: 128 - CreationDateTime: - $ref: '#/definitions/CreationDateTime' - Status: - $ref: '#/definitions/OBExternalRequestStatus1Code' - StatusUpdateDateTime: - $ref: '#/definitions/StatusUpdateDateTime' - Permissions: - type: array - items: - $ref: '#/definitions/OBExternalPermissions1Code' - minItems: 1 - ExpirationDateTime: - description: >- - Specified date and time the permissions will expire. - - If this is not populated, the permissions will be open ended.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - TransactionFromDateTime: - description: >- - Specified start date and time for the transaction query period. - - If this is not populated, the start date will be open ended, and - data will be returned from the earliest available transaction.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - TransactionToDateTime: - description: >- - Specified end date and time for the transaction query period. - - If this is not populated, the end date will be open ended, and - data will be returned to the latest available transaction.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - Risk: - $ref: '#/definitions/OBRisk2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadDataStatement2: - type: object - properties: - Statement: - type: array - items: - $ref: '#/definitions/OBStatement2' - OBReadDataTransaction5: - type: object - properties: - Transaction: - type: array - items: - $ref: '#/definitions/OBTransaction5' - OBReadDirectDebit1: - type: object - required: - - Data - properties: - Data: - type: object - properties: - DirectDebit: - type: array - items: - type: object - description: Account to or from which a cash entry is made. - required: - - AccountId - - MandateIdentification - - Name - properties: - AccountId: - $ref: '#/definitions/AccountId' - DirectDebitId: - description: >- - A unique and immutable identifier used to identify the - direct debit resource. This identifier has no meaning to the - account owner. - type: string - minLength: 1 - maxLength: 40 - MandateIdentification: - description: >- - Direct Debit reference. For AUDDIS service users provide - Core Reference. For non AUDDIS service users provide Core - reference if possible or last used reference. - type: string - minLength: 1 - maxLength: 35 - DirectDebitStatusCode: - description: Specifies the status of the direct debit in code form. - type: string - enum: - - Active - - Inactive - Name: - description: Name of Service User. - type: string - minLength: 1 - maxLength: 70 - PreviousPaymentDateTime: - description: >- - Date of most recent direct debit collection.All dates in the - JSON payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. - An example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - PreviousPaymentAmount: - type: object - required: - - Amount - - Currency - description: The amount of the most recent direct debit collection. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadOffer1: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Offer: - type: array - items: - type: object - required: - - AccountId - properties: - AccountId: - $ref: '#/definitions/AccountId' - OfferId: - description: >- - A unique and immutable identifier used to identify the offer - resource. This identifier has no meaning to the account - owner. - type: string - minLength: 1 - maxLength: 40 - OfferType: - description: 'Offer type, in a coded form.' - type: string - enum: - - BalanceTransfer - - LimitIncrease - - MoneyTransfer - - Other - - PromotionalRate - Description: - description: Further details of the offer. - type: string - minLength: 1 - maxLength: 500 - StartDateTime: - description: >- - Date and time at which the offer starts.All dates in the - JSON payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. - An example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - EndDateTime: - description: >- - Date and time at which the offer ends.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. - An example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - Rate: - description: Rate associated with the offer type. - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - Value: - description: Value associated with the offer type. - type: integer - Term: - description: Further details of the term of the offer. - type: string - minLength: 1 - maxLength: 500 - URL: - description: >- - URL (Uniform Resource Locator) where documentation on the - offer can be found - type: string - minLength: 1 - maxLength: 256 - Amount: - type: object - required: - - Amount - - Currency - description: Amount of money associated with the offer type. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - Fee: - type: object - required: - - Amount - - Currency - description: Fee associated with the offer type. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadParty1: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Party: - type: object - required: - - PartyId - properties: - PartyId: - $ref: '#/definitions/PartyId' - PartyNumber: - $ref: '#/definitions/PartyNumber' - PartyType: - $ref: '#/definitions/OBExternalPartyType1Code' - Name: - $ref: '#/definitions/Name_1' - EmailAddress: - $ref: '#/definitions/EmailAddress' - Phone: - $ref: '#/definitions/PhoneNumber_0' - Mobile: - $ref: '#/definitions/PhoneNumber_1' - Address: - type: array - items: - type: object - description: Postal address of a party. - required: - - Country - properties: - AddressType: - $ref: '#/definitions/OBAddressTypeCode' - AddressLine: - type: array - items: - description: >- - Information that locates and identifies a specific - address, as defined by postal services, that is - presented in free format text. - type: string - minLength: 1 - maxLength: 70 - minItems: 0 - maxItems: 5 - StreetName: - $ref: '#/definitions/StreetName' - BuildingNumber: - $ref: '#/definitions/BuildingNumber' - PostCode: - $ref: '#/definitions/PostCode' - TownName: - $ref: '#/definitions/TownName' - CountrySubDivision: - $ref: '#/definitions/CountrySubDivision' - Country: - $ref: '#/definitions/CountryCode' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadParty2: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Party: - $ref: '#/definitions/OBParty2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadParty3: - type: object - required: - - Data - properties: - Data: - type: object - properties: - Party: - type: array - items: - $ref: '#/definitions/OBParty2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadProduct2: - type: object - required: - - Data - description: >- - Product details of Other Product which is not avaiable in the standard - list - properties: - Data: - type: object - description: Aligning with the read write specs structure. - properties: - Product: - type: array - items: - type: object - description: Product details associated with the Account - required: - - AccountId - - ProductType - properties: - ProductName: - description: >- - The name of the Product used for marketing purposes from a - customer perspective. I.e. what the customer would - recognise. - type: string - minLength: 1 - maxLength: 350 - ProductId: - description: >- - The unique ID that has been internally assigned by the - financial institution to each of the current account banking - products they market to their retail and/or small to medium - enterprise (SME) customers. - type: string - minLength: 1 - maxLength: 40 - AccountId: - description: Account Identification of the customer for Product Details - type: string - minLength: 1 - maxLength: 40 - SecondaryProductId: - description: >- - Any secondary Identification which supports Product - Identifier to uniquely identify the current account banking - products. - type: string - minLength: 1 - maxLength: 70 - ProductType: - description: >- - Product type : Personal Current Account, Business Current - Account - type: string - enum: - - BusinessCurrentAccount - - CommercialCreditCard - - Other - - PersonalCurrentAccount - - SMELoan - MarketingStateId: - description: >- - Unique and unambiguous identification of a Product - Marketing State. - type: string - minLength: 1 - maxLength: 35 - OtherProductType: - type: object - required: - - Name - - Description - description: Other product type details associated with the account. - properties: - Name: - description: Long name associated with the product - type: string - minLength: 1 - maxLength: 350 - Description: - description: Description of the Product associated with the account - type: string - minLength: 1 - maxLength: 350 - ProductDetails: - type: object - properties: - Segment: - type: array - items: - description: >- - Market segmentation is a marketing term referring - to the aggregating of prospective buyers into - groups, or segments, that have common needs and - respond similarly to a marketing action. Market - segmentation enables companies to target different - categories of consumers who perceive the full - value of certain products and services differently - from one another. - - Read more: Market Segmentation - http://www.investopedia.com/terms/m/marketsegmentation.asp#ixzz4gfEEalTd - type: string - enum: - - GEAS - - GEBA - - GEBR - - GEBU - - GECI - - GECS - - GEFB - - GEFG - - GEG - - GEGR - - GEGS - - GEOT - - GEOV - - GEPA - - GEPR - - GERE - - GEST - - GEYA - - GEYO - - PSCA - - PSES - - PSNC - - PSNP - - PSRG - - PSSS - - PSST - - PSSW - FeeFreeLength: - description: The length/duration of the fee free period - type: integer - FeeFreeLengthPeriod: - description: >- - The unit of period (days, weeks, months etc.) of the - promotional length - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - MonthlyMaximumCharge: - description: >- - The maximum relevant charges that could accrue as - defined fully in Part 7 of the CMA order - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the Core - product details - type: string - minLength: 1 - maxLength: 2000 - OtherSegment: - $ref: '#/definitions/OB_OtherCodeType1_0' - CreditInterest: - type: object - required: - - TierBandSet - description: >- - Details about the interest that may be payable to the - Account holders - properties: - TierBandSet: - type: array - items: - type: object - description: >- - The group of tiers or bands for which credit - interest can be applied. - required: - - TierBandMethod - - Destination - - TierBand - properties: - TierBandMethod: - description: >- - The methodology of how credit interest is - paid/applied. It can be:- - - 1. Banded - - Interest rates are banded. i.e. Increasing - rate on whole balance as balance increases. - - 2. Tiered - - Interest rates are tiered. i.e. increasing - rate for each tier as balance increases, but - interest paid on tier fixed for that tier and - not on whole balance. - - 3. Whole - - The same interest rate is applied irrespective - of the product holder's account balance - type: string - enum: - - INBA - - INTI - - INWH - CalculationMethod: - $ref: >- - #/definitions/OB_InterestCalculationMethod1Code - Destination: - description: >- - Describes whether accrued interest is payable - only to the BCA or to another bank account - type: string - enum: - - INOT - - INPA - - INSC - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the - Tier Band Set details - type: string - minLength: 1 - maxLength: 2000 - OtherCalculationMethod: - $ref: '#/definitions/OB_OtherCodeType1_0' - OtherDestination: - $ref: '#/definitions/OB_OtherCodeType1_0' - TierBand: - type: array - items: - type: object - description: Tier Band Details - required: - - TierValueMinimum - - ApplicationFrequency - - FixedVariableInterestRateType - - AER - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for the Product. - type: string - minLength: 1 - maxLength: 35 - TierValueMinimum: - description: >- - Minimum deposit value for which the - credit interest tier applies. - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - TierValueMaximum: - description: >- - Maximum deposit value for which the - credit interest tier applies. - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - CalculationFrequency: - description: >- - How often is credit interest calculated - for the account. - type: string - enum: - - FQAT - - FQDY - - FQHY - - FQMY - - FQOT - - FQQY - - FQSD - - FQWY - - FQYY - ApplicationFrequency: - description: >- - How often is interest applied to the - Product for this tier/band i.e. how - often the financial institution pays - accumulated interest to the customer's - account. - type: string - enum: - - FQAT - - FQDY - - FQHY - - FQMY - - FQOT - - FQQY - - FQSD - - FQWY - - FQYY - DepositInterestAppliedCoverage: - description: Amount on which Interest applied. - type: string - enum: - - INBA - - INTI - - INWH - FixedVariableInterestRateType: - $ref: >- - #/definitions/OB_InterestFixedVariableType1Code - AER: - description: >- - The annual equivalent rate (AER) is - interest that is calculated under the - assumption that any interest paid is - combined with the original balance and - the next interest payment will be based - on the slightly higher account balance. - Overall, this means that interest can be - compounded several times in a year - depending on the number of times that - interest payments are made. - - Read more: Annual Equivalent Rate (AER) - http://www.investopedia.com/terms/a/aer.asp#ixzz4gfR7IO1A - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - BankInterestRateType: - description: >- - Interest rate types, other than AER, - which financial institutions may use to - describe the annual interest rate - payable to the account holder's account. - type: string - enum: - - INBB - - INFR - - INGR - - INLR - - INNE - - INOT - BankInterestRate: - description: Bank Interest for the product - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the Tier Band details - type: string - minLength: 1 - maxLength: 2000 - OtherBankInterestType: - type: object - required: - - Name - - Description - description: >- - Other interest rate types which are not - available in the standard code list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_1' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_2' - minItems: 1 - minItems: 1 - Overdraft: - type: object - required: - - OverdraftTierBandSet - description: Borrowing details - properties: - Notes: - type: array - items: - description: Associated Notes about the overdraft rates - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBandSet: - type: array - items: - type: object - description: Tier band set details - required: - - TierBandMethod - - OverdraftTierBand - properties: - TierBandMethod: - description: >- - The methodology of how overdraft is charged. - It can be: - - 'Whole' Where the same charge/rate is applied - to the entirety of the overdraft balance - (where charges are applicable). - - 'Tiered' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount tiers defined by the - lending financial organisation - - 'Banded' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount bands defined by a - government organisation. - type: string - enum: - - INBA - - INTI - - INWH - OverdraftType: - description: >- - An overdraft can either be 'committed' which - means that the facility cannot be withdrawn - without reasonable notification before it's - agreed end date, or 'on demand' which means - that the financial institution can demand - repayment at any point in time. - type: string - enum: - - OVCO - - OVOD - - OVOT - Identification: - description: >- - Unique and unambiguous identification of a - Tier Band for a overdraft product. - type: string - minLength: 1 - maxLength: 35 - AuthorisedIndicator: - description: >- - Indicates if the Overdraft is authorised (Y) - or unauthorised (N) - type: boolean - BufferAmount: - description: >- - When a customer exceeds their credit limit, a - financial institution will not charge the - customer unauthorised overdraft charges if - they do not exceed by more than the buffer - amount. Note: Authorised overdraft charges may - still apply. - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the - overdraft Tier Band Set details - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBand: - type: array - items: - type: object - description: >- - Provides overdraft details for a specific - tier or band - required: - - TierValueMin - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a overdraft. - type: string - minLength: 1 - maxLength: 35 - TierValueMin: - description: Minimum value of Overdraft Tier/Band - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - TierValueMax: - description: Maximum value of Overdraft Tier/Band - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - EAR: - description: >- - EAR means Effective Annual Rate and/or - Equivalent Annual Rate (frequently - - used interchangeably), being the actual - annual interest rate of an Overdraft. - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - AgreementLengthMin: - description: >- - Specifies the minimum length of a band - for a fixed overdraft agreement - type: integer - AgreementLengthMax: - description: >- - Specifies the maximum length of a band - for a fixed overdraft agreement - type: integer - AgreementPeriod: - description: >- - Specifies the period of a fixed length - overdraft agreement - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - OverdraftInterestChargingCoverage: - description: >- - Refers to which interest rate is applied - when interests are tiered. For example, - if an overdraft balance is �2k and the - interest tiers are:- 0-�500 0.1%, - 500-1000 0.2%, 1000-10000 0.5%, then the - applicable interest rate could either be - 0.5% of the entire balance (since the - account balance sits in the top interest - tier) or - (0.1%*500)+(0.2%*500)+(0.5%*1000). In - the 1st situation, we say the interest - is applied to the �Whole� of the account - balance, and in the 2nd that it is - �Tiered�. - type: string - enum: - - INBA - - INTI - - INWH - BankGuaranteedIndicator: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it�s part of a government scheme, or - whether the rate may vary dependent on - the applicant�s circumstances. - type: boolean - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the Tier/band details - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeesCharges: - type: array - items: - type: object - description: Overdraft fees and charges - required: - - OverdraftFeeChargeDetail - properties: - OverdraftFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FBAO - - FBAR - - FBEB - - FBIT - - FBOR - - FBOS - - FBSC - - FBTO - - FBUB - - FBUT - - FTOT - - FTUT - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_0' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_0' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Notes related to Overdraft fee charge - cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OverdraftFeeChargeDetail: - type: array - items: - type: object - description: Details about the fees/charges - required: - - FeeType - - ApplicationFrequency - properties: - FeeType: - $ref: '#/definitions/OB_OverdraftFeeType1Code' - NegotiableIndicator: - description: >- - Indicates whether fee and charges are - negotiable - type: boolean - OverdraftControlIndicator: - description: >- - Indicates if the fee/charge is already - covered by an 'Overdraft Control' fee or - not. - type: boolean - IncrementalBorrowingAmount: - $ref: '#/definitions/OB_Amount1_1' - FeeAmount: - $ref: '#/definitions/OB_Amount1_2' - FeeRate: - $ref: '#/definitions/OB_Rate1_0' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_0' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_0' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_1' - Notes: - type: array - items: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FBAO - - FBAR - - FBEB - - FBIT - - FBOR - - FBOS - - FBSC - - FBTO - - FBUB - - FBUT - - FTOT - - FTUT - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_0' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_0' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Notes related to Overdraft fee charge - cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherFeeType: - $ref: '#/definitions/OB_OtherCodeType1_3' - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_4' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_1' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_2' - minItems: 1 - minItems: 1 - OverdraftFeesCharges: - type: array - items: - type: object - description: Overdraft fees and charges details - required: - - OverdraftFeeChargeDetail - properties: - OverdraftFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FBAO - - FBAR - - FBEB - - FBIT - - FBOR - - FBOS - - FBSC - - FBTO - - FBUB - - FBUT - - FTOT - - FTUT - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_0' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_0' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Notes related to Overdraft fee charge - cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OverdraftFeeChargeDetail: - type: array - items: - type: object - description: Details about the fees/charges - required: - - FeeType - - ApplicationFrequency - properties: - FeeType: - $ref: '#/definitions/OB_OverdraftFeeType1Code' - NegotiableIndicator: - description: >- - Indicates whether fee and charges are - negotiable - type: boolean - OverdraftControlIndicator: - description: >- - Indicates if the fee/charge is already - covered by an 'Overdraft Control' fee or - not. - type: boolean - IncrementalBorrowingAmount: - $ref: '#/definitions/OB_Amount1_1' - FeeAmount: - $ref: '#/definitions/OB_Amount1_2' - FeeRate: - $ref: '#/definitions/OB_Rate1_0' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_0' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_0' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_1' - Notes: - type: array - items: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FBAO - - FBAR - - FBEB - - FBIT - - FBOR - - FBOS - - FBSC - - FBTO - - FBUB - - FBUT - - FTOT - - FTUT - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_0' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_0' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Notes related to Overdraft fee charge - cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherFeeType: - $ref: '#/definitions/OB_OtherCodeType1_3' - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_4' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_1' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_2' - minItems: 1 - minItems: 1 - LoanInterest: - type: object - required: - - LoanInterestTierBandSet - description: >- - Details about the interest that may be payable to the - SME Loan holders - properties: - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the - LoanInterest - type: string - minLength: 1 - maxLength: 2000 - LoanInterestTierBandSet: - type: array - items: - type: object - description: >- - The group of tiers or bands for which debit - interest can be applied. - required: - - TierBandMethod - - CalculationMethod - - LoanInterestTierBand - properties: - TierBandMethod: - description: >- - The methodology of how credit interest is - charged. It can be:- - - 1. Banded - - Interest rates are banded. i.e. Increasing - rate on whole balance as balance increases. - - 2. Tiered - - Interest rates are tiered. i.e. increasing - rate for each tier as balance increases, but - interest paid on tier fixed for that tier and - not on whole balance. - - 3. Whole - - The same interest rate is applied irrespective - of the SME Loan balance - type: string - enum: - - INBA - - INTI - - INWH - Identification: - description: >- - Loan interest tierbandset identification. Used - by loan providers for internal use purpose. - type: string - minLength: 1 - maxLength: 35 - CalculationMethod: - $ref: >- - #/definitions/OB_InterestCalculationMethod1Code - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the - Tier Band Set details - type: string - minLength: 1 - maxLength: 2000 - OtherCalculationMethod: - $ref: '#/definitions/OB_OtherCodeType1_0' - LoanInterestTierBand: - type: array - items: - type: object - description: Tier Band Details - required: - - TierValueMinimum - - TierValueMinTerm - - MinTermPeriod - - FixedVariableInterestRateType - - RepAPR - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a SME Loan. - type: string - minLength: 1 - maxLength: 35 - TierValueMinimum: - description: >- - Minimum loan value for which the loan - interest tier applies. - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - TierValueMaximum: - description: >- - Maximum loan value for which the loan - interest tier applies. - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - TierValueMinTerm: - description: >- - Minimum loan term for which the loan - interest tier applies. - type: integer - MinTermPeriod: - description: >- - The unit of period (days, weeks, months - etc.) of the Minimum Term - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - TierValueMaxTerm: - description: >- - Maximum loan term for which the loan - interest tier applies. - type: integer - MaxTermPeriod: - description: >- - The unit of period (days, weeks, months - etc.) of the Maximum Term - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - FixedVariableInterestRateType: - $ref: >- - #/definitions/OB_InterestFixedVariableType1Code - RepAPR: - description: >- - The annual equivalent rate (AER) is - interest that is calculated under the - assumption that any interest paid is - combined with the original balance and - the next interest payment will be based - on the slightly higher account balance. - Overall, this means that interest can be - compounded several times in a year - depending on the number of times that - interest payments are made. - - For SME Loan, this APR is the - representative APR which includes any - account fees. - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - LoanProviderInterestRateType: - description: >- - Interest rate types, other than APR, - which financial institutions may use to - describe the annual interest rate - payable for the SME Loan. - type: string - enum: - - INBB - - INFR - - INGR - - INLR - - INNE - - INOT - LoanProviderInterestRate: - description: >- - Loan provider Interest for the SME Loan - product - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the Tier Band details - type: string - minLength: 1 - maxLength: 2000 - OtherLoanProviderInterestRateType: - type: object - required: - - Name - - Description - description: >- - Other loan interest rate types which are - not available in the standard code list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - LoanInterestFeesCharges: - type: array - items: - type: object - description: >- - Contains details of fees and charges - which are not associated with either - LoanRepayment or features/benefits - required: - - LoanInterestFeeChargeDetail - properties: - LoanInterestFeeChargeDetail: - type: array - items: - type: object - description: Other fees/charges details - required: - - FeeType - - ApplicationFrequency - - CalculationFrequency - properties: - FeeType: - $ref: '#/definitions/OB_FeeType1Code' - NegotiableIndicator: - description: >- - Fee/charge which is usually negotiable - rather than a fixed amount - type: boolean - FeeAmount: - $ref: '#/definitions/OB_Amount1_3' - FeeRate: - $ref: '#/definitions/OB_Rate1_1' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_1' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_2' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_3' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the fee/charge details. - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - $ref: >- - #/definitions/OB_OtherFeeChargeDetailType - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_5' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_6' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_7' - minItems: 1 - LoanInterestFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (minimum/maximum - charges) that apply to a particular - fee/charge - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_1' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_4' - CappingPeriod: - $ref: '#/definitions/OB_FeeFrequency1Code_4' - Notes: - type: array - items: - description: >- - Free text for adding extra details for - fee charge cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - minItems: 1 - LoanInterestFeesCharges: - type: array - items: - type: object - description: >- - Contains details of fees and charges which - are not associated with either LoanRepayment - or features/benefits - required: - - LoanInterestFeeChargeDetail - properties: - LoanInterestFeeChargeDetail: - type: array - items: - type: object - description: Other fees/charges details - required: - - FeeType - - ApplicationFrequency - - CalculationFrequency - properties: - FeeType: - $ref: '#/definitions/OB_FeeType1Code' - NegotiableIndicator: - description: >- - Fee/charge which is usually negotiable - rather than a fixed amount - type: boolean - FeeAmount: - $ref: '#/definitions/OB_Amount1_3' - FeeRate: - $ref: '#/definitions/OB_Rate1_1' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_1' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_2' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_3' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the fee/charge details. - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - $ref: >- - #/definitions/OB_OtherFeeChargeDetailType - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_5' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_6' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_7' - minItems: 1 - LoanInterestFeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (minimum/maximum - charges) that apply to a particular - fee/charge - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_1' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_4' - CappingPeriod: - $ref: '#/definitions/OB_FeeFrequency1Code_4' - Notes: - type: array - items: - description: >- - Free text for adding extra details for - fee charge cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - minItems: 1 - Repayment: - type: object - description: Repayment details of the Loan product - properties: - RepaymentType: - description: Repayment type - type: string - enum: - - USBA - - USBU - - USCI - - USCS - - USER - - USFA - - USFB - - USFI - - USIO - - USOT - - USPF - - USRW - - USSL - RepaymentFrequency: - description: Repayment frequency - type: string - enum: - - SMDA - - SMFL - - SMFO - - SMHY - - SMMO - - SMOT - - SMQU - - SMWE - - SMYE - AmountType: - description: >- - The repayment is for paying just the interest only - or both interest and capital or bullet amount or - balance to date etc - type: string - enum: - - RABD - - RABL - - RACI - - RAFC - - RAIO - - RALT - - USOT - Notes: - type: array - items: - description: >- - Optional additional notes to supplement the - Repayment - type: string - minLength: 1 - maxLength: 2000 - OtherRepaymentType: - type: object - required: - - Name - - Description - description: >- - Other repayment type which is not in the standard - code list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherRepaymentFrequency: - type: object - required: - - Name - - Description - description: >- - Other repayment frequency which is not in the - standard code list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherAmountType: - type: object - required: - - Name - - Description - description: >- - Other amount type which is not in the standard code - list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - RepaymentFeeCharges: - type: object - required: - - RepaymentFeeChargeDetail - description: >- - Applicable fee/charges for repayment such as - prepayment, full early repayment or non repayment. - properties: - RepaymentFeeChargeDetail: - type: array - items: - type: object - description: >- - Details about specific fees/charges that are - applied for repayment - required: - - FeeType - - ApplicationFrequency - - CalculationFrequency - properties: - FeeType: - $ref: '#/definitions/OB_FeeType1Code' - NegotiableIndicator: - description: >- - Fee/charge which is usually negotiable - rather than a fixed amount - type: boolean - FeeAmount: - $ref: '#/definitions/OB_Amount1_3' - FeeRate: - $ref: '#/definitions/OB_Rate1_1' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_1' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_2' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_3' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the fee/charge details. - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - $ref: '#/definitions/OB_OtherFeeChargeDetailType' - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_8' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_6' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_7' - minItems: 1 - RepaymentFeeChargeCap: - type: array - items: - type: object - description: >- - RepaymentFeeChargeCap sets daily, weekly, - monthly, yearly limits on the fees that are - charged - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_1' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_4' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Free text for adding extra details for - fee charge cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - RepaymentHoliday: - type: array - items: - type: object - description: Details of capital repayment holiday if any - properties: - MaxHolidayLength: - description: >- - The maximum length/duration of a Repayment - Holiday - type: integer - MaxHolidayPeriod: - description: >- - The unit of period (days, weeks, months etc.) - of the repayment holiday - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - Notes: - type: array - items: - description: >- - Free text for adding details for repayment - holiday - type: string - minLength: 1 - maxLength: 2000 - OtherFeesCharges: - type: array - items: - type: object - description: >- - Contains details of fees and charges which are not - associated with either Overdraft or features/benefits - required: - - FeeChargeDetail - properties: - TariffType: - description: TariffType which defines the fee and charges. - type: string - enum: - - TTEL - - TTMX - - TTOT - TariffName: - description: Name of the tariff - type: string - minLength: 1 - maxLength: 350 - OtherTariffType: - type: object - required: - - Name - - Description - description: >- - Other tariff type which is not in the standard - list. - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - FeeChargeDetail: - type: array - items: - type: object - description: Other fees/charges details - required: - - FeeCategory - - FeeType - - ApplicationFrequency - properties: - FeeCategory: - $ref: '#/definitions/OB_FeeCategory1Code' - FeeType: - $ref: '#/definitions/OB_FeeType1Code' - NegotiableIndicator: - description: >- - Fee/charge which is usually negotiable - rather than a fixed amount - type: boolean - FeeAmount: - $ref: '#/definitions/OB_Amount1_3' - FeeRate: - $ref: '#/definitions/OB_Rate1_1' - FeeRateType: - $ref: '#/definitions/OB_InterestRateType1Code_1' - ApplicationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_2' - CalculationFrequency: - $ref: '#/definitions/OB_FeeFrequency1Code_3' - Notes: - type: array - items: - description: >- - Optional additional notes to supplement - the fee/charge details. - type: string - minLength: 1 - maxLength: 2000 - FeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) - that apply to a particular or group of - fee/charge - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_1' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_4' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Free text for adding extra details for - fee charge cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not - available in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OtherFeeCategoryType: - $ref: '#/definitions/OB_OtherCodeType1_0' - OtherFeeType: - $ref: '#/definitions/OB_OtherFeeChargeDetailType' - OtherFeeRateType: - $ref: '#/definitions/OB_OtherCodeType1_8' - OtherApplicationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_6' - OtherCalculationFrequency: - $ref: '#/definitions/OB_OtherCodeType1_7' - FeeApplicableRange: - type: object - description: >- - Range or amounts or rates for which the - fee/charge applies - properties: - MinimumAmount: - description: >- - Minimum Amount on which fee/charge is - applicable (where it is expressed as an - amount) - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - MaximumAmount: - description: >- - Maximum Amount on which fee is - applicable (where it is expressed as an - amount) - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - MinimumRate: - description: >- - Minimum rate on which fee/charge is - applicable(where it is expressed as an - rate) - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - MaximumRate: - description: >- - Maximum rate on which fee/charge is - applicable(where it is expressed as an - rate) - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - minItems: 1 - FeeChargeCap: - type: array - items: - type: object - description: >- - Details about any caps (maximum charges) that - apply to a particular or group of fee/charge - required: - - FeeType - - MinMaxType - properties: - FeeType: - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - minItems: 1 - MinMaxType: - $ref: '#/definitions/OB_MinMaxType1Code' - FeeCapOccurrence: - $ref: '#/definitions/Number_1' - FeeCapAmount: - $ref: '#/definitions/OB_Amount1_4' - CappingPeriod: - $ref: '#/definitions/OB_Period1Code' - Notes: - type: array - items: - description: >- - Free text for adding extra details for - fee charge cap - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - type: array - items: - type: object - description: >- - Other fee type code which is not available - in the standard code set - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - BCA: - type: object - title: BCA - properties: - ProductDetails: - type: object - title: ProductDetails - properties: - Segment: - description: >- - Market segmentation is a marketing term referring to - the aggregating of prospective buyers into groups, - or segments, that have common needs and respond - similarly to a marketing action. Market segmentation - enables companies to target different categories of - consumers who perceive the full value of certain - products and services differently from one another. - - - Read more: Market Segmentation - http://www.investopedia.com/terms/m/marketsegmentation.asp#ixzz4gfEEalTd - - With respect to BCA products, they are segmented in - relation to different markets that they wish to - focus on. - title: Segment - type: array - items: - description: >- - Market segmentation is a marketing term referring - to the aggregating of prospective buyers into - groups, or segments, that have common needs and - respond similarly to a marketing action. Market - segmentation enables companies to target different - categories of consumers who perceive the full - value of certain products and services differently - from one another. - - - Read more: Market Segmentation - http://www.investopedia.com/terms/m/marketsegmentation.asp#ixzz4gfEEalTd - - With respect to BCA products, they are segmented - in relation to different markets that they wish to - focus on. - type: string - enum: - - ClientAccount - - Standard - - NonCommercialChaitiesClbSoc - - NonCommercialPublicAuthGovt - - Religious - - SectorSpecific - - Startup - - Switcher - FeeFreeLength: - description: The length/duration of the fee free period - title: FeeFreeLength - type: number - format: float - FeeFreeLengthPeriod: - description: >- - The unit of period (days, weeks, months etc.) of the - promotional length - title: FeeFreeLengthPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Optional additional notes to supplement the Core - product details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - additionalProperties: false - CreditInterest: - description: >- - Details about the interest that may be payable to the - BCA account holders - type: object - title: CreditInterest - properties: - TierBandSet: - description: >- - The group of tiers or bands for which credit - interest can be applied. - type: array - title: TierBandSet - items: - description: >- - The group of tiers or bands for which credit - interest can be applied. - type: object - properties: - TierBandMethod: - description: >- - The methodology of how credit interest is - paid/applied. It can be:- - - - 1. Banded - - Interest rates are banded. i.e. Increasing - rate on whole balance as balance increases. - - - 2. Tiered - - Interest rates are tiered. i.e. increasing - rate for each tier as balance increases, but - interest paid on tier fixed for that tier and - not on whole balance. - - - 3. Whole - - The same interest rate is applied irrespective - of the BCA balance - title: TierBandMethod - type: string - enum: - - Banded - - Tiered - - Whole - CalculationMethod: - description: Methods of calculating interest - title: CalculationMethod - type: string - enum: - - Compound - - SimpleInterest - Destination: - description: >- - Describes whether accrued interest is payable - only to the BCA or to another bank account - title: Destination - type: string - enum: - - PayAway - - SelfCredit - Notes: - description: >- - Optional additional notes to supplement the - Tier Band Set details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - TierBand: - description: Tier Band Details - type: array - title: TierBand - items: - description: Tier Band Details - type: object - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a BCA. - title: Identification - type: string - minLength: 1 - maxLength: 35 - TierValueMinimum: - description: >- - Minimum deposit value for which the - credit interest tier applies. - title: TierValueMinimum - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - TierValueMaximum: - description: >- - Maximum deposit value for which the - credit interest tier applies. - title: TierValueMaximum - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CalculationFrequency: - description: >- - How often is credit interest calculated - for the account. - title: CalculationFrequency - type: string - enum: - - Daily - - HalfYearly - - Monthly - - Other - - Quarterly - - PerStatementDate - - Weekly - - Yearly - ApplicationFrequency: - description: >- - How often is interest applied to the BCA - for this tier/band i.e. how often the - financial institution pays accumulated - interest to the customer's BCA. - title: ApplicationFrequency - type: string - enum: - - Daily - - HalfYearly - - Monthly - - Other - - Quarterly - - PerStatementDate - - Weekly - - Yearly - DepositInterestAppliedCoverage: - description: Amount on which Interest applied. - title: DepositInterestAppliedCoverage - type: string - enum: - - Banded - - Tiered - - Whole - FixedVariableInterestRateType: - description: 'Type of interest rate, Fixed or Variable' - title: FixedVariableInterestRateType - type: string - enum: - - Fixed - - Variable - AER: - description: >- - The annual equivalent rate (AER) is - interest that is calculated under the - assumption that any interest paid is - combined with the original balance and - the next interest payment will be based - on the slightly higher account balance. - Overall, this means that interest can be - compounded several times in a year - depending on the number of times that - interest payments are made. - - - Read more: Annual Equivalent Rate (AER) - http://www.investopedia.com/terms/a/aer.asp#ixzz4gfR7IO1A - title: AER - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - BankInterestRateType: - description: >- - Interest rate types, other than AER, - which financial institutions may use to - describe the annual interest rate - payable to the BCA. - title: BankInterestRateType - type: string - enum: - - Gross - - Other - BankInterestRate: - description: Bank Interest for the BCA product - title: BankInterestRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement - the Tier Band details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherBankInterestType: - description: >- - Other interest rate types which are not - available in the standard code list - type: object - title: OtherBankInterestType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - required: - - TierValueMinimum - - ApplicationFrequency - - FixedVariableInterestRateType - - AER - additionalProperties: false - minItems: 1 - required: - - TierBandMethod - - Destination - - TierBand - additionalProperties: false - minItems: 1 - additionalProperties: false - required: - - TierBandSet - Overdraft: - description: Borrowing details - type: object - title: Overdraft - properties: - Notes: - description: Associated Notes about the overdraft rates - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBandSet: - description: Tier band set details - type: array - title: OverdraftTierBandSet - items: - description: Tier band set details - type: object - properties: - TierBandMethod: - description: >- - The methodology of how overdraft is charged. - It can be: - - 'Whole' Where the same charge/rate is applied - to the entirety of the overdraft balance - (where charges are applicable). - - 'Tiered' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount tiers defined by the - lending financial organisation - - 'Banded' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount bands defined by a - government organisation. - title: TierBandMethod - type: string - enum: - - Banded - - Tiered - - Whole - OverdraftType: - description: >- - An overdraft can either be 'committed' which - means that the facility cannot be withdrawn - without reasonable notification before it's - agreed end date, or 'on demand' which means - that the financial institution can demand - repayment at any point in time. - title: OverdraftType - type: string - enum: - - Committed - - OnDemand - Identification: - description: >- - Unique and unambiguous identification of a - Tier Band for a overdraft product. - title: Identification - type: string - minLength: 1 - maxLength: 35 - AuthorisedIndicator: - description: >- - Indicates if the Overdraft is authorised (Y) - or unauthorised (N) - title: AuthorisedIndicator - type: boolean - BufferAmount: - description: >- - When a customer exceeds their credit limit, a - financial institution will not charge the - customer unauthorised overdraft charges if - they do not exceed by more than the buffer - amount. Note: Authorised overdraft charges may - still apply. - title: BufferAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement the - overdraft Tier Band Set details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBand: - description: >- - Provides overdraft details for a specific tier - or band - type: array - title: OverdraftTierBand - items: - description: >- - Provides overdraft details for a specific - tier or band - type: object - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a overdraft. - title: Identification - type: string - minLength: 1 - maxLength: 35 - TierValueMin: - description: Minimum value of Overdraft Tier/Band - title: TierValueMin - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - TierValueMax: - description: Maximum value of Overdraft Tier/Band - title: TierValueMax - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - EAR: - description: >- - EAR means Effective Annual Rate and/or - Equivalent Annual Rate (frequently - - used interchangeably), being the actual - annual interest rate of an Overdraft. - title: EAR - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - AgreementLengthMin: - description: >- - Specifies the minimum length of a band - for a fixed overdraft agreement - title: AgreementLengthMin - type: number - format: float - AgreementLengthMax: - description: >- - Specifies the maximum length of a band - for a fixed overdraft agreement - title: AgreementLengthMax - type: number - format: float - AgreementPeriod: - description: >- - Specifies the period of a fixed length - overdraft agreement - title: AgreementPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - OverdraftInterestChargingCoverage: - description: >- - Refers to which interest rate is applied - when interests are tiered. For example, - if an overdraft balance is £2k and the - interest tiers are:- 0-£500 0.1%, - 500-1000 0.2%, 1000-10000 0.5%, then the - applicable interest rate could either be - 0.5% of the entire balance (since the - account balance sits in the top interest - tier) or - (0.1%*500)+(0.2%*500)+(0.5%*1000). In - the 1st situation, we say the interest - is applied to the ‘Whole’ of the account - balance, and in the 2nd that it is - ‘Tiered’. - title: OverdraftInterestChargingCoverage - type: string - enum: - - Banded - - Tiered - - Whole - BankGuaranteedIndicator: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it’s part of a government scheme, or - whether the rate may vary dependent on - the applicant’s circumstances. - title: BankGuaranteedIndicator - type: boolean - Notes: - description: >- - Optional additional notes to supplement - the Tier/band details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeesCharges: - description: Overdraft fees and charges - type: array - title: OverdraftFeesCharges - items: - description: Overdraft fees and charges - type: object - properties: - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it’s part of a government scheme, or - whether the rate may vary dependent on - the applicant’s circumstances. - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OverdraftFeeChargeDetail: - description: Details about the fees/charges - type: array - title: OverdraftFeeChargeDetail - items: - description: Details about the fees/charges - type: object - properties: - FeeType: - description: Overdraft fee type - title: FeeType - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - NegotiableIndicator: - description: >- - Indicates whether fee and charges are - negotiable - title: NegotiableIndicator - type: boolean - OverdraftControlIndicator: - description: >- - Indicates if the fee/charge is already - covered by an 'Overdraft Control' fee or - not. - title: OverdraftControlIndicator - type: boolean - IncrementalBorrowingAmount: - description: >- - Every additional tranche of an overdraft - balance to which an overdraft fee is - applied - title: IncrementalBorrowingAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeAmount: - description: >- - Amount charged for an overdraft - fee/charge (where it is charged in terms - of an amount rather than a rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRateType - type: string - enum: - - Gross - - Other - ApplicationFrequency: - description: >- - Frequency at which the overdraft charge - is applied to the account - title: ApplicationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: >- - How often is the overdraft fee/charge - calculated for the account. - title: CalculationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it’s part of a government scheme, or - whether the rate may vary dependent on - the applicant’s circumstances. - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OtherFeeType: - description: >- - Other Fee type which is not available in - the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type code which is not - available in the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - required: - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - required: - - OverdraftFeeChargeDetail - additionalProperties: false - required: - - TierValueMin - additionalProperties: false - minItems: 1 - OverdraftFeesCharges: - description: Overdraft fees and charges details - type: array - title: OverdraftFeesCharges - items: - description: Overdraft fees and charges details - type: object - properties: - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it’s part of a government scheme, or - whether the rate may vary dependent on - the applicant’s circumstances. - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OverdraftFeeChargeDetail: - description: Details about the fees/charges - type: array - title: OverdraftFeeChargeDetail - items: - description: Details about the fees/charges - type: object - properties: - FeeType: - description: Overdraft fee type - title: FeeType - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - NegotiableIndicator: - description: >- - Indicates whether fee and charges are - negotiable - title: NegotiableIndicator - type: boolean - OverdraftControlIndicator: - description: >- - Indicates if the fee/charge is already - covered by an 'Overdraft Control' fee or - not. - title: OverdraftControlIndicator - type: boolean - IncrementalBorrowingAmount: - description: >- - Every additional tranche of an overdraft - balance to which an overdraft fee is - applied - title: IncrementalBorrowingAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeAmount: - description: >- - Amount charged for an overdraft - fee/charge (where it is charged in terms - of an amount rather than a rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRateType - type: string - enum: - - Gross - - Other - ApplicationFrequency: - description: >- - Frequency at which the overdraft charge - is applied to the account - title: ApplicationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: >- - How often is the overdraft fee/charge - calculated for the account. - title: CalculationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge. - Capping can either be based on an amount - (in gbp), an amount (in items) or a - rate. - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - AnnualReview - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - Indicates whether the advertised - overdraft rate is guaranteed to be - offered to a borrower by the bank e.g. - if it’s part of a government scheme, or - whether the rate may vary dependent on - the applicant’s circumstances. - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OtherFeeType: - description: >- - Other Fee type which is not available in - the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type code which is not - available in the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - required: - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - required: - - OverdraftFeeChargeDetail - additionalProperties: false - required: - - TierBandMethod - - OverdraftTierBand - additionalProperties: false - minItems: 1 - additionalProperties: false - required: - - OverdraftTierBandSet - OtherFeesCharges: - description: >- - Contains details of fees and charges which are not - associated with either Overdraft or features/benefits - type: array - title: OtherFeesCharges - items: - description: >- - Contains details of fees and charges which are not - associated with either Overdraft or features/benefits - type: object - properties: - TariffType: - description: TariffType which defines the fee and charges. - title: TariffType - type: string - enum: - - Electronic - - Mixed - - Other - TariffName: - description: Name of the tariff - title: TariffName - type: string - minLength: 1 - maxLength: 350 - OtherTariffType: - description: >- - Other tariff type which is not in the standard - list. - type: object - title: OtherTariffType - properties: - Code: - description: >- - The four letter Mnemonic used within an XML - file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - FeeChargeDetail: - description: Other fees/charges details - type: array - title: FeeChargeDetail - items: - description: Other fees/charges details - type: object - properties: - FeeCategory: - description: >- - Categorisation of fees and charges into - standard categories. - title: FeeCategory - type: string - enum: - - Other - - Servicing - FeeType: - description: Fee/Charge Type - title: FeeType - type: string - enum: - - Other - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCAccountFeeQuarterly - - ServiceCFixedTariff - - ServiceCBusiDepAccBreakage - - ServiceCMinimumMonthlyFee - - ServiceCOther - NegotiableIndicator: - description: >- - Fee/charge which is usually negotiable - rather than a fixed amount - title: NegotiableIndicator - type: boolean - FeeAmount: - description: >- - Fee Amount charged for a fee/charge (where - it is charged in terms of an amount rather - than a rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for Fee/Charge (where it is - charged in terms of a rate rather than an - amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for Fee/Charge (where it is - charged in terms of a rate rather than an - amount) - title: FeeRateType - type: string - enum: - - Gross - - Other - ApplicationFrequency: - description: >- - How frequently the fee/charge is applied to - the account - title: ApplicationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: How frequently the fee/charge is calculated - title: CalculationFrequency - type: string - enum: - - OnClosing - - OnOpening - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAnniversary - - Other - - PerHundredPounds - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Optional additional notes to supplement the - fee/charge details. - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - FeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular or group of - fee/charge - type: array - title: FeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular or group of - fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - Other - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCAccountFeeQuarterly - - ServiceCFixedTariff - - ServiceCBusiDepAccBreakage - - ServiceCMinimumMonthlyFee - - ServiceCOther - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: >- - Cap amount charged for a fee/charge - (where it is charged in terms of an - amount rather than a rate) - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Free text for adding extra details for - fee charge cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OtherFeeCategoryType: - type: object - title: OtherFeeCategoryType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeType: - description: >- - Other Fee/charge type which is not available - in the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - FeeCategory: - description: >- - Categorisation of fees and charges into - standard categories. - title: FeeCategory - type: string - enum: - - Other - - Servicing - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - FeeCategory - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type which is not available - in the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies not covered in - the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - FeeApplicableRange: - description: >- - Range or amounts or rates for which the - fee/charge applies - type: object - title: FeeApplicableRange - properties: - MinimumAmount: - description: >- - Minimum Amount on which fee/charge is - applicable (where it is expressed as an - amount) - title: MinimumAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - MaximumAmount: - description: >- - Maximum Amount on which fee is - applicable (where it is expressed as an - amount) - title: MaximumAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - MinimumRate: - description: >- - Minimum rate on which fee/charge is - applicable(where it is expressed as an - rate) - title: MinimumRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - MaximumRate: - description: >- - Maximum rate on which fee/charge is - applicable(where it is expressed as an - rate) - title: MaximumRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - additionalProperties: false - required: - - FeeCategory - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - FeeChargeCap: - description: >- - Details about any caps (maximum charges) that - apply to a particular or group of fee/charge - type: array - title: FeeChargeCap - items: - description: >- - Details about any caps (maximum charges) that - apply to a particular or group of fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - Other - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCAccountFeeQuarterly - - ServiceCFixedTariff - - ServiceCBusiDepAccBreakage - - ServiceCMinimumMonthlyFee - - ServiceCOther - minItems: 1 - MinMaxType: - description: Min Max type - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on the - number of occurrences rather than capped at - a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: >- - Cap amount charged for a fee/charge (where - it is charged in terms of an amount rather - than a rate) - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for which - the fee/charge is capped - title: CappingPeriod - type: string - enum: - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Free text for adding extra details for fee - charge cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not available - in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not available - in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - required: - - FeeChargeDetail - additionalProperties: false - additionalProperties: false - PCA: - type: object - title: PCA - properties: - ProductDetails: - type: object - title: ProductDetails - properties: - Segment: - description: >- - Market segmentation is a marketing term referring to - the aggregating of prospective buyers into groups, - or segments, that have common needs and respond - similarly to a marketing action. Market segmentation - enables companies to target different categories of - consumers who perceive the full value of certain - products and services differently from one another. - - - Read more: Market Segmentation - http://www.investopedia.com/terms/m/marketsegmentation.asp#ixzz4gfEEalTd - - With respect to PCA products, they are segmented in - relation to different markets that they wish to - focus on. - title: Segment - type: array - items: - description: >- - Market segmentation is a marketing term referring - to the aggregating of prospective buyers into - groups, or segments, that have common needs and - respond similarly to a marketing action. Market - segmentation enables companies to target different - categories of consumers who perceive the full - value of certain products and services differently - from one another. - - - Read more: Market Segmentation - http://www.investopedia.com/terms/m/marketsegmentation.asp#ixzz4gfEEalTd - - With respect to PCA products, they are segmented - in relation to different markets that they wish to - focus on. - type: string - enum: - - Basic - - BenefitAndReward - - CreditInterest - - Cashback - - General - - Graduate - - Other - - Overdraft - - Packaged - - Premium - - Reward - - Student - - YoungAdult - - Youth - MonthlyMaximumCharge: - description: >- - The maximum relevant charges that could accrue as - defined fully in Part 7 of the CMA order - title: MonthlyMaximumCharge - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement the Core - product details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - additionalProperties: false - CreditInterest: - description: >- - Details about the interest that may be payable to the - PCA account holders - type: object - title: CreditInterest - properties: - TierBandSet: - description: >- - The group of tiers or bands for which credit - interest can be applied. - type: array - title: TierBandSet - items: - description: >- - The group of tiers or bands for which credit - interest can be applied. - type: object - properties: - TierBandMethod: - description: >- - The methodology of how credit interest is - charged. It can be:- - - - 1. Banded - - Interest rates are banded. i.e. Increasing - rate on whole balance as balance increases. - - - 2. Tiered - - Interest rates are tiered. i.e. increasing - rate for each tier as balance increases, but - interest paid on tier fixed for that tier and - not on whole balance. - - - 3. Whole - - The same interest rate is applied irrespective - of the PCA balance - title: TierBandMethod - type: string - enum: - - Tiered - - Whole - CalculationMethod: - description: Methods of calculating interest - title: CalculationMethod - type: string - enum: - - Compound - - SimpleInterest - Destination: - description: >- - Describes whether accrued interest is payable - only to the PCA or to another bank account - title: Destination - type: string - enum: - - PayAway - - SelfCredit - Notes: - description: >- - Optional additional notes to supplement the - Tier Band Set details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - TierBand: - description: Tier Band Details - type: array - title: TierBand - items: - description: Tier Band Details - type: object - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a PCA. - title: Identification - type: string - minLength: 1 - maxLength: 35 - TierValueMinimum: - description: >- - Minimum deposit value for which the - credit interest tier applies. - title: TierValueMinimum - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - TierValueMaximum: - description: >- - Maximum deposit value for which the - credit interest tier applies. - title: TierValueMaximum - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CalculationFrequency: - description: >- - How often is credit interest calculated - for the account. - title: CalculationFrequency - type: string - enum: - - PerAcademicTerm - - Daily - - HalfYearly - - Monthly - - Other - - Quarterly - - PerStatementDate - - Weekly - - Yearly - ApplicationFrequency: - description: >- - How often is interest applied to the PCA - for this tier/band i.e. how often the - financial institution pays accumulated - interest to the customer's PCA. - title: ApplicationFrequency - type: string - enum: - - PerAcademicTerm - - Daily - - HalfYearly - - Monthly - - Other - - Quarterly - - PerStatementDate - - Weekly - - Yearly - DepositInterestAppliedCoverage: - description: Amount on which Interest applied. - title: DepositInterestAppliedCoverage - type: string - enum: - - Tiered - - Whole - FixedVariableInterestRateType: - description: 'Type of interest rate, Fixed or Variable' - title: FixedVariableInterestRateType - type: string - enum: - - Fixed - - Variable - AER: - description: >- - The annual equivalent rate (AER) is - interest that is calculated under the - assumption that any interest paid is - combined with the original balance and - the next interest payment will be based - on the slightly higher account balance. - Overall, this means that interest can be - compounded several times in a year - depending on the number of times that - interest payments are made. - - - Read more: Annual Equivalent Rate (AER) - http://www.investopedia.com/terms/a/aer.asp#ixzz4gfR7IO1A - title: AER - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - BankInterestRateType: - description: >- - Interest rate types, other than AER, - which financial institutions may use to - describe the annual interest rate - payable to the PCA. - title: BankInterestRateType - type: string - enum: - - LinkedBaseRate - - Gross - - Net - - Other - BankInterestRate: - description: Bank Interest for the PCA product - title: BankInterestRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement - the Tier Band details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherBankInterestType: - description: >- - Other interest rate types which are not - available in the standard code list - type: object - title: OtherBankInterestType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - required: - - TierValueMinimum - - ApplicationFrequency - - FixedVariableInterestRateType - - AER - additionalProperties: false - minItems: 1 - required: - - TierBandMethod - - TierBand - additionalProperties: false - minItems: 1 - additionalProperties: false - required: - - TierBandSet - Overdraft: - description: 'Details about Overdraft rates, fees & charges' - type: object - title: Overdraft - properties: - Notes: - description: Associated Notes about the overdraft rates - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBandSet: - description: Tier band set details - type: array - title: OverdraftTierBandSet - items: - description: Tier band set details - type: object - properties: - TierBandMethod: - description: >- - The methodology of how overdraft is charged. - It can be: - - 'Whole' Where the same charge/rate is applied - to the entirety of the overdraft balance - (where charges are applicable). - - 'Tiered' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount tiers defined by the - lending financial organisation - - 'Banded' Where different charges/rates are - applied dependent on overdraft maximum and - minimum balance amount bands defined by a - government organisation. - title: TierBandMethod - type: string - enum: - - Tiered - - Whole - - Banded - OverdraftType: - description: >- - An overdraft can either be 'committed' which - means that the facility cannot be withdrawn - without reasonable notification before it's - agreed end date, or 'on demand' which means - that the financial institution can demand - repayment at any point in time. - title: OverdraftType - type: string - enum: - - Committed - - OnDemand - - Other - Identification: - description: >- - Unique and unambiguous identification of a - Tier Band for a overdraft product. - title: Identification - type: string - minLength: 1 - maxLength: 35 - AuthorisedIndicator: - description: >- - Indicates if the Overdraft is authorised (Y) - or unauthorised (N) - title: AuthorisedIndicator - type: boolean - BufferAmount: - description: >- - When a customer exceeds their credit limit, a - financial institution will not charge the - customer unauthorised overdraft charges if - they do not exceed by more than the buffer - amount. Note: Authorised overdraft charges may - still apply. - title: BufferAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement the - overdraft Tier Band Set details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftTierBand: - description: >- - Provides overdraft details for a specific tier - or band - type: array - title: OverdraftTierBand - items: - description: >- - Provides overdraft details for a specific - tier or band - type: object - properties: - Identification: - description: >- - Unique and unambiguous identification of - a Tier Band for a overdraft. - title: Identification - type: string - minLength: 1 - maxLength: 35 - TierValueMin: - description: Minimum value of Overdraft Tier/Band - title: TierValueMin - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - TierValueMax: - description: Maximum value of Overdraft Tier/Band - title: TierValueMax - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - OverdraftInterestChargingCoverage: - description: >- - Interest charged on whole amount or - tiered/banded - title: OverdraftInterestChargingCoverage - type: string - enum: - - Tiered - - Whole - BankGuaranteedIndicator: - description: >- - Indicates that a bank provides the - overdraft limit up to TierValueMIn to - all customers automatically - title: BankGuaranteedIndicator - type: boolean - EAR: - description: >- - EAR means Effective Annual Rate and/or - Equivalent Annual Rate (frequently - - used interchangeably), being the actual - annual interest rate of an Overdraft. - title: EAR - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - Notes: - description: >- - Optional additional notes to supplement - the Tier/band details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OverdraftFeesCharges: - description: Overdraft fees and charges - type: array - title: OverdraftFeesCharges - items: - description: Overdraft fees and charges - type: object - properties: - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - MinMaxType: - description: >- - Indicates that this is the minimum/ - maximum fee/charge that can be applied - by the financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OverdraftFeeChargeDetail: - description: Details about the fees/charges - type: array - title: OverdraftFeeChargeDetail - items: - description: Details about the fees/charges - type: object - properties: - FeeType: - description: Overdraft fee type - title: FeeType - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - IncrementalBorrowingAmount: - description: >- - Every additional tranche of an overdraft - balance to which an overdraft fee is - applied - title: IncrementalBorrowingAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeAmount: - description: >- - Amount charged for an overdraft - fee/charge (where it is charged in terms - of an amount rather than a rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRateType - type: string - enum: - - LinkedBaseRate - - Gross - - Net - - Other - ApplicationFrequency: - description: >- - Frequency at which the overdraft charge - is applied to the account - title: ApplicationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: >- - How often is the overdraft fee/charge - calculated for the account. - title: CalculationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other Fee type which is not available in - the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type code which is not - available in the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: object - title: OverdraftFeeChargeCap - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - MinMaxType: - description: >- - Indicates that this is the minimum/ - maximum fee/charge that can be applied - by the financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - additionalProperties: false - required: - - FeeType - - MinMaxType - required: - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - required: - - OverdraftFeeChargeDetail - additionalProperties: false - required: - - TierValueMin - additionalProperties: false - minItems: 1 - OverdraftFeesCharges: - description: Overdraft fees and charges details - type: array - title: OverdraftFeesCharges - items: - description: Overdraft fees and charges details - type: object - properties: - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: array - title: OverdraftFeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - MinMaxType: - description: >- - Indicates that this is the minimum/ - maximum fee/charge that can be applied - by the financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - OverdraftFeeChargeDetail: - description: Details about the fees/charges - type: array - title: OverdraftFeeChargeDetail - items: - description: Details about the fees/charges - type: object - properties: - FeeType: - description: Overdraft fee type - title: FeeType - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - IncrementalBorrowingAmount: - description: >- - Every additional tranche of an overdraft - balance to which an overdraft fee is - applied - title: IncrementalBorrowingAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeAmount: - description: >- - Amount charged for an overdraft - fee/charge (where it is charged in terms - of an amount rather than a rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for overdraft fee/charge - (where it is charged in terms of a rate - rather than an amount) - title: FeeRateType - type: string - enum: - - LinkedBaseRate - - Gross - - Net - - Other - ApplicationFrequency: - description: >- - Frequency at which the overdraft charge - is applied to the account - title: ApplicationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: >- - How often is the overdraft fee/charge - calculated for the account. - title: CalculationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Free text for capturing any other info - related to Overdraft Fees Charge Details - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other Fee type which is not available in - the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type code which is not - available in the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies that are - not available in the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in the standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OverdraftFeeChargeCap: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: object - title: OverdraftFeeChargeCap - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Overdraft fee type - type: string - enum: - - ArrangedOverdraft - - EmergencyBorrowing - - BorrowingItem - - OverdraftRenewal - - AnnualReview - - OverdraftSetup - - Surcharge - - TempOverdraft - - UnauthorisedBorrowing - - UnauthorisedPaidTrans - - Other - - UnauthorisedUnpaidTrans - minItems: 1 - OverdraftControlIndicator: - description: >- - Specifies for the overdraft control - feature/benefit - title: OverdraftControlIndicator - type: boolean - MinMaxType: - description: >- - Indicates that this is the minimum/ - maximum fee/charge that can be applied - by the financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: Cap amount charged for a fee/charge - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Notes related to Overdraft fee charge - cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - additionalProperties: false - required: - - FeeType - - MinMaxType - required: - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - required: - - OverdraftFeeChargeDetail - additionalProperties: false - required: - - TierBandMethod - - OverdraftTierBand - additionalProperties: false - minItems: 1 - additionalProperties: false - required: - - OverdraftTierBandSet - OtherFeesCharges: - description: >- - Contains details of fees and charges which are not - associated with either borrowing or features/benefits - type: object - title: OtherFeesCharges - properties: - FeeChargeDetail: - description: Other fees/charges details - type: array - title: FeeChargeDetail - items: - description: Other fees/charges details - type: object - properties: - FeeCategory: - description: >- - Categorisation of fees and charges into - standard categories. - title: FeeCategory - type: string - enum: - - Other - - Servicing - FeeType: - description: Fee/Charge Type - title: FeeType - type: string - enum: - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCOther - - Other - FeeAmount: - description: >- - Fee Amount charged for a fee/charge (where it - is charged in terms of an amount rather than a - rate) - title: FeeAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - FeeRate: - description: >- - Rate charged for Fee/Charge (where it is - charged in terms of a rate rather than an - amount) - title: FeeRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - FeeRateType: - description: >- - Rate type for Fee/Charge (where it is charged - in terms of a rate rather than an amount) - title: FeeRateType - type: string - enum: - - LinkedBaseRate - - Gross - - Net - - Other - ApplicationFrequency: - description: >- - How frequently the fee/charge is applied to - the account - title: ApplicationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - CalculationFrequency: - description: How frequently the fee/charge is calculated - title: CalculationFrequency - type: string - enum: - - AccountClosing - - AccountOpening - - AcademicTerm - - ChargingPeriod - - Daily - - PerItem - - Monthly - - OnAccountAnniversary - - Other - - PerHour - - PerOccurrence - - PerSheet - - PerTransaction - - PerTransactionAmount - - PerTransactionPercentage - - Quarterly - - SixMonthly - - StatementMonthly - - Weekly - - Yearly - Notes: - description: >- - Optional additional notes to supplement the - fee/charge details. - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeCategoryType: - type: object - title: OtherFeeCategoryType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherFeeType: - description: >- - Other Fee/charge type which is not available - in the standard code set - type: object - title: OtherFeeType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - FeeCategory: - description: >- - Categorisation of fees and charges into - standard categories. - title: FeeCategory - type: string - enum: - - Other - - Servicing - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - FeeCategory - - Name - - Description - OtherFeeRateType: - description: >- - Other fee rate type which is not available in - the standard code set - type: object - title: OtherFeeRateType - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherApplicationFrequency: - description: >- - Other application frequencies not covered in - the standard code list - type: object - title: OtherApplicationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - OtherCalculationFrequency: - description: >- - Other calculation frequency which is not - available in standard code set. - type: object - title: OtherCalculationFrequency - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of the - code - title: Description - type: string - minLength: 1 - maxLength: 350 - additionalProperties: false - required: - - Name - - Description - FeeChargeCap: - description: >- - Details about any caps (maximum charges) that - apply to a particular fee/charge - type: array - title: FeeChargeCap - items: - description: >- - Details about any caps (maximum charges) - that apply to a particular fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCOther - - Other - minItems: 1 - MinMaxType: - description: >- - Indicates that this is the minimum/ - maximum fee/charge that can be applied - by the financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on - the number of occurrences rather than - capped at a particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: >- - Cap amount charged for a fee/charge - (where it is charged in terms of an - amount rather than a rate) - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for - which the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Free text for adding extra details for - fee charge cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not - available in the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not - available in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - FeeApplicableRange: - description: >- - Range or amounts or rates for which the - fee/charge applies - type: object - title: FeeApplicableRange - properties: - MinimumAmount: - description: >- - Minimum Amount on which fee/charge is - applicable (where it is expressed as an - amount) - title: MinimumAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - MaximumAmount: - description: >- - Maximum Amount on which fee is applicable - (where it is expressed as an amount) - title: MaximumAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - MinimumRate: - description: >- - Minimum rate on which fee/charge is - applicable(where it is expressed as an - rate) - title: MinimumRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - MaximumRate: - description: >- - Maximum rate on which fee/charge is - applicable(where it is expressed as an - rate) - title: MaximumRate - type: string - pattern: '^(-?\d{1,3}){1}(\.\d{1,4}){0,1}$' - additionalProperties: false - required: - - FeeCategory - - FeeType - - ApplicationFrequency - additionalProperties: false - minItems: 1 - FeeChargeCap: - description: >- - Details about any caps (maximum charges) that apply - to a particular fee/charge - type: array - title: FeeChargeCap - items: - description: >- - Details about any caps (maximum charges) that - apply to a particular fee/charge - type: object - properties: - FeeType: - description: Fee/charge type which is being capped - title: FeeType - type: array - items: - description: Fee/charge type which is being capped - type: string - enum: - - ServiceCAccountFee - - ServiceCAccountFeeMonthly - - ServiceCOther - - Other - minItems: 1 - MinMaxType: - description: >- - Indicates that this is the minimum/ maximum - fee/charge that can be applied by the - financial institution - title: MinMaxType - type: string - enum: - - Minimum - - Maximum - FeeCapOccurrence: - description: >- - fee/charges are captured dependent on the - number of occurrences rather than capped at a - particular amount - title: FeeCapOccurrence - type: number - format: float - FeeCapAmount: - description: >- - Cap amount charged for a fee/charge (where it - is charged in terms of an amount rather than a - rate) - title: FeeCapAmount - type: string - pattern: '^(-?\d{1,14}){1}(\.\d{1,4}){0,1}$' - CappingPeriod: - description: >- - Period e.g. day, week, month etc. for which - the fee/charge is capped - title: CappingPeriod - type: string - enum: - - AcademicTerm - - Day - - Half Year - - Month - - Quarter - - Week - - Year - Notes: - description: >- - Free text for adding extra details for fee - charge cap - title: Notes - type: array - items: - description: maxLength 2000 text - type: string - minLength: 1 - maxLength: 2000 - OtherFeeType: - description: >- - Other fee type code which is not available in - the standard code set - type: array - title: OtherFeeType - items: - description: >- - Other fee type code which is not available - in the standard code set - type: object - properties: - Code: - description: >- - The four letter Mnemonic used within an - XML file to identify a code - title: Code - type: string - pattern: '^\w{0,4}$' - minLength: 0 - maxLength: 4 - Name: - description: Long name associated with the code - title: Name - type: string - minLength: 1 - maxLength: 70 - Description: - description: >- - Description to describe the purpose of - the code - title: Description - type: string - minLength: 1 - maxLength: 350 - required: - - Name - - Description - additionalProperties: false - required: - - FeeType - - MinMaxType - additionalProperties: false - additionalProperties: false - required: - - FeeChargeDetail - additionalProperties: false - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadScheduledPayment2: - type: object - required: - - Data - properties: - Data: - type: object - properties: - ScheduledPayment: - type: array - items: - $ref: '#/definitions/OBScheduledPayment2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadStandingOrder4: - type: object - required: - - Data - properties: - Data: - type: object - properties: - StandingOrder: - type: array - items: - $ref: '#/definitions/OBStandingOrder4' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadStandingOrder5: - type: object - required: - - Data - properties: - Data: - type: object - properties: - StandingOrder: - type: array - items: - $ref: '#/definitions/OBStandingOrder5' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadStatement1: - type: object - required: - - Data - properties: - Data: - $ref: '#/definitions/OBReadDataStatement2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadStatement2: - type: object - required: - - Data - properties: - Data: - $ref: '#/definitions/OBReadDataStatement2' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadTransaction4: - type: object - required: - - Data - properties: - Data: - $ref: '#/definitions/OBReadDataTransaction5' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBReadTransaction5: - type: object - required: - - Data - properties: - Data: - $ref: '#/definitions/OBReadDataTransaction5' - Links: - $ref: '#/definitions/Links' - Meta: - $ref: '#/definitions/Meta' - OBRisk2: - description: >- - The Risk section is sent by the initiating party to the ASPSP. It is used - to specify additional details for risk scoring for Account Info. - type: object - properties: {} - additionalProperties: false - OBScheduledPayment2: - type: object - required: - - AccountId - - ScheduledPaymentDateTime - - ScheduledType - - InstructedAmount - properties: - AccountId: - $ref: '#/definitions/AccountId' - ScheduledPaymentId: - $ref: '#/definitions/ScheduledPaymentId' - ScheduledPaymentDateTime: - $ref: '#/definitions/ScheduledPaymentDateTime' - ScheduledType: - $ref: '#/definitions/OBExternalScheduleType1Code' - Reference: - $ref: '#/definitions/Reference' - InstructedAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_9' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - OBScheduledPayment2Basic: - type: object - required: - - AccountId - - ScheduledPaymentDateTime - - ScheduledType - - InstructedAmount - properties: - AccountId: - $ref: '#/definitions/AccountId' - ScheduledPaymentId: - $ref: '#/definitions/ScheduledPaymentId' - ScheduledPaymentDateTime: - $ref: '#/definitions/ScheduledPaymentDateTime' - ScheduledType: - $ref: '#/definitions/OBExternalScheduleType1Code' - Reference: - $ref: '#/definitions/Reference' - InstructedAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_9' - OBScheduledPayment2Detail: - type: object - required: - - AccountId - - ScheduledPaymentDateTime - - ScheduledType - - InstructedAmount - - CreditorAccount - properties: - AccountId: - $ref: '#/definitions/AccountId' - ScheduledPaymentId: - $ref: '#/definitions/ScheduledPaymentId' - ScheduledPaymentDateTime: - $ref: '#/definitions/ScheduledPaymentDateTime' - ScheduledType: - $ref: '#/definitions/OBExternalScheduleType1Code' - Reference: - $ref: '#/definitions/Reference' - InstructedAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_9' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - OBStandingOrder4: - type: object - required: - - AccountId - - Frequency - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_0' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStandingOrder4Basic: - type: object - required: - - AccountId - - Frequency - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_0' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStandingOrder4Detail: - type: object - required: - - AccountId - - Frequency - - CreditorAccount - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_0' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStandingOrder5: - type: object - required: - - AccountId - - Frequency - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_1' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStandingOrder5Basic: - type: object - required: - - AccountId - - Frequency - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_1' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStandingOrder5Detail: - type: object - required: - - AccountId - - Frequency - - CreditorAccount - properties: - AccountId: - $ref: '#/definitions/AccountId' - StandingOrderId: - $ref: '#/definitions/StandingOrderId' - Frequency: - $ref: '#/definitions/Frequency_1' - Reference: - $ref: '#/definitions/Reference' - FirstPaymentDateTime: - $ref: '#/definitions/FirstPaymentDateTime' - NextPaymentDateTime: - $ref: '#/definitions/NextPaymentDateTime' - FinalPaymentDateTime: - $ref: '#/definitions/FinalPaymentDateTime' - StandingOrderStatusCode: - $ref: '#/definitions/OBExternalStandingOrderStatus1Code' - FirstPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_0' - NextPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_1' - FinalPaymentAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_2' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification5_1' - CreditorAccount: - $ref: '#/definitions/OBCashAccount5_0' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBStatement2: - type: object - description: Provides further details on a statement resource. - required: - - AccountId - - Type - - StartDateTime - - EndDateTime - - CreationDateTime - properties: - AccountId: - $ref: '#/definitions/AccountId' - StatementId: - $ref: '#/definitions/StatementId' - StatementReference: - $ref: '#/definitions/StatementReference' - Type: - $ref: '#/definitions/OBExternalStatementType1Code' - StartDateTime: - $ref: '#/definitions/StartDateTime' - EndDateTime: - $ref: '#/definitions/EndDateTime' - CreationDateTime: - $ref: '#/definitions/CreationDateTime' - StatementDescription: - type: array - items: - description: Other descriptions that may be available for the statement resource. - type: string - minLength: 1 - maxLength: 500 - StatementBenefit: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a benefit or reward - amount for the statement resource. - required: - - Type - - Amount - properties: - Type: - $ref: '#/definitions/OBExternalStatementBenefitType1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_3' - StatementFee: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a fee for the statement - resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_1' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementFeeType1Code' - Rate: - $ref: '#/definitions/OBRate1_0' - RateType: - $ref: '#/definitions/OBExternalStatementFeeRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementFeeFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_4' - StatementInterest: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic interest amount - related to the statement resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_2' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementInterestType1Code' - Rate: - $ref: '#/definitions/OBRate1_1' - RateType: - $ref: '#/definitions/OBExternalStatementInterestRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementInterestFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_5' - StatementAmount: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic amount for the - statement resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementAmountType1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_6' - StatementDateTime: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic date time for - the statement resource. - required: - - DateTime - - Type - properties: - DateTime: - $ref: '#/definitions/DateTime' - Type: - $ref: '#/definitions/OBExternalStatementDateTimeType1Code' - StatementRate: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic rate related to - the statement resource. - required: - - Rate - - Type - properties: - Rate: - $ref: '#/definitions/OBExternalStatementRateType1Code' - Type: - $ref: '#/definitions/Type_0' - StatementValue: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic number value - related to the statement resource. - required: - - Value - - Type - properties: - Value: - $ref: '#/definitions/OBExternalStatementValueType1Code' - Type: - $ref: '#/definitions/Type_1' - OBStatement2Basic: - type: object - description: Provides further details on a statement resource. - required: - - AccountId - - Type - - StartDateTime - - EndDateTime - - CreationDateTime - properties: - AccountId: - $ref: '#/definitions/AccountId' - StatementId: - $ref: '#/definitions/StatementId' - StatementReference: - $ref: '#/definitions/StatementReference' - Type: - $ref: '#/definitions/OBExternalStatementType1Code' - StartDateTime: - $ref: '#/definitions/StartDateTime' - EndDateTime: - $ref: '#/definitions/EndDateTime' - CreationDateTime: - $ref: '#/definitions/CreationDateTime' - StatementDescription: - type: array - items: - description: Other descriptions that may be available for the statement resource. - type: string - minLength: 1 - maxLength: 500 - StatementBenefit: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a benefit or reward - amount for the statement resource. - required: - - Type - - Amount - properties: - Type: - $ref: '#/definitions/OBExternalStatementBenefitType1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_3' - StatementFee: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a fee for the statement - resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_1' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementFeeType1Code' - Rate: - $ref: '#/definitions/OBRate1_0' - RateType: - $ref: '#/definitions/OBExternalStatementFeeRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementFeeFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_4' - StatementInterest: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic interest amount - related to the statement resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_2' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementInterestType1Code' - Rate: - $ref: '#/definitions/OBRate1_1' - RateType: - $ref: '#/definitions/OBExternalStatementInterestRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementInterestFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_5' - StatementDateTime: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic date time for - the statement resource. - required: - - DateTime - - Type - properties: - DateTime: - $ref: '#/definitions/DateTime' - Type: - $ref: '#/definitions/OBExternalStatementDateTimeType1Code' - StatementRate: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic rate related to - the statement resource. - required: - - Rate - - Type - properties: - Rate: - $ref: '#/definitions/OBExternalStatementRateType1Code' - Type: - $ref: '#/definitions/Type_0' - StatementValue: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic number value - related to the statement resource. - required: - - Value - - Type - properties: - Value: - $ref: '#/definitions/OBExternalStatementValueType1Code' - Type: - $ref: '#/definitions/Type_1' - OBStatement2Detail: - type: object - description: Provides further details on a statement resource. - required: - - AccountId - - Type - - StartDateTime - - EndDateTime - - CreationDateTime - properties: - AccountId: - $ref: '#/definitions/AccountId' - StatementId: - $ref: '#/definitions/StatementId' - StatementReference: - $ref: '#/definitions/StatementReference' - Type: - $ref: '#/definitions/OBExternalStatementType1Code' - StartDateTime: - $ref: '#/definitions/StartDateTime' - EndDateTime: - $ref: '#/definitions/EndDateTime' - CreationDateTime: - $ref: '#/definitions/CreationDateTime' - StatementDescription: - type: array - items: - description: Other descriptions that may be available for the statement resource. - type: string - minLength: 1 - maxLength: 500 - StatementBenefit: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a benefit or reward - amount for the statement resource. - required: - - Type - - Amount - properties: - Type: - $ref: '#/definitions/OBExternalStatementBenefitType1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_3' - StatementFee: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a fee for the statement - resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_1' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementFeeType1Code' - Rate: - $ref: '#/definitions/OBRate1_0' - RateType: - $ref: '#/definitions/OBExternalStatementFeeRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementFeeFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_4' - StatementInterest: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic interest amount - related to the statement resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - Description: - $ref: '#/definitions/Description_2' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementInterestType1Code' - Rate: - $ref: '#/definitions/OBRate1_1' - RateType: - $ref: '#/definitions/OBExternalStatementInterestRateType1Code' - Frequency: - $ref: '#/definitions/OBExternalStatementInterestFrequency1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_5' - StatementAmount: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic amount for the - statement resource. - required: - - CreditDebitIndicator - - Type - - Amount - properties: - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_0' - Type: - $ref: '#/definitions/OBExternalStatementAmountType1Code' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_6' - StatementDateTime: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic date time for - the statement resource. - required: - - DateTime - - Type - properties: - DateTime: - $ref: '#/definitions/DateTime' - Type: - $ref: '#/definitions/OBExternalStatementDateTimeType1Code' - StatementRate: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic rate related to - the statement resource. - required: - - Rate - - Type - properties: - Rate: - $ref: '#/definitions/OBExternalStatementRateType1Code' - Type: - $ref: '#/definitions/Type_0' - StatementValue: - type: array - items: - type: object - description: >- - Set of elements used to provide details of a generic number value - related to the statement resource. - required: - - Value - - Type - properties: - Value: - $ref: '#/definitions/OBExternalStatementValueType1Code' - Type: - $ref: '#/definitions/Type_1' - OBSupplementaryData1: - type: object - properties: {} - additionalProperties: true - description: >- - Additional information that can not be captured in the structured fields - and/or any other specific block. - OBTransaction5: - type: object - description: Provides further details on an entry in the report. - required: - - AccountId - - CreditDebitIndicator - - Status - - BookingDateTime - - Amount - properties: - AccountId: - $ref: '#/definitions/AccountId' - TransactionId: - $ref: '#/definitions/TransactionId' - TransactionReference: - $ref: '#/definitions/TransactionReference' - StatementReference: - type: array - items: - $ref: '#/definitions/StatementReference' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_1' - Status: - $ref: '#/definitions/OBEntryStatus1Code' - BookingDateTime: - $ref: '#/definitions/BookingDateTime' - ValueDateTime: - $ref: '#/definitions/ValueDateTime' - TransactionInformation: - $ref: '#/definitions/TransactionInformation' - AddressLine: - $ref: '#/definitions/AddressLine' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_7' - ChargeAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_8' - CurrencyExchange: - $ref: '#/definitions/OBCurrencyExchange5' - BankTransactionCode: - $ref: '#/definitions/OBBankTransactionCodeStructure1' - ProprietaryBankTransactionCode: - $ref: '#/definitions/ProprietaryBankTransactionCodeStructure1' - Balance: - $ref: '#/definitions/OBTransactionCashBalance' - MerchantDetails: - $ref: '#/definitions/OBMerchantDetails1' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_0' - CreditorAccount: - $ref: '#/definitions/OBCashAccount6_0' - DebtorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_1' - DebtorAccount: - $ref: '#/definitions/OBCashAccount6_1' - CardInstrument: - $ref: '#/definitions/OBTransactionCardInstrument1' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBTransaction5Basic: - type: object - description: Provides further details on an entry in the report. - required: - - AccountId - - CreditDebitIndicator - - Status - - BookingDateTime - - Amount - properties: - AccountId: - $ref: '#/definitions/AccountId' - TransactionId: - $ref: '#/definitions/TransactionId' - TransactionReference: - $ref: '#/definitions/TransactionReference' - StatementReference: - type: array - items: - $ref: '#/definitions/StatementReference' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_1' - Status: - $ref: '#/definitions/OBEntryStatus1Code' - BookingDateTime: - $ref: '#/definitions/BookingDateTime' - ValueDateTime: - $ref: '#/definitions/ValueDateTime' - AddressLine: - $ref: '#/definitions/AddressLine' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_7' - ChargeAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_8' - CurrencyExchange: - $ref: '#/definitions/OBCurrencyExchange5' - BankTransactionCode: - $ref: '#/definitions/OBBankTransactionCodeStructure1' - ProprietaryBankTransactionCode: - $ref: '#/definitions/ProprietaryBankTransactionCodeStructure1' - CardInstrument: - $ref: '#/definitions/OBTransactionCardInstrument1' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBTransaction5Detail: - type: object - description: Provides further details on an entry in the report. - required: - - AccountId - - CreditDebitIndicator - - Status - - BookingDateTime - - Amount - properties: - AccountId: - $ref: '#/definitions/AccountId' - TransactionId: - $ref: '#/definitions/TransactionId' - TransactionReference: - $ref: '#/definitions/TransactionReference' - StatementReference: - type: array - items: - $ref: '#/definitions/StatementReference' - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_1' - Status: - $ref: '#/definitions/OBEntryStatus1Code' - BookingDateTime: - $ref: '#/definitions/BookingDateTime' - ValueDateTime: - $ref: '#/definitions/ValueDateTime' - TransactionInformation: - $ref: '#/definitions/TransactionInformation' - AddressLine: - $ref: '#/definitions/AddressLine' - Amount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_7' - ChargeAmount: - $ref: '#/definitions/OBActiveOrHistoricCurrencyAndAmount_8' - CurrencyExchange: - $ref: '#/definitions/OBCurrencyExchange5' - BankTransactionCode: - $ref: '#/definitions/OBBankTransactionCodeStructure1' - ProprietaryBankTransactionCode: - $ref: '#/definitions/ProprietaryBankTransactionCodeStructure1' - Balance: - $ref: '#/definitions/OBTransactionCashBalance' - MerchantDetails: - $ref: '#/definitions/OBMerchantDetails1' - CreditorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_0' - CreditorAccount: - $ref: '#/definitions/OBCashAccount6_0' - DebtorAgent: - $ref: '#/definitions/OBBranchAndFinancialInstitutionIdentification6_1' - DebtorAccount: - $ref: '#/definitions/OBCashAccount6_1' - CardInstrument: - $ref: '#/definitions/OBTransactionCardInstrument1' - SupplementaryData: - $ref: '#/definitions/OBSupplementaryData1' - OBTransactionCardInstrument1: - type: object - required: - - CardSchemeName - description: Set of elements to describe the card instrument used in the transaction. - properties: - CardSchemeName: - description: Name of the card scheme. - type: string - enum: - - AmericanExpress - - Diners - - Discover - - MasterCard - - VISA - AuthorisationType: - description: The card authorisation type. - type: string - enum: - - ConsumerDevice - - Contactless - - None - - PIN - Name: - description: Name of the cardholder using the card instrument. - type: string - minLength: 1 - maxLength: 70 - Identification: - description: >- - Identification assigned by an institution to identify the card - instrument used in the transaction. This identification is known by - the account owner, and may be masked. - type: string - minLength: 1 - maxLength: 34 - OBTransactionCashBalance: - type: object - required: - - CreditDebitIndicator - - Type - - Amount - description: >- - Set of elements used to define the balance as a numerical representation - of the net increases and decreases in an account after a transaction entry - is applied to the account. - properties: - CreditDebitIndicator: - $ref: '#/definitions/OBCreditDebitCode_2' - Type: - $ref: '#/definitions/OBBalanceType1Code' - Amount: - type: object - required: - - Amount - - Currency - description: >- - Amount of money of the cash balance after a transaction entry is - applied to the account.. - properties: - Amount: - $ref: '#/definitions/OBActiveCurrencyAndAmount_SimpleType' - Currency: - $ref: '#/definitions/ActiveOrHistoricCurrencyCode_1' - OB_Amount1_0: - description: Cap amount charged for a fee/charge - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - OB_Amount1_1: - description: >- - Every additional tranche of an overdraft balance to which an overdraft fee - is applied - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - OB_Amount1_2: - description: >- - Amount charged for an overdraft fee/charge (where it is charged in terms - of an amount rather than a rate) - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - OB_Amount1_3: - description: >- - Fee Amount charged for a fee/charge (where it is charged in terms of an - amount rather than a rate) - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - OB_Amount1_4: - description: >- - Cap amount charged for a fee/charge (where it is charged in terms of an - amount rather than a rate) - type: string - pattern: '^(-?\\d{1,14}){1}(\\.\\d{1,4}){0,1}$' - OB_CodeMnemonic: - description: The four letter Mnemonic used within an XML file to identify a code - type: string - pattern: '^\\w{0,4}$' - OB_FeeCategory1Code: - description: Categorisation of fees and charges into standard categories. - type: string - enum: - - FCOT - - FCRE - - FCSV - OB_FeeFrequency1Code_0: - description: Frequency at which the overdraft charge is applied to the account - type: string - enum: - - FEAC - - FEAO - - FECP - - FEDA - - FEHO - - FEI - - FEMO - - FEOA - - FEOT - - FEPC - - FEPH - - FEPO - - FEPS - - FEPT - - FEPTA - - FEPTP - - FEQU - - FESM - - FEST - - FEWE - - FEYE - OB_FeeFrequency1Code_1: - description: How often is the overdraft fee/charge calculated for the account. - type: string - enum: - - FEAC - - FEAO - - FECP - - FEDA - - FEHO - - FEI - - FEMO - - FEOA - - FEOT - - FEPC - - FEPH - - FEPO - - FEPS - - FEPT - - FEPTA - - FEPTP - - FEQU - - FESM - - FEST - - FEWE - - FEYE - OB_FeeFrequency1Code_2: - description: How frequently the fee/charge is applied to the account - type: string - enum: - - FEAC - - FEAO - - FECP - - FEDA - - FEHO - - FEI - - FEMO - - FEOA - - FEOT - - FEPC - - FEPH - - FEPO - - FEPS - - FEPT - - FEPTA - - FEPTP - - FEQU - - FESM - - FEST - - FEWE - - FEYE - OB_FeeFrequency1Code_3: - description: How frequently the fee/charge is calculated - type: string - enum: - - FEAC - - FEAO - - FECP - - FEDA - - FEHO - - FEI - - FEMO - - FEOA - - FEOT - - FEPC - - FEPH - - FEPO - - FEPS - - FEPT - - FEPTA - - FEPTP - - FEQU - - FESM - - FEST - - FEWE - - FEYE - OB_FeeFrequency1Code_4: - description: 'Period e.g. day, week, month etc. for which the fee/charge is capped' - type: string - enum: - - FEAC - - FEAO - - FECP - - FEDA - - FEHO - - FEI - - FEMO - - FEOA - - FEOT - - FEPC - - FEPH - - FEPO - - FEPS - - FEPT - - FEPTA - - FEPTP - - FEQU - - FESM - - FEST - - FEWE - - FEYE - OB_FeeType1Code: - description: Fee/Charge Type - type: string - enum: - - FEPF - - FTOT - - FYAF - - FYAM - - FYAQ - - FYCP - - FYDB - - FYMI - - FYXX - OB_InterestCalculationMethod1Code: - description: Methods of calculating interest - type: string - enum: - - ITCO - - ITOT - - ITSI - OB_InterestFixedVariableType1Code: - description: 'Type of interest rate, Fixed or Variable' - type: string - enum: - - INFI - - INVA - OB_InterestRateType1Code_0: - description: >- - Rate type for overdraft fee/charge (where it is charged in terms of a rate - rather than an amount) - type: string - enum: - - INBB - - INFR - - INGR - - INLR - - INNE - - INOT - OB_InterestRateType1Code_1: - description: >- - Rate type for Fee/Charge (where it is charged in terms of a rate rather - than an amount) - type: string - enum: - - INBB - - INFR - - INGR - - INLR - - INNE - - INOT - OB_MinMaxType1Code: - description: Min Max type - type: string - enum: - - FMMN - - FMMX - OB_OtherCodeType1_0: - type: object - required: - - Name - - Description - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_1: - type: object - required: - - Name - - Description - description: >- - Other application frequencies that are not available in the standard code - list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_2: - type: object - required: - - Name - - Description - description: >- - Other calculation frequency which is not available in the standard code - set. - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_3: - type: object - required: - - Name - - Description - description: Other Fee type which is not available in the standard code set - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_4: - type: object - required: - - Name - - Description - description: Other fee rate type code which is not available in the standard code set - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_5: - type: object - required: - - Name - - Description - description: Other fee rate type which is not in the standard rate type list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_6: - type: object - required: - - Name - - Description - description: Other application frequencies not covered in the standard code list - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_7: - type: object - required: - - Name - - Description - description: Other calculation frequency which is not available in standard code set. - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherCodeType1_8: - type: object - required: - - Name - - Description - description: Other fee rate type which is not available in the standard code set - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OtherFeeChargeDetailType: - type: object - required: - - FeeCategory - - Name - - Description - description: Other Fee/charge type which is not available in the standard code set - properties: - Code: - $ref: '#/definitions/OB_CodeMnemonic' - FeeCategory: - $ref: '#/definitions/OB_FeeCategory1Code' - Name: - $ref: '#/definitions/Name_3' - Description: - $ref: '#/definitions/Description_3' - OB_OverdraftFeeType1Code: - description: Overdraft fee type - type: string - enum: - - FBAO - - FBAR - - FBEB - - FBIT - - FBOR - - FBOS - - FBSC - - FBTO - - FBUB - - FBUT - - FTOT - - FTUT - OB_Period1Code: - description: 'Period e.g. day, week, month etc. for which the fee/charge is capped' - type: string - enum: - - PACT - - PDAY - - PHYR - - PMTH - - PQTR - - PWEK - - PYER - OB_Rate1_0: - description: >- - Rate charged for overdraft fee/charge (where it is charged in terms of a - rate rather than an amount) - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - OB_Rate1_1: - description: >- - Rate charged for Fee/Charge (where it is charged in terms of a rate rather - than an amount) - type: string - pattern: '^(-?\\d{1,3}){1}(\\.\\d{1,4}){0,1}$' - PartyId: - description: >- - A unique and immutable identifier used to identify the customer resource. - This identifier has no meaning to the account owner. - type: string - minLength: 1 - maxLength: 40 - PartyNumber: - description: Number assigned by an agent to identify its customer. - type: string - minLength: 1 - maxLength: 35 - PhoneNumber_0: - description: >- - Collection of information that identifies a phone number, as defined by - telecom services. - type: string - pattern: '\+[0-9]{1,3}-[0-9()+\-]{1,30}' - PhoneNumber_1: - description: >- - Collection of information that identifies a mobile phone number, as - defined by telecom services. - type: string - pattern: '\+[0-9]{1,3}-[0-9()+\-]{1,30}' - PostCode: - description: >- - Identifier consisting of a group of letters and/or numbers that is added - to a postal address to assist the sorting of mail. - type: string - minLength: 1 - maxLength: 16 - ProprietaryBankTransactionCodeStructure1: - type: object - required: - - Code - description: Set of elements to fully identify a proprietary bank transaction code. - properties: - Code: - description: >- - Proprietary bank transaction code to identify the underlying - transaction. - type: string - minLength: 1 - maxLength: 35 - Issuer: - description: Identification of the issuer of the proprietary bank transaction code. - type: string - minLength: 1 - maxLength: 35 - Reference: - description: >- - Unique reference, as assigned by the creditor, to unambiguously refer to - the payment transaction. - - Usage: If available, the initiating party should provide this reference in - the structured remittance information, to enable reconciliation by the - creditor upon receipt of the amount of money. - - If the business context requires the use of a creditor reference or a - payment remit identification, and only one identifier can be passed - through the end-to-end chain, the creditor's reference or payment - remittance identification should be quoted in the end-to-end transaction - identification. - type: string - minLength: 1 - maxLength: 35 - ScheduledPaymentDateTime: - description: >- - The date on which the scheduled payment will be made.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - ScheduledPaymentId: - description: >- - A unique and immutable identifier used to identify the scheduled payment - resource. This identifier has no meaning to the account owner. - type: string - minLength: 1 - maxLength: 40 - SecondaryIdentification: - description: >- - This is secondary identification of the account, as assigned by the - account servicing institution. - - This can be used by building societies to additionally identify accounts - with a roll number (in addition to a sort code and account number - combination). - type: string - minLength: 1 - maxLength: 34 - StandingOrderId: - description: >- - A unique and immutable identifier used to identify the standing order - resource. This identifier has no meaning to the account owner. - type: string - minLength: 1 - maxLength: 40 - StartDateTime: - description: >- - Date and time at which the statement period starts.All dates in the JSON - payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - StatementId: - description: >- - Unique identifier for the statement resource within an servicing - institution. This identifier is both unique and immutable. - type: string - minLength: 1 - maxLength: 40 - StatementReference: - description: >- - Unique reference for the statement. This reference may be optionally - populated if available. - type: string - minLength: 1 - maxLength: 35 - StatusUpdateDateTime: - description: >- - Date and time at which the resource status was updated.All dates in the - JSON payloads are represented in ISO 8601 date-time format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - StreetName: - description: Name of a street or thoroughfare. - type: string - minLength: 1 - maxLength: 70 - TownName: - description: 'Name of a built-up area, with defined boundaries, and a local government.' - type: string - minLength: 1 - maxLength: 35 - TransactionId: - description: >- - Unique identifier for the transaction within an servicing institution. - This identifier is both unique and immutable. - type: string - minLength: 1 - maxLength: 210 - TransactionInformation: - description: |- - Further details of the transaction. - This is the transaction narrative, which is unstructured text. - type: string - minLength: 1 - maxLength: 500 - TransactionReference: - description: >- - Unique reference for the transaction. This reference is optionally - populated, and may as an example be the FPID in the Faster Payments - context. - type: string - minLength: 1 - maxLength: 35 - Type_0: - description: 'Statement rate type, in a coded form.' - type: string - minLength: 1 - maxLength: 40 - Type_1: - description: 'Statement value type, in a coded form.' - type: string - minLength: 1 - maxLength: 40 - ValueDateTime: - description: >- - Date and time at which assets become available to the account owner in - case of a credit entry, or cease to be available to the account owner in - case of a debit transaction entry. - - Usage: If transaction entry status is pending and value date is present, - then the value date refers to an expected/requested value date. - - For transaction entries subject to availability/float and for which - availability information is provided, the value date must not be used. In - this case the availability component identifies the number of availability - days.All dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - OBReadData1: - type: object - required: - - Permissions - properties: - Permissions: - type: array - items: - $ref: '#/definitions/OBExternalPermissions1Code' - minItems: 1 - description: >- - Specifies the Open Banking account access data types. This is a list - of the data clusters being consented by the PSU, and requested for - authorisation with the ASPSP. - ExpirationDateTime: - description: >- - Specified date and time the permissions will expire. - - If this is not populated, the permissions will be open ended.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - TransactionFromDateTime: - description: >- - Specified start date and time for the transaction query period. - - If this is not populated, the start date will be open ended, and - data will be returned from the earliest available transaction.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - TransactionToDateTime: - description: >- - Specified end date and time for the transaction query period. - - If this is not populated, the end date will be open ended, and - data will be returned to the latest available transaction.All - dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An - example is below: - - 2017-04-05T10:43:07+00:00 - type: string - format: date-time - OBExternalPermissions1Code: - description: >- - Specifies the Open Banking account access data types. This is a - list of the data clusters being consented by the PSU, and - requested for authorisation with the ASPSP. - type: string - enum: - - ReadAccountsDetail - - ReadBalances - - ReadTransactionsDetail - OBExternalRequestStatus1Code: - description: Specifies the status of consent resource in code form. - type: string - enum: - - Authorised - - AwaitingAuthorisation - - Rejected - - Revoked x-wso2-security: apim: x-wso2-scopes: diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml index 7d466883..14724f43 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml @@ -23,7 +23,7 @@ - + @@ -31,7 +31,7 @@

-
+
diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/pom.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/pom.xml index 0a7146a7..de89a00e 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/pom.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/pom.xml @@ -203,6 +203,7 @@ com.fasterxml.jackson.core;version="${jackson.databinding.version}", org.owasp.encoder;version="${encoder.wso2.version}" + * !org.wso2.financial.services.accelerator.consent.mgt.extensions.internal, org.wso2.financial.services.accelerator.consent.mgt.extensions.*;version="${project.version}", diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java index 86efe5c2..1b914de0 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java @@ -24,6 +24,7 @@ public class ConsentExtensionConstants { //Common Constants + public static final String TENANT_DOMAIN = "carbon.super"; public static final String UUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; public static final String ACCOUNTS = "accounts"; diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java index 48549d0c..3c19c6f5 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java @@ -66,8 +66,8 @@ public static boolean isTransactionFromToTimeValid(String fromDateVal, String to OffsetDateTime fromDate = OffsetDateTime.parse(fromDateVal); OffsetDateTime toDate = OffsetDateTime.parse(toDateVal); - // From date is earlier than To date - return toDate.isAfter(fromDate); + // From date is equal or earlier than To date + return toDate.isEqual(fromDate) || toDate.isAfter(fromDate); } catch (DateTimeParseException e) { return false; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java index 4e59bf0e..27008450 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java @@ -26,6 +26,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.user.api.UserStoreException; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.AuthorizationResource; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.DetailedConsentResource; import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ConsentException; @@ -81,7 +83,7 @@ public void validate(ConsentValidateData consentValidateData, ConsentValidationR } //User Validation - String userIdFromToken = consentValidateData.getUserId(); + String userIdFromToken = resolveUsernameFromUserId(consentValidateData.getUserId()); boolean userIdMatching = false; ArrayList authResources = consentValidateData.getComprehensiveConsent() .getAuthorizationResources(); @@ -164,8 +166,8 @@ private void validateAccountSubmission(ConsentValidateData consentValidateData, log.error(PERMISSION_MISMATCH_ERROR); consentValidationResult.setValid(false); consentValidationResult.setErrorMessage(PERMISSION_MISMATCH_ERROR); - consentValidationResult.setErrorCode(ResponseStatus.UNAUTHORIZED.getReasonPhrase()); - consentValidationResult.setHttpCode(401); + consentValidationResult.setErrorCode(ResponseStatus.FORBIDDEN.getReasonPhrase()); + consentValidationResult.setHttpCode(403); return; } @@ -396,4 +398,25 @@ public static boolean isJsonObjectsSimilar(JSONObject object1, JSONObject object return false; } } + + /** + * Method to resolve username from user ID. + * + * @param userID User ID + * @return Username + */ + private String resolveUsernameFromUserId(String userID) { + String username = null; + try { + if (userID.contains(ConsentExtensionConstants.TENANT_DOMAIN)) { + username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, + userID.split("@")[0]); + } else { + username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, userID); + } + } catch (UserStoreException e) { + return null; + } + return username; + } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml index 42bbebf7..7d8b94b7 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/pom.xml @@ -43,13 +43,17 @@ commons-lang3 - io.swagger.parser.v3 + org.wso2.orbit.io.swagger.v3 swagger-parser commons-io.wso2 commons-io + + io.jsonwebtoken + jjwt + org.wso2.carbon.apimgt org.wso2.carbon.apimgt.impl @@ -78,10 +82,6 @@ mockito-testng test - - io.jsonwebtoken - jjwt - @@ -212,14 +212,17 @@ org.osgi.framework; version="${osgi.framework.imp.pkg.version.range}", org.osgi.service.component; version="${osgi.service.component.imp.pkg.version.range}", org.apache.commons.io;version="${org.apache.commons.io.version.range}", - io.swagger.parser.*;version="${swagger.parser.version}", + io.swagger.parser.*; version="${swagger.parser.v3.version}", + io.swagger.v3.*; version="${swagger.parser.v3.version}", org.apache.commons.logging;version="${commons.logging.version}", org.apache.http.*;version="${orbit.httpcore.version}", org.json;version="${org.json.version.range}", org.wso2.carbon.apimgt.common.gateway.*;version="${org.wso2.carbon.apimgt.version.range}", org.wso2.carbon.apimgt.impl;version="${org.wso2.carbon.apimgt.version.range}", - org.wso2.financial.services.accelerator.common.*;version="${project.version}" + org.wso2.financial.services.accelerator.common.*;version="${project.version}", + io.jsonwebtoken;version="${jjwt.version.range}" + * !org.wso2.financial.services.accelerator.gateway.internal, org.wso2.financial.services.accelerator.gateway.*;version="${project.version}", diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java index bfa40267..f5a69397 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java @@ -20,11 +20,12 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.json.JSONObject; @@ -32,6 +33,7 @@ import org.wso2.financial.services.accelerator.common.constant.FinancialServicesErrorCodes; import org.wso2.financial.services.accelerator.common.exception.FinancialServicesException; import org.wso2.financial.services.accelerator.common.util.Generated; +import org.wso2.financial.services.accelerator.common.util.HTTPClientUtils; import org.wso2.financial.services.accelerator.gateway.executor.core.FinancialServicesGatewayExecutor; import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; @@ -227,8 +229,8 @@ protected static Key getJWTSigningKey() { protected String generateJWT(String payload) { return Jwts.builder() - .content(payload) - .signWith(getJWTSigningKey()) + .setPayload(payload) + .signWith(SignatureAlgorithm.RS512, getJWTSigningKey()) .compact(); } @@ -251,7 +253,7 @@ private String invokeConsentValidationService(String enforcementJWTPayload) thro String userName = GatewayUtils.getAPIMgtConfig(GatewayConstants.API_KEY_VALIDATOR_USERNAME); String password = GatewayUtils.getAPIMgtConfig(GatewayConstants.API_KEY_VALIDATOR_PASSWORD); httpPost.setHeader(GatewayConstants.AUTH_HEADER, GatewayUtils.getBasicAuthHeader(userName, password)); - HttpResponse response = GatewayDataHolder.getHttpClient().execute(httpPost); + CloseableHttpResponse response = HTTPClientUtils.getHttpsClient().execute(httpPost); InputStream in = response.getEntity().getContent(); return IOUtils.toString(in, String.valueOf(StandardCharsets.UTF_8)); } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java new file mode 100644 index 00000000..b458e2e5 --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.impl.consent; + +import org.apache.commons.io.FileUtils; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.json.JSONObject; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.apimgt.common.gateway.dto.APIRequestInfoDTO; +import org.wso2.carbon.apimgt.common.gateway.dto.MsgInfoDTO; +import org.wso2.carbon.apimgt.impl.APIManagerConfiguration; +import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService; +import org.wso2.financial.services.accelerator.common.config.FinancialServicesConfigurationService; +import org.wso2.financial.services.accelerator.common.constant.FinancialServicesConstants; +import org.wso2.financial.services.accelerator.common.util.HTTPClientUtils; +import org.wso2.financial.services.accelerator.gateway.GatewayTestConstants; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSExecutorError; +import org.wso2.financial.services.accelerator.gateway.internal.GatewayDataHolder; +import org.wso2.financial.services.accelerator.gateway.util.GatewayUtils; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Test for enforcement executor. + */ +public class TestEnforcementExecutor { + + private static ConsentEnforcementExecutor consentEnforcementExecutor; + private static MockedStatic httpClientUtilsMockedStatic; + + @BeforeClass + public static void beforeClass() throws IOException { + + GatewayDataHolder dataHolder = GatewayDataHolder.getInstance(); + String path = "src/test/resources"; + File file = new File(path); + String absolutePathForTestResources = file.getAbsolutePath(); + dataHolder.setKeyStoreLocation(absolutePathForTestResources + "/wso2carbon.jks"); + dataHolder.setKeyAlias("wso2carbon"); + dataHolder.setKeyPassword("wso2carbon"); + dataHolder.setKeyStorePassword("wso2carbon".toCharArray()); + + Map configs = new HashMap<>(); + configs.put(FinancialServicesConstants.CONSENT_VALIDATION_ENDPOINT, "http://localhost:8080"); + configs.put(FinancialServicesConstants.REQUEST_ROUTER, + "org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter"); + FinancialServicesConfigurationService financialServicesConfigurationService = + Mockito.mock(FinancialServicesConfigurationService.class); + Mockito.when(financialServicesConfigurationService.getConfigurations()).thenReturn(configs); + dataHolder.setFinancialServicesConfigurationService(financialServicesConfigurationService); + + APIManagerConfiguration apiManagerConfiguration = Mockito.mock(APIManagerConfiguration.class); + Mockito.when(apiManagerConfiguration.getFirstProperty(Mockito.anyString())).thenReturn("admin"); + APIManagerConfigurationService apimConfigurationService = Mockito.mock(APIManagerConfigurationService.class); + Mockito.when(apimConfigurationService.getAPIManagerConfiguration()).thenReturn(apiManagerConfiguration); + dataHolder.setApiManagerConfiguration(apimConfigurationService); + + File responseFile = new File("src/test/resources/test-validation-response.json"); + byte[] crlBytes = FileUtils.readFileToString(responseFile, String.valueOf(StandardCharsets.UTF_8)) + .getBytes(StandardCharsets.UTF_8); + InputStream inStream = new ByteArrayInputStream(crlBytes); + + HttpEntity httpEntityMock = Mockito.mock(HttpEntity.class); + Mockito.doReturn(inStream).when(httpEntityMock).getContent(); + + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + CloseableHttpResponse httpResponse = Mockito.mock(CloseableHttpResponse.class); + Mockito.doReturn(httpEntityMock).when(httpResponse).getEntity(); + Mockito.doReturn(httpResponse).when(httpClient).execute(Mockito.any()); + + httpClientUtilsMockedStatic = Mockito.mockStatic(HTTPClientUtils.class); + httpClientUtilsMockedStatic.when(() -> HTTPClientUtils.getHttpsClient()).thenReturn(httpClient); + + consentEnforcementExecutor = new ConsentEnforcementExecutor(); + } + + @AfterClass + public static void afterClass() { + httpClientUtilsMockedStatic.close(); + } + + @Test(priority = 1) + public void testSigningKeyRetrieval() { + + Assert.assertNotNull(consentEnforcementExecutor.getJWTSigningKey()); + } + + @Test(priority = 2) + public void testJWTGeneration() { + + String jwtToken = consentEnforcementExecutor.generateJWT(GatewayTestConstants.CUSTOM_PAYLOAD); + Assert.assertNotNull(jwtToken); + String[] parts = jwtToken.split("\\."); + Assert.assertEquals(parts.length, 3); + } + + @Test(priority = 2) + public void testValidationPayloadCreation() { + + Map headers = new HashMap<>(); + headers.put("customHeader", "headerValue"); + headers.put("customHeader2", "headerValue2"); + JSONObject jsonObject = + consentEnforcementExecutor.createValidationRequestPayload(headers, + GatewayTestConstants.CUSTOM_PAYLOAD, new HashMap<>()); + Assert.assertNotNull(jsonObject); + Assert.assertEquals(((JSONObject) jsonObject.get(ConsentEnforcementExecutor.HEADERS_TAG)).get("customHeader"), + "headerValue"); + Assert.assertEquals(((JSONObject) jsonObject.get(ConsentEnforcementExecutor.BODY_TAG)).get("custom"), + "payload"); + } + + @Test(priority = 3) + public void testB64Decoder() throws UnsupportedEncodingException { + + String jwtToken = "eyJjdXN0b20iOiJwYXlsb2FkIn0"; + JSONObject jsonObject = GatewayUtils.decodeBase64(jwtToken); + Assert.assertEquals(jsonObject.get("custom").toString(), "payload"); + } + + @Test + public void testHandlerError() { + FSAPIRequestContext fsapiRequestContext = Mockito.mock(FSAPIRequestContext.class); + ArrayList errors = new ArrayList<>(); + Mockito.when(fsapiRequestContext.getErrors()).thenReturn(errors); + consentEnforcementExecutor.handleError(fsapiRequestContext, "Error", "Error", + "400"); + } + + @Test + public void testPostProcessRequest() { + String consentID = String.valueOf(UUID.randomUUID()); + + MsgInfoDTO msgInfoDTOMock = Mockito.mock(MsgInfoDTO.class); + Mockito.doReturn("/accounts").when(msgInfoDTOMock).getElectedResource(); + Mockito.doReturn("/accounts").when(msgInfoDTOMock).getResource(); + Mockito.doReturn("GET").when(msgInfoDTOMock).getHttpMethod(); + + APIRequestInfoDTO apiRequestInfoDTOMock = Mockito.mock(APIRequestInfoDTO.class); + Mockito.doReturn("admin@wso2.com").when(apiRequestInfoDTOMock).getUsername(); + Mockito.doReturn("test-client-id").when(apiRequestInfoDTOMock).getConsumerKey(); + Mockito.doReturn("/open-banking/v3.1/aisp").when(apiRequestInfoDTOMock).getContext(); + + FSAPIRequestContext fsapiRequestContext = Mockito.mock(FSAPIRequestContext.class); + Mockito.doReturn(false).when(fsapiRequestContext).isError(); + Mockito.doReturn(consentID).when(fsapiRequestContext).getConsentId(); + Mockito.doReturn(apiRequestInfoDTOMock).when(fsapiRequestContext).getApiRequestInfo(); + Mockito.doReturn(msgInfoDTOMock).when(fsapiRequestContext).getMsgInfo(); + + consentEnforcementExecutor.postProcessRequest(fsapiRequestContext); + } + +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json new file mode 100644 index 00000000..28effa17 --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json @@ -0,0 +1,12 @@ +{ + "isValid": true, + "errorCode": "", + "errorMessage": "", + "httpCode": "", + "modifiedPayload": { + "key": "value" + }, + "consentInformation": { + "key": "value" + } +} \ No newline at end of file diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml index 8e4cd5f5..dd517ea2 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml @@ -22,6 +22,7 @@ + diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/wso2carbon.jks b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/wso2carbon.jks new file mode 100644 index 0000000000000000000000000000000000000000..c8775783fb3555d6e88255c1236576e7a4a7df1f GIT binary patch literal 98874 zcmdqJ1z1(>+Ac~rNOw#?>6nuaX%LW*Zj_pIcc^p;NC`+uHxkktE;W!*v<&7=!taImUC}&;7*vUT$4(K|w*mK>ldl{7YbDW@2Utws+Kb zaB#8*8<`o|+t@f7>O(<6TTa&#bV0#uuy6u-p`c*vfIhHQKp&WtR9F~j7#K{n)wyMY zu}7ET%X;S|yFgwbIs!67fKzBG;zp` zVvrdD($IXk!Js52U(21&c%S`!_^xZxSFa4p4&JCO}LC zNKSwb}z}p`!0(5rB==bnHbsIj=RCgZ$G&~fn5A;1Kpbs<(j1M$4)O!rPo+U9a zozlRAD!m_e*jYtWG;f0P4||RPymT6RXeVA1d3Z5w`VT8g>HJ>3no2=I^CfT(k0^Y# z>ZYs%@?-dZ?=2gbbN9^j!`$>8^E1kQCH>hHz$nE+>G&m~(EzsgvmY9Sob{4l)3|^3S}Q3V0C^b(BslsS~umB*iExkIGgnYxoeim7lX)^*L842EXqrEp`Lx8KKJ)7+7d1=nO-k zJ`e-)QpxT?LtnwcK@2GXc<*|Qb3KL!!b1K5l3gFbfWwEwmDorxK8)(}0Wm9vcQKl!Z@skOIDb}plDjK_S6An7A^?@gaAtw207|8=G%kZo!edyAG44O1dyX5x^ zpMHFbSFB=0MCxH#QTUK^iXy?9zPo<|byNtbY>aH|9rS;}Te0Fdyy2&SKJa4@h=2SE zZ`u4Gos)((a}>w&W7@C*$#<029` zf*m0L1!3O2bvQ&EL}h(TV;g%T5HXPO`f?N;EU_nYPsE=)W+V-(d`=l5U_(lyno=I2QuVgW9Q`Mi&e7g+aCI#d>qSWo5_#Mp(j9qNQ2yO&o z0jZ@F;29~T+D!SpecwmiejDGqy`{uVQ2l-{#xbt3Mu)Nv^{@FOd~l-Xart zN|&6}-BhMHkt4b6r+iD0qCnw0{8v}}5=P)Od+(v&>-XC{Z#T!;*oOYXp9ufRpLh5V z0#w**<3iaRe&knGq*!P9K~4l0Dq1$FYx$!}Z|Z~BFh4Fz($S9qZy(|?7pkR~jJ(8( zT4Skw$C6-Q+CU_3sn407P>zpnrWHCrDws+9jTQE=NS_RogdsD;{qrS!_WZ|@j>~sN z;f%Ec@h4*r$QqBVvy%=-X$NB_xP9$O2OZ|pCF5avbcdzgpy)~4HxmJ*i1*FZT|e-i z^ZBJ#Bs~vnVu?1Ot>T>^AS`Nlh-`Z0m-uja1Qm8s{T=Rc|HhM!hCu47N@+1Sf1TQm zDAcsJZ>G7j_@%`0DBqNsqUzF+37ycuxgK5ARm3fH{+~#$`WOv%GChzZ-Fu+eN0|TEPSf~DQ%1RXe zru>06`Ekuh7s{}!!2Ox}&>e!=DrwaIeQO{pF^0VJ7S{REn%ajKP3=}F(VJtQ17^&9rL28ij~fj%I0f_QO z7C>AE;m;3Jxc!!Z>q{ZNNKEAB!+r@=WN3V%pU;8$_CW~WKAdaedF@hoxquKa^4q1b zfgl6WZWcfF>`QmFf*~X zwS=e@YbPrcwqH1V4E~KX_|Fi<)eON}-mT(7lK%i@f(EPm|q`VbP#D5%C_&NWzxMBuKvi%H}fNY#V9(FDu@H$xH0{t=kcTV+p zdZ%+X9cE$TZpuWPpKdzzBes=CCN^ zf_gP8se+1Z3xEkH^q3oVZR`c}q%E{4pJpKoZ(3kRjL8_56EXQtByk54FLEHFSyvd%EzH zLQ}vC8~$8>5V9v~`#12~qALcn2{44i=S+95JK5XHU>k`Xj!8ao^D^(rvkNoV9b}5M z@}|>+od|MS_z?1S_el3oqswQ&dP@>ajkm|d&-&=uU~wMF>6`n@Dmk(uP2VLT)RQS5MUwW z7}_w&htb@tg&&^vcHcSp0}eJns*6iu-SR{F$5Hm3`oxS+Wfy!MT~0Zr-2LUk5|mqK zT|46LkoEi> zp*5i)KR^~B69N(=0wiC72m_4|Fm-gaF2iw{>m^s?myRrO+^0i}N{R=X0q!p5X z_7OhXxj1pN;oJ|^M@Z+kq@r}Oud6;ci#W+6+qo@U#dgYM1?lwx9jiN$rpkfugc0#A zz%ee+MVVEcsNNHHczKQlJRTaIKUmK`f}O*(p=(~u_y`zPH{v>Gpr{No#Sjv39w}M~ zg{Q~r^SWBdEN7>lD9eJy!2&&JdC8oj;-#dx4&J>C z(3}aNVdiivzgaIi^+lo2eohm?ZNR#X@GdH()S~=7SW7+`Ipi!1`!+QTN>O?02e_t1 z&M42(5N(ZqB(Q#m=cQ>w3n%gvdun~CjmJ8vue)NxDundrhhF(x(1hcjU$mXvvok%7 z{DMWijI!{m0A_s&8!G^LQ_Qv)a}P%6`3Jax2W}`I=eM0W zm{~&f#vg>wPVxugL&zS!0Ydm$KMDT{$q=hb9jnY=%nB&^Un2ZJB+nm&uY5E6Kc#y% z4z9a&e?9#7P4stU-?|s*Ip_*H=J|5+DPF9;{kWccRo<>vCOpf5Ovn0d5v+O<54RXA zfdEBEk11xeYY`DFBjzcSoA38;HKsMH`*7kpyg$z2Ldf^h7fr{h#^=nM8) z_OG0sKHVZKf0xRsofg%#D;;&|j{+~aGLE|Nq;4vP`#f@ujgzXMf=`~-Ke+_&4cml1 zx}=<*Xi=2pH(xCUjkQ-HHM(h2)A5ffxJ>G#mV7I@H>CKA@0o-XAbx}E+%X3Q4t=cr z@y5=>#HhM5^0BA~m<4E#b`-H4v$FryXLCI5@#gmrGRqSFD^{-2a4bnaR$O_lAR~$Z zUsQ%eP88QKs=Xp5l54<}_(uI)1iu3z({o_bC zt0&IVdpAbmDG_Nl{<6;1z8T>jLr=KBt$tzZSJY}=dNn^b`G7isR)+CW3j6I##WX6B zQacz!VxJO&f~V@(6ti8^PN^St-6BFs<`hE@ zO&F5-@lz2;>rYrO5ew<{B87N5)iKc5W)n7hAu9#$U0@sA+^G~3Tqs(5jU*GL9!Z7`5GfaOHk;;(!!^sCf? za$D+f)VBm&%X)oBNJR#qay=6yFlGS}0P$`mK8Com%C7_9dUhz-OG z;dw~F3K@bpAem53E#Uw01bX5F5FCr z8#4Ee-C3GpQHWAHx=`-?X~3*@8^fytmImARpHYb)L^#@i_AoEt7y$-fEt}$asGxR?6m4oHMUe{_m4&v= zHhmGX-Nd&arrG3C6BSj0eM4Fk!tX$a18El=N`K z*K7$hk%*y^UVU*{ATebZM)#KAu9J8FP|f&rxdY)&cCR(3G}D90?sRfqCc^2e6;IDU z81pZtaZ|j=AZAJ=fYQk$-=;I>=4PXRp>67G0 zO!o3E@pKoCOc_9YDRQm*{6*HJ`SRt;#hmvMn-b1rz z3vAmJuT!?S5<8-iN>DPX(kQqzakg`vL{C@4#o@gV@=wo&f#{3*X$8c37~}}34I@Ln zMEv@oFW4&sFCs&J2(X2L%h#TlsynZLuB78_!UL0be0Pa8M+tL59g9STdyP0rUx~7v z?8C{4c>_9h2e2Uo4L7j+6-<+N{(-rA2cjnPOFnMaQozE8j#$!@&XIPY6*pcCAG zeqUR^Yz_-cgp|yBx4Q0RCda!rTdFOEtfBFBo`_)en=N|cq1OYjWUxC{=q9A9@2>Z8r&5RUEjpAP7pt?1(j@0O)LWidrSX=i~Wh4W=6ClNAWJp{G2kpa+3j>Eh4hJO$B?3gf4g>G~ece%%trGtf z?&4Bc#{JX9v8*temRK>6D8q>Vp94pW&jRy(P;X>%55+l~u$JSyxb@`myau0=y>>-*2AD9rPLbbbEqQ$~MQ2U|?1A2?yII2T2dFg5b?W^Wg0|stReo zv6P3JU)fDnm(Ir5Kc`e-T+&FdQ>QO_mzP0Ab1$9+sbuHEtT&z*IczZ}h4Bj)hsXUg zSJ@bXOavP8N+Tt%XL=^%NuS{zQCCf<2awK#)`~(r=YfaZFE85ICYaxe1A&&?h%*Bm z=L1*5-(?PsA0en@@3Wy8(f}%AYv*f*4f@hk4^7}*=?#f(;6_f`WHS;14>>F||~i)^EXVgKlG}U&a65od_mBeJjH0Z=Azi`_)H~s`}TTI7dy>IwhM;t&kQk z{(3*vaubMxfCTA6=n;T}h5e7z{v~hRFNpm*UkqZq&KO^ZS=R&3pTqyuZ8$@W4e{o? z!$2dzGkU{8QNbEO!4a+27m9R2V2dGk!_2JVhem zhT7exjFKFt3p1XlIRm5hJ%#}zjOXzy>9+bdSRTY@suM4ftrt9>_DKNlX*y$$q~70U zp5fZJr031}FSxycJ8rM5uOV;#Excly9Cx~}TTd7n!;;_Kw+3qh1vG*IKFIWzfU&@q zadLdpLA_|Z?_@voJb1S^(q8}FQ_Lt$wHn`c%vGK>4H~W&!%G|iyfDD$T*|w^WdlRL z_=U<0{zQ&b@(Ii+yQh&jIn$ZeQQd4KN0i3xtn0Cc=3t4a80hkUWp!+~ijKJVx7EQW zK)g#V#PVMLvbtzez7bxNbrb5QPsIs`e~REy|3kemx9Qyf_YIE?g2sO=ZQt#E8DFJi zRTAQ|mkx^NI5;sU2dQ|ayjKyakerRp9V+%>u0iQ_x(LpCasPCY3XQQoh5(hliAYWa zAp)uaO%C%Cv0|0fno1P}j%yes7$7$|m|VA12o>@}i|wuIf}QKMO2d^U37+19Dp7h~ z9BEm~gVVo;jr0+!gp?6$!o`*=v8cx_aLUJa1=-P7)vP$fG^J^}(yeSi*_K}Qu;402 z|KN+T4OxF!)lM z1NI_(7#DG^Rd1AsIpQpC)k1LfI;ny4B*luaziu*}o?Ke(@;%QFSu%+Mh5eV+;a!;Y ze0p3Huex77sn?YEvO%pPjw@&6uJ3@D*^Te$%u)?q>=3awqs?)`+IbkjWiyc6=E_56 z7MmVr#=>Kn_yTSyII9%I{pt&4cY)>C5YmK^CaD27g1&TJvp5<}b=Q#|}V{Kkn}VK&J(ZjNN-FZ%iE zO2vri)<6 zc6X=?_gTZ{e?@y893OAF`w&>nGiy_y(MS)VEpWWTp>myxQ&J`Otxy5LZWn!gqBg_b z{mC;Qj<#;lrP^SU;CT`q6^?^tFU`>_`qf($JAy6E^c`%h!S?#M{cFw;r*hrFrg8Zj z#qbXy`8OU2k^#3U#)67z#7{kDd9>%j>tlZ!@-_DE^S!RTaD#Ax*f+Wm4OQiO2IZR> zKomE01_z6Vf9L4$fXo?m6OeI3Duz7QE`amKBV3mde+~clKltwwZ7q!(w7ZlU2WB*? zFOxs3;z#OS>`*o-d$}_YM1~rIb~o?ue##oKe~xK&$X&I(^4x7bw8AH(NUE|BY6|SM z$sqTvIq(PoX7tXL6bz=00pdq^ep5%3S|uuKk}xUdCP84hocU4s!uRZH?NWH0Nu$i` zFe5~D9#u0AE&&{=ohJ)}#^jm3#8pqZ*oVpy8VI2;T>KQEB#uILzfsq}yyTqUQt-$F zh7k=Szcw||ZF(3-s9Z;H@VWD;eRF&4&?IweIfWVQ=K6xd9@@j*!UJXIUDBg1OI}_F zFZ>Rr66&vWf>YBEB~$A-UoE^f4QX6%vfmy!&Dz4=I)6);N8>z2CxZzf9rVO^VcxRw zI+m_ehOg5#Z6E4z!WRnG3OOc_9FpA|2zC>c6L_1eKf^~paPky(uK5dT=wO)KU<_9o z_cKGoM0VU=W*Q6S=fRc?Cn1YwAoU zrj%=(HMx?-mH~4DYW>_dck8B8`zP*a)YONQ~`Q&U$?xm&8au5!J%8tSlV$p^-PCfDozTsg11qU~Ej>aV&1Nr-hXh>JPgJ`Bp(b7~x;;SLc+?y1Pph>Qzp8Y0qG2u14FB9~Q ziHdWVsI&PAV@Ye zCAZl8Bo|PP17y{Ggf1yIOp>pOQKJ)vAoboc@LBsgNJRu^p`8-r-5fdk{6`Nl|A^18 zG}^!@uIl@3)c2sr0Wvf9&Z7LC7<3%MtUMk>c5X`)A;>^=50+{wOz;Kq(~CWN8po*8 zqihrQcs2I4$qqv_2u)Ds$NRl?4cZ${FV>J=2e31_ zxgeWm1)rDMMg%&C4^q{M_B1%40%#ce5(R9@?^}Kibj~A)t?EUOI(to+_g1?9-KCzm zJ!K8iR?87+k5?)v;=Yp#{`;Q86Ej>R-m{GAT3g|ETGFOgHRV%7aNhwb2L6#nfQB65 z&=UIVtYq!TDd+nNLTwCZ<5fu&BP)I5--tr+?4;gTt&eF24MjBJmyNF#YU-sl+x{W%iHzHJjh!9kXWq}guba1aBK?wW;RaVUQu11Q|w z0HnXe5K^f7&Am6&dz1F!0&#P)v$I``@#`$uui?LYy1ygjN~$;KnNr2yBrQ{;v&#L&O7-F?1*{gn_B&2q6A0`ix*ZdSZE{}=jX_`gX9NQ=x#nv!-FNg9 zK6G_vtIcs27jWS4KKRCKy!6&RANy^Rrg4Da`@ND*L47Wty$w0^a=3!m=s_AIitaPe$!0N$af|z|VGqI2nvtx4)NxciF?9 zzZEfibWe=b>@=~WPv87&r_8u)#G?W{%`{0BdKO>0XQVCZ1@;6`2(KkJLl)QtJST|6 zmkjJb%~G}Ps|`&*i?QFbdUh-Yt9|jAA@#chwI3!4^y(kl@2`o#e<0{TirkBKbMMA- zM8_xXA}!uTZ+yz=$IbW)Y;HJN@^l)d1S^u~y`1jxGDe&Vk&jfMN@%`O{Fv5d5cFq_ ztoTxD73wN;GJyIrw1Ei0Ao8SBm$gcS(&eqeWG#yGJ+jf>pYEq>cMuLE&B(ax}^@beVzz$$bR%* z`edh1uN(?f7lW%w?eN-WK%UrB^Hzz)tKU+bTxAO$@h9{&!4vAr;XTApAX0!!_>f=& z4pDmcPy>$c!AE0#(94=RryAm1?-6@~vd^#}PApf~^tm-)sHfMg#_Ypd+Smmz4y(nt zgGRK4QZMOTblM*1KREG61E03Cv1Yx6UphfG!N+g&sb(Yk@uS9VisI`=aRJ6O$)v=R z`zp*rjPAMFuCkeajFTJcQ`Hq7(l>dU?C(149Njz>gDX4CtAQQnJfE!J&2!_g!yCwI zV@q!3l$U9!wM!p(Vw)m^>~^hAB4XO? zh1ms^T2@tI<@>5mLFk>N8%`$BH#Ro2x4OxUT7w;3Z0s!{?Q_4FnF;ncGs7xCSXmYl z#d6#-Gt%=XaJ-~L@yvHk>st1T|Cy2h(7+-$Gu?9X9~$`1QGopK8@SfM*NxebWGDxa zhmGT!h_74dKtG57rdj?j_AK}7W+z~=cw=dyuavrx(d+g`cKMB0e3z<`I5^zIXaQkS zhgMEFySju3YKw%bAjd#7@@F;gm`@9o8K>%;1*2t=kV47RsY!>--&57bL$g?cyBhBs ztKOFp7B3|kop15~ID;u9uvLA&w@0l!)dY^S8UG<{7xtndW~iqz^(ig&%tdw|4F(kb z#=}Ru0(wnC3IkI#B8u;DxkceJY}Io~0=Jk?J+?zp7W0gQiMm97Op9~r_b#|R>Ui<} zFv^?jVv=Le{^bE0enUClAN=zNy&zHytR_vgSf>k@m$)lnDmb=wH2kAp{3~DGX)C_& zw)253s|Z&AfbX8$axpTVn8?wRRL+Jgmyr zk;k)T3H;PdW;NASK|92x)~}?@CqCfN(Mkdj)-s4n+7<}|O|EjRYf=tFyN?=G8}WYq zvQ@k#11pu28V~I?1D=uS?;f_h6dUD6&we-a_0ijx7UkH|f>QA(#pjAb(>cw4 z-!yrHKh&SaK(O;K%&AWQ#vEJ>&<8FOf;+cc%)v=mDEwmsZONYjhY!TP2|97mC2s4C zOxH`LSRhLrxUY|+;sCGLO8uOc1%i22F$HCSnFGMg+R)~Fu(cuh zSHy~ngHLBj4}b)vT!24+HYBZmE3xkXJpNNs{p(d1*Q>7BxY>Yg>>SrBi|yx-^LhyM z{=e@l{T-83##D-lp0-&r*htO3k>yo~-~Qx=C1T38wRxEU9iGNRcYcid$e2ZroVb)& zzODabOi;;}D^LhK>|~X%hakZ3>DC7|x}1}_8eu!`&s~xjaSWy(sbUQ9CMCy@`WUZ# zWWKx?oROW3@KO*$e&q)R$z%%F}!FI0+^$yW?p!T}L|lb1`GEG#o0uIE*;>vVVb zjF^5w<~8nJr%BU-b-5C~aO|}NKI7oVp!c$sE9MDcdFQEK{Enb#gS3l|@>wP6*lIc) z)sMl6v94Pt@h$;+zyJG&y=!A4!-Rt@;q$t8ZEbf<#T#NP9Av2x36dH&3y6<{1qcfR7x5SGc0*jzL%@iu(*J+1ha$`~eBhx|efzu)Vk zfsRzLJbZ?`5bgL8=N*twVX<8AGwCjsc$l4JVF&S-wZsG{J)A4sm(Q@|q)E!~lsvwu zOf)6O6k8;Wy<6K`5(`_v66b3wZtxX#PzuSf%c1W*G9pEh>#xr*OkwD|^7Nc|v0l0b ze9-JS!7-ri>Z`Vy#Px}00qeVoG+9(Z=-I}V`?mk%90jl49G%B+Sf0VhKU5N#`&w?; z3oTZg*|c$`WE?zk@VpfD$JN($RfOawvaX_3I!k&YgodLITp*0LgUU z{QN4f@B-N(Qi5CSe_+OcQAv@Mz=%D2q1&{^+fR9tf?7H3K_0~?rAfML0Al{=PIpP1 zqf=Jd9k(hmq;cBsVkMUTF%ZmwLg7BW@sK_3bFy5fjYtfS=NEFnjsADiQGss)(fIry z8>b{|M$5(NBLqCYsigeX?fx&q1c4QLsl;`}PD=5|G&9yS1HOGJns?s?tSMv_E({22 z*$n%O^<*2U7FlFI?&RzuVc`Y#m#ybUgI(|sLe$1nSD)(2qxvRkJJY8k(@>flzyR$A zD#CTv`R84c{d1k7_&aniq;}99sdAAPtUpWlw4>GM=$q{w^UpJM{a`#Wj^O^+_QY{( zPk4jB?Fo7rVoURoKY>5(iGe4RG!_MJM)U(+7V0;Sw=j3U1I&N%?Q;Aw9hAGK^XuUM zggyOtX4Hg~qkmWCZp)Lf(1@_2P;k|4ZiC^L4=_?#c!@}1Gm8?Ic@dHEI}J-~0#073 zH>>xEo;su*gGH~%o1s2fJ78S3Q!W!`=8xM6?yCPy&V$YAPx%6(_qFwq&K1GA`m2SDhJlbNk_-b!V=nbA9?u9Sndr z*PoU%QLQawUvi+zngj-lAm1b#FLLJe&87$HNs8Z%Ng!RXEO)Z{C5)u*tTMuch$1wI z6&&7L0c3eF;4e%K6ucvg6l8y`Fh#+^R<$;B2RqsTlxY#K=Q_r1Xx4n0RwyBlYR`A%v3Yn2VJO5zh^$K2!lZnX6bvjsVuaB(H5(Q zWqrJ6oJTX}8Kb;~;vZD|gg#3&^l9a}KL9G<6g7?5A3CY^k)cmeB~|J6W&R`0m%W@! z!<7@d$5QOLDwP}KHa%a1MhZwKV)@B5ZxsWC=>IXe_$R?cby&~NDsFA>c8PeDWjU{4 zx}Wv?YD$-ZAs3&(6@ZBM_QDBPm0>>tcO#MWeNs4L*$shw6C38Cua{tu?Pro~7?fH) z4=at91nEmdHdKp6zK?|k{3YK8krhKRecL$ZJ~Pe{SL)%?TC%x>)_<^n+xxX!sHWq- zDDH^*`3j9#9@(;2L=UVAo#3(1;ECLpISOAiP9i@pHI8ADP=6q;+p?_UBJ_KIU1I*y zVF%32!Tow`qa!V~h}?DzS;u=UcKpX3l80tCc>7pSeaVxaD-~V9Okn3~nh;Lb+fFBz znr(UY1Pe=wh+Oi)npu#h0)3L?5tZdzIxV6Y%NEI`5ez-{aTeM(FOvK4a4cZ?{$a_Fz8kBRc5 z@kXPOkWP$x;n&Ev9qHPrea_a*&@-g_X83_xEU4LeqX!^krTbcMxY1wysgOey;WOTn zi=@)eNoSv&PwEk2>1SphNbUH=_#?I-&Af`lCwaigL=zH&G$|XACLU8Du&ceE>C?0K z9OsHu5lH{QAYhE=iTqhof%DbXID2@r+n6(x?N=7D_Ny9oVu4pJn@xVY;|~4qtjs?i z`LLzwKN$(-($KhXDV`6~AI(>7Jy`;-9j;-qIbW8DzV*H=Ox<13U+~9ELmc;bP^h|F*J<@SPQqbeAuPy)49_$Epa0_=*QW1*Z= z7%5*Fb$u~=o_xUBG-?v={|d%{IGu)%Oxwv%c`~3dz{^)@{PgmB1Z&{C>H}IRIm~-a z;25mpQDN#D)6Sd`y9vgc4~pdb8CDb@)y#1;lQOb>H_yNJZ#yUM5*?1q!ZRY1uZ>m( z(V%$ldZrGx^7_NR>A>2rNtzy=Q^U4c({;P}a5}=VH(hD9s`W5pB)eS&dwvoz!=6Xo zb-R)`MJBlgJ7$jOzH=H)cRo3BhJ2cFHA3sBcSl61pB|;G%ocu$t5nK;w8m#!Nehi0 zc`q;hNL*c9-#b6SeNZ=l-RJ(RD1A212cz$Ogtq!I%0sc6Rm=KzO*A><`~%Ov>|J~( zlpPbxo#nSX_)_uMwyG-Q+qSS=F}sMFTt&&%2AH5%(pnE?=aILF^c3{0BfWA~Xge?P6cj`pB z6&De)s)qTE{20S;zc=xNX0Zb61N5mRRVAIt$r^O^g{vEEZ%EW9r7~mw-Iviay)e#R zQjJHgHLuJ>^Lvv729px?D^K*gnz3VT<2u&G2lsdMCbx@2oh^p1*1!&5FGR6f;37Th*kMX z$Z=3SY{kA$vCiAv^JtAZYkQq_pY*V4swHL03nScV*AjIMThaulns6_xZs9qn_`MgW zllN}u@%Lsl?;+|T4-%I|{!FXrmO%eCLk={$BOf3woLfXC^Q00H&Z-V=n zK;+cs<7@VZ=V#KzSR;51O~)hbaIAVkqC}XFf}N=9eAjK|6Qs6v-gv*Hz5k{y*l0<= znxT227O|ZVVRR%mp&e=fc@{38D-}Et8lPi=O?d`BEu(Dlq$VVHa{ODNQO}Eh0Z-EFQ$~K@VcjGG!U6> zds<_UT?V)Zr}ZUH{~h(`A5ToCzgN87MqwhsU(oA%eGltA(Tj-lN=gJFT-c4w2udjL zLu;DDBq^*ZW$}~s=|*X0{#z+>z5UE}YK;sF4)UUX?!3i6lB2?FOyu`4&^BE#zvJm{ zs5umzqr=`NARfS7Tg}hqg0RrP{|9rYuM}?gu895Bqw@suqEOh|b?ex!4cWUWUUXza zDbpv7^=WZA~5@Mt3}umyGf!cZ2%)VQ2~I@C5ye3FMQ1 zM}}~u5S>E^K{nJasv&E3{;U)K)Jdr<@w+<*2UWz#=%y?I5E1>gsQ@Yt?(d^2Dhj_! z6L+=>_veP2+e+O84v@YbNWB8c&chD6#`N9cf9f{=4%D%+LzL2b3l`r* z5Yc8@=caI|zczEWpN+_4p_n{9LeRFzLgLq^$LCuO{J??mxgI_X_PI|NJ!a8-kaxq# zyTMClwPw7YMVe$h7R)n*a3I`FPPt+ERcTVu!(x|8wa*m=>1$?u0vPOWY(lm^QStg# zVL+sCJE3eTb)u68Q!>UnS&rdvi#mRMM4(zNbePRzIa8V{#RCIQIgQO`W|{V^>w%L@ z_pv88I5|xtx}ZwpC;=5(DuFhKB&}CH_Fp+_rVus7K)~y%`V;pK=0l=DZ-f2 z^4&L;4eb;CSjd!(&Bf9em>zc!3jI%ZOz{rCCIxCZ??9lppBL^w`-C*JOq2i6dxun3 z{$lqxIZGxhBNB?Q=+iaOr?G|{Y*Lc(MR8jf_W0Qjl3Irw(`!c~XA<4?!Q#42k9Y;X zZP&}G1dx+iE)vj+%?GP7=GoZGOS3Q9KWCs$s29AVW7x{LBBibsQ%5$DlQBSGKDn|? z)*~Mym3dJCVjb^$u6r_L;Y;Yst)%4O%eVJdKr&}F~zd(?W1_<$Xu7YBrFogQtA1xAlzPH4==Ree2}ViB)2 znZz^Nme=y8(hR+{D4$aHDy!_)+5}JL=EwAgEZTLGlK{k#KDt*5U$ic9&XeJj_SG}K z9OSR#pyoN;5y}E4+WMuRyk6fNXmO(7)Zb${kXkadH3y zY^*Kat~&&;6QQs;^#7I0%OCqVK&nBIK0ZZ(YDRL#0Nf$-7T#W zpF`u^5iA(6Qu0u&rTA?ohp>o5kHZn0j+CDz2_6zKt(X0~_|FGVG1v{Y<+*%DVrJe6hJb zje8&Y&WlxkGsPjxu#^aF-+ z{vaNzWXtG;R)HoT60lJAJ>C5CXrNTB#J+bZ3%3^NsC6duSG@;*jak5`CqJS%{|s zJ$4RX8l2|o=$jxtIwkM%O3Efg;`=Sr*g4ra>l>Ll+)~ZYr6@v(znKOh4d{cA3}GAp zTc&}5Z@c?2uNx^O?$C^=+^@YZQE)I7oov*uKl}HVcRzQ}gT=xAed=0?Tq}~><(=0R zG!76iOG#vwA5JN8zd&m>$F1)G|iYNZMpQ|s_8KrhivvI$_52E)s51+Q?46R^3 z*UIfFuhN0R_$0n<3oVPXu&Uvnq?X1k8CLw#cc%OZHbpN|PG&Z{>2uy@JP>2u*?UGa zA0&73)OvIr*P4jkEQjAOIJ#9?6(>#6RrsX(OZv7)t&*tkJKQaJB`r<0oVfF3Ar$(t zsV9*bIQ26znX@7BjfIKqK|2-R#UyQ1${pX^x8rb2K0kL=>e%hEcG2_8USn_uY-vCf z0HfE4L*^U|$IYYONXwVMf9&6yK?OWV-ZG_f$PPkNrPTO5&zHh5-8mmIH||hi=~ev= z)hIeODU|4{YyfRDk|yv~S^B(fI1|e9S1VJ+jR_%m&k(j1936|xXTl3q9p&XlPey8A zND1bep^erB*nM%R7UEBSATx0#ym8ApKG2Ys2I$CtOzC>hUyCRJocB1;dj#k`_;cY~ zqXrZ#(EBscJNj3^1Oo%~4j}gS6|r`^^@7F#M^gwE{@CLS3^0W31LkPv=;R35sfBIzw8%S%a8-VrB!RtLE73}p59U0hApf`%_v$lmrWq z`S(ph<5T`z&v)(Te}VtDM0&HN&(PN4&oc6lm~sCj&YX{!;Mq2-30o|u&SZ5LOdrtt zR#QS$qZjT!yC6+EQ}W*gU7vsCo}2!_C+}04WKTct+Z-FK7Ep6+uhLI^(ylD=9q;Q~ zpgcR!%i-76Q>IN%{EkG$u-cSrznpa698?7Upb)YG{4i}}f^q!A5U+vhlMl|H&UtY1(Pl)=7_AQ;Zg|m7 z9%YN8r)a5aq7I`gNqsI#*=P0oyw0Y;i5t=Tt0?>wTLm%f-03w*SYr?T2E(2#5E6KQ5#e}WzObVFjy^5`wh zVCtCaD};EW^i-FA;JdS;Sr*_$@$H=HuB!-lZCTQyha;w6!bv8KLPC`*~0j;M^X@2;CGsaj zN-3Zh_dj6ue$Pf8@u(>yfQFd-@+B!DpH7k<0Rx|-FFWvsz6w^?R>9oLK>Eug3md{J z6jbJmHNhQ}wld7Msg_3sC%PAz6Ibt~wUSoYSMJ9i3)N~flDc!YjF?;&7%PNW(djYn zjOu-AkFF2niahW%K=E~d@LhmksZncw!yPVVXGpb407i^?B{Lh| zriHRxviR(&1y%vt5C0AEwG%jp{k2P-x1mQ4#TMl+zYgiZFv{}Ie~5`;77ZL7E{hPW zvc%b&nf~5-5-im3bQJX|a`;tHU&Q8=)l6^;*e*5%9rhxrNgKZ~YI6P3?fLtS;$!@- z$*&I>mnqH?QWg}u`IOP5RxldAlsh9RtzhB!+Z)2d&B1Xo0*GL~Ip$DQeo-^tb$T#m z(u|?67gQ$LpL?D$K~~ZaX*Zq~cedIR8EgJPRjEum%Skqs=E4A4Vm?}4p6r;rTJ{=6 zHB7x?PrFykgon2tS2mDd(YPNPOHLe4W|^taT@&EIJ{tG`aQ7BqRju2*INe>+DP4;O zr5glk>FzFRq$Q<6q`OPHLmEUyq+42ALXglq0a4uhoW0Mz|8vg$-M`PnX3xc3YtFUS zH^%qABi`{mjpB)4j*RbO=KDrd-FVn+YV;i(G>>L6X_-o>;o{xV3nYcN@)mF`U!$jx zB8{{lJMV>&3fP33^4%Nz_CzzdU*~L4eRH{7jOd_r;tUHbB=H>SY{8EzR^{0@auPjbPu(@D zs$ZKw1dgp;UY)hs-PT*d^UDOkBe`KhSzIPs7C zdvOYoElsM^@lxCkZd(G&e7XZ9lu1A#)f}l+>$aLcW(3#+x~DQh${FwLr@4q#UII zJ;SrkVw-)amdbz|KM3h${etO3ls9ZN9nqMV)>Hq)m)vjypLD)*z%KV2*rZ1B_4&glF!A=!eLg^q=1QyEBL#xKc*A6%#XTqmV>m)Zbs zF&a;6_%qpT;~inY1bbg4mcsNePYl`|K4!-f4Te%_z;h419Y^-Tsdml17bFuNnma0< zu+Mi|*S1uXLP47I?=bwHaLIm)P0swgHz9uYa&?=9b_jPhOI68AsM~lTAZWV$+g7j~3 zm;WRA{vVPWkeGLV%J*MadR^Cn{R@5b|AkxlCzW2a`IAkd^@wMP-ekjjt*wp+Y})wE z>>G1E$<0IYU-kxv5F7P16~w*L7}Iwm43X|^!P*_Y3t$rTPgxfbvc6yXAqPvH(=`=R zWCsn?%3-c7I6u>}M&zyO48?ay!t6!<|J|PXs%gobubJHwWt>8T=!WJ7w9OBCZhu=h zm0qRI`&I%N((f60IuYO#91nvzp9NDltp%lPG z!j#LrTv%UvZzM6Nmhw}wC751}-gh}NDcobzsA7GXl*s+YPt~7dSS@P~RM)i$-aI2J zj)#huI^bY@z9eNm>#}qv^`N>$1-39UzN(s{Nyb`i?@M82JgE9YM&%i`v?`XTU+Npj zm=8G8Fi6kj-$xR|x8BKIB^$W#UqNg3&qe4mrTOBBg~ccC+!=%wLdGszjw*(fCzaXQ zl+sQF;Q1Z z9DCXjckip6yq$y)x+>no_Q*1ru6WEl%mkF_PWu%R ziJ7*T6+-skg6aC?&`4Fr53s~m7DNv>g5p_YQNAgOpE9I=4=+O$hA91E#}Fl2R2GPW$&rX|`(@&kF1PLY zGVEpb|8?v5H2~u0)*%VpDyh)r_?N3dfY_s(YT~P89S|M+Iq~H>I`;GMzkD};XWc!6 zD~_vYBTXxejI4R{E4~8#Zz!c|?ddv4j zclt4Rj`W}`rQfl1t`Y|K&ViJ*eYBThZE>D%XHLM0oPk2?N%z(?cgzyZ3S2Z&7mbk0 z*=vmuY%lwDjy72bc)r$q-13?(ECk_Iy?4cu@=L72ur#3R8ZkE&5MBrgkv}xn5Aqd~ z&vVlXnZuJ-9c5b8CRAeZdhrGy1}DeNaflrXQ!CT{gNCQdw2sk7t{~7Os1@{TS|5zh zZal$R5a^!NCUwDeuKNQB1>D65MjdS?+VKIeGAW6JwDIK;Nihb6nrsjixa%MM=py*v zoC8zA-)Rnc(k1~u@`4H<4v-NxqVN1o2l5}D@O$p~DzZ(850fx6o|4WRYytzPN;XX< zaYAQW)P~FIEQqH1D)hj865Tk0<23DDtR`(zhUGzh^tuQ`M3w8?=_MsM`>_?WIPfWz zprpG-UqVd==Tf-d6tGvACsXvLxsub{i7cr|u;}CMs&zkQehB)K!Gc54x3*}E!}huj zpCE(pWI!j3B}@-4gb@iwOR=u&E5eKS*t+Q$t)85Q+%2Lx-pf?<2we&0$e*4qwxg-m z_c>!ktL#gfPc+j(XrhXakg`l4Hri2XN9mY9@#H=5d`qi-3@djCg8Ab|k zN?Y2wk5;nbn=OeVN@~*#t^+W?C(7f6C!d%wvt8Cnq_4~$)704ZDiQBwV-9H5I_g{9 z>!|Y2GGg47EfXYD*JyP{<|*ocQJuHY?X}co&}O;sbuowuUhFfYjV;uCmqyU zzzAg8^!u@h#QchTh*V|vExNgId~6!*}B}Z(wV4qsM6IGKZl1<0nzl4{{^=;^4r?ja~`aU7eq}p zFqxHPI=N%=X)|JQ{A5D;>RPJGQ>x1_F)vXLJ@)84-`En?2Ac4vGeZxYmBRbEA+?+ zO?poVnL1?A%*u-AH}-gaYlK2yrMOUsKTCAq*-I3yoNcTx|Lvkr~|_+ z7N#~rffKWgV?$%84w{z-PcaO^PoKUM=V!;&c{eRdVoYP;VS#yw14*>iv{xEIX)lT6 zEQ1`L@r8G+AR5!~-ZpO<6AiyP6H|Kl&v=6pqDpwYO%zK2EUTzN>&^OPr|V=>d&e;7i2wh)SMFQI%q& z04?*vp(JkJ(-jhq711Rhw>Yz$rml3pkL+(i;$h14@wvOl0*L_^9INkGdS)!z^2aIQ zKEBZD)PQp?%qw?cuG4D~6}Q+;`0g10Dq=nM?1H6kOBEemEE)6}KbYs09B)3@XzzKk zwGX*nF4BTF6fMNr>x}64o@1R}@}sG0twP+J4Nkn+lga(k{_5>hN*8QRoVdwPQL$%t z7)zXE8z#ZSCD97bj<^FqPb2iS1Y}bP&5gO(J{@^VD8Epq5ZV~d@4VPBK)tVP zf3t*#(Ub{i>zD36pf)iHEHR7zKDnwyh#ID4 zZj37_Um;#f@IXfB>*F3q*h@=B=1()EN&F2~WT_6)H|6Pst=JYgSm-BpZ zLyH)6JVDvHxP?r)qk>57l`B1KOKU{fqL*z zrpL*Cm}gf`c+Y7bA*v^W)b<7HNeP9n9W7^0f8bh066tHD+^1`NtxuA^-4$b-amL<8 z&9XO=F=p~L6U)DHCx7@#E(esLIVcuFW8tyF3GH=TA`E(=lHt8sHl5KeBv>6-cT6Rp zcwyrRsUO|#`BF^KMl?12WySNy`i#hhV#7HxbYn?Q2{r;s7h;KeL%^zBN8n>aI+r15rx>{&I2LWAu z!Plt&CSFATYa|>L6Oi1d10Vp#58ST@o=_*AzfoPCH1>e`IO^>7`@1TuDf@lR{Tlgr zJzVFN{x{!fZ(e$9@&egcsE zyI##VS#@9>%5`F$8-N->3C`ue%fvbtP>=I#(9i#CZ|LvXM`ar|!`bwN=B%p+L7Iz+ z?`!^M&$5F&>Lm7TGZuSpHiP8@dlR>cLgVb*u+d08Mgif$B1_hGiq{N6v&-}6`YDT! zmE`g0BDlI;wIr%m2+(TeeVj7)JagZ_3oH-L7lpulZ10F5tPXKPfMqE>Z>!sA>6ewN zxhCC04DK-f;PLu>Z|^50P}Nk)3)LDW5_~H4<1eWcX~0$zokY%KNbn5K;q!S?iy6Q6h5hZ-|)9|z{~TOle9w8Xr{gK=0z(&9Gng&J$N zdaL>6*eqp+5?eAj^Hbr;iMSyO>+xu(uJ5(!tNs*x-1@FgDag-yDDrQ_yANF4j%%?OxTkxWX)=%mT&NyT zQesT8OyLvH&}0{f5AqM+!f{TeH5nk+&qu|lnp`&i z=yr4yD9Zeo9Lt~F1=qjRU0kOxxqv{!6;b)6^ZIMl%efU$5Q>cCIl??$-y%2qU$u_NeouE#TdeR*D$NgscMN*JmbB!a zTV!+ASY_948l1&30*Lrtly>a0oE9`3oWb=(zdxgm|5sw#x6LYP0t^JUX6gMDcPFg& zZ79+S`p2f*-#Vl@7{~C?Lb;>u`$Q)Hs39^P7}59MwO z(Gifags)$kR7i=D2Pi1#1cb@BZZyXsk?!35`38ag*rcv{OMn`5R$v$%D>uibv^XGT z_UjPz@4nvOk>uPdgm@hJrTBfFRA&|#R`=x3XwLOuNi~X`Z|+LGOvg29PWo~>>NXK& zAVbbP$IUmkQ1FS6VcHe_Q-qlUwnOg$ThMZbHlo&oDl8r7=uH=w3ymyp^lICv;)Ok< z9sgQSSLo6n4dS8mi_EYfmCAdV=XLzlToL3;1#)z|dQE(FJe?@u5{cRva?>KGs+rqg zQ(Ms`s5CKO6l>#zzT_fQO%?kd{G79`X#m#4Hpp;%h}B|D+Y0G(l?~6LB?}V|7&X=$ zcl#EeNg&30ssj$Dkz#Kl7vY5;XgdIx=&(%dyB#LjX2lVk`-3r+6X@^<<}Z(`XgIa7 zx)dB`EfGJn!%pAf8K0r?usljXV0tBYhYsyvaA1A&!m^ktDV@>J25LkRJeH9!@Z9Ts zepi?%&vLhcwCA`bBR~Eke>LRp#Nab_t3?XtNeq0l29;=A#Ar7>q*B3PM7u@zVU!6o z$tFKc9yGO26LhdW7}ILg!}nFtB%rZP4ZLs-^TY+(0sds(Wrzao4XmDvz z4#8o#jM&1paIOq6s*vGPYzH;& zJ$nH0vbOFow$EQAS)%^U_Z8cnlgcgThbUhuUqtxchyyCB zL(o8PcacM8T%cr;J*qy_*7+0%Cr6Tt;Zsz{KA`%RKH&kdv=sf0y-3AL4$q z#Yt>QC2WUo`t)F5Xi=+$?MErJL6&i!rl!7$EEFTvjjW@NLKf^{Qj4u~a&Ve=f%QJb zg@fq1y!$v+5~=6IK4e|5ha~KTG6($e;25m;wOvv|u`owC31jwFV7O-TR-=-+Beg6E z!~;cyb@{WH}=gYoj`{Buyr0!;QR5N2CAz}&PXqFkN57gLs zMI>{RGz+qHAmQdBCoR>{)I`_5lm0ueGG{|+cR}OWpc7)BXMrn$W05H5N(_4IIQsZm z{30H2c=VJlj`l;Sz|H(6v)I=Ap`t6%+0pqohD7j4AY-GDzANyl9tzofe4Wv{*>Cv9 zdT{xKv&bxUyG1*}P87Kog=6L;l6F&uLfmUFoK1A*>=>~=8T-lR{zOGpac22^F_SZTYXTIyT2|(3%JR^4tBV0CMcyP^J!Y#y@N-=U@Y$@b}XJl zOm0IyJ9DyL+o)H;fyPNXT+pcqZZ~?Wg)JN0Ti3YY^r;k@FSR#Ba-oT)5Aq`@)<{RO zUEPkHLp3<`b3B6ocr%M5IQ#alxc!19L;;6`WkRya!fQe|-3fdsGhzvd)l|Jj&IIuJ zw;VC^t){)!F6#FqQ^%sP>N@0c$cK174Dv*-8=PxX5^?lvM}7?l@Fzz*As}QMl!(zGu`WklL46}9YazJlzAyw;x`l8SF$TRxA85``x?k=cM9P8APR#9!y zICsVQr60bf#~DRbBswQC&4LhPk$Zb^3YqVgs$?-aWq9wzXZ%Cr$J~#tBP#YUIPSH^ zZj_Y@S4P<`oRi6N7_x;(jU_)E=v{jV4|Y7&yPIf`=u#U`t`nM3d|2=LVt^YjvEL!h z_05Bla=Z*j494>gp+?{6DI5)!y06vtJ{XdT4vre?O0NtR*|zihhj}3EP8TYKO=ydf z6}CajoT-mG1jZ* zG*&V^*HOudG(M?cSt{zSWy%7!dXITbUMz&;UXSJUw`nl5f(1x)A})Ej zbXjS$IF2f%_i={#q>swy6kNCH_zol`<@pij#Z|(ntvk_Vk)stwCW02UA9B`^4e0U+ zLg{LH4QBVy7YY(jat4e^9>fr{i<91cm7@?LjlU}|V6KhlE!No!9;whzvF=b--TVmN z8{;)$MLBI0Lchu&>W6ClZ3zwy>FpFpD6*lV79`9XumLKoE=#01jrh1J`J~U^bLmZ3 za{9S5=7~t^;h7Hd*G(0R<4G38`6qHZ2Q<1_eAXqC(&Nm(;muAsdioALpNrV8Vudyi zU|(ImP(`cJb#7yRiH6*LZG?W{_&~%G=da@G7gJbW>7Y5X7GhzZ^I}5^sfEu0e}W7^ zMLAGkP6Flo8syst^7YB^%J2+yw=&Yd9NB4P?r_hZFSvVtx_ zHSp^SV*|h0F8^N$_z&yl53qU|2uBW!1gB;MIJXKLQ~@Rna?P z8q_zRlk$Yj-N)`N7KV8+R=l1lcuD0akX*8ShslIjIH<;s<<*nbRsGOO1a&|sz=6#xHi9m(C5c@)}%&X^aLa4rObc9I^ zd=TfGuw+L5FA5{_BkD9Q{`-S89!i1M6I8)=;~UX$_!|b=qii&KbRHXzYkkXJyml|Q z+1StV$x1^%-OGQAjj`S?GypXGujm&r-{rCm=Z1}49sZlx*stsT9UB|()j56zJK}OC zLUwp~WWKz^3O=13NV2Ss+_>iCueiBEsE;k#L_6=+*6d# z^zW6ipYVP>uErBKHPCTc$Wqy=oix8Qdaqn`Te>@K*`3;R#c90gHRam8CBihSZ0y5= zj5i)k`|98pb;$LwzFuM61l+3#s)>9%Wb4^SNI0`$TkH`jbcA5ocf!s5hwajF;ohK| zjrLnAzUdN=oT)jOP<15!6vqSA_F;~Du3Bl!oG{C)D{`5@u5>@^>)A1mk%@V_ltR`b zHm)g*wD+i0jcgU$g8rs<>!%l}tm8xvc$qZ76{F$z=QqM9U<3_|(3ih;a;0G@3LGvD z?(4_Czc95NS1fBYD;5;VP~}i^-BUWz7lt+J8MjTA;YKzrF(~H@%W_0OJ|b%YL^3uU zAf={zb%_iNdoO0Ax7c=$tgCH6W^;XGR4<)$JgQ+yUt~J7*2`MS{RPJP#m4%5Tow#s z{Eoy^4jPYTI_K)MOL_j}TL0*T05+ziOLo-w@}43OL(EGnu>Re*q`NTt&(l8TJoa|S zIj=jaJ;NptD9&ql&_=7U-)WXxDlLJuX0m50?^-I&Zb3rNul_Dw>4AnulCLUM*(v&~*o9o}n8Xkk;=t-ajfabu5Q&a| zZ|yUl=Pw1G`wH3WMJ=v>JtMcDd{V_H-c}t@9z}>6iD6N`yF=_=1|c1Khu-;HiE_wY z204M+%KV4V3ZfIvXnQ=g7#m)j)YcVfVkkVVf%h_Swfk0Rp;t?U;)ZlFuyNPNp))~X z-=$cD9HDo!7Ae4w!N*c46s~ymWcMjII>&cPf5v_HwyBGV zZ@I6<7)ZK;mC9UDCX<}IBN&LX8%MWT%i4=dj^De>ZjQh%N*EM1>KAGz27}U3I-RG~P!(?2j}r%5OG)Rb^awW}Ddc5uEN2Vkf7hS+tRZW~ zN+GgSQn0fljRDWAZQs%AcG?kSMud2wWj+Wql=MV7k*=QyAFuoYoydWqwuagw^Agc= z;n)zuP+Hr#FZ}2JUMl307dVeHuADG7AP@Q2+}ak{TipzReeiN~ykTTiJwIJBqz8cV zTmVLZY9cRw!Kb=T<&ae4HbT4j zaXsFr;^mTN1U+q%(R`-%_DgtZ>uDOMw+2}T->QSBFgCe2$x_>e=)n~|f?YlkM)o!~ z1lL%=uQ{%x3+BHc954uHT!}=?lOHaS@1)r{Dy9qyO?pmPXj(6TG|GVFUYiD6KPv| z?rp8v1m3eucx=elVus7I=Nj(ZcAgfYO&6Fi8#P0X|0Crh1`@d?(Xf&Vgi$5b21`!i zlsN-E?$toa?p+PqA;JU){|*HQf&qR(fJ6S3qXO2{6&ZVqq#dzrt6(M!oiW=aM@<<3 z*>Y@j!8i_D%0aucIYmngG%`DX!Na7eM??6kmNTMn*8-()ff0IV6rrWN8RR+k35ha1 zP*84bFx!i}hYMY9d)AC4LT0!M{P9k!lMbvTzPH_X9Ib$Mx2N^c7v8PL=-H_45Y;D& zTs%;tg)Hv4Z5ecj7>UQ(Vtz}!I!CE#HGOHGOno_N58<#LSYOy#TjCqG7_E}jz1`bS zXKEs-+m{b^jOkch!U<1eg(!}|o&AJ{zT?GF997N_igO=gp1ktRAqakih`L z0tev-VDUQEzHguG^PrxVBB2}2zm?tXgTxII$SgkN%}d|w%$ypTu%TCqI=h?0AF!9U zuDi8DEqX+gC*U!+K)2UduEd-+01X~X9BD$xlP`O!QR@grX|n0!y{6tHok)qLyh+u2 z;Egv5vWqN#DNgsjbdz>_JiQ1iD0}2d@hca+Avm~6pxbVpHjsT=0j913m})nx%t&|@@=Gi;HYXgA2=FJ zUB562zvIJZqa&@I*iRf(or>Q+YJuuAmd;fNpB=AsErDSFttplYncn#zmbIHyC36)TgZ;%hxKRPGV zD#zl#_tp9V3mob4o-m=HLBRO$?|;~kuAB#Ssz7k+`+j_t%o6j11NEt7@F09S)xu0S zc)ICh=1SthULA`G|Deo-T`3NJ7Z@QmgSH5@dIVE}t*Tiv6Ow-AQ-?u58NmtPa+frJ z_ol4gBlbHHljwqn=L~eR*xvaD+Dw!mzz|9CiOjyiFU)tRJ2Pj! z#9|V&)D3Xp>3L*CM?#FuG8Z(Z3!$at)sKuL0+l0UMQG=E#9gyN6pe58o?xoBYY+Qd zO3HK6GDp*kq>K zX8!G`*1MGG`w95M`p41<>tPjWu&2hpm03LRj(NZ1p7ngz2vc8#DDj9HdWa>bF7^dU zher41x`5cbcjd{pX8|8vG~#Vb;sVlkn%ku#rDIP-$rf3}^yMF-O)PPE%HUPcLAPbY zr1#alStc331B2`y|5ydt4F4RBz`!WgdLcfeGY(c28Edr#`m7$%zYJUKS@U}SMXB3| zU7kj54@2Jm4ybX})v)=nvNV~#^4mwAXF83Dq;Yc|{`KFN9);JTf4cu{zv7j4Y7U|d z{bVKXQBC@TYuq~bv*$z4I;I@nFhk)Q5`g6qWwov{*e8wMR+y8*r zA$m9jwd>_c$povd5(OV)8A;|%s3D-pVR8V?x+B{#EPcNG7NfKG@mY7G_|S)$4kJ1Z znJ7$Nnzq{3wqx?>6=ktg#yTm^<)@aS1cfNtTj22G5&54a(HgfN3M5VL9cToR+55=8 zUf@m^XgXa0$-WExV2p-``#u4pg5E*ctR$o5p~e($o8=4Np^Kze9c;-r=-3VIv|82c z!N>fZWpxG@#0GPAu4sgknp4d=+>NH&D*QUC^COUgGQK&(a)$Zv!thw)v?#^{H&n?v9hkF;E17B{}3u zg0&^zWSrlf#j&^e|nK`*4GG5Ut#r_sNOMtZKtl6at}-f ztkSC%F^uq_T|SMnVF7NA)8so>?)xTXBZhwJng=mn#@|0Mer$Ny?42V-7A548$3wD_ zwv;|(Vhg=h$8O<_V*6h+KO=02rYT> zD!w-G7jl*A$`btS9CS0f+PD*!sS06stWpFv0OTh3lg<;HI# zU=j<7kc#}g6seLBsfd&~sSwcp&(6lm&2_U2AY$U*ilW}!5m2MbWb0shivf!KxYtV^ z5Ro7E`-=gxfp`GLK28?4D-}*QuAhg1M9%;98~j_K$*HoEtBngGny!=t zP}WuEBZ9FIN=uGbX5$); zBean0@88dz%0;3$ra(zyRnf6e34m5eV!T*=NXM=*Hz3Gmilj`Y;xdJRG?-B4GZV)t zeaQco6O&kJx#ZzJ=&k88hZOFxA)fmo2>dSJoGyW;AGjz0yaoRO6GQ^>UP3t%C=Q@C zge8MHgE5f)$BMa}zW#?5!+djmiD9my?7tlCQruC6?5F8WT20k3S!WpMkMFZW|3ov< zVc_)wCBXwvgu%ftr^SQ8f~YUAj}8SiHTx3YetE&0gCDE)`b3O>;Y78|S0sf}0Lg)5 zVGysBy?uk25Fo)p{J^{YAu|dABu;MLsGZ}V->9AA?{C)Zv9;B0BzD7+k%SUD@B`b) zJ=4#k4)EVYR_tPaCDVmUnd;9Cr?z??6x}Km1?~{LM*KwgOGuA)z92kbiIkYw3{yDB ztP;NQtdn`L5piC?#Y6nbJ&d~%&m(!E5p0L!Xo#$l+L?mMMgz162h$p%%l1`@?GRZfMnpHQplV$>2}0 z;zDnCg?!7@=KZtE2F6`T*1&jEZ6lUh;fZ8j*1S$&6Hn0(#B8t~vf zMuz)bEr!V8S1sAIsmWXBUoRBHhLvOM{fTa{=Vh>EcWub~p33i7Zxyh}wxnkXGGmThpOkyXSG#rLVh^F{IM3T-x$ zAtMwq8|UK{KeUG%GQW)KcC?X2LN{#3 zBc6emN6x^*KEQ%eslikhzJo({64Md3p7)~h{P_sSPO5PS3 zoYVQLK8}FP=wx(40D{#BgzV#XtnCY)Gyi=ZtW2nvh@ZagM1HhEtc0u5H-WdPfVpS< zT)lmdSlYDD80FU)=rn3|8D}2Njjy?x6aJ|0zt4lv zzdn7(CV4bqZG^?(t|s^40i0BKyb-=3iW}ci)w2P$(iCAaHye~7JT2u4-Sh4v#aPl(X>Hg!0TtGeyVFGe4?zIwBW9$iX-%QyScLG*cbyiU7EM4~H> z2<9Wzs;sK}H7+xP4kg)}$sGI@aAcWlT_Rj=tgSU`DGV~rBD9GSpbsTv3rj?&6R>Q~ z%3$Pl4YH(6c4kv6O6UGsA5!<=%sh=buvMKC)oOnG89+# zPRg@#gmhBN+54E07|#}k@Fm?HGhEDjYqh-n`iQ@&5zx>VSe5Enw?hwm)NKOuy|}(X zB%758Yj^G(4bJSzI}LN1r>uHF&D+43*UllpOR5KLdSL0f8x@(0>_+#>Y~jvY?J6-o zOZ=#_@4){~xv#}^D+g6Z?E8lX5CyDEBwAbG^T|Yw>O@t-oWUmS5T!;2Xw>DdG`tF2 z_EDMC#_eO0^1Ja+t_6@yJH9JZx}IUunNg1^^Q-tD^f5eY>FwL)Uet=&b30B6Xnf45 z4^Hak^?qLtR$VuYQABZ594YSOk%@$x%c!y@N$Fj#*J2tEGejl(CSE3XV+X*08IzJ~ zH?CjBx3|eC`0TeX#&0zSF^*vB53SLkgLdVy@gD<&-HjcM4SxxnTm?9PHb*g{|Mc9D zy@1#51OlAJKTzs#D!NzwDSo$LDo}3nqs<%viCmdc$;}bagmb)9Z8dfPiq8Ob)}Q?- zS4Aj4=3OEJGv9xne3hX9qMVn{xrvuTA`vPx0%PHgm5dEZueSt60EUoW$3%bhoB$r4 z1;`}xa9qVc*)9)nYQ}v3C*0)UB{f={V)qQlZSR&XxI33TX_>q`T3>hmW>0uw(>3^! zPI8hmkt|Z^mk;mk4i#%AVxgAA1MP*R>G$XuR3OJBI~RDkxA(Pp1w@-;`Sgn)zCmu? zBc)bD5%q?`56T^Xj>Q=@B;}FmbcS!~SaI^U5T0RQX|xkdv_3HROBg-RR5qLPZ5uKJ7A>mPmPFX&kVdF*^&-_HW02v=znk}R(6+uMQL zc;c!M7Kk}N;@gcIZJBbcRBJ7ZDt$qn@D04;r6O3p>@oq_V*;=C9UXHz%5tqSqN!oG z{(}VCg}|UWD0S4&>LW%^A0(DjLJ{|i7beX4RT9{xr+g{5XPNbY&Sm(}Fd*?|VE@Zn zX@jIdGfv$r>%$HB@~wlKHR!ZF;!q`pu~^^1$FW};rt&DEAG>LI3G(|rph5@Pd=wDs zl>p-yJ^FC0%Y0ae146}WlJPw_a`C` z>qBxq#N z*p_@3C#~Bb@Fid~xa9)=k7SL=u68p(t{L~IHpP`203@BS>|BEVs%-zSL}vaQ_waY% zu5C5S(QK}1SzDSHL=G}%+v>=tWa=m5+Yg;k@AycY&!<-Gob;y5gCGF83q*22ki(6$ z`KTwsny9$aZGQlLcv*K0npY83k)$ULJJ+ijsy6zCV3b>@ zYj15uSB(WOHzZp4#0Xao6ZDhdBR_t$XuDz+KOxrfX(&fJn`m%PGMSO)G6F~^ZC8`Y zdn}?!Y=UiY;~G*q9jk>Irn5QJ&6#pMHRx)0j_+!TM;aZ!G7ols;moJ5E$kc=l3?yG zxwnvoL12>fcwp`U@fwO@+3Lt{Ow7GBz8SD(G0KqIBvCwiulBg=VSVdOw#n}K2T)*x zPYB~XBag`{E79wky!S_+*)a&|6;&AGipJwR8+d*3d3m;Ol*jfy1omS~-Kt0qZhf8$ z_(#+@Br>8(Wm?J*s}&SQ1&PP4R3)`SPIK-hy5R!-%^!r&F*H}uy9wQ%TP=iM!h)vnsq0s1lEs&6!^iF&<48RuZic{L3Ef zw#v@d_>$kSy|$6eJd>%wTH}i{LR7gw7}yndYto;5QfSqBrz@JEGcyY;e96cR_0c<* zGP|!ANz#c8{jOR_NV?0bShSB2Ii<8k4HrsNp5vMjP$2WUeX_P3Q$88yeI+7TMkIfv zcpi1X=|!TsW$&x}0<#?Inbfb;WuT`GUrc51@kgrjkSmF9_Gv>7Y|FbpF(52oxB zp8>6>yPjK}UtVx*&5`1%5jvK|op|6Hb|^bciZ}H>fhhu*PNZ zD&>Xj`2=L`C<2kBXMBeTS>pnb;ouVp<=S^FNgsoT7kJpwo|0jM!>y~Z&e=;%(%g}k zU2TOq?yhcXAhDO7UYB;CBAYqtz zaa%g9nczY~8hPH-#H&(MXz2d(;r&A>!%w>%%3ul^p>+LkT?a=uf}n7#-j+)RmmkC= z)>f_RqYgg>Ta(9MCWueP%WO!gNUi`Q-W@`f6SnF{XxCrg#faS@{3{M3Mm zbvT1wc{@q<)$}RaJG{CuX@W@bTIzVE_1X|?&1SO^iuEvstVdt>@_aJJh^v}WQI(u% zi#9M_mgk=Nl9j@uNuZYo@xi`#YJPWM;&sioe{W|u<+zC~O8k-@g&GGUiyr{@-SoqK z<4(O#qk6L5Bu%FFX3+A!^zGGkSw-SI@Vo#1r)}*wa19gZrr(|P8m;}9P|k7>#C@57 zLd0bGIT88h%)jj}2C5k@p9_h}d-IeVc=s$=B(M z@`S4*Pa0I}D(W6oeT(#hRo&VZnKm)N)_}ntv>!bB@VpJ%CG+KB7Uh09VT>v6 z${`HYZdAiE*z)YX8ociijfcED3t^jFVOxjeCHr6Y_{wLUqS^3>2nAn1zKcf|JBjtJ zgif(HauiBxIowbpnAhC@^*4zYdM~-|_%f8#k@q6)rjAQka9%3{>5lbm2=BB7*n^&j z?kKHOVqZIC-%5~g`JWR%e*~4*H}OGmBq)85Zl-pICP*Em3R1y=2in|CjhRi&O+Wxr z0l~sR)1yE_;6q_Wu6)1`ED4XW?0n~AF(Qe};DN^kqQs&25nTjoI&7J4EcQz8+S&2y zvCCKbg<-uv80UA)ZZ++J;15K2P9e(K06D!|?ENO*7<*S5q-*eQ0{;aw-^NTLV~fj| zTsLy=t*-nGSBz2v6frxa?zIFH#uxBjy5(boG{*bjMR52Mb6>xD#%REqlOj*@Tifs= z+f1y&L<{0JoV?nepB5@wcuv~?#t56+w?A$tD}WXe(>7unX_gXVIwuTIbXdq8+FV*q zPH(`=&jJymi1H3k&9wJgfH7TsXiMOnjxUZsBii8HMS&lkH4O7bWRa<{}QkD zMVl3lEl0T)SHMhbUs(Dnx3a(P`*nu>67;cxZ9|K{WUoKVbF}aN1hW56u-EGrXzpwF z3KTzI4IcTQ#$EyaXK?b{Yx26N2lcY32jQxyr$~2-KzH3SdSy!MFl$v}<-klu51I%f zrz%IQyvM{F8gPRiVAy#%tGRz4G3!z=7oS&QXrhZqyX%yw6htZ!(c{%Vwxc>vwugKA9yr*g*?z;1nAGLy zskCMHY?YK-)ZiJPNOet159Ei`!3z`38twg;vES|Hs5W&{PB6EO406k7qx<^9^$f55 ze%qaI*eeTUmOdiM<8=ql9_z2NX{Y1`AAKLad()J@Q5=oOU0&pgEqhR#9iDp_@kO*S zf1ND8lpo9hCwLE{@m!x`l`R?MZpmrjQ3^#7pk}-{O&_{Osl;WVq}x0NZa|xrM{U1) zkKF5(Em6E3sWdG6{Kj`|pOwaO_X$lWPiS{q2`|9{7EG5ng7>QxB;1+Mmn6{VQcZ@! zMwLxY{Z%$RJ7_})KG{~iU4#zkXXbZ(YGBi^`^weuv0A$%E>a@TX3ijfjhjX|cFF`6 z@)vhl)n0OcXhz;-N-93wszMRZzP@6kNR;fh6oORH^wba@QxWgyGd3URC(VZi`QTKK znsm}X#QapbCka`qUQFJzt%JcnUEpj zQt4@Mdpr_e$Uo-{P>3dFaF!L}>mmxE7%Ca_0%6pe6nr!QCk z6^@|nS*U*0iZkgebeJc_3^sX3=t2>vjHPt?>N%!VGDGXlikfz$P-^ep`*X~j{qGo+ zLWeaEsl5$ZvL^{wJ>`Ds<&6vF9rO z1E7%gnThOeIv)XVH)B)se1-Ei241xK-+$Y^V{-ci~Uofyeu}X{uOt_4`~Kmqfd>~P*#=hJ|@s1 z!cfxeWmxlul%;Gk#00MO(qwZ$nMOk zIy;2T++FI8O~Pg1$Um~C`}PjxG{-6ZbeSH!F0pQ;O%o#Dh6W}vlAdi%dkqCS6D;EE z!pE;AnuuPF=UNUj4+~#N&E|)^Q)-+7)1Ybdn?Gs9B$5yGyNCcq(u%(%=Hr;+Ho(D^ zE?uovu$n73*aROUzWdER5(6K@tp3a-5erL9t8A^z{EjcZ23Nlh-C@sSrj~MYo-)|G zz0a?DUx4uMbK*>A+iYBvR(4G7LP%hSwtuc-DU~DqD3?->ij;cniac@1@yKVY&iYPd zDzUH8$8X(Yjir_i2<{K%9t7+>`21uR{c7(=c6HFVw!2PP-E8|zm_K&Ajq|(Ld`jgGRyk~WRuLs!fPx5hvow@S|GORg{BdC@bCF5-# zqC&ximO$RYGdk3uBoAk2sQh9^gkk+5lo)HOD>849;_0Y6a*M^nr>(6!O~M4GMU>lO z{$%H``aMSt_)qt{89!W%82<<{q=S4@K$t(qbNO*$K|sL}LxG8a3EduV75L{M#IGno zk6acXj9~zL%3FhFsFHB4noqR^S5kdIg%&diSXvph8PtK4H-$?;h1Q>zDYKe{h|pDS z7TV7{!3GCvt^Oo5eC1i9v3VfD2~5wV3BMxfX=YBzCK!CZt3*BSXY??NsZ)x8K56E> zz}1xFGtnM;OP%Br+85+`f#LqzB9$D;Mu_P-btY`h`>8ljB*wWmbY-0}IGaeuGku}N zNwXQO3)8^81Zdxa#m-;!$2f+B7^SVzFB3*H3^%-e^469m@xyBCFmco#JeHf*|03@# zz^Yo?c2T-Px{;7ZVA9>)C=Jq$ba#h@go3n$NQ=@TErN8TAdMg;B^`1GAfao0%l+;1 z?f?JxIcr^9YrV`d$2;cay`O&W=T3p#z!)j9T%ud*OfOX$YBJ#?16b((5U>F!5Ey zEAta~dEVnK*2I<})ESr)_&`*iwHDd!RVq*6W=Bu|F!=T|NQ&BX$$|0E*KilPJ zf6p~rSW$IGYp6aP0%AIL= zk9cR!U?@)lmcNVSoXPF?kmEnp{8So##h-Y*1edK6kTq?gC}fN#EOqm&L7IYrj37Yz zZlC6bC8h|AloD|&YzVz(8qi|Sxd+_I3RSpt?7_@oP9!DVqURGcD~C@ul!c@aRf(09LRu7hMSYU^YcS_1!9HVb zaI!i-!^TF|#*QvF_75)=vFg$lRv>{+7D%9z1>$EX3nY|iwY4B}wn05SwX&m1!_{qtf8`tN?eKWW^#Bd-_sHO(9Cm?1k4!R2SH1z~HfsI-0d zwCGDU+wSQWWHD#!hg4YK3LXGK#P5GJSIfZV(zY;iYX#RRZ;2B0XksSa!AJPSSiUI1jJf;Fgm;i+m1t?dH{LH1MAnM(sRj!t-Ww9h@Tl%WVd zig3MBp?*J$|8~3aOA)6OOP6y{8W%vO`+N&M;@-aJ7kZzaLfsYYv|{@HnywgO7g>t- zXil5Y8(l|{x8pVVhxOzapsBw}xnaoENPcEKupi{=_(A-M)qbZ;u$Kw^-RdM7J&rbU zm14gRUXJ-BEDLpqV%$NFa;I{zqFCO6)IUVpuC*zgb9X1~Df#aE?r(3SYi9#F#GUs3 zQj+{PG4|}-S7bfleAzEf4f-Tg!*%T6xQfn=6TXH&xg&X4N83`Z0#%Os;xW}EqQ7C^ z+9VQ(n~)Y+$(tmh2idmMLwzotcLu?xEmaw3%#o2t^BYM06h~oR=AOoJ^yUAUE!|0^ z7YhuV7l!}n2*J1)`YCa#mTE}iaSgIKp-V4j7GZT3Q%ef9BP8o73m)amC1l4+{`gks zMXF~nOeSWEwuH1g5>x{mY`+BK4=@SUFxJy!Xgb@-FUtF&2xvi)rK8uIsG~`DLbjCA z($YV}ndVZil^k@YN=>4N$!=fe8YuXjmV`(J!A4Uo829yHF-7GOp7=;5>yCF1SJEiw z=Dj_I3)1Z0-S(^4I(5S}(qYbAb^z%QAyBVc|0%V3CN;P?f&TaFyy7`C_jQTc{|7$f zKaqcT>GY#%KQQkr7_7FCO(fPx07YU4k%(JK2n+?01kPBM?q_{N+QhlVbC7dD@v*5l z&~VGsus2g;t5u67(nt`>tjV|9h)~R>?UVIR91XAHBI`$2;@lc5k(jtBF*P0U_eULB zP%F#v9LgY@x8M@YEZyU3$wub-`^iLvp$ZR7|JFYS$iMH&b=N=WOu$RQDgozmWstjN zYHOR=imDl#r6i|pMfVz6TQJ;c@Z;K~s{Rw*>=q(aefGh-RJ@Bc@YQHWj$P#rX@rt_ zwff*|vpCsEbAoU@C|_4Z9cYs_&7Z8>VZP%kw9AQsxA|fRQ(B{;M(GB&yN{g70-_$u zS5Hq(HIF$tSC1$cp6PiJ{8fyLcm5hIh5%S}0kBA1n2?B2XXYa~sk#ze=1ui@R#5rp z>UWlZulgO}@L~ZBxXT#R*(8vw<$rj$KdG)R3g>7y7*{gvoyLHQzz*u?C(zDl?_WK# zKNg1&X)6g!h0-POWRRKB^B14FUx4H%VQV>I)W5QG_r)!LJ|w!L_>UEs!KiL;c`$rk zBH(7E1}qF1iFFR2nN5XK#c`&=eB^KMq|`umn0)hLi4r9Mt94x~d;m(XLn@nk*PLg! z^8?rEG3&hlmLw6cX;nvYd>i z5`}06X1yh~&T6P9W@hu8lzL7jX)dQRnCnF(Qpc|Zu_XH;fB`i+Rq^YuTq=!{+#7V8 zFxHa|j*u;?d!{TV_4XMUyij2&MsP4SEKA431;^?dF#4h_gu$K7&0Ap& z4yMupZK58o)32>9Z6IkPS-8aBMSiJPv&=t6f3~XBO89c7Ll?iB*z-BaEI6_i^s-s~&y)THWX80bCq!ruwq!?=^$v&Z}} z7Cblc>s=ONZXSw(V8I@qyTPvQ`iz)0YoAqYrUKWX;pOV6KiT&T8YmjyUU?E9z%*J~ z-XWnbpnHKtAZ%k}&iy3NQy#rW;%~GnzcMzQ;ZOU9u98-~tUx|vY!DMsko#Hi za#`6dqQG!hR$26>g?belI%}o_rU6}aexLhZB0?9*UM>(DfI@)rnuY7!`n<9HKfV2* z5ZZ^ub-(z2Y0Y~L{}%20Mcg9BzA|lF&uTxj$B$$iMda#i%pnl&O~v!c6zyuSl9me< zbXR*jr_~?uv$Ll2H0?5>i8(&yQ}ca)5SrLAUtlp*J|+~6+B7mE9NOHl&50f-s|>xz z{zWXn1Jih997(Q!(i^FOrviBF_aSLFS%xeKX~lr~=tlfQWgsn|r%i#;LAN`>!q zbdAt!TM{8C&&3!z!Bfz0|5p56&P{|>-rm31l7DgKZWx!BX_W_M6R&uVK2ym%T@5=(lsIM6gvf{L4ATm+qlu}@Dg(k~ zNjqwHz+g#n1f;q8p`{RWDmeUJYDeU===f24=6F%9fE7LF_#1@&47Z?Mhg%?Z09~mG z=t|)WUHL0Q&yqljzakVDgnec-B4FIQfzUIm!W(YmKZ5c+>H+jX0-dfPR%Q_EIVf)~ z{}1f=CmfcR{_q*JDqhF-RC|eJEvf~l4Z!)ak>@8 zKQS*2^Q5431vE|&Z;4Vw$ ziKi5Kkx?t9VJ~|J6auckD_>7&rfTxKTS)susT{3}cmeU)?O8#-&)r_tPkd82CXk*P zrH+&B8HG>jPh!R1$4d9Q-!79&+TL3Ht@B-+UjMC)0^9wmPC4(@k;8(~Iv?e&mrHJk zqa)7b)D9Oo6aewvsFO-}kV-Tg57IK8OQvR2ORHn4g_XP+UWNHSRX?>zS-9KpYq_ zlWf82TsnZg^+ZuBY=S9Xw|$8tpGU(uK9;$?G>xCzK>fWGP`73Gk~yETHPl%|5Cg-M zFH@NUwR4kuYxMB5l>4 zHL9P-a{zsL)}(HJJ+h92?^<6f0%(*4(8zj$MmQH27%IxVV$a_!5Rm8%Py#L<=6S<0 z6ao^W(D_d1aTunbUKS)0_Kn|OcAWzca2|d<>)YW15+dxN^YG(Y7Z20V<$q|GKY?$T zK*1E2E(2=^hOCnqykyQn3`SJy?prAe%#ogVl?8VdT7{Tn)eg~fv`}WFh;Iku<>Z&s zSL-5&HNOsGb7Q7|OK8BcRjeh=*U@dKyChs6yZB-9fK1Y;b-tjL&gvEP#^EqxZ_@mR zVpceh#LGKc?(X|60#)mY@pB|iBh;b2u}9Tn-{u5y(1sj05SeD_C@qp@)rty9_-n-6 zp}*H9n`0Gu$&(niusL(XLbP+7v`MTjZaNb=U~R=`m49$|8x_u{^Lg;2hf?8%i67f* z(HiC3gdk?))CJzBI*Rh{bKFDLUHC@_0~|%aTeF-K@QVze^>kUbqc~(%p!Bif}SigRM(b=V6wp zE;Ud_#3AQH80>AucC!Esem*dvv@LMupkuc(?jy1P3)piSr3MM z?nQpX1uuSTSXaBR@0;|zxJTG_HqQ%&!iy^7hs|o~ZSt`>85Mjm9=0-`(U01o6F-d5 zt!5ayZ>BAg?II-)9%6+;)EXSiZ@+>llHh(tZ%wtiN+_WNKd5jz( zpvRFq*kR?(>W!Rc?;K8$W`A;@O-Kyl86#myl7kd!v6O_EMA^CpD_Xl;QkO>M2*h(u zdE@ffB7!NJH`tpEV9#N>ag55L%I}aSBE<2;d#+#L@3JuBYFy-n!FrK=fZe~s-x);& zR5gIXj0^ms3gWjIk(OeRLZZC=f{y>5=yx3Y)%^;@e;I4}Q^e!9a0)<4_HTq!eol>n z#9+KEgCe;ePDF$c#Z152)`BYsq z;?A8By~vo4(FpJ*Io8+EozpaAW@*Lq@m)n~9CzR$=v8Ro9zb4_(d4O@a z3$eJ)u*`l(#rFxhTfe)rCpKQ;Y;!OLiYSmud}LfIW(7)NK--| zZeE6Ec82YbhsFC1iRV7K^#1-E$c4M69$#Wz47;pl54=#2$3IlFRof~U9+Dqg21H1t z@)&+kI&fx=iz#-gEljE}{=!7s>KCV~iBjA=RGh6*xM|o~B}=3cExjI%O--y>gnHzs z$cbdxwaNEx$8fH6jT$bRf#s!OtVbyDmZhlUPVX^!t@yP%B%*#_2niM*%YH&`)6OwyF}I zR<3sY>u+){D8whwYCgWO_-{nVu0qMT)~{q7WIZ6?Y5*G}^Oxw@tzgOp(whLc{aJYI%ozvLKv&EE@NR#Ces(W8NU}l$-P#fK>J<&I**=(HT^%WlaxR@m ztgE%INN@>(mbu+BW$9i>ZgLw{^qr&~!0>;4kT(4k;a+^0X3y(_;k|tLe7(By zQQax~JUt8@Fz|NiLxHSiSV&TcNdFGV=H#fK&`$yRCH~pHzIM-(XJOS$32!%Az)WhC zCDBif;{`MPbK%7Gc|2#dKU2N*_RQyn6F1qw{`STVWg>wP{g$qDd~9mF0Zvdft(D@P8u{3WmDBs0GNSTMIPMK>O2-1+@$9HkVo>L zC+t}9o0P}I1*QjS3#^pMcDN0?No;CpLO)K#tPMWMF&+L@X+5}{uj<|-J-oT?1( zYxKn<6ljBOdp64z!u^v@K65f;bZ!kmIe=zGuoW}aS5`a-ibc62bdHnexMbu@Rd zH+B8#*>mz<;T+lq=qr8*U|s0~=V)O5sJ(9n1m`q>ZpF8T{^4LZ(S8<-Q~1@Ep*bf4 z0z7>ZsWWx}z{S5V#JUeK8tB^_nvqD@+c?=?PQ8G{K$23rFL6=%png%_zy^30tU%cn zE6dsNFP59j|NMr3Y8igDNbazW$?-k;Ih`6cVMzUW2cRo0wQe zdGp#fyrLn4`TnJG!MA8uCaN;gI~Fo+P$J$lQSy$`B4?2Tf=vKJzm!^puVcj+V;|>Z z4@g^50;eqy?_pHY#F$~h3bsOXB$|wOFXG~9xeLQ~iHG#GEII{}?PExq^cf}cRgBR! zyav=#=?H<)7y9=m3qxRIw+-!98D?#?68vUDAF#SvIWxtF`wHV*-t5XBA`_jQ)z88W-m^CJ}dVxr_)$O9?pPks~7WwawgONHRhaR|3Q zO%2zf6SFZ=<5&(PH08n@xmZhZ)btc;=$NJY*pGz1hY_OT*WjO^+#zpA%h7xxD@ZJ! zkyxI?ea||W=ed`C4mz}}#y9?Zu2_hWQIGo=p-cEZqzPplpwW+8m!}mmJVl;5mUcLP zjU)z#>jMpG*|C?w)--`Y4?5;RwA0limTxroa+ zq^d!iiYOW@J<}RXHSJjI!KqP!zPn$RVxq@!k_`yNHca9MJZ+kndDe-0J4cGBHuK(m zC_3@-PC6umS~(r{S&pG2E5<>I+6rUUQvY-e-mY9-McEbhh$U2(K9JXcYVDaCbj{tj zdo7i1@;v_aOdrS`9G>(y`(|-*p|afZm*q_hE>TG!s;3E#AWMEe`;bSC+uv4h9KJ2I z69HDva#1@Hpw2I7ds|K?FE%3GN7*%R-vMXwGHwnlV=wDl`(53aN2i6<5DfJnJ>JgQ zc|>Cm!s>`Cd2+xCM+~++OBqfqr=5<&_mwP?<$~?$lE}IrM^{sI^lE@b$!nfS&T2TL zxJ}q*L6C~=!3*k2*vDTVvqXQrlXj$-;$|s6T!vNp%-TB{=^1y`eR_crt(haZ=D;81 zxi3P;3N2=EX^V43^tA?w$9Wz*Z_zJ4cACOrvE)-a^5_)OJsr>N)90LdR#m7S&hgP{ zn58ihtteHf$=)fj9zsZ1*OXi`wm+Gw9#){bO2NV_$1|-OEi7J~;bhUS5!3cdThf^!rvIyGzML|I zfpo>-a8?nE2g12X=%b$1)m}cyc}Xq6`gT#ChI91}2}>J*9RFvT+C`xb(8~L>JpP=y z3s_w(|0^EhPbS+e&kUOVWNy~r3$=X3%qT>c==RK)Yv?`Cw7}s>y1<|2$b%}C_C#9D z!PHV`ACpalzzBl*!T0nK1T;)_^Vo^cj+nGTmpW5nM|nKI9p|-HDft3!Y~-~&5*LiI zkF+*%EzW*H{TlU0U_gXxqDGLJ^@BUiG7a*Li<(w*whb<*4aeZSL8G~%sOrplPPoJU zne`I0R2JJwEc;S$df`L5W!!-c8Trz(Ui`fZx|Ys5=`tKK&&SCYta>o6do$56k;Aj8d~4?8-Dv(U7(y`QHna#c8m#XVy^^yOBO*MHusw} zpH8{Yi|BW+sV+>mGuqW(#n(SpxzZk!p@)OF0Xq1c+PDZJ* z88Lp^YIdbwZbg_qhQ$^&DNB7)++nC!uDo}-eIMqmyvD*@`V`t!&1pFsV;Od8RP92c z25KZAe=#$@G$r9#UPcZF4O*qILu?RhP^`}TzLEfr{_4Y{)&y4F#|XTwOT%$-R+H!W zM!L3UFAMV6ZnVI<0l0PoaIO7|Z-phJ`7ZXR$~c3x+6_nxi(O(D0R!~{m**sW*3R?} z`b?q+Bem4Xi#+`x3uC}I(=E3UOL>>V%N--_{?wi;#wMe2w+YzZw5Tg;V& za;tK2+Q9HWGS{M&d!M6RWf|I)e8Af=e-gTb&ilmm*~qw~^;1#({>N?NB~B>8i+Ewf zR+huA*m0Rl9}qZKw#YFQ9F(781aTA8-exTihmNRr2`~OkOcGN7uVfwFLr};dM?!6B5 z{tDl}$)sFcA9@}p_+rTM9ifVhUQpevxu?e`GQS%3vp`F26$A|br9PO~?eTiUe+7?x& zxIWX+Ai4v~Y;EyWq3^}9c`&m3gKtu(3%1DceuI+IHr-O&NyVPO zZqR@b?q|PJD8l!|S-v({B<+((j}VAPOjKr%CEIBz&8N@NiQ#Cb@8W3ubMod*<;xFs zrF@~tfgmpl093f2%9mAsSdC=(+p5$yV-Lq?I;-ElUXWkj4X6UymTT>MvE5(F%zurx zUXiegnme020A=7nRU7+-o@Trv?znj;pg3II#-8Mi%-+TxDAF||0XTF?fRSx(S1BC~ z?29*$T+a8r0sgajo=hBP&_4(NZ-gBG7k!pL$&o+ zW}B8;l4epG9LI<(c+D<8U8wYNtb_|Ia_~8X16A$u>=2cqL*%+Jja{O#v5z4PY*sWU z&>mAKJqV0O1v2Z7CX5!G>9fPiqANtOX|OdmYfC5ch%`I=f$89zDFyq4-l18G8K3No zkzd^NSgRc%(?hP-N}Z}XzNdxwI-z8VVF_x{AY_v6!$u~(h41ButbEZ z(yhacFnO;?zKUX2H_V?m9{4N6{g@Y@loF~so}_d6%y*|S_ed4wp64)a^n zm>+RTQQn4s>FXg#zp_Xb)c!;N#CyRro@hArTAuvHd-+|!T+0&=EwSF$J)Q0+Oi%DF zD!z-as7WgRcHqzDi5ps@OleHT@y3$r5VEycy~^U#fgVVKZ(|g1h}1f7b33QYt|~e& z8rdUdlLThvxE!9h1AsFtr78T9Q247V%AabdC5Gg6;nZl!nR`|++ zBa+eeLA05~sGi=!F)SY$2~Nu%_EO6y`-2Y-o>SP2Sb6*>7E~1(E1ES4#KUuPMhK}G zAM|T>xCldPs_Hz*e0F~n3cAe~qT2995x6;rw{Vn_;s(yMgRK~nBvvo|V1aro^q~pj zK-7@rxnGa);E2Fo=Rw01jftr28I*Jep*@B;iQd{1mhZcW`rh6#bbb=eRG_sSRBeu^ z2V8Vtpf)@nt45fe%aiM&G-tBJ^eUVxgn1=RfRVBOkQ0E)e}5rOel4Il3#<4={VG0w zs&hCDSRnx2HrD!=Ayill1SLZoM@I)EeM{q;rBMG1!)qXK4zIZo23IK`<6rttBrdC> z01M$-NC0goEZ5bw|KpzcPokjiTNC8*ZqzYEv5i)m#DKSjYFNlJ%Bl+gWeE8*#WjXMCyCfUh-=6hI#y?f`l*$HHhgn>?$8@2&P`T})Guja!IbwF?)JCRTgQ<|KLKB@ z3N!U!nl)vJh=ZhL5J{D0+C?cxdRsZm;X_sf_5#J@{?Ws?JS~H6_vE{yw@qR3KJbdW zBZGUrSO03Y5Cp>BKJ_Inb<2H{@ zcq1`H9znl7FHh#3gbwvcFWR+e4F{tGc1InGj2#nb1|Kf0JlZ40@W>$Z?Q>CpdO6nn z!U4E6{zav)C4o9136udzz19!l@)uJ&qcP50pd*yC!^kHZhK4=z@VZqdKdcVua4O9|P>7)ap?HV=*kqwIGK&ij ztTGOQD+Uqe2PZ>65p>e79KX#iSfbZ=Z%65I%^tEVVQ>xUP||(o%f+l?UcrT=z7J(L zU%UdF6W< zS9NsB|KfT7?FE11d9%&YSj;nOD@W2O#-=6WV}6Npr8S7nq%wWp*Nd+svD8$O)jJ`C z4=tZ8vnFk$a=(;3g9Bm3$eEVm?)MWOr|5=5yH9O=90TRhaR$}dlc9X<9-JF_MKJQS4pg>s;pH8G}e+#)^Nrwed#FcH~xHQ)C zwN{SxcHRRy7TqWCjE_vTUEhi|&|IMxc1hr@! zX|~jk4diESqKDWV$C7n*KECv9JTo@Arg;sJgmMu!%o4*wW?2|eO>9U>fg;Bbh)zRG z7jbaAV|0%~=aw7fwLJ+a2c6u$Ed{bsNXX5pA)L0eTUS@}Q*?ju z&`}&E05aiSWUlp=GqW{SK(oX5aookSr^`;F525r(?9@V1zt^Wn-{A`*pBW?l{O$s3 zKzr?9p@s(h{fJPY5TM|s)AQ8>2X%OVEl~U$C~5YD9AMjHsmTzHCV$PO1g$$A0xEi99WV>}KH)`e^2<)3#U zJ&qk86-&`s6;?DX+SYCWS`Tf!BPS^e+_|dD#_`Aj$v~y#apk zemcmeA7&Qk>Iv(PV|XI7?}SMGuGRd)Zy-xk=+Z%k!$49q1~|#>t$_(Tw#H09s}x}| z5JjEr4fL&@%`E{t_Zo)E=gvD55s2WzJI1{D#jowBH(cPKFDsi{0lNS^5$4vWKe5Wi zan9^;Zr+}k8JsPz=*j;_J?%erfsN3qyXuPtD=o8n+)MfPW`O}0<;S#cF4bUJ`?RNq zJT{7hvR;$VZW_Z9cMv%i?`H`p!*|bHV(~SnWZl1yiV*Rly1P+)j{dQ6?<;6T2<#+{ zddjUn_HAkf=G>j2k9k7VvRQ6#=E~JFH{~+keDy0X+;5 z+Fzmf8?=DdFqx%%96#tIHw31%_d?sM$^}c!^YM|mKVsUE_$iIrGi0fZa_YdJs@PfM zC~%Dvj7zbha@{%YNdC}kA}}tCKja4^e4luRvKSkuX2I5;Npw;EvDTNtgTA>a3L~bk zwEMV^*zxbbM;2luFt4QC!Rczk(~lsMq8T^?<@ z(5s^jJp?4h`+)dQ)y2_<;;v8(|NS`MoU|k?cc~VD$m~5Q8`ZO$lgOG|nL8RAUHWK< z7=*vMT)#B?Gckbfo}Fk&42r8e{L}U4Re4N6-xwDYCkyb;#R+BscuOvpzl?(VZ+XB! z>8u9*@;Id$wd2DrQ$%k3DKuZBxU&A*Zuj0@nr95;%7Wyad-y@)>av|#RKoFnX#rvH zzQ90gfxWzU+ZM`kwrPmF;NvSESX}7HlYqiylTlm(v#=dC{DhjFZ_a$4qm!`~JTf>< z!%=m{oZNAJd@AhnQ*LTZw6{NL7)z;>BzPC-dZ#Uo$gKuA=CxUVTBDH;qJ_acP+k}u zGlmT}~2fbgh7s5h&5&5>DaJ$zsr16`QriS?ETt(QIUn{ zw->?v_dCdm-z3|2SyK6H10<--5{@YRJe<7TH59R$PdHsQL3t>Z; zp_ndzx(Ujfc<>sB|}StDwQIgK~233A+O27TNV38!mC!N;OqU+Zu;mxj^^4oqTN(E{E9gd zk~^$RnSzVS&jW+(UX8eA#@pwIP?l1Zj!^>+D_x+>g=G!0kp1`_l#}JICt27n_i>j= zdx|#~0{!M|@`9Px`{@_1=pP&mJ0OuG_5!NQ)u6&d510;6Cr1_T}7MEFd zB%GTa7K#_U0}L-BH(M-#sjg?NQvm7gd5gvQ@;|lBzo1z`2I&xBr9$>)xUp4o^FDF! zz#{B6_?|-f0G4lF`S8U^7y9xxrtV@SUI8P6ZIj32s%{iX$L$nN9xC~RpOf`8MAf}AYqLVk)eB2HG}ZXnmv!a)%a3$g&*#~(wA@eKRwW} zDKa`#pAWx1XINkE*KGq4;dXvv@Boo@F%{gw*xva}44qjG*HPXFq*rGK@;l&Mt^?Bi zuLtHo8|CEzaa~vt%u1Kfd`*V-n=SG0hkO6$KKh>wjbSA27TN>(*`0d4 zc-_cHO2xzioxN!!7*;ggA8vW^?mcwoG#q8u%T3mK0Oni5sNu64azG&x{MDS`ep2*| z-P|X7WO{WTA-BJQuXOb24S&3e5U8W428U91vQ)sxZ2RuH{+ zW3scqP6pvhd78+-;I_&uUt?vcL58einUYv3e)yPTG`YvGUre0&nQh1<1d5bMP=%$# zTcc%tp~Chd<*5+HHnI}Rt~3p0v$W%STE-R?LMK5zoFE7lu5cgd3qwN~B=~2TUBfg* z7i=mY{3D2t^HFA%m2}D$T1>!A8~8HrE5n{0=wd#>gO_G{2hIjXzx^a|T=x^ZU-u^h zA^J4Ei8g$*(SGaTnMZeeF(yTNXIV?3Qi*AX8}@ZR3^&!SJ+WbS+=}sMtn(Lke??nK z;w4w>u7ci-wOqGn5yxQZnAS^Jw)GzTMNpqT|0{`GTrX{yO zR4;wXwSfpXMV6yfvl}BhZjx4fTufbaE+h~E6U@fQMqmHRQ|7*=Wr1P`TnT`!8xsGg zSomdN*ICTTm z?Rvq~ZR?q3=MX|UG116+@ectosZF#$aET+oKmg21+KOS=Oo&M^eeZ3_x~1rq4ZA9= z&BPIvmOEw18*A<5mp8GBww0665#G6%u;|T>g{pifQ~y3W-n$+UoEZ_TkY=`(5G@`^ z)#ltV*Tr@>TnE<3*uPQoO>WGAews6hVs1>EN%@Ma0i^0i7(T`_(~H~bi6@-9yHw2T zHn2MntBx?~URUOKjTq`WM2tZv=5pqC;<&QkD_nZM?aKYQ_tvQJYywgNV;Tzh#+~I$=3A;M`nr7YqrB5FytL(RGu)irM@i?=2iJ=@p_8`u5_H@=@ z<{QL@7YE!>|9oTuaF%WyrOVQV^J0DAed)}>SF>EthSr`R%JZuJe|X>D7_4xr#}FII zi>FzKBOa}yzC8*p?{~q}yRs{4F{R9)oEI0?v{xZ+>5V*A#8se_2cKJb^Kz}B)=^og z;p7;-s!c(2J5&$aN3RlE`upx?b3;qDV{iq%*Tb%X@8T1TtrTY4DMS-z3)Gom|@3baX>+{h~4M`BGF{I_{5~;&# zGGxXy$E$QK+^EAEh@PCgd8in7hvwlZ3&`U2_M>$ihcc6t>bCOCrZcUZKUx;Qxb?hY z#q6u8DLfRJW)9F>am9QN&>u(CkF$3th^io*h z%~@m^ZD>ZPWavrNkq;Q*dhIMqO~ExbwZ*@`aecXqBJ4irL(tW?QAvG5yZpTLO*K^h z$!=R&aTRU)k@_ZHx{WP`H@~^G&n>b~>#yL@l4ufV@o*L z#Nd2Yi$$?H(o>Mx$TY&3HhqezaUA$`G{jRD81<6YPmi{b%gsGWF}gP>_$>n@fLJB?`I{v@C2v z)JSoUKBobx^xHa|k0q}-{xYt}lyldCNpQ+tvz;ZJeWp^}mJvy>JF;d?-fzJ%ZQhSs zPrii`w%Ff&;59#X7_x%QcHgw`dAu|9Q-QcP@C9=fP9IeL#P&Je><)f^L&^MEI7$Be zt&$*v?6REEI*t^xa(Y#-Hb`=4 z@}OMqyE!$#dQ}2 z9{CvoM5Caj8g=i1cLsw-6q!RM@w_hDhO{hCj=TMlla=E_cFI>Gy0!W{K#c8=#LK&p z(aLb6*7`H}wXqhdd#Bz-CtG&)BZ;|`a<7C%6D_?qvIzCa;}dZ_{9;O$aWpR1vSZNR zK~dK3tH+Wz;gJr}@QeaMr(KuEqYTF{+{zEHZ#3Wmbd-=ezPX5RTJG!VrHGhtD!NQc zz3y9ESuY?0B60nial%Bf4y?Wtp=N2Oi0a=Vb^mnKf784`KZ7|2IQS_P90&&Z4+J>W zUu+7s3ll;SBoC5Jmrj=q7XN8NoU1THVD06u(8Ly6n?%9#h z{YjpD?U$@q-ihzajq%Cu%_ePRP3v+In(6x`U;eij+%PXL9T$8i^F-n1dBLE}v?uQj zhoVTia`8xixF5fh6Z}rMC{w5)TD{N(f- z1Tn4r0;|HfPc0$I!>kWTOzAwW3Rv4|$yks-dPS`bup0?~U`w*b)9-w2x?z^nM-IL4 z4yQ(}$Wx@;*A-HZ!~yzP1T~e-T-Yg=!k;diCmMIMDev`n`nZO;oL=1c8I9S@*ZA1_ z$a@%eN>4dXcwd7NS%zc27Jn8|ao@9xUcP%~Nd{pp>4$s5SDG4vF~XYhhuu&pl!AFH z4!l1UbJ3_&>OM!xtSjJt!#b@CPB2+ZqOfDNRNO4N*J+X}cgB_s0fv5QUKqOqe;Vs+ zz7fo8el93(z`6i@7|7fI%DS+*Ap-uTbwLRtKNAdy7(`d*gV@haNp6}MWFV5W>j1vk zzh3uuRtD#J&md66eqnT+EkXa~5A-KNQn`oEvakgPMb9-Yul0?%*78<&`Qe?4p2=i1 zmiR0hS+A#Qb<(v@zd_>9MfJ{GDQb?y)5L1}zL9eO%n)}{)zQGgNq?I^M(CBTYTJf_ zz#{W=$Bqxt%1f=fw_H<`1bI;k_49Qwqn?LkSSXHvOp<>MU7Bqdn_^e0sE5#ok;(}h z43WE(Yfgz9T_F2L;Z6a!Lg1&Vp0pWro3|f@KRy)w()Mc2N=VmU(+-ZK)Xg|cBR!U5 z4~!AH`HB5lAE*6i%v`Y+ZCbC&vXPdx!fp)F%67U55ip8F{c3Xy^*d;lIV@4CP8vJi zG#|w)5$KF728C^~N~%1L)|a%EhFLX#Hl_HDgZxHsSR1eE65LtKDEAIqD3#YC>!!&& zmGS>%Z6uzF7$7nq1M-XX#r(TFWH@Bm{grq_o^F~Fclt{HYIR$H zJ>d*@!~TW08Sm+u+3PWo7wQbX-8^#Okp&AZCZfXL?e0e43q#)<65C13F(5U=A`#(D z{OVap{wXg8$KjEHDghHGZkgS!r0x$W&m$QKW+D;-I*FM!MP*x|9HgA}Znf>#;rKpe zSn85R(M)o?MS{RE{M?Z8w#t*(PyRTX9(}cKQS!lOoSG0|n3hhi=L+DWD()nRll1WHwleoRhX^|<7Ay&FT5BB27QK0KL!#5^Xm5(9WYle z3ed3zWJ*C?Y)ltq(>IsD5E}jO@A4;!P~wv#xIxx|w7XKg*@+UTCg}7!U4Xx5HJ@2+ z0k7CpBti3`XKB@;Yf650V2K`quTYO+H!%)eV%E+&KYE+&8?~edu7a+|VvomSOvkzJ zRa-eMRB}Z+bC(fA@0hC`JneOTBwe4M{Lw=~OB_npA3K|E_tWm0M~%#8 zle=aJGYO9#@rFS=Va`<*eTNH7wrCPy!hKEkQYJa;#Eoe(fe%H$BP5||KMa&|$j>tT zvIP=$X78CJE*KRjRX<&R{)~`VxXmU-^LS=_T$?Z^i?&>41>V5RCx^4T1!$ioci?H@p9Fu!2J2 z8OcLz1c(T}Y&~-eki_V@Ax{4G<-gJVnj3_DLyEmZ-V0T!Uhi!Cbl6X=Bi$J%CDDdi z8F(wjnw2SXW}0qu1XRQpRftJKF2~icq*#=3JRoKmeCY2dqJF_$3H40O^9&3{?mb5SdIozQ?jzV-%S0Z2jukhBEmKvgs9 zbv;VUa2JQK(z3M-dwmm}XF<&WZANM4{~s_){}1|%|5Qtq^9Y>O@$OfA$c==Vc-~v2 zaGNQgUHmb=(>FuUG;7ixcS|#MB-O(o)x)4ZDDXVc_h@p-Cb-B#JHPp zz68JBmIsj~+dK9Z3AD45X@Y}(H1j>f&84i5sEp6|ZbxfdpwP-V8COc?NKHnr~bTgHOP zG9&zU$H(QDbaA`ndcks{3;{1&=+g&_T+MCl$1IEBHVmI|EWB+2;k?A`_=X`cQ9e$CwRtK5hi*E5t#p#&? zEqTLw{^vrSpCgeVF|d9s%n%}BA(1$rUIz4U0xg|D+L)b%`7DoobNL_K=}#OQrJX3K zrjg5-@j1-22&(KpX0mOiHr#hGV@~mWUUn3&l{%-oFNG5nJY@QOh^Qp{`u$6MH{LMW z)50l=N%>KKW<=4+j?WQ=FRZZxc(RRTm_v`Su{ph0L}mvarJD^991td?O4SD5AOxk_ z@lL+zY-Iip&P5_Zz}Bx+TD4mY57rlJ>1ui7L-zY#d=R9rJF+X+D03S8~Mz-Yw5+EEkl}>uV4BC|gUs$llEp4wBTksB(tzWif3g z)1(R1UZdlp-Q+2sM%Ob+^zfiKHT;5yPKcs>Xw+LO`ge$(@pDX$%Fn+WTrhF`k&%4m>|7Oau z6{oK@cNs!tivT83n=YE9`eVwMo5JASbB6#2W7Qv3Ek_Sgk(+qI#w7kW3)a3?SKnaq zgibAx)|e#7{s88*9;5Q z1dJ(qOM;{N;7+TYF|8-(sa6YI%ay7t1QtmS26F55#XCYG0>2+@rkf>`-S(qQMum#8 zYlI;lPamvS&6!J}_X$Xyil9qjj%1GA0Ouv6O%rKmc$dHh%jt2f113!Tote@tAaHkn zqv;-cBjRnSJ4nheWf`U3OZQh;>mffpei$A^Zd4hHQa~Z0#oOyGRoAwJ&^or~je-L= zC>h^p_hjQ17eA{7q1UG^vZA-<+bm_-k}CbTP$(ByD-h{(g4nuGG(M#Dck^q~uvYU0 z_7ocrX~e{+I*~THJU?)s(MuhC1%Ri+lM&jb%6o@6(mAMZxR^8egN|C28?KC5G( zPQ;?d#msWJ(iswwGbB9$^Jv*9k}2UOXY}oaUIs(D4e?shlF*DsY9uLJ(zrCE#_Xr) z5XO|jtuI424nGE_+rC!7j~9Ru9d2K-NA>!`M14f4zd#Ku>hOfjJO}cTWvbza`(p`I zU*TkJ`ODhTo8FrSXtR8WL|e7!Q=09sVb_cGeGBxBXyJHI3C7+oKyD_bwtaLuLTPEK z2Sp*T;_bEG5Gy(P|5W#tVO6ee+H{9>cY}1dbazU3cXuNl(hY)iNl8eEl$3yUH`3iG zda}F~>V|%=}owv(|$9S$N%7p7(jy&G@aLrPLQk_I9Fo?VfeUj-)hj=u94vJA`u|NET~j398Txa zGF(|r)Jjp5rlQ-Qbxoq6YdG}On*(=xR0RT?c}EGnO2BL<)B1XN8we8O1rI+&2c;$X z44!Y-Uw?yiwr>f67P6X8buQ{mAQ)0S9S*i%g5P_${u`i;8irlE_YOY~2nq^=IzWq2 zTQr;&@kP4Rw8+iavnr1gGuju`sl?0eG(8!YC%Q!e$U9g_v=6!4zwj16gb27Ne|Za# zYJjJx06bWKcnhBDQ*juemD?RsZXG?o4$Hq_|NhIag8jeQReV3X`=9Dg|5shbfB&uj z2N*@Ps?BepXhQ2{J0a{y1_z%5*%ZA+)LxAOIj-eYZh2fTSCM$CH_XnH5lrFh9QhcP;Zzg-GSvzE1=x0U!I+2f4%V2|!3CQIcXvP&O#lf%3$9APR#>Xx_CX z(fFziM6|0BAQ%`U&hx5s5M)wV5oElMPX3duQF*JcGF;yA;E&NWWyd76;s^;O5(#(~N+S9@mhMSrQjhXtO4N@Q%=$AV$cCF4iA$9!oa#nN!93=KwqP^0v1AZI#$eVyrT|y z=8aFa%M=bs{VGaSlWnicy=V0KQ(SrSU%thSB%d+Pg|=>AQC@BAla}_2arM+o!(V)R zERK)RB&%$lvb3Cwo$D$;)M*&ED~6T1vtjwE{d;KjF9Yzi{H?Y4xBdQme`}I?CcQRR z@M+*YdGBVTv&r@4aQ`n;#(((+(fdv&A=te{0yhM-Ua%AJb6^kv9O3?G4-^dqFgQ3Q z2rxR~kC~{0v7N1>xs$DfJKeAS`!EdsV@E#l22wPwCU_lHykEfBSf3g{Q+^Y8`fgAM z+*2;w*`$(ZQ`P6IgBu6vG>ricP~+v6-r&F|h43DEMw?|xA541l=Wlt9{ApGLgEojm zqP>^)K~nV_@J}0PLXpvfJ}|~}Brz5=ax3w7kjATOn#b*SwZG#4THVDcZkX%w0uIb( z5^GWmO`D2I_1=1BhZBIOZ)~qhMrQ$SQ_z`Nia1>f` zeOt>y0rq|c?TxrA#_hY^O3X8Ag+tDuEYdI#iw}VBhV#Xp%iqDmc5QP|cxXmofP|uQ82G+Xi74Q4_C0~*nm2w9%S<$P{=m&3% z+JUpS`f@B%5Qe7oj_D;D)&!S1SkX^E+Q?n_t8O;swUSg;e6s~z0^w$GxyG>;7y_=^ zYUnjNoh3ivdhK*9L8{sNW>~HLn$Kf7x%a`(!vhBB_mZ^$K@^Uk6BLw{Up^j;4lt;t z2Kad7@A(TDhvCBIAI8MsU;7CCCG!3EJt(t0W8ixzX;9hk56k>m=>fJUB(J1x3ChI6$j%Co{JTHx2B^URmn@8|j7$I#-2a;&>z^v4av7;& zefj1{*Cwrpn-2htH&iQE4#h zi$u+2CV5-lk4+|Ut6y6hB-&lVB(T1{FKP+PGo|m&ZnnqLYoOhF+8gU);+e2g={8}k zq|GytQ9VQjdPVDNatDK#YQLxB_Ha*Br-4&vYQxx54rriGWs(Y z`%j#=%9{TOl3(joSR+&(Eo17pk8sjvO5J_|C@I%(%V)HCk3QL)qah^(!0q!V zRw9)mPSP(BfX1jj#qWKWhAo`9gDF=)FzBuJggzY%S|g~=W(tpMc_M4X%3a4D7qx43 zSUR5Dm}hU17ZAo1u{-LpI$Crz-`$ZMm|^uJ$5Uv5(%Lc%158YaIj&72y-LJ()t(ee#9Jon9wnL>et>d6$@dAWaB1dCo63 zs>U?kRvZdVb*Sx##!Dy7W$SrtmscPwlCi>rPeC>`bG9)xbJaJtvowD^nw4_@98>8wF~ZqEGUugjqGsHLr}_7gb@1BWg2V=;iBu+_aP z96)G>?kS&#Jl)DuIeeqbUC_U#~fNGWnnXdx@2EQ^ohpl5pK zF$0IhYXn~|EQlHFpQ0Tv-2zz%-Gvx;*I~C8N?Q}#5rs1zCp(2fBa`E1^-Uw^Ci2W| znYKaY7_CkS){7h@pPrvh)=8p)sC%cVOX)QaPQs_5Fe{DdUwMpAy49pHUjV)MgeQ~0 zQCYO>reGJY^(0V%=mLqC<%Q0=_+YYs0IM|CuJO5L7x=4vF_eiKB8r43Fw>;mO|8Nx zq73}WJX4dC7W>>Bt2ATD2#q*jASHa+sjpn!hLra-MKkmK5MBzkPiX3*Hf0w?k%~Qg z-sZqj&1S`ZmpY=2@EoSp{NjBzn23imP(`cKdEJ-gOP)N&&&9Vyibh=priadu5DBRM zD%})oz%dx@7WR{^Qa1_e-abgoRe~1=R+tk*&}MwTR_FLJ2IcvNv(SYE#DW6&wyEKK zlUMxDJlx;p1IK;NK-%M*rj5c_i=K2gupw$?j+vwBmUcWyFKpG?=++N0}9Oja^u zomo@Xwht)Y^VV`Al6jiRJE>&A zJ@?k&6-Xdcgjy1NipGdF-Ug}C{SLV<`BXPBxXHeK+R7iv1qS9#TR`%~Ht+TIE^RSo z0=Me?X9c>7_bpIpk*My2x8=hT_2cnZXt7op5?JH3 z5#B;pV)@00#VSbA$;=0@e1=hk!ASdx{YD;S9XQXBok&`^GhHDS3BfS~B1W06kWxpO zGenh^y(L&UVHr;9X+}p}FpGXaVne#5km4;1mv_S!)tpQ5M}|vOd%tdcHgP9n0j-dL zO-%a;fiU4OKI)o94N)jtg;pJ6do|dZ1{F~O+X-3o&!2RZ&#%a=GLHA`ZXUn6jl)NW z!TvgZa!K*Fw-v9}$|%MVa;zg88mJcialh=M# zK~@cnIj0uNFS>J8xw1oIzmhhhZ$hM7Qc0n(Zi9n9M(ZvAM#;6sjZ1IE2qfL>MfwDG zbom_tn(ufbK63vCY7kiaB3li5cKB-VYzoV#DP{~x(XWNQho5W5WuP4-u8u=ii)XxF zd0SRr07JKJKQ5Nk8D5{2k4jE*(8;#+*v_O+v2hBBrNs1JHP*S<%|f(kc60 z*k+!)N(Eb}ta*!re%Wy1H@K%qZITVwq0%D8>2O{o3)-Q~U9z-?`OiFr8rcIUgkb3nIG@8tbsvpjg?my3S z`r&O4+wkervAttOph)^=!oscZ;CO_tB?h4^0py{M{08PA_*+#2v$I7!F7k$+jxC;V zF9At^kbX9Pfbf-Wxq%94nqtbH!k1S*$y^)s1V}SVeouq5N?D)G66;!Y=_D&1Gl4)e zVfC7MNKHPoXd zUad+D91Jd)r$_u^s%Z0$KG^F_((y|d-}@FKWN`kk^`H_UnjgtFR+Kr;+(RPql&WQe zJs~)Ho&vO@9HPk9T@_D3am5SdbwddmY>oqjN_mrmz9`)gwaXzAr@d%+PJJ%Mixo<{ z8N8ge8w_PpWyR8qw<}J=?Y3vq8SQQ!xZoT4;Egd1^#Qt$_cD+AhGzQK2F4EeP1~QP zJOj=zpA0F(5CACw$hsN7XWY<0y@R7$m|LIB*`!PpT%yx~N6U*-S4orlKLk5{RF9^wE;ffnFkVrOAy z;$Z)IDisC=SwILt)%5do5Gv6L{L)$mMZy1pv=sXN@xG_>w1j|co#P>M=Ky>KfK-?P z3X=DkI|lL}t&j6hA?5v|>gXC*x{F9H%IsbHTdW9n0n9{2u^o&|s?caw zD+ne!F@qBcur_aDwkrF977b{}@#$dUyO(NYi#lx4b|YH$LU_|N2U?$Ra9X(P`pfeM zgbU#-h|u^OJv8H-r(Pp-gkgo(`+Z(vvQ^D^IT88eCl6p*xcJtKw& zl?MzaDMKE6okuaSH~GXBLiF>aZ#c|#RNNTc0&j`t$KLbiXJ-=#$@ji%w(>|$*&mD~ zxXDnch_Gv+$hLoPS>_#UvN@^E1^;IJL)7@k%*=M{;o&xgwbjcG4z`MkLg$UjAR+De zI5-Q)1#Y|);)$|$*4exqvO9-57rX`Qt*0hW6@!LF;y&T7uQTJ#K`JTdl>~67vD44Z z=NN5m%(qm0H44V#DeQ$<2)eV}w8ZA=%B>N+iGb$Qg;MM>=o|yLZ|gK#yL=V6$%mX? zgg?e6LvMc`MR^`rjPhB({pCCH_bf32ky6sL=E!&Q2^oZe&mH%?UL7BOo4=446;jVp zHSFW%U1wE&zJ<_7I6xy6PlfnBqyzw40GLEf9`slLdV~hl zq!|yvN~go%zYN&T_jvbd2LA~^)PMD!SN_$n{VV^E?VNx|9_`QI%6tEj2?0k=0AL3Q zFu=D!4EN1}-yL+^A02eN?~Ffx^3U_8$P)BIpD^MF z3puO=_Y|@|WIZ&OW zr6J)tqX-ly!l|9lGdm6bVN1bcCtgZ=5Nl_LMCd4S!|rYpwYYd4*7m3?n2V>G2DsI(kgO~y zlyymC#_^90?B7-jG5)m_Vh2Rs2OgjY{3he~%YRQP^sv%D#jTuH`6tm^$5zj3!7_py z8T;mF*N}?AcyLtOzo;|dk=Aw`1TREBU2f-H4A_t>7Yni4)s$$L9Df(H8&c*QkoIDi z7c^B7_%mx(+03C*uL;js83^zU=_q#*h-l?&+|^4*b(i{cgjo2x!=Zuet8VG_ju#9t zQ5Zg~J6((K5Hj9Jg{+2h1e;#-^^`85WPoF)48@;S#npZ#866t$q>xz3;z@_fi_oSf zTKdXaqBP^}GlUJT)T|8-HuOp|Cc-5E{?+B@9#mJ&ST;!ibUuh{MIFPg0-)RcEkzu~ zx;Rb;P-ugeH+twc1@mMdKuzXPz($^}$VR+4wOtR)p$O6eYd?!^Vc_jNC^B#6NPqo0 zX~ba0{5^0|MSrIP%Qjre9Z~f27)h-){j|c*Thwk*3cOrs`?=4fi?tTIu40MTLq+W8 zU#41lWb^W-cwOa%<&NNSbwUMkkm|p@oXxU2wOEXVNC7P$pghPIJ5l;Cl|p&R6DK;kPnJtXvUxC! zc=mzqG6w$GEDzoo^IFsSE1yS?u4_-<;*f3!a_2McF{Y9Ri^*{`G|?22{Eb*m%v9;s za>2X+Mz+wwt(#7;B=HE~o+#iUO=gHBQ4=RTxxMzcjkWOl1BO16bgq#}xYQ(OmceQ+ zcFZWZ%W?%Iu9(-@k(({Xy+iz`y%=*lJQT~ank}Ih zn*@+3ob4!0F{XQN)n`iWgrW~!X>){2FvRQ~?O>g{Vl!gOm?~S*+ZtcZ^6nDt>YScN z^ywKZkJuZXUKQl|GqW+T40fg+I(J?dF-OWc8qdbxdh##Pi=Lvlv(la zg(Rgd-F~h6U@UV-jm!1fp;abu>~&V`d4|J~*h~k(8Y`jFvGT~GeqaH}3K_>jFcYng zW2~aKVCN@ZspTuIAiVKh`2m3&0o0C`iC&F7LlY3c7H}?Zv5Z|Ha_O^Bg|;FRUrfk1 zuzL~YuZ%|ZC3m#47iua^L2C%+t+ma~%u98+cD>A-d$@yTv(OrA(d0T3J=7mY)UzZ^285}*!}1Jpt6KkJ~p89|@dRTVRj4eOx}s`%ef z2mO%41VthISpeOW8VcE31Ii@;t>QgJ1^^!OXC=hM`M{L%vl4o^1aJ`kANxT66jLgJ z`t8_h{4^xFtlSd3^L()h&RNG>X8YlV9r(zrV-=eWxhd6c^J}aV3Qp#Y;8f1fzZ?@2 z`{^tjbibuq_P082TlZ$NP|jh(dVWg~G0Cg66{*IRk1&aq$g&1%+K$fXA}CI2 z0^v<;c=&2a6vSvtEW%3aE1VYyGifmIVD;r86pyleU3U8shw9g_{b9y*k zRzqU3*aUWfr`ye|FavM(Q?ybm_<|my z)oQAHwDRT(a~VD#gC$$Qk!x88&KmqoHJ*)u1!LO=7dr~AJG~mtI^eD$gT)?K-`QSX zdFoD@Rt*eWRjHM9;>UEO#hY@J!!NneTA4sMt&)aeqnBxD)nM3L;je|sx)l4al}C=9 zxY1{4D9e?ofS1GnMtp{mmu5BWY`Ct)XGy*jIjn{x?FnP>Y=!Ls2Q#J%($8AR3Q!A~ z{X;EuMOubfU3cmJ{#q{-r*m2cBntzJ!GHBfEi}vEKjnw|uio>@zxK6%)&IG@1E_|6 z`L+N4YA6=Dl9=^F9_Jz5!dBguo0oVizqd%7&elv7)>m|%>{?BeEQ>E4joP7`h~+~h zhzn#~Is4Rfb+zp|OtZ=9EL<=JD|m9TTw-E!uGoP%SLY@iIeUKF?%0w58xAC83V}7E8r? z9KXnt-+gg;5Q%6Y=~Xd$rhFOoHcd4IJ%Ee3RD&4mBA{uTZb>YywoW7uoO^nXPUIQ) z_MGn~_s+;J`4sDGhAyr$wX7PKd_vDFd`WNv-~vi#z2rD}8$KC!A^1473{2udh147> zY#!ZkCT2~Bo-lf`6vRBx?(?IvMW(}Pf9$o4mUBPU!;zRUh+-)^Smi_Yqna zsfrI+lr%@zEKq_V-ZFu7DpnAGo~tGj#GZL}NY%Rw1E;kz+%zCQ$__Jt3={7Dh8NV= zTQ0JG25%wH4Z7KFMUD)7m;+?`;Z&V0USE)|+8X zj{J1_1s}|mIuY)3_u9=1Y)^>5o8gF=cRekbrBJp!ewNSpv9Owq!#7&TwMN+{51uKK z>T#HYG495k1avPns+@2$zSNkjgF-8JA%lVUqzhSrD}ZPwe`3v^xjXL`>E+jTIPEGv z{1AT;08G0M4``xC1gK2-zalS4JOBtC19+hQnE7i(q;_c}bbYp0T^J1W8z6rScV+Mo zeH5$!OsD`l@cS+#6bg*2u`8h@pwDUS{u6fupmzWM1fh_*lRM*&Lv1i9XvzS_VHG72 zMP)@5CFNhVQ@?q!A6~o1Ki!k2{zrhJ`wk<3$_`LB{bG;-#8pN{j$h2~|9furKL9&p zmnmx$Uzw47@fatyRqEE5k}B;Ue><|_XkRg(F#)IlZ8j$iv;iD-SMAfjHvbAy&8Lg8 zaQCIWKp)!VVg1gd)H$-3hNZp?d9`*cJ+GKEPTJ_Sl&W-VyqSE%SytWM{~%=LT84fx zHiY#d$;C;+Wp0VJ+t4uWpRtmU30cj67>q#}*i2ucL(Q!v0}0^Cwk{h24{?_WA{pgm zCUAlvxodgiT^lv-qYKuwg@1tV++C}Z0bvB%qShCSRFssG#k4!05T39(3v9AhZy}vl z72iJS%oX(ywcrvRDNdK^)t?#BRwU6l_*ZNF!IB7rf_ZO(Vzz|B_rL$UZ21q}J^&^6y+OmGQ2u&Z$O^!FO$bo_ z_|HHp3~WrC0Hy&priWHHKpW)O<^Rl01OO90&Xj>4*s$OMe;i;a5CCTd&_d?qwi>3y zE6trrUGW#&t@C$l2d22E#%48Y_BlGP@AlFqw(R_PRWS{rY-f%KhWUL#l!=v}OjY}j zy?5r7^6hc$;Bza*2CXBsq_?1{>I(Jge24$0eM*GFwuD{%B7}T$iuSx1qn^NlO|}4o z-#8_1mfA);_cnA1cENW&3T=6(pKE*tITwX?D!Nh^nKuUt9O|uBzA*=wNGg7wo}N>e zK)!XvYe^UkcPuXr9XR#} zaDnsi5-49Yo_lmEPoA$cs)ObZd@`6#5OJ()6X_+b8xH^}Cvi8AYIrO2X-K+{nPd|; zv?Mb^zy`gSa6K$*kbB1N!Y7rS5LKD1)W$J*Bb_J{#wq}z=<);H^%H!nq=OLnPDK5P z^8G-X#LMWoAP3GJ!tKDlz`o0%tXV>ou46vXd;9VjlABF4gf6e0^bl>rl z{Hy!;O?f5(SnYv`gpq=Q?A~7h24cUS`&kA8Zs7;IO@NT`AzT6IR~gwD0j#S`_c9L5 zk1zkNYyDG&Rw$3Pz@N%H#w>bdtDqlHT`U)48m1jItuzluIX!uJSCJ^>ka|_8?G@fG zxXdW;G~y5MHJDCRDzs8ouJgj?1Uj5RgQ+EVC*f*S3ugB&<#VNdO|4S4Y{)@ebIYzA z6GNyJhK^dN(uZ@9X61~O%}acl!o zKYTsE{w9~)qi=~V9}E5LTm3-=|Fbh|4X|&YSo%okoJerAnNg6nJAuj08RkK>vzNxj zT55N2UcHl$n99}!ISF4Qja@U4lWVVxKybn+8gU4<@Bo$=s183PaFS9fAO7TrZOUP9W4G9d13-<-{axc({`IS`el5De2JZBK(g|MB^Eu_YJ zRoMvUz%lE(galNdYA^2%BBpl)cRR`lnV4QV1okb0R|po126X+b5DM;3Kaz2rUP~2* zX=Qv~h39-B>Et#3JmdqvES#3&Der}1z|nSI**6ImY|0aXP%f1H&4s&EJl&@LP5k3V zdA;|HqmJtbtvP$luuiRngq@~Vhfts%Dt^AAvPp%f_!OS zY?GVg)2=d*W8FObLeM51oHA6L+^U+EZS26BWkQWD z-I;420U-V?LjDGbcm#kD#(F<`2obUW+Ot*g3%T;gXf~HWHq1lU556Ln>AXP7OV|ZV zAD@D9P)GTIta%kG_KIqJ6v%L^gTQPC3cb+WE`nq^8kBepax#!HD z{0VE@-VUk^V~^hH%uz_jiug)*7yAvwlWXVaCX8ZKGcIYkTLBdZX^jdOp(JVMi3U9D zDt>H}${=?~joP2aNNlG>f+RJK55nN)Q40zCG4si4;je%Xb3dN~eRPU`MJ*m}Jd4t~ zLWbeJ(;A|QL8z<{FR26yyQ|&Dyl3!Dq=#N}PCdtubK`pQ76r9nk2gV5&}pp0^||9< zjA*M0$HDdHOQ!8)bui)uj*fw5>t1Fq3m9|$oR8ZMF_DHZNLs9UlClz)B&17n8>w+J z*!DGWXd%GB5E*S<<7mWfW;SoJ;Ed{;5pC+Tmg1AC&d>_oHW zLs2-LDj|EB?>9?^h-+vN-pg!i$@3(zyL_Q&5Is8T5R%@qur3FS@hI=Ix zFi<#@N6!K@kss6_ev}K0fV7MSzyiev@V`H`7alJE9ZUR^+wDYNEY`ghn4dob0LRYD zmU-OLnx_tZJ#v_-=b{KTD`;)M#80!?S(pt)E93=hnu6s#A+^<}3}{bQn-Zr&dAr>D zQf4t2P6;y&pEOr4<}f!Hw*QpM_w#G@eR7%Jk|NKv>1&P!VKub%%gB$@;w$jz6nOX_ z1B1!xn@Us!20)3WXkSJ}Y=l*s^laVHZ1I%Me6HzOHp>XR^Z-&_I8{lsx@j_+H;7n- zb!FNy7$8qee^+7RMk!D=CSqT{ z^gF{BeFLyzpLafIa;<6BGgsyORtw?zbi#;jx$9!fw?-;ES}D(k2iK%^lnLkglr35h~_Av`Oa)9PzBwK zWuI2nC+hhYnof3Do=xTYpSkhVkLQ1#&K=VML%;wa3lG<;9j=7~m<2a!)h|>d4fvY` z-(0(E z`fegwiM#_ij*FFFVzI_2SX*#)RS&-ldlA2x-iydp=+W@%&`tFED?9yqc+kn>E!gxc zvmAQT73f5W@x;Dm(xe@)^LX%gdyZSQBhH+YUr1-CWOapNvW8JP+f?ktQ`H!2GxjA@ zK$92<^dRZ_}F8RX42h@>Cy4 zM{e&xww1D`sk~^$ET}8esSUKUG=DalJ$KY$ZrQkO4Sjl4BJTp@;q8I(N+n4!l@WSg z4=Dm;=wrV#;1z(wT>v=T?jOw_;BVFpU-SADig22Z z5LiL~LM(9ac>jm{J&Hd?H z7jx6Ryj-yz-bE*jh2~q?bn3;deK}@eH0zrOaTJPa5lhU|e2a1zg6es`Mi4!=rqGRK zDhj30{{mH+znS^g$139vJrlDj%u?aQZDs;2`~_H6R4JWV?;@d9 zi9Oa)=!B_5>0q|x)=Ad9$1+kcexb7mZgH8ZVj!Md!7b4=VxL#_7H-%a5eM;vjNFph z`oc!++EHlT2O;02ipq;A$WSG$6TUrOnE?x>Y}A{*a7UZVidAb?U=n$zSORu&$}b!` z-XOZpWQZ(uFsPx5|1~G#G)UGCvUH-?i6wZk4tn4~gXzj9O6GuOz^HW1p61=cx?>p$ zH!V;btBMn}#FT_Owr?EUhC&>|3As0}3`TSBt`PZfn}F`KVqxLa)GulwS@HyG%0+zN zoDAu!)CK8;u%&Ac$;6&tgW<=Ik&8;o`&Tcty48O*MO*Gw3&KYuS)XogVMV#Wt$?2X6gM+EH)HqE{zGBV)Wd2GlGS0Mi3x$#}zvFmsI1HluvzQweQ zjUX3i$!T@0GaaUHLL`eRvq+-vv?wf>-^iUn?gP<{JrC(u6iD1Y6`R~Q5>WAqdJd8} zZ;<(+abJVdDJVa^KDGxAa`7yT&53D(vi;2)*rg2V4%~^atLAlLqW<)`0i~mc!gife zceKn+VBh`Hd+i+*Hq z)fZdz?I92#eHW8>Xq_?}>N6R;eD8*6fc=UEU}AvosSqF*(0q>tpwNM*kMr*P28jHl zyjxi07e@pfoG|S}`-4!-+Ccp0i9S#`BEZuRJ^hFI_xHXj^F31p6QPLe{WP5O0T;{( zpk)QnMX)lk+^6AyjQscIf5VFZbo4g$D2TxBDv0pLJz-vE3DKWNK zEq7ZVeCkT3lj7Q@eD7*f;leWi=eU*>&?_eVd+PbyLyabcOB~#sSvNQ2*2U4 z7QL~9v5T#t{=MF$mARu6gJ_O$j^K-DKLDlonE1yx0HCU;|8cE*e>PV4VdLmB>l*6o zGU*z-0FK2oa6ahXJaV{z4f4={W!dx8;CY=8dVr1O3`t*e%X5vdK)w@lPPMRfG}cWD zz12RXf>X;d{R#UPLZJZdHSIyQR2v~BnZ!r|n=8wv(V}7%Qjep@AZONx@<@B4V-3s7 zMIKp|%IY9v)UZa^erm?np*-s$Tk!-YQQI%}kvRoHSnQC;pd!#TFPyy=b^RJ}sF_i@w!gpfU1p+glGJYjBcxlr9gg zxoCrY-L_+{0hDMv8~y01@V- z5paNk86fliQ!wj~0ptIY`}_~4i=bkj^v=BjXu&tmYcJ8qN)1xa_!MCwJ_!K%CNIN~ z^56l3?el()f=-58a$dW>aSf+F1J#pai$EtqFNd~2l{}@Cue}Y$wc)p-r-y#7nXd`U zoVwD%8+K*!^q_haV(X9TqO*M=mN-^(X6Ckl*kVGbxNGXD``^67-`b0Rn=Uc}9e`?j zl-0YzIMjzO-qnesLc?6=Y24O%wjpval zWcG*^6{|7yMoE{gx5Z@lQ@WieTw+aQ8|eXV+?bm`xx;(Pg|0Ro$2sv3A%9L6@3;GJ zCM8jwUq%bs3y6(f0HZDcZnS@$F5X8+|G-Cv3K)j}f2utG)Nw;W60?06K;ECp`Rn)* z^wIe7do2C!*a!ujod9ycfRXxd0wzvCWPLOT2k-^{)^+>eahLxTh1={6ppLGevs~cx zV&+}=NvLlT2ci^o5eflw2OVt$OJq}Qp9kT{=G4yGrSvcXSAp|E%I=?D%h5k)ApTN{ zrE0n22l@2`-Pw;*!E^nJ^{ta^aZ~`x+FSl&(7@~5)^Icn-v!u?w^*p)ecK&HUu z*gx`Vu}~7pVi}iz8Uj@pZt<2j{@bi91{lsyN1=WWQx$UU= zZK^giLV>-r$F63Tto1V6o_Fy{v}WnQMh(c777Y2sl=AJt zc?-CJ4Pkr24C2BcDcnPZN}bhqAHPa|ZVIY@HFj~$yr1qj9qj_qJet1@6 zf5<7)v6e${2Hf)!#JS;He$`~f`Q!phztR(*aM6)ufCxMdQfcfP3cst(Y>h4U?9#PG ztD0bpOx|fyIro>W6S`nKeu1TFAhdN)PN!mfz#O0LIgsTPXYXc}njxpEb;lrcLBDPh z{sw`@-k!e3l()yRIc0S6)Cct|$aePN9i$n?EO9Ictw+>|@{#$fE_0s;uP4lEpRX?G z`igT?mkTEIr>BSX(&ZObUp~ndb*4)yLv;Cik3%7@;NJRRG?eL_kD*r!LBq+M7h2Y) o1{wExgs!Xzw`yH%${oltu.version} - io.swagger.parser.v3 + org.wso2.orbit.io.swagger.v3 swagger-parser - ${swagger.parser.version} + ${swagger.parser.v3.version} io.jsonwebtoken @@ -597,8 +597,8 @@ 1.2.5 9.0.11 2.0.1.Final - 2.0.24 - 0.12.6 + 2.1.22.wso2v1 + 0.9.1 7.0.75 [7.0.75, 8.0.0) @@ -617,6 +617,7 @@ [1.7.0, 4.0.0) [1.2.0, 4.0.0) + [4.9.26, 4.9.28) [2.6.0, 3.0.0) [2.9,3) [4.4.0, 5.0.0) @@ -624,5 +625,6 @@ [7.9.0, 10.0) [2.15.1, 2.16.0) [3.0.0, 4.0.0) + [0.9.1, 0.9.2) From e884de12de7084d742303004208ebbaee405dc3e Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 15 Oct 2024 14:42:35 +0530 Subject: [PATCH 04/15] Fixing test failures in consent implementation --- .../common/ConsentExtensionUtils.java | 24 +++++++++++++++ .../impl/DefaultConsentValidator.java | 28 ++--------------- .../validate/DefaultConsentValidatorTest.java | 30 +++++++++++++++---- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java index c92e2be6..14086134 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java @@ -21,6 +21,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONObject; +import org.wso2.carbon.identity.oauth2.util.OAuth2Util; +import org.wso2.carbon.user.api.UserStoreException; import org.wso2.financial.services.accelerator.common.exception.ConsentManagementException; import org.wso2.financial.services.accelerator.common.exception.ConsentManagementRuntimeException; import org.wso2.financial.services.accelerator.common.util.Generated; @@ -137,4 +139,26 @@ public static T getClassInstanceFromFQN(String classpath, Class classNam throw new ConsentManagementRuntimeException("Defined class" + classpath + "cannot be instantiated.", e); } } + + /** + * Method to resolve username from user ID. + * + * @param userID User ID + * @return Username + */ + @Generated(message = "Ignoring because OAuth2Util cannot be mocked with no constructors") + public static String resolveUsernameFromUserId(String userID) { + String username = null; + try { + if (userID.contains(ConsentExtensionConstants.TENANT_DOMAIN)) { + username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, + userID.split("@")[0]); + } else { + username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, userID); + } + } catch (UserStoreException e) { + return null; + } + return username; + } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java index 27008450..aaa3efc3 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java @@ -26,12 +26,11 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.wso2.carbon.identity.oauth2.util.OAuth2Util; -import org.wso2.carbon.user.api.UserStoreException; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.AuthorizationResource; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.DetailedConsentResource; import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ConsentException; import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ConsentExtensionConstants; +import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ConsentExtensionUtils; import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ResponseStatus; import org.wso2.financial.services.accelerator.consent.mgt.extensions.validate.ConsentValidator; import org.wso2.financial.services.accelerator.consent.mgt.extensions.validate.model.ConsentValidateData; @@ -83,12 +82,12 @@ public void validate(ConsentValidateData consentValidateData, ConsentValidationR } //User Validation - String userIdFromToken = resolveUsernameFromUserId(consentValidateData.getUserId()); + String userIdFromToken = ConsentExtensionUtils.resolveUsernameFromUserId(consentValidateData.getUserId()); boolean userIdMatching = false; ArrayList authResources = consentValidateData.getComprehensiveConsent() .getAuthorizationResources(); for (AuthorizationResource resource : authResources) { - if (userIdFromToken.contains(resource.getUserID())) { + if (userIdFromToken.equals(resource.getUserID())) { userIdMatching = true; break; } @@ -398,25 +397,4 @@ public static boolean isJsonObjectsSimilar(JSONObject object1, JSONObject object return false; } } - - /** - * Method to resolve username from user ID. - * - * @param userID User ID - * @return Username - */ - private String resolveUsernameFromUserId(String userID) { - String username = null; - try { - if (userID.contains(ConsentExtensionConstants.TENANT_DOMAIN)) { - username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, - userID.split("@")[0]); - } else { - username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, userID); - } - } catch (UserStoreException e) { - return null; - } - return username; - } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java index bab1a70d..8012118c 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java @@ -20,11 +20,15 @@ import org.apache.http.HttpStatus; import org.json.JSONObject; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.testng.Assert; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.AuthorizationResource; import org.wso2.financial.services.accelerator.consent.mgt.dao.models.DetailedConsentResource; +import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ConsentExtensionUtils; import org.wso2.financial.services.accelerator.consent.mgt.extensions.common.ResponseStatus; import org.wso2.financial.services.accelerator.consent.mgt.extensions.util.TestConstants; import org.wso2.financial.services.accelerator.consent.mgt.extensions.util.TestUtil; @@ -33,7 +37,9 @@ import org.wso2.financial.services.accelerator.consent.mgt.extensions.validate.model.ConsentValidationResult; import java.util.ArrayList; +import java.util.List; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -46,11 +52,21 @@ public class DefaultConsentValidatorTest { DefaultConsentValidator validator = new DefaultConsentValidator(); ConsentValidateData consentValidateDataMock; ConsentValidationResult consentValidationResultMock; + MockedStatic extensionsUtilMockedStatic; @BeforeClass public void beforeTest() { consentValidateDataMock = mock(ConsentValidateData.class); consentValidationResultMock = spy(ConsentValidationResult.class); + + extensionsUtilMockedStatic = Mockito.mockStatic(ConsentExtensionUtils.class); + extensionsUtilMockedStatic.when(() -> ConsentExtensionUtils.resolveUsernameFromUserId(anyString())) + .thenReturn(TestConstants.SAMPLE_USER_ID); + } + + @AfterClass + public void afterTest() { + extensionsUtilMockedStatic.close(); } @Test @@ -98,12 +114,16 @@ public void testValidateWithStringReceipt() { public void testValidateWithUserIdMismatch() { DetailedConsentResource consentResource = mock(DetailedConsentResource.class); doReturn(TestConstants.VALID_INITIATION).when(consentResource).getReceipt(); - ArrayList authResources = TestUtil.getSampleAuthorizationResourceArray( - TestConstants.SAMPLE_CONSENT_ID, TestConstants.SAMPLE_AUTH_ID); + AuthorizationResource authorizationResource = new AuthorizationResource(TestConstants.SAMPLE_CONSENT_ID, + "admin", TestConstants.SAMPLE_AUTHORIZATION_STATUS, + TestConstants.SAMPLE_AUTH_TYPE, System.currentTimeMillis() / 1000); + authorizationResource.setAuthorizationID(TestConstants.SAMPLE_AUTH_ID); + + ArrayList authResources = new ArrayList<>(List.of(authorizationResource)); doReturn(authResources).when(consentResource).getAuthorizationResources(); doReturn(consentResource).when(consentValidateDataMock).getComprehensiveConsent(); - doReturn("admin").when(consentValidateDataMock).getUserId(); + doReturn("admin@wso2.com").when(consentValidateDataMock).getUserId(); validator.validate(consentValidateDataMock, consentValidationResultMock); @@ -242,10 +262,10 @@ public void testValidateWithInvalidPermissionsForAccounts() { validator.validate(consentValidateDataMock, consentValidationResultMock); Assert.assertFalse(consentValidationResultMock.isValid()); - Assert.assertEquals(consentValidationResultMock.getErrorCode(), ResponseStatus.UNAUTHORIZED.getReasonPhrase()); + Assert.assertEquals(consentValidationResultMock.getErrorCode(), ResponseStatus.FORBIDDEN.getReasonPhrase()); Assert.assertEquals(consentValidationResultMock.getErrorMessage(), "Permission mismatch. " + "Consent does not contain necessary permissions"); - Assert.assertEquals(consentValidationResultMock.getHttpCode(), HttpStatus.SC_UNAUTHORIZED); + Assert.assertEquals(consentValidationResultMock.getHttpCode(), HttpStatus.SC_FORBIDDEN); } @Test From fd1ca2620c4d3bc4052570c2fd155e9e91901acc Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 15 Oct 2024 14:42:50 +0530 Subject: [PATCH 05/15] update Configurations --- .../resources/wso2am-4.4.0-deployment.toml | 52 ++++++++++++------- .../resources/wso2is-7.0.0-deployment.toml | 3 ++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index 85e8d087..208386f7 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -6,8 +6,8 @@ base_path = "${carbon.protocol}://${carbon.host}:${carbon.management.port}" server_role = "default" [super_admin] -username = "admin@wso2.com" -password = "wso2123" +username = "admin" +password = "admin" create_admin_account = true [realm_manager] @@ -17,21 +17,21 @@ data_source= "WSO2UM_DB" type = "database_unique_id" class = "org.wso2.carbon.user.core.jdbc.UniqueIDJDBCUserStoreManager" -[user_store.properties] -UsernameJavaRegEx = "a-zA-Z0-9@._-{3,30}$" -UsernameJavaScriptRegEx = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$" -SCIMEnabled = false -IsBulkImportSupported = false -LeadingOrTrailingSpaceAllowedInUserName = false -UsernameWithEmailJavaScriptRegEx = "^[\\S]{3,30}$" +#[user_store.properties] +#UsernameJavaRegEx = "a-zA-Z0-9@._-{3,30}$" +#UsernameJavaScriptRegEx = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$" +#SCIMEnabled = false +#IsBulkImportSupported = false +#LeadingOrTrailingSpaceAllowedInUserName = false +#UsernameWithEmailJavaScriptRegEx = "^[\\S]{3,30}$" -[authorization_manager] -class = "org.wso2.carbon.user.core.authorization.JDBCAuthorizationManager" +#[authorization_manager] +#lass = "org.wso2.carbon.user.core.authorization.JDBCAuthorizationManager" -[authorization_manager.properties] -AdminRoleManagementPermissions = "/permission" -AuthorizationCacheEnabled = true -GetAllRolesOfUserEnabled = false +#[authorization_manager.properties] +#AdminRoleManagementPermissions = "/permission" +#AuthorizationCacheEnabled = true +#GetAllRolesOfUserEnabled = false #================configs related to master-datasources.xml============= # for api manager data @@ -194,8 +194,8 @@ token = "" [apim.key_manager] enable_lightweight_apikey_generation = true #service_url = "https://localhost:${mgt.transport.https.port}/services/" -#username = "$ref{super_admin.username}" -#password = "$ref{super_admin.password}" +username = "admin@wso2.com" +password = "wso2123" #pool.init_idle_capacity = 50 #pool.max_idle = 100 #key_validation_handler_type = "default" @@ -347,7 +347,6 @@ HostnameVerifier = "AllowAll" HttpsProtocols = "TLSv1.2" PreferredCiphers = "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" - [[event_handler]] name="userPostSelfRegistration" subscriptions=["POST_ADD_USER"] @@ -390,6 +389,20 @@ iat_validity_period = "1h" #service_username = "admin" #service_password = "admin" +#[apimgt.mutual_ssl] +#enable_certificate_chain_validation = true + +[transport.passthru_https.listener.cert_revocation_validation] +enable = true +allow_cert_expiry_validation = true +allow_full_cert_chain_validation = false +cache_delay = 1000 +cache_size = 1024 + +[[apim.extension.listener]] +class = "org.wso2.financial.services.accelerator.gateway.executor.core.FSExtensionListenerImpl" +type = "AUTHENTICATION" + #================configs related to financial-services.xml============= [financial_services] publisher_url="https://localhost:9443" @@ -408,3 +421,6 @@ cache_modified_expiry_minutes=60 [financial_services.http_connection_pool] max_connections = 2000 max_connections_per_route = 1500 + +[financial_services.identity] +consent_id_claim_name = "consent_id" diff --git a/financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml b/financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml index 77bf4c82..a5944ecf 100644 --- a/financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-is/repository/resources/wso2is-7.0.0-deployment.toml @@ -283,6 +283,9 @@ read_timeout = 5000 [application_mgt] enable_role_validation = true +[transport.https.properties] +maxHttpHeaderSize = "65536" + #================configs related to financial-services.xml============= [[financial_services.jdbc_persistence_manager]] data_source.name = "WSO2FS_DB" From fea8fea10f80390ca3336408b338bcbf4f805096 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Mon, 21 Oct 2024 14:50:23 +0530 Subject: [PATCH 06/15] Adding gateway fixes --- .../apis/Accounts/account-info-swagger.yaml | 21 ++++++++++++--- .../resources/wso2am-4.4.0-deployment.toml | 27 ++++++++++++++----- .../common/ConsentExtensionUtils.java | 14 +++++++++- .../impl/DefaultConsentManageHandler.java | 7 ++--- .../core/FSExtensionListenerImpl.java | 8 +++--- 5 files changed, 59 insertions(+), 18 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml index 45f61d34..3bb2e50a 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml @@ -1411,14 +1411,29 @@ definitions: required: - Self Meta: - title: MetaData type: object - description: Meta Data relevant to the payload properties: TotalPages: type: integer format: int32 - additionalProperties: false + FirstAvailableDateTime: + $ref: '#/definitions/ISODateTime' + LastAvailableDateTime: + $ref: '#/definitions/ISODateTime' + title: MetaData + description: Meta Data relevant to the payload + additionalProperties: { } + ISODateTime: + type: string + format: date-time + description: >- + All dates in the JSON payloads are represented in ISO 8601 date-time + format. + + All date-time fields in responses must include the timezone. An example is + below: + + 2017-04-05T10:43:07+00:00 OBError1: type: object properties: diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index 208386f7..64b16200 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -6,10 +6,13 @@ base_path = "${carbon.protocol}://${carbon.host}:${carbon.management.port}" server_role = "default" [super_admin] -username = "admin" -password = "admin" +username = "admin@wso2.com" +password = "wso2123" create_admin_account = true +[tenant_mgt] +enable_email_domain = true + [realm_manager] data_source= "WSO2UM_DB" @@ -106,9 +109,6 @@ pool_options.validationQuery="SELECT 1" pool_options.validationInterval="30000" pool_options.defaultAutoCommit=true -[tenant_mgt] -enable_email_domain = true - [keystore.tls] file_name = "wso2carbon.jks" type = "JKS" @@ -253,18 +253,24 @@ allow_methods = ["GET","PUT","POST","DELETE","PATCH","OPTIONS"] allow_headers = ["authorization","Access-Control-Allow-Origin","Content-Type","SOAPAction","apikey","Internal-Key"] allow_credentials = false -#[apim.throttling] +[apim.throttling] +username = "$ref{super_admin.username}@carbon.super" #enable_data_publishing = true #enable_policy_deploy = true #enable_blacklist_condition = true #enable_persistence = true #throttle_decision_endpoints = ["tcp://localhost:5672","tcp://localhost:5672"] +[apim.throttling.policy_deploy] +username = "$ref{super_admin.username}@carbon.super" + #[apim.throttling.blacklist_condition] #start_delay = "5m" #period = "1h" -#[apim.throttling.jms] +[apim.throttling.jms] +password = "$ref{super_admin.password}" +username = "admin!wso2.com!carbon.super" #start_delay = "5m" #[apim.throttling.event_sync] @@ -410,6 +416,13 @@ publisher_url="https://localhost:9443" [financial_services.gateway] request_router="org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter" +#============executors========================= +[[financial_services.gateway.gateway_executors.type]] +name = "Default" +[[financial_services.gateway.gateway_executors.type.executors]] +name = "org.wso2.financial.services.accelerator.gateway.executor.impl.consent.ConsentEnforcementExecutor" +priority = 1 + [financial_services.gateway.consent.validation] endpoint="https://IS_HOSTNAME:9446/api/fs/consent/validate" diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java index 14086134..afa82844 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java @@ -148,11 +148,18 @@ public static T getClassInstanceFromFQN(String classpath, Class classNam */ @Generated(message = "Ignoring because OAuth2Util cannot be mocked with no constructors") public static String resolveUsernameFromUserId(String userID) { + + if (!startsWithUUID(userID)) { + // If the user ID is not starting with a UUID that means request has sent the username, + // return the same user ID as the username. + return userID; + } + String username = null; try { if (userID.contains(ConsentExtensionConstants.TENANT_DOMAIN)) { username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, - userID.split("@")[0]); + userID.split("@" + ConsentExtensionConstants.TENANT_DOMAIN)[0]); } else { username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, userID); } @@ -161,4 +168,9 @@ public static String resolveUsernameFromUserId(String userID) { } return username; } + + public static boolean startsWithUUID(String input) { + Pattern uuidPattern = Pattern.compile("^" + ConsentExtensionConstants.UUID_REGEX + ".*$"); + return uuidPattern.matcher(input).matches(); + } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java index 0ac208ed..79036753 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/impl/DefaultConsentManageHandler.java @@ -197,10 +197,11 @@ public void handleDelete(ConsentManageData consentManageData) throws ConsentExce ConsentManageConstants.NO_CONSENT_FOR_CLIENT_ERROR); } - if (ConsentExtensionConstants.REVOKED_STATUS.equals(consentResource.getCurrentStatus())) { - log.error("Consent already in revoked state"); + if (ConsentExtensionConstants.REVOKED_STATUS.equals(consentResource.getCurrentStatus()) || + ConsentExtensionConstants.REJECTED_STATUS.equals(consentResource.getCurrentStatus())) { + log.error("Consent is already in revoked or rejected state"); throw new ConsentException(ResponseStatus.BAD_REQUEST, - "Consent already in revoked state"); + "Consent is already in revoked or rejected state"); } //Revoke tokens related to the consent if the flag 'shouldRevokeTokens' is true. diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java index 4c1b03cc..01de306e 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java @@ -140,8 +140,8 @@ protected ExtensionResponseDTO getResponseDTOForRequest(FSAPIRequestContext fsap ExtensionResponseDTO extensionResponseDTO = new ExtensionResponseDTO(); if (fsapiRequestContext.isError()) { int statusCode = (!fsapiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? - HttpStatus.SC_INTERNAL_SERVER_ERROR : - (int) (fsapiRequestContext.getContextProperty(GatewayConstants.ERROR_STATUS_PROP)); + HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsapiRequestContext + .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); extensionResponseDTO.setStatusCode(statusCode); extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); } else if (fsapiRequestContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && @@ -181,8 +181,8 @@ protected ExtensionResponseDTO getResponseDTOForResponse(FSAPIResponseContext fs ExtensionResponseDTO extensionResponseDTO = new ExtensionResponseDTO(); if (fsapiResponseContext.isError()) { int statusCode = (!fsapiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? - HttpStatus.SC_INTERNAL_SERVER_ERROR : - (int) (fsapiResponseContext.getContextProperty(GatewayConstants.ERROR_STATUS_PROP)); + HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsapiResponseContext + .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); extensionResponseDTO.setStatusCode(statusCode); extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); } else if (fsapiResponseContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && From efa77d1fcb556022b404c1d6f1381a845de217e0 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 22 Oct 2024 10:23:11 +0530 Subject: [PATCH 07/15] Fixed issue in toml --- .../fs-apim/repository/resources/wso2am-4.4.0-deployment.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index 64b16200..07ec6df4 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -194,8 +194,8 @@ token = "" [apim.key_manager] enable_lightweight_apikey_generation = true #service_url = "https://localhost:${mgt.transport.https.port}/services/" -username = "admin@wso2.com" -password = "wso2123" +username = "$ref{super_admin.username}" +password = "$ref{super_admin.password}" #pool.init_idle_capacity = 50 #pool.max_idle = 100 #key_validation_handler_type = "default" From 8edaedf31f83750c18ff84f011f5f64d813c55cc Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Thu, 24 Oct 2024 13:47:08 +0530 Subject: [PATCH 08/15] Adding Gateway implementation --- .../apis/Accounts/account-info-swagger.yaml | 71 +++---- .../resources/wso2am-4.4.0-deployment.toml | 3 + .../impl/DefaultConsentValidator.java | 6 +- .../validate/DefaultConsentValidatorTest.java | 4 +- .../consent/ConsentEnforcementExecutor.java | 1 - .../DefaultErrorHandlingExecutor.java | 181 ++++++++++++++++++ .../executor/core/FSExtensionImplTest.java | 2 +- ...ockOBExecutor.java => MockFSExecutor.java} | 4 +- ...va => ConsentEnforcementExecutorTest.java} | 2 +- .../DefaultErrorHandlingExecutorTest.java | 102 ++++++++++ .../src/test/resources/testng.xml | 3 +- 11 files changed, 322 insertions(+), 57 deletions(-) create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java rename financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/{MockOBExecutor.java => MockFSExecutor.java} (94%) rename financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/{TestEnforcementExecutor.java => ConsentEnforcementExecutorTest.java} (99%) create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutorTest.java diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml index 3bb2e50a..28ab170f 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/account-info-swagger.yaml @@ -1424,71 +1424,50 @@ definitions: description: Meta Data relevant to the payload additionalProperties: { } ISODateTime: - type: string - format: date-time - description: >- - All dates in the JSON payloads are represented in ISO 8601 date-time - format. - - All date-time fields in responses must include the timezone. An example is - below: - - 2017-04-05T10:43:07+00:00 + type: string + format: date-time + description: >- + All dates in the JSON payloads are represented in ISO 8601 date-time + format. + + All date-time fields in responses must include the timezone. An example is + below: + + 2017-04-05T10:43:07+00:00 OBError1: type: object properties: - ErrorCode: - description: 'Low level textual error code, e.g., UK.OBIE.Field.Missing' + code: + description: 'error code' type: string - Message: + message: description: >- A description of the error that occurred. e.g., 'A mandatory field isn't supplied' or 'RequestedExecutionDateTime must be in future' - - OBIE doesn't standardise this field type: string minLength: 1 maxLength: 500 + description: + description: >- + A description of the error that occurred. e.g., 'A mandatory field + isn't supplied' or 'RequestedExecutionDateTime must be in future' + type: string + minLength: 1 + maxLength: 1000 required: - - ErrorCode - - Message + - code + - message + - description additionalProperties: false minProperties: 1 OBErrorResponse1: - description: >- - An array of detail error codes, and messages, and URLs to documentation to - help remediation. type: object properties: - Code: - description: 'High level textual error code, to help categorize the errors.' - type: string - minLength: 1 - maxLength: 40 - Id: - description: >- - A unique reference for the error instance, for audit purposes, in case - of unknown/unclassified errors. - type: string - minLength: 1 - maxLength: 40 - Message: - description: >- - Brief Error message, e.g., 'There is something wrong with the request - parameters provided' - type: string - minLength: 1 - maxLength: 500 - Errors: + errors: + type: array items: $ref: '#/definitions/OBError1' - type: array minItems: 1 - required: - - Code - - Message - - Errors - additionalProperties: false x-wso2-security: apim: x-wso2-scopes: diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index 07ec6df4..b396c89b 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -422,6 +422,9 @@ name = "Default" [[financial_services.gateway.gateway_executors.type.executors]] name = "org.wso2.financial.services.accelerator.gateway.executor.impl.consent.ConsentEnforcementExecutor" priority = 1 +[[financial_services.gateway.gateway_executors.type.executors]] +name = "org.wso2.financial.services.accelerator.gateway.executor.impl.error.handling.DefaultErrorHandlingExecutor" +priority = 1000 [financial_services.gateway.consent.validation] endpoint="https://IS_HOSTNAME:9446/api/fs/consent/validate" diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java index aaa3efc3..d4e011a0 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/impl/DefaultConsentValidator.java @@ -166,7 +166,7 @@ private void validateAccountSubmission(ConsentValidateData consentValidateData, consentValidationResult.setValid(false); consentValidationResult.setErrorMessage(PERMISSION_MISMATCH_ERROR); consentValidationResult.setErrorCode(ResponseStatus.FORBIDDEN.getReasonPhrase()); - consentValidationResult.setHttpCode(403); + consentValidationResult.setHttpCode(HttpStatus.SC_FORBIDDEN); return; } @@ -185,8 +185,8 @@ private void validateAccountSubmission(ConsentValidateData consentValidateData, log.error(CONSENT_EXPIRED_ERROR); consentValidationResult.setValid(false); consentValidationResult.setErrorMessage(CONSENT_EXPIRED_ERROR); - consentValidationResult.setErrorCode(ResponseStatus.UNAUTHORIZED.getReasonPhrase()); - consentValidationResult.setHttpCode(HttpStatus.SC_UNAUTHORIZED); + consentValidationResult.setErrorCode(ResponseStatus.BAD_REQUEST.getReasonPhrase()); + consentValidationResult.setHttpCode(HttpStatus.SC_BAD_REQUEST); return; } consentValidationResult.setValid(true); diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java index 8012118c..c6194e24 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/test/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/validate/DefaultConsentValidatorTest.java @@ -312,9 +312,9 @@ public void testValidateWithExpiredInitiationForAccounts() { validator.validate(consentValidateDataMock, consentValidationResultMock); Assert.assertFalse(consentValidationResultMock.isValid()); - Assert.assertEquals(consentValidationResultMock.getErrorCode(), ResponseStatus.UNAUTHORIZED.getReasonPhrase()); + Assert.assertEquals(consentValidationResultMock.getErrorCode(), ResponseStatus.BAD_REQUEST.getReasonPhrase()); Assert.assertEquals(consentValidationResultMock.getErrorMessage(), "Provided consent is expired"); - Assert.assertEquals(consentValidationResultMock.getHttpCode(), HttpStatus.SC_UNAUTHORIZED); + Assert.assertEquals(consentValidationResultMock.getHttpCode(), HttpStatus.SC_BAD_REQUEST); } @Test diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java index f5a69397..787de8c9 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java @@ -140,7 +140,6 @@ public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { String errorCode = jsonResponse.get(ERROR_CODE).toString(); String errorMessage = jsonResponse.get(ERROR_MESSAGE).toString(); String httpCode = jsonResponse.get(HTTP_CODE).toString(); - fsapiRequestContext.setError(true); handleError(fsapiRequestContext, errorCode, errorMessage, httpCode); return; } else if (!jsonResponse.isNull(MODIFIED_PAYLOAD)) { diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java new file mode 100644 index 00000000..aca18060 --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.impl.error.handling; + +import org.apache.http.HttpStatus; +import org.json.JSONArray; +import org.json.JSONObject; +import org.wso2.financial.services.accelerator.gateway.executor.core.FinancialServicesGatewayExecutor; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSExecutorError; +import org.wso2.financial.services.accelerator.gateway.util.GatewayConstants; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Default Executor to handle gateway errors. + */ +public class DefaultErrorHandlingExecutor implements FinancialServicesGatewayExecutor { + + private static final String ERRORS_TAG = "errors"; + + /** + * Method to handle pre request. + * + * @param fsapiRequestContext FS request context object + */ + @Override + public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { + + handleRequestError(fsapiRequestContext); + + } + + /** + * Method to handle post request. + * + * @param fsapiRequestContext FS request context object + */ + @Override + public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { + + handleRequestError(fsapiRequestContext); + } + + /** + * Method to handle pre response. + * + * @param fsapiResponseContext FS response context object + */ + @Override + public void preProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + handleResponseError(fsapiResponseContext); + } + + /** + * Method to handle post response. + * + * @param fsapiResponseContext FS response context object + */ + @Override + public void postProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + handleResponseError(fsapiResponseContext); + } + + private void handleRequestError(FSAPIRequestContext fsapiRequestContext) { + + if (!fsapiRequestContext.isError()) { + return; + } + JSONObject payload = new JSONObject(); + ArrayList errors = fsapiRequestContext.getErrors(); + JSONArray errorList = getErrorJSON(errors); + HashSet statusCodes = new HashSet<>(); + + for (FSExecutorError error : errors) { + statusCodes.add(error.getHttpStatusCode()); + } + + payload.put(ERRORS_TAG, errorList); + if (errorList.length() != 0) { + fsapiRequestContext.setModifiedPayload(payload.toString()); + Map addedHeaders = fsapiRequestContext.getAddedHeaders(); + addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); + fsapiRequestContext.setAddedHeaders(addedHeaders); + } + int statusCode; + if (fsapiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { + statusCode = Integer.parseInt(fsapiRequestContext + .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); + } else if (isAnyClientErrors(statusCodes)) { + statusCode = HttpStatus.SC_BAD_REQUEST; + } else { + statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; + } + fsapiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, + String.valueOf(statusCode)); + } + + private void handleResponseError(FSAPIResponseContext fsapiResponseContext) { + + if (!fsapiResponseContext.isError()) { + return; + } + JSONObject payload = new JSONObject(); + ArrayList errors = fsapiResponseContext.getErrors(); + JSONArray errorList = getErrorJSON(errors); + HashSet statusCodes = new HashSet<>(); + + for (FSExecutorError error : errors) { + statusCodes.add(error.getHttpStatusCode()); + } + + payload.put(ERRORS_TAG, errorList); + fsapiResponseContext.setModifiedPayload(payload.toString()); + Map addedHeaders = fsapiResponseContext.getAddedHeaders(); + addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); + fsapiResponseContext.setAddedHeaders(addedHeaders); + int statusCode; + if (fsapiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { + statusCode = Integer.parseInt(fsapiResponseContext + .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); + } else if (isAnyClientErrors(statusCodes)) { + statusCode = HttpStatus.SC_BAD_REQUEST; + } else { + statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; + } + fsapiResponseContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, + String.valueOf(statusCode)); + } + + private JSONArray getErrorJSON(List errors) { + + JSONArray errorList = new JSONArray(); + for (FSExecutorError error : errors) { + JSONObject errorObj = new JSONObject(); + errorObj.put("code", error.getCode()); + errorObj.put("message", error.getTitle()); + errorObj.put("description", error.getMessage()); + Map links = error.getLinks(); + if (links != null && links.size() > 0) { + JSONObject linksObj = new JSONObject(); + links.forEach(linksObj::put); + errorObj.put("Links", linksObj); + } + errorList.put(errorObj); + } + return errorList; + } + + private boolean isAnyClientErrors(HashSet statusCodes) { + + for (String statusCode : statusCodes) { + if (statusCode.startsWith("4")) { + return true; + } + } + return false; + } +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionImplTest.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionImplTest.java index 589cbe12..d576d328 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionImplTest.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionImplTest.java @@ -36,7 +36,7 @@ import java.util.Map; /** - * Test for open Banking extension implementation. + * Test for FS extension implementation. */ public class FSExtensionImplTest { diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockOBExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockFSExecutor.java similarity index 94% rename from financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockOBExecutor.java rename to financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockFSExecutor.java index 55cc2eb7..8122dca0 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockOBExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/core/MockFSExecutor.java @@ -23,9 +23,9 @@ import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; /** - * Mock Open banking executor for testing. + * Mock FS executor for testing. */ -public class MockOBExecutor implements FinancialServicesGatewayExecutor { +public class MockFSExecutor implements FinancialServicesGatewayExecutor { @Override public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutorTest.java similarity index 99% rename from financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java rename to financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutorTest.java index b458e2e5..79464dd7 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/TestEnforcementExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutorTest.java @@ -56,7 +56,7 @@ /** * Test for enforcement executor. */ -public class TestEnforcementExecutor { +public class ConsentEnforcementExecutorTest { private static ConsentEnforcementExecutor consentEnforcementExecutor; private static MockedStatic httpClientUtilsMockedStatic; diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutorTest.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutorTest.java new file mode 100644 index 00000000..150abfdc --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutorTest.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.impl.error.handling; + +import org.mockito.Mockito; +import org.testng.annotations.Test; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSExecutorError; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test class for OBDefaultErrorHandler. + */ +public class DefaultErrorHandlingExecutorTest { + + Map contextProps = new HashMap<>(); + + @Test + public void testPreRequestFlow() { + + FSAPIRequestContext fsapiRequestContext = Mockito.mock(FSAPIRequestContext.class); + Mockito.when(fsapiRequestContext.isError()).thenReturn(true); + Mockito.when(fsapiRequestContext.getErrors()).thenReturn(getErrorList()); + Mockito.when(fsapiRequestContext.getContextProps()).thenReturn(contextProps); + + DefaultErrorHandlingExecutor errorHandlingExecutor = Mockito.spy(DefaultErrorHandlingExecutor.class); + errorHandlingExecutor.preProcessRequest(fsapiRequestContext); + verify(fsapiRequestContext, times(0)).setError(false); + } + + @Test + public void testPostRequestFlow() { + + FSAPIRequestContext fsapiRequestContext = Mockito.mock(FSAPIRequestContext.class); + Mockito.when(fsapiRequestContext.isError()).thenReturn(true); + Mockito.when(fsapiRequestContext.getErrors()).thenReturn(getErrorList()); + Mockito.when(fsapiRequestContext.getContextProps()).thenReturn(contextProps); + + DefaultErrorHandlingExecutor errorHandlingExecutor = Mockito.spy(DefaultErrorHandlingExecutor.class); + errorHandlingExecutor.postProcessRequest(fsapiRequestContext); + verify(fsapiRequestContext, times(0)).setError(false); + } + + @Test + public void testPreResponseFlow() { + + FSAPIResponseContext fsapiResponseContext = Mockito.mock(FSAPIResponseContext.class); + Mockito.when(fsapiResponseContext.isError()).thenReturn(true); + Mockito.when(fsapiResponseContext.getErrors()).thenReturn(getErrorList()); + Mockito.when(fsapiResponseContext.getContextProps()).thenReturn(contextProps); + + DefaultErrorHandlingExecutor errorHandlingExecutor = Mockito.spy(DefaultErrorHandlingExecutor.class); + errorHandlingExecutor.preProcessResponse(fsapiResponseContext); + verify(fsapiResponseContext, times(0)).setError(false); + } + + @Test + public void testPostResponseFlow() { + + FSAPIResponseContext fsapiResponseContext = Mockito.mock(FSAPIResponseContext.class); + Mockito.when(fsapiResponseContext.isError()).thenReturn(true); + Mockito.when(fsapiResponseContext.getErrors()).thenReturn(getErrorList()); + Mockito.when(fsapiResponseContext.getContextProps()).thenReturn(contextProps); + + DefaultErrorHandlingExecutor errorHandlingExecutor = Mockito.spy(DefaultErrorHandlingExecutor.class); + errorHandlingExecutor.postProcessResponse(fsapiResponseContext); + verify(fsapiResponseContext, times(0)).setError(false); + } + + private ArrayList getErrorList() { + + FSExecutorError error = new FSExecutorError("400", "Invalid Request", + "Mandatory parameter is missing", "400"); + + ArrayList errors = new ArrayList<>(); + errors.add(error); + return errors; + } +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml index dd517ea2..b5603e04 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/testng.xml @@ -22,7 +22,8 @@ - + + From a64924871c4c482a43ff087ebf75c5b542b4aa55 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Thu, 24 Oct 2024 13:48:40 +0530 Subject: [PATCH 09/15] Adding Gateway implementation --- .../apis/Accounts/accounts-dynamic-endpoint-insequence.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml index 14724f43..01f142f7 100644 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/apis/Accounts/accounts-dynamic-endpoint-insequence.xml @@ -23,7 +23,7 @@ - + From 1a98ecd470a29b499d5ba2af40440a0dc48ca250 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Sat, 26 Oct 2024 16:10:18 +0530 Subject: [PATCH 10/15] Adding Gateway implementation --- .../resources/wso2am-4.4.0-deployment.toml | 2 +- .../extensions/common/ConsentException.java | 27 ++++++++++++++----- .../common/ConsentExtensionConstants.java | 6 +++-- .../gateway/GatewayTestConstants.java | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index b396c89b..85980b7c 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -427,7 +427,7 @@ name = "org.wso2.financial.services.accelerator.gateway.executor.impl.error.hand priority = 1000 [financial_services.gateway.consent.validation] -endpoint="https://IS_HOSTNAME:9446/api/fs/consent/validate" +endpoint="https://IS_HOSTNAME:9446/api/fs/consent/validate/validate" [financial_services.gateway.cache] cache_access_expiry_minutes=60 diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java index 085e1e5b..d8301e22 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java @@ -18,6 +18,7 @@ package org.wso2.financial.services.accelerator.consent.mgt.extensions.common; +import org.json.JSONArray; import org.json.JSONObject; import java.net.URI; @@ -52,28 +53,36 @@ public ConsentException(ResponseStatus status, String errorMessage) { errorMessage, null); } + public ConsentException(ResponseStatus status, JSONObject payload) { + + this.status = status; + this.payload = payload; + } + /** * This method is created to send error redirects in the authorization flow. The parameter validations are done * in compliance with the OAuth2 and OIDC specifications. * - * @param errorRedirectURI REQUIRED The base URI which the redirect should go to. + * @param errorURI REQUIRED The base URI which the redirect should go to. * @param error REQUIRED The error code of the error. Should be a supported value in OAuth2/OIDC * @param errorDescription OPTIONAL The description of the error. * @param state REQUIRED if a "state" parameter was present in the client authorization request. */ - public ConsentException(URI errorRedirectURI, AuthErrorCode error, String errorDescription, String state) { + public ConsentException(URI errorURI, AuthErrorCode error, String errorDescription, String state) { - if (errorRedirectURI != null && error != null) { + if (errorURI != null && error != null) { //add 302 as error code since this will be a redirect + errorRedirectURI = errorURI; this.status = ResponseStatus.FOUND; - this.payload = createDefaultErrorObject(errorRedirectURI, error.toString(), errorDescription, state); + this.payload = createDefaultErrorObject(errorURI, error.toString(), errorDescription, state); } } public JSONObject createDefaultErrorObject(URI redirectURI, String errorCode, String errorMessage, String state) { JSONObject error = new JSONObject(); - error.put(ConsentExtensionConstants.ERROR, errorCode); + error.put(ConsentExtensionConstants.ERROR_CODE, errorCode); + error.put(ConsentExtensionConstants.ERROR_MSG, "Consent Management Error"); error.put(ConsentExtensionConstants.ERROR_DESCRIPTION, errorMessage); if (state != null) { error.put(ConsentExtensionConstants.STATE, state); @@ -81,7 +90,13 @@ public JSONObject createDefaultErrorObject(URI redirectURI, String errorCode, St if (redirectURI != null) { error.put(ConsentExtensionConstants.REDIRECT_URI, redirectURI.toString()); } - return error; + + JSONArray errorList = new JSONArray(); + errorList.put(error); + + JSONObject errorObj = new JSONObject(); + errorObj.put(ConsentExtensionConstants.ERRORS, errorList); + return errorObj; } public JSONObject getPayload() { diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java index 1b914de0..f2ade68e 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionConstants.java @@ -93,8 +93,10 @@ public class ConsentExtensionConstants { public static final String COF_ACCOUNT = "cofAccount"; public static final String PRIMARY = "primary"; public static final String ACCOUNT_IDS = "accountIds"; - public static final String ERROR = "error"; - public static final String ERROR_DESCRIPTION = "error_description"; + public static final String ERRORS = "errors"; + public static final String ERROR_CODE = "code"; + public static final String ERROR_MSG = "message"; + public static final String ERROR_DESCRIPTION = "description"; public static final String STATE = "state"; public static final String REDIRECT_URI = "redirect_uri"; //Consent Admin Handler Constants diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/GatewayTestConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/GatewayTestConstants.java index 4629ba3c..1697dfbf 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/GatewayTestConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/java/org/wso2/financial/services/accelerator/gateway/GatewayTestConstants.java @@ -29,7 +29,7 @@ public class GatewayTestConstants { public static final String VALID_EXECUTOR_CLASS = - "org.wso2.financial.services.accelerator.gateway.executor.core.MockOBExecutor"; + "org.wso2.financial.services.accelerator.gateway.executor.core.MockFSExecutor"; public static final Map VALID_EXECUTOR_MAP = Stream.of( new AbstractMap.SimpleImmutableEntry<>(1, VALID_EXECUTOR_CLASS)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); From edb99723b666242ae3b634377232e41d1a4105f9 Mon Sep 17 00:00:00 2001 From: Ashirwada Date: Tue, 29 Oct 2024 08:44:40 +0530 Subject: [PATCH 11/15] Fixed review comments --- .../core/FSExtensionListenerImpl.java | 100 +++++++++--------- .../consent/ConsentEnforcementExecutor.java | 66 ++++++------ .../DefaultErrorHandlingExecutor.java | 60 +++++------ 3 files changed, 113 insertions(+), 113 deletions(-) diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java index 01de306e..9f33d8e4 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/core/FSExtensionListenerImpl.java @@ -49,19 +49,19 @@ public class FSExtensionListenerImpl implements ExtensionListener { @Generated(message = "Ignoring since the method has covered in other tests") public ExtensionResponseDTO preProcessRequest(RequestContextDTO requestContextDTO) { - FSAPIRequestContext fsapiRequestContext = new FSAPIRequestContext(requestContextDTO, new HashMap<>()); + FSAPIRequestContext fsApiRequestContext = new FSAPIRequestContext(requestContextDTO, new HashMap<>()); for (FinancialServicesGatewayExecutor gatewayExecutor : - GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForRequest(fsapiRequestContext)) { + GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForRequest(fsApiRequestContext)) { if (log.isDebugEnabled()) { log.debug("Executing preProcessRequest for executor: " + gatewayExecutor.getClass().getName()); } - gatewayExecutor.preProcessRequest(fsapiRequestContext); + gatewayExecutor.preProcessRequest(fsApiRequestContext); } - if (!fsapiRequestContext.isError()) { - setPropertiesToCache(requestContextDTO.getMsgInfo().getMessageId(), fsapiRequestContext.getContextProps()); + if (!fsApiRequestContext.isError()) { + setPropertiesToCache(requestContextDTO.getMsgInfo().getMessageId(), fsApiRequestContext.getContextProps()); } - return getResponseDTOForRequest(fsapiRequestContext); + return getResponseDTOForRequest(fsApiRequestContext); } @Override @@ -71,20 +71,20 @@ public ExtensionResponseDTO postProcessRequest(RequestContextDTO requestContextD Map contextProps = getPropertiesFromCache(requestContextDTO.getMsgInfo().getMessageId() + GatewayConstants.CONTEXT_PROP_CACHE_KEY); - FSAPIRequestContext fsapiRequestContext = new FSAPIRequestContext(requestContextDTO, contextProps); + FSAPIRequestContext fsApiRequestContext = new FSAPIRequestContext(requestContextDTO, contextProps); for (FinancialServicesGatewayExecutor gatewayExecutor : - GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForRequest(fsapiRequestContext)) { + GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForRequest(fsApiRequestContext)) { if (log.isDebugEnabled()) { log.debug("Executing postProcessRequest for executor: " + gatewayExecutor.getClass().getName()); } - gatewayExecutor.postProcessRequest(fsapiRequestContext); + gatewayExecutor.postProcessRequest(fsApiRequestContext); } - if (!fsapiRequestContext.isError()) { + if (!fsApiRequestContext.isError()) { setPropertiesToCache(requestContextDTO.getMsgInfo().getMessageId() + - GatewayConstants.CONTEXT_PROP_CACHE_KEY, fsapiRequestContext.getContextProps()); + GatewayConstants.CONTEXT_PROP_CACHE_KEY, fsApiRequestContext.getContextProps()); } - return getResponseDTOForRequest(fsapiRequestContext); + return getResponseDTOForRequest(fsApiRequestContext); } @Override @@ -93,20 +93,20 @@ public ExtensionResponseDTO preProcessResponse(ResponseContextDTO responseContex Map contextProps = getPropertiesFromCache(responseContextDTO.getMsgInfo().getMessageId() + GatewayConstants.CONTEXT_PROP_CACHE_KEY); - FSAPIResponseContext fsapiResponseContext = new FSAPIResponseContext(responseContextDTO, contextProps); + FSAPIResponseContext fsApiResponseContext = new FSAPIResponseContext(responseContextDTO, contextProps); for (FinancialServicesGatewayExecutor gatewayExecutor : - GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForResponse(fsapiResponseContext)) { + GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForResponse(fsApiResponseContext)) { if (log.isDebugEnabled()) { log.debug("Executing preProcessResponse for executor: " + gatewayExecutor.getClass().getName()); } - gatewayExecutor.preProcessResponse(fsapiResponseContext); + gatewayExecutor.preProcessResponse(fsApiResponseContext); } - if (!fsapiResponseContext.isError()) { + if (!fsApiResponseContext.isError()) { setPropertiesToCache(responseContextDTO.getMsgInfo().getMessageId() + - GatewayConstants.CONTEXT_PROP_CACHE_KEY, fsapiResponseContext.getContextProps()); + GatewayConstants.CONTEXT_PROP_CACHE_KEY, fsApiResponseContext.getContextProps()); } - return getResponseDTOForResponse(fsapiResponseContext); + return getResponseDTOForResponse(fsApiResponseContext); } @Override @@ -115,15 +115,15 @@ public ExtensionResponseDTO postProcessResponse(ResponseContextDTO responseConte Map contextProps = getPropertiesFromCache(responseContextDTO.getMsgInfo().getMessageId() + GatewayConstants.CONTEXT_PROP_CACHE_KEY); - FSAPIResponseContext fsapiResponseContext = new FSAPIResponseContext(responseContextDTO, contextProps); + FSAPIResponseContext fsApiResponseContext = new FSAPIResponseContext(responseContextDTO, contextProps); for (FinancialServicesGatewayExecutor gatewayExecutor : - GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForResponse(fsapiResponseContext)) { + GatewayDataHolder.getInstance().getRequestRouter().getExecutorsForResponse(fsApiResponseContext)) { if (log.isDebugEnabled()) { log.debug("Executing postProcessResponse for executor: " + gatewayExecutor.getClass().getName()); } - gatewayExecutor.postProcessResponse(fsapiResponseContext); + gatewayExecutor.postProcessResponse(fsApiResponseContext); } - ExtensionResponseDTO responseDTOForResponse = getResponseDTOForResponse(fsapiResponseContext); + ExtensionResponseDTO responseDTOForResponse = getResponseDTOForResponse(fsApiResponseContext); removePropertiesFromCache(responseContextDTO.getMsgInfo().getMessageId() + GatewayConstants.CONTEXT_PROP_CACHE_KEY); return responseDTOForResponse; @@ -132,27 +132,27 @@ public ExtensionResponseDTO postProcessResponse(ResponseContextDTO responseConte /** * Method to get response DTO for request path. * - * @param fsapiRequestContext API Request Context + * @param fsApiRequestContext API Request Context * @return ExtensionResponseDTO Extension Response DTO */ - protected ExtensionResponseDTO getResponseDTOForRequest(FSAPIRequestContext fsapiRequestContext) { + protected ExtensionResponseDTO getResponseDTOForRequest(FSAPIRequestContext fsApiRequestContext) { ExtensionResponseDTO extensionResponseDTO = new ExtensionResponseDTO(); - if (fsapiRequestContext.isError()) { - int statusCode = (!fsapiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? - HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsapiRequestContext + if (fsApiRequestContext.isError()) { + int statusCode = (!fsApiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? + HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsApiRequestContext .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); extensionResponseDTO.setStatusCode(statusCode); extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); - } else if (fsapiRequestContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && - Boolean.parseBoolean(fsapiRequestContext.getContextProps() + } else if (fsApiRequestContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && + Boolean.parseBoolean(fsApiRequestContext.getContextProps() .get(GatewayConstants.IS_RETURN_RESPONSE).toString())) { - Map headers = fsapiRequestContext.getMsgInfo().getHeaders(); + Map headers = fsApiRequestContext.getMsgInfo().getHeaders(); headers.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); - fsapiRequestContext.getMsgInfo().setHeaders(headers); + fsApiRequestContext.getMsgInfo().setHeaders(headers); extensionResponseDTO.setHeaders(headers); - if (fsapiRequestContext.getContextProps().containsKey(GatewayConstants.MODIFIED_STATUS)) { - extensionResponseDTO.setStatusCode(Integer.parseInt(fsapiRequestContext.getContextProps() + if (fsApiRequestContext.getContextProps().containsKey(GatewayConstants.MODIFIED_STATUS)) { + extensionResponseDTO.setStatusCode(Integer.parseInt(fsApiRequestContext.getContextProps() .get(GatewayConstants.MODIFIED_STATUS).toString())); } extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); @@ -160,40 +160,40 @@ protected ExtensionResponseDTO getResponseDTOForRequest(FSAPIRequestContext fsap extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.CONTINUE.toString()); } - String modifiedPayload = fsapiRequestContext.getModifiedPayload(); + String modifiedPayload = fsApiRequestContext.getModifiedPayload(); if (modifiedPayload != null) { extensionResponseDTO.setPayload(new ByteArrayInputStream(modifiedPayload.getBytes(StandardCharsets.UTF_8))); } - setHeadersToResponse(extensionResponseDTO, fsapiRequestContext.getAddedHeaders(), - fsapiRequestContext.getMsgInfo().getHeaders()); + setHeadersToResponse(extensionResponseDTO, fsApiRequestContext.getAddedHeaders(), + fsApiRequestContext.getMsgInfo().getHeaders()); return extensionResponseDTO; } /** * Method to get response DTO for response path. * - * @param fsapiResponseContext API Response Context + * @param fsApiResponseContext API Response Context * @return ExtensionResponseDTO Extension Response DTO */ - protected ExtensionResponseDTO getResponseDTOForResponse(FSAPIResponseContext fsapiResponseContext) { + protected ExtensionResponseDTO getResponseDTOForResponse(FSAPIResponseContext fsApiResponseContext) { ExtensionResponseDTO extensionResponseDTO = new ExtensionResponseDTO(); - if (fsapiResponseContext.isError()) { - int statusCode = (!fsapiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? - HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsapiResponseContext + if (fsApiResponseContext.isError()) { + int statusCode = (!fsApiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) ? + HttpStatus.SC_INTERNAL_SERVER_ERROR : Integer.parseInt(fsApiResponseContext .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); extensionResponseDTO.setStatusCode(statusCode); extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); - } else if (fsapiResponseContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && - Boolean.parseBoolean(fsapiResponseContext.getContextProps() + } else if (fsApiResponseContext.getContextProps().containsKey(GatewayConstants.IS_RETURN_RESPONSE) && + Boolean.parseBoolean(fsApiResponseContext.getContextProps() .get(GatewayConstants.IS_RETURN_RESPONSE).toString())) { - Map headers = fsapiResponseContext.getMsgInfo().getHeaders(); + Map headers = fsApiResponseContext.getMsgInfo().getHeaders(); headers.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); - fsapiResponseContext.getMsgInfo().setHeaders(headers); + fsApiResponseContext.getMsgInfo().setHeaders(headers); extensionResponseDTO.setHeaders(headers); - if (fsapiResponseContext.getContextProps().containsKey(GatewayConstants.MODIFIED_STATUS)) { - extensionResponseDTO.setStatusCode((Integer.parseInt(fsapiResponseContext.getContextProps() + if (fsApiResponseContext.getContextProps().containsKey(GatewayConstants.MODIFIED_STATUS)) { + extensionResponseDTO.setStatusCode((Integer.parseInt(fsApiResponseContext.getContextProps() .get(GatewayConstants.MODIFIED_STATUS).toString()))); } extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.RETURN_ERROR.toString()); @@ -201,13 +201,13 @@ protected ExtensionResponseDTO getResponseDTOForResponse(FSAPIResponseContext fs extensionResponseDTO.setResponseStatus(ExtensionResponseStatus.CONTINUE.toString()); } - String modifiedPayload = fsapiResponseContext.getModifiedPayload(); + String modifiedPayload = fsApiResponseContext.getModifiedPayload(); if (modifiedPayload != null) { extensionResponseDTO.setPayload(new ByteArrayInputStream(modifiedPayload.getBytes(StandardCharsets.UTF_8))); } - setHeadersToResponse(extensionResponseDTO, fsapiResponseContext.getAddedHeaders(), - fsapiResponseContext.getMsgInfo().getHeaders()); + setHeadersToResponse(extensionResponseDTO, fsApiResponseContext.getAddedHeaders(), + fsApiResponseContext.getMsgInfo().getHeaders()); return extensionResponseDTO; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java index 787de8c9..30ab8f33 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java @@ -87,42 +87,42 @@ public class ConsentEnforcementExecutor implements FinancialServicesGatewayExecu /** * Method to handle request. * - * @param fsapiRequestContext FS request context object + * @param fsApiRequestContext FS request context object */ @Generated(message = "Unit testable components are covered") @Override - public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { + public void preProcessRequest(FSAPIRequestContext fsApiRequestContext) { } /** * Method to handle post request. * - * @param fsapiRequestContext FS request context object + * @param fsApiRequestContext FS request context object */ @Override - public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { + public void postProcessRequest(FSAPIRequestContext fsApiRequestContext) { // Consent ID is required for consent enforcement. If the consent ID is null, we are assume this is a // pre-consent creation call. Therefore consent enforcement is not required. - if (fsapiRequestContext.isError() || fsapiRequestContext.getConsentId() == null) { + if (fsApiRequestContext.isError() || fsApiRequestContext.getConsentId() == null) { return; } - Map requestHeaders = fsapiRequestContext.getMsgInfo().getHeaders(); + Map requestHeaders = fsApiRequestContext.getMsgInfo().getHeaders(); Map additionalParams = new HashMap<>(); - additionalParams.put(ELECTED_RESOURCE_TAG, fsapiRequestContext.getMsgInfo().getElectedResource()); - additionalParams.put(CONSENT_ID_TAG, fsapiRequestContext.getConsentId()); - additionalParams.put(USER_ID_TAG, fsapiRequestContext.getApiRequestInfo().getUsername()); - additionalParams.put(CLIENT_ID_TAG, fsapiRequestContext.getApiRequestInfo().getConsumerKey()); - additionalParams.put(RESOURCE_PARAMS, getResourceParamMap(fsapiRequestContext)); + additionalParams.put(ELECTED_RESOURCE_TAG, fsApiRequestContext.getMsgInfo().getElectedResource()); + additionalParams.put(CONSENT_ID_TAG, fsApiRequestContext.getConsentId()); + additionalParams.put(USER_ID_TAG, fsApiRequestContext.getApiRequestInfo().getUsername()); + additionalParams.put(CLIENT_ID_TAG, fsApiRequestContext.getApiRequestInfo().getConsumerKey()); + additionalParams.put(RESOURCE_PARAMS, getResourceParamMap(fsApiRequestContext)); JSONObject validationRequest; - if (StringUtils.isNotBlank(fsapiRequestContext.getModifiedPayload())) { + if (StringUtils.isNotBlank(fsApiRequestContext.getModifiedPayload())) { validationRequest = createValidationRequestPayload(requestHeaders, - fsapiRequestContext.getModifiedPayload(), additionalParams); + fsApiRequestContext.getModifiedPayload(), additionalParams); } else { validationRequest = createValidationRequestPayload(requestHeaders, - fsapiRequestContext.getRequestPayload(), additionalParams); + fsApiRequestContext.getRequestPayload(), additionalParams); } String enforcementJWTPayload = generateJWT(validationRequest.toString()); JSONObject jsonResponse; @@ -130,7 +130,7 @@ public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { String response = invokeConsentValidationService(enforcementJWTPayload); jsonResponse = new JSONObject(response); } catch (IOException | FinancialServicesException e) { - handleError(fsapiRequestContext, FinancialServicesErrorCodes.CONSENT_VALIDATION_REQUEST_FAILURE, + handleError(fsApiRequestContext, FinancialServicesErrorCodes.CONSENT_VALIDATION_REQUEST_FAILURE, e.getMessage(), FinancialServicesErrorCodes.SERVER_ERROR_CODE); return; } @@ -140,18 +140,18 @@ public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { String errorCode = jsonResponse.get(ERROR_CODE).toString(); String errorMessage = jsonResponse.get(ERROR_MESSAGE).toString(); String httpCode = jsonResponse.get(HTTP_CODE).toString(); - handleError(fsapiRequestContext, errorCode, errorMessage, httpCode); + handleError(fsApiRequestContext, errorCode, errorMessage, httpCode); return; } else if (!jsonResponse.isNull(MODIFIED_PAYLOAD)) { Object modifiedPayloadObj = jsonResponse.get(MODIFIED_PAYLOAD); if (modifiedPayloadObj != null) { - fsapiRequestContext.setModifiedPayload(modifiedPayloadObj.toString()); + fsApiRequestContext.setModifiedPayload(modifiedPayloadObj.toString()); } } else if (!jsonResponse.isNull(CONSENT_INFO)) { Object consentInformationObj = jsonResponse.get(CONSENT_INFO); if (consentInformationObj != null) { requestHeaders.put(INFO_HEADER_TAG, consentInformationObj.toString()); - fsapiRequestContext.setAddedHeaders(requestHeaders); + fsApiRequestContext.setAddedHeaders(requestHeaders); } } } @@ -159,20 +159,20 @@ public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { /** * Method to handle response. * - * @param fsapiResponseContext FS response context object + * @param fsApiResponseContext FS response context object */ @Override - public void preProcessResponse(FSAPIResponseContext fsapiResponseContext) { + public void preProcessResponse(FSAPIResponseContext fsApiResponseContext) { } /** * Method to handle post response. * - * @param fsapiResponseContext FS response context object + * @param fsApiResponseContext FS response context object */ @Override - public void postProcessResponse(FSAPIResponseContext fsapiResponseContext) { + public void postProcessResponse(FSAPIResponseContext fsApiResponseContext) { } @@ -260,19 +260,19 @@ private String invokeConsentValidationService(String enforcementJWTPayload) thro /** * Method to handle errors. * - * @param fsapiRequestContext API Context + * @param fsApiRequestContext API Context * @param errorCode Error Code * @param errorMessage Error Message * @param httpCode HTTP status code ( in 4XX range) */ - protected void handleError(FSAPIRequestContext fsapiRequestContext, String errorCode, String errorMessage, + protected void handleError(FSAPIRequestContext fsApiRequestContext, String errorCode, String errorMessage, String httpCode) { - fsapiRequestContext.setError(true); - ArrayList errors = fsapiRequestContext.getErrors(); + fsApiRequestContext.setError(true); + ArrayList errors = fsApiRequestContext.getErrors(); errors.add(new FSExecutorError(errorCode, ERROR_TITLE, errorMessage, httpCode)); - fsapiRequestContext.setErrors(errors); - fsapiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, httpCode); + fsApiRequestContext.setErrors(errors); + fsApiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, httpCode); } /** @@ -302,16 +302,16 @@ protected JSONObject createValidationRequestPayload(Map requestH /** * Method to construct resource parameter map to invoke the validation service. * - * @param fsapiRequestContext FS request context object + * @param fsApiRequestContext FS request context object * @return A Map containing resource path(ex: /aisp/accounts/{AccountId}?queryParam=urlEncodedQueryParamValue), * http method and context(ex: /open-banking/v3.1/aisp) */ - private Map getResourceParamMap(FSAPIRequestContext fsapiRequestContext) { + private Map getResourceParamMap(FSAPIRequestContext fsApiRequestContext) { Map resourceMap = new HashMap<>(); - resourceMap.put(RESOURCE_TAG, fsapiRequestContext.getMsgInfo().getResource()); - resourceMap.put(HTTP_METHOD, fsapiRequestContext.getMsgInfo().getHttpMethod()); - resourceMap.put(CONTEXT_TAG, fsapiRequestContext.getApiRequestInfo().getContext()); + resourceMap.put(RESOURCE_TAG, fsApiRequestContext.getMsgInfo().getResource()); + resourceMap.put(HTTP_METHOD, fsApiRequestContext.getMsgInfo().getHttpMethod()); + resourceMap.put(CONTEXT_TAG, fsApiRequestContext.getApiRequestInfo().getContext()); return resourceMap; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java index aca18060..6eeca3aa 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java @@ -42,55 +42,55 @@ public class DefaultErrorHandlingExecutor implements FinancialServicesGatewayExe /** * Method to handle pre request. * - * @param fsapiRequestContext FS request context object + * @param fsApiRequestContext FS request context object */ @Override - public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { + public void preProcessRequest(FSAPIRequestContext fsApiRequestContext) { - handleRequestError(fsapiRequestContext); + handleRequestError(fsApiRequestContext); } /** * Method to handle post request. * - * @param fsapiRequestContext FS request context object + * @param fsApiRequestContext FS request context object */ @Override - public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { + public void postProcessRequest(FSAPIRequestContext fsApiRequestContext) { - handleRequestError(fsapiRequestContext); + handleRequestError(fsApiRequestContext); } /** * Method to handle pre response. * - * @param fsapiResponseContext FS response context object + * @param fsApiResponseContext FS response context object */ @Override - public void preProcessResponse(FSAPIResponseContext fsapiResponseContext) { + public void preProcessResponse(FSAPIResponseContext fsApiResponseContext) { - handleResponseError(fsapiResponseContext); + handleResponseError(fsApiResponseContext); } /** * Method to handle post response. * - * @param fsapiResponseContext FS response context object + * @param fsApiResponseContext FS response context object */ @Override - public void postProcessResponse(FSAPIResponseContext fsapiResponseContext) { + public void postProcessResponse(FSAPIResponseContext fsApiResponseContext) { - handleResponseError(fsapiResponseContext); + handleResponseError(fsApiResponseContext); } - private void handleRequestError(FSAPIRequestContext fsapiRequestContext) { + private void handleRequestError(FSAPIRequestContext fsApiRequestContext) { - if (!fsapiRequestContext.isError()) { + if (!fsApiRequestContext.isError()) { return; } JSONObject payload = new JSONObject(); - ArrayList errors = fsapiRequestContext.getErrors(); + ArrayList errors = fsApiRequestContext.getErrors(); JSONArray errorList = getErrorJSON(errors); HashSet statusCodes = new HashSet<>(); @@ -100,31 +100,31 @@ private void handleRequestError(FSAPIRequestContext fsapiRequestContext) { payload.put(ERRORS_TAG, errorList); if (errorList.length() != 0) { - fsapiRequestContext.setModifiedPayload(payload.toString()); - Map addedHeaders = fsapiRequestContext.getAddedHeaders(); + fsApiRequestContext.setModifiedPayload(payload.toString()); + Map addedHeaders = fsApiRequestContext.getAddedHeaders(); addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); - fsapiRequestContext.setAddedHeaders(addedHeaders); + fsApiRequestContext.setAddedHeaders(addedHeaders); } int statusCode; - if (fsapiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { - statusCode = Integer.parseInt(fsapiRequestContext + if (fsApiRequestContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { + statusCode = Integer.parseInt(fsApiRequestContext .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); } else if (isAnyClientErrors(statusCodes)) { statusCode = HttpStatus.SC_BAD_REQUEST; } else { statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; } - fsapiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, + fsApiRequestContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, String.valueOf(statusCode)); } - private void handleResponseError(FSAPIResponseContext fsapiResponseContext) { + private void handleResponseError(FSAPIResponseContext fsApiResponseContext) { - if (!fsapiResponseContext.isError()) { + if (!fsApiResponseContext.isError()) { return; } JSONObject payload = new JSONObject(); - ArrayList errors = fsapiResponseContext.getErrors(); + ArrayList errors = fsApiResponseContext.getErrors(); JSONArray errorList = getErrorJSON(errors); HashSet statusCodes = new HashSet<>(); @@ -133,20 +133,20 @@ private void handleResponseError(FSAPIResponseContext fsapiResponseContext) { } payload.put(ERRORS_TAG, errorList); - fsapiResponseContext.setModifiedPayload(payload.toString()); - Map addedHeaders = fsapiResponseContext.getAddedHeaders(); + fsApiResponseContext.setModifiedPayload(payload.toString()); + Map addedHeaders = fsApiResponseContext.getAddedHeaders(); addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); - fsapiResponseContext.setAddedHeaders(addedHeaders); + fsApiResponseContext.setAddedHeaders(addedHeaders); int statusCode; - if (fsapiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { - statusCode = Integer.parseInt(fsapiResponseContext + if (fsApiResponseContext.getContextProps().containsKey(GatewayConstants.ERROR_STATUS_PROP)) { + statusCode = Integer.parseInt(fsApiResponseContext .getContextProperty(GatewayConstants.ERROR_STATUS_PROP).toString()); } else if (isAnyClientErrors(statusCodes)) { statusCode = HttpStatus.SC_BAD_REQUEST; } else { statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; } - fsapiResponseContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, + fsApiResponseContext.addContextProperty(GatewayConstants.ERROR_STATUS_PROP, String.valueOf(statusCode)); } From 1e13ec5af065388311c13d8ca7f04ef804d84184 Mon Sep 17 00:00:00 2001 From: Ashi1993 Date: Wed, 6 Nov 2024 14:28:13 +0530 Subject: [PATCH 12/15] Foxed review comments --- .../repository/conf/financial-services.xml.j2 | 6 ++--- .../resources/wso2am-4.4.0-deployment.toml | 8 +++---- .../extensions/common/ConsentException.java | 5 +++-- .../common/ConsentExtensionUtils.java | 1 + .../manage/utils/ConsentManageUtils.java | 1 + .../consent/ConsentEnforcementExecutor.java | 2 +- .../DefaultErrorHandlingExecutor.java | 22 ++++++++----------- .../gateway/util/GatewayConstants.java | 4 ++++ .../resources/test-validation-response.json | 2 +- 9 files changed, 27 insertions(+), 24 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 index 9004c7fa..962b245c 100644 --- a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 +++ b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 @@ -25,9 +25,9 @@ org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter {% endif %} - {% for type in financial_services.gateway.gateway_executors.type %} - <{{type.name}}> - {% for executor in type.executors %} + {% for executor in financial_services.gateway.executors %} + <{{executor.type}}> + {% for executor in executor.executor %} {% endfor %} diff --git a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml index 85980b7c..f489441e 100755 --- a/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml +++ b/financial-services-accelerator/accelerators/fs-apim/repository/resources/wso2am-4.4.0-deployment.toml @@ -417,12 +417,12 @@ publisher_url="https://localhost:9443" request_router="org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter" #============executors========================= -[[financial_services.gateway.gateway_executors.type]] -name = "Default" -[[financial_services.gateway.gateway_executors.type.executors]] +[[financial_services.gateway.executors]] +type = "Default" +[[financial_services.gateway.executors.executor]] name = "org.wso2.financial.services.accelerator.gateway.executor.impl.consent.ConsentEnforcementExecutor" priority = 1 -[[financial_services.gateway.gateway_executors.type.executors]] +[[financial_services.gateway.executors.executor]] name = "org.wso2.financial.services.accelerator.gateway.executor.impl.error.handling.DefaultErrorHandlingExecutor" priority = 1000 diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java index d8301e22..a81c94f0 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentException.java @@ -78,12 +78,13 @@ public ConsentException(URI errorURI, AuthErrorCode error, String errorDescripti } } - public JSONObject createDefaultErrorObject(URI redirectURI, String errorCode, String errorMessage, String state) { + public JSONObject createDefaultErrorObject(URI redirectURI, String errorCode, String errorDescription, + String state) { JSONObject error = new JSONObject(); error.put(ConsentExtensionConstants.ERROR_CODE, errorCode); error.put(ConsentExtensionConstants.ERROR_MSG, "Consent Management Error"); - error.put(ConsentExtensionConstants.ERROR_DESCRIPTION, errorMessage); + error.put(ConsentExtensionConstants.ERROR_DESCRIPTION, errorDescription); if (state != null) { error.put(ConsentExtensionConstants.STATE, state); } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java index afa82844..36291da8 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/common/ConsentExtensionUtils.java @@ -164,6 +164,7 @@ public static String resolveUsernameFromUserId(String userID) { username = OAuth2Util.resolveUsernameFromUserId(ConsentExtensionConstants.TENANT_DOMAIN, userID); } } catch (UserStoreException e) { + log.debug("Returning null since user ID is not found in the database"); return null; } return username; diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java index 3c19c6f5..d80667b4 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.consent.mgt.extensions/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/extensions/manage/utils/ConsentManageUtils.java @@ -69,6 +69,7 @@ public static boolean isTransactionFromToTimeValid(String fromDateVal, String to // From date is equal or earlier than To date return toDate.isEqual(fromDate) || toDate.isAfter(fromDate); } catch (DateTimeParseException e) { + log.debug("Returning false since datetime cannot be parsed"); return false; } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java index 30ab8f33..88be3af0 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/consent/ConsentEnforcementExecutor.java @@ -135,7 +135,7 @@ public void postProcessRequest(FSAPIRequestContext fsApiRequestContext) { return; } - boolean isValid = (boolean) jsonResponse.get(IS_VALID); + boolean isValid = jsonResponse.getBoolean(IS_VALID); if (!isValid) { String errorCode = jsonResponse.get(ERROR_CODE).toString(); String errorMessage = jsonResponse.get(ERROR_MESSAGE).toString(); diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java index 6eeca3aa..205f6d8c 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/error/handling/DefaultErrorHandlingExecutor.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Default Executor to handle gateway errors. @@ -99,7 +100,7 @@ private void handleRequestError(FSAPIRequestContext fsApiRequestContext) { } payload.put(ERRORS_TAG, errorList); - if (errorList.length() != 0) { + if (!errorList.isEmpty()) { fsApiRequestContext.setModifiedPayload(payload.toString()); Map addedHeaders = fsApiRequestContext.getAddedHeaders(); addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); @@ -155,27 +156,22 @@ private JSONArray getErrorJSON(List errors) { JSONArray errorList = new JSONArray(); for (FSExecutorError error : errors) { JSONObject errorObj = new JSONObject(); - errorObj.put("code", error.getCode()); - errorObj.put("message", error.getTitle()); - errorObj.put("description", error.getMessage()); + errorObj.put(GatewayConstants.CODE, error.getCode()); + errorObj.put(GatewayConstants.MESSAGE, error.getTitle()); + errorObj.put(GatewayConstants.DESCRIPTION, error.getMessage()); Map links = error.getLinks(); - if (links != null && links.size() > 0) { + if (links != null && !links.isEmpty()) { JSONObject linksObj = new JSONObject(); links.forEach(linksObj::put); - errorObj.put("Links", linksObj); + errorObj.put(GatewayConstants.LINKS, linksObj); } errorList.put(errorObj); } return errorList; } - private boolean isAnyClientErrors(HashSet statusCodes) { + private boolean isAnyClientErrors(Set statusCodes) { - for (String statusCode : statusCodes) { - if (statusCode.startsWith("4")) { - return true; - } - } - return false; + return statusCodes.stream().anyMatch(statusCode -> statusCode.startsWith("4")); } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java index 39159bbf..0575c608 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java @@ -59,4 +59,8 @@ public class GatewayConstants { public static final String ERROR_STATUS_PROP = "errorStatusCode"; public static final String IS_RETURN_RESPONSE = "isReturnResponse"; public static final String MODIFIED_STATUS = "ModifiedStatus"; + public static final String CODE = "code"; + public static final String MESSAGE = "message"; + public static final String DESCRIPTION = "description"; + public static final String LINKS = "links"; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json index 28effa17..a0ba4412 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/test/resources/test-validation-response.json @@ -9,4 +9,4 @@ "consentInformation": { "key": "value" } -} \ No newline at end of file +} From 4a2e3f9e3c7a11e97bf2613f8edff9748fda887c Mon Sep 17 00:00:00 2001 From: Ashi1993 Date: Wed, 6 Nov 2024 15:43:39 +0530 Subject: [PATCH 13/15] fixed review comments --- .../templates/repository/conf/financial-services.xml.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 index 962b245c..0f57a600 100644 --- a/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 +++ b/financial-services-accelerator/accelerators/fs-apim/carbon-home/repository/resources/conf/templates/repository/conf/financial-services.xml.j2 @@ -25,12 +25,12 @@ org.wso2.financial.services.accelerator.gateway.executor.core.DefaultRequestRouter {% endif %} - {% for executor in financial_services.gateway.executors %} - <{{executor.type}}> - {% for executor in executor.executor %} + {% for executors in financial_services.gateway.executors %} + <{{executors.type}}> + {% for executor in executors.executor %} {% endfor %} - + {% endfor %} {% if financial_services.gateway.consent.validation.endpoint is defined %} From 9f5aba237853e9f152df13937ea29a1091d8c43b Mon Sep 17 00:00:00 2001 From: ashirwadadayarathne Date: Thu, 12 Dec 2024 15:15:31 +0530 Subject: [PATCH 14/15] Changing to support Nimbus versions in both APIM and IS --- .../common/util/FinancialServicesUtils.java | 29 +++---------------- .../claims/FSDefaultClaimProvider.java | 8 ++--- .../api/ConsentValidationEndpoint.java | 2 +- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/FinancialServicesUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/FinancialServicesUtils.java index 8bcc5ca9..bed67f65 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/FinancialServicesUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/FinancialServicesUtils.java @@ -18,11 +18,10 @@ package org.wso2.financial.services.accelerator.common.util; -import com.nimbusds.jose.JWSObject; -import net.minidev.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; @@ -98,8 +97,8 @@ public static String getSoftwareEnvironmentFromSSA(String softwareStatement) thr return FinancialServicesConstants.PRODUCTION; } - final JSONObject softwareStatementBody = JWTUtils.decodeRequestJWT(softwareStatement, - FinancialServicesConstants.JWT_BODY); + String decodedSSA = JWTUtils.decodeRequestJWT(softwareStatement, FinancialServicesConstants.JWT_BODY); + final JSONObject softwareStatementBody = new JSONObject(decodedSSA); // Retrieve the SSA property name used for software environment identification final String sandboxEnvIdentificationPropertyName = FinancialServicesConfigParser.getInstance() .getSoftwareEnvIdentificationSSAPropertyName(); @@ -107,7 +106,7 @@ public static String getSoftwareEnvironmentFromSSA(String softwareStatement) thr final String sandboxEnvIdentificationValue = FinancialServicesConfigParser.getInstance() .getSoftwareEnvIdentificationSSAPropertyValueForSandbox(); return sandboxEnvIdentificationValue.equals(softwareStatementBody - .getAsString(sandboxEnvIdentificationPropertyName)) + .getString(sandboxEnvIdentificationPropertyName)) ? FinancialServicesConstants.SANDBOX : FinancialServicesConstants.PRODUCTION; } @@ -147,24 +146,4 @@ public static boolean isRegulatoryApp(String clientId) throws RequestObjectExcep "provider for clientId: " + clientId, e); } } - - /** - * Decode request JWT. - * - * @param jwtToken jwt sent by the tpp - * @param jwtPart expected jwt part (header, body) - * @return json object containing requested jwt part - * @throws java.text.ParseException if an error occurs while parsing the jwt - */ - public static JSONObject decodeRequestJWT(String jwtToken, String jwtPart) throws java.text.ParseException { - - JWSObject plainObject = JWSObject.parse(jwtToken); - - if (FinancialServicesConstants.JWT_HEAD.equals(jwtPart)) { - return plainObject.getHeader().toJSONObject(); - } else if (FinancialServicesConstants.JWT_BODY.equals(jwtPart)) { - return plainObject.getPayload().toJSONObject(); - } - return null; - } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.identity.extensions/src/main/java/org/wso2/financial/services/accelerator/identity/extensions/claims/FSDefaultClaimProvider.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.identity.extensions/src/main/java/org/wso2/financial/services/accelerator/identity/extensions/claims/FSDefaultClaimProvider.java index 66b39912..e4468c94 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.identity.extensions/src/main/java/org/wso2/financial/services/accelerator/identity/extensions/claims/FSDefaultClaimProvider.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.identity.extensions/src/main/java/org/wso2/financial/services/accelerator/identity/extensions/claims/FSDefaultClaimProvider.java @@ -18,10 +18,10 @@ package org.wso2.financial.services.accelerator.identity.extensions.claims; -import net.minidev.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; import org.wso2.carbon.identity.oauth.cache.SessionDataCache; import org.wso2.carbon.identity.oauth.cache.SessionDataCacheEntry; import org.wso2.carbon.identity.oauth.cache.SessionDataCacheKey; @@ -31,7 +31,7 @@ import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeRespDTO; import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; -import org.wso2.financial.services.accelerator.common.util.FinancialServicesUtils; +import org.wso2.financial.services.accelerator.common.util.JWTUtils; import org.wso2.financial.services.accelerator.identity.extensions.util.IdentityCommonConstants; import org.wso2.financial.services.accelerator.identity.extensions.util.IdentityCommonUtils; @@ -69,7 +69,7 @@ public Map getAdditionalClaims(OAuthAuthzReqMessageContext authA /* State is an optional parameter, so the authorization server must successfully authenticate and * must NOT return state nor s_hash. (FAPI1-ADV-5.2.2.1-5) */ - final String state = requestBody.getAsString(OAuthConstants.OAuth20Params.STATE); + final String state = requestBody.getString(OAuthConstants.OAuth20Params.STATE); if (StringUtils.isNotEmpty(state)) { claims.put(IdentityCommonConstants.S_HASH, IdentityCommonUtils.getHashValue(state, null)); } else { @@ -120,7 +120,7 @@ private JSONObject getRequestBodyFromCache(String[] cachedRequests) { try { if (cachedRequests.length > 0) { - return FinancialServicesUtils.decodeRequestJWT(cachedRequests[0], "body"); + return new JSONObject(JWTUtils.decodeRequestJWT(cachedRequests[0], "body")); } } catch (ParseException e) { log.error("Exception occurred when decoding request. Caused by, ", e); diff --git a/financial-services-accelerator/internal-webapps/org.wso2.financial.services.accelerator.consent.mgt.endpoint/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/endpoint/api/ConsentValidationEndpoint.java b/financial-services-accelerator/internal-webapps/org.wso2.financial.services.accelerator.consent.mgt.endpoint/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/endpoint/api/ConsentValidationEndpoint.java index 1f773d9d..36976b64 100644 --- a/financial-services-accelerator/internal-webapps/org.wso2.financial.services.accelerator.consent.mgt.endpoint/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/endpoint/api/ConsentValidationEndpoint.java +++ b/financial-services-accelerator/internal-webapps/org.wso2.financial.services.accelerator.consent.mgt.endpoint/src/main/java/org/wso2/financial/services/accelerator/consent/mgt/endpoint/api/ConsentValidationEndpoint.java @@ -115,7 +115,7 @@ public Response validate(@Context HttpServletRequest request, @Context HttpServl try { JWTUtils.validateJWTSignatureWithPublicKey(payload, requestSignatureAlias); String decodedRequest = JWTUtils.decodeRequestJWT(payload, ConsentExtensionConstants.BODY) != null - ? JWTUtils.decodeRequestJWT(payload, ConsentExtensionConstants.BODY).toJSONString() + ? JWTUtils.decodeRequestJWT(payload, ConsentExtensionConstants.BODY) : null; if (Objects.nonNull(decodedRequest)) { requestData = new JSONObject(decodedRequest); From 2d2c1f8c9f3c68c9ad1d2add7a106adf02e8c9e9 Mon Sep 17 00:00:00 2001 From: ashirwadadayarathne Date: Thu, 12 Dec 2024 15:15:48 +0530 Subject: [PATCH 15/15] Adding DCR gateway implementation --- .../pom.xml | 6 +- .../config/FinancialServicesConfigParser.java | 82 ------- ...FinancialServicesConfigurationService.java | 5 - ...ncialServicesConfigurationServiceImpl.java | 11 - .../constant/FinancialServicesConstants.java | 45 ++-- .../accelerator/common/util/JWTUtils.java | 20 +- .../common/test/FSConfigParserTests.java | 9 - .../exception/FSExecutorException.java | 78 ++++++ .../executor/impl/dcr/DCRExecutor.java | 231 ++++++++++++++++++ .../gateway/util/GatewayConstants.java | 15 ++ 10 files changed, 357 insertions(+), 145 deletions(-) create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/exception/FSExecutorException.java create mode 100644 financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/dcr/DCRExecutor.java diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/pom.xml b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/pom.xml index 1bfefffb..91ba9336 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/pom.xml +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/pom.xml @@ -88,8 +88,8 @@ nimbus-jose-jwt - net.minidev - json-smart + org.json.wso2 + json @@ -235,7 +235,7 @@ com.nimbusds.jose;version="${org.wso2.orbit.nimbus.version.range}", com.nimbusds.jwt;version="${org.wso2.orbit.nimbus.version.range}", javax.cache, - net.minidev.json;version="${json-smart.version}", + org.json;version="${org.json.version.range}", org.apache.axiom.*;version="${axiom.osgi.version.range}", org.apache.commons.lang3;version="${commons-lang3.version}", org.apache.commons.logging;version="${commons.logging.version}", diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigParser.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigParser.java index f6084dbd..3f3e99b4 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigParser.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigParser.java @@ -36,12 +36,10 @@ import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Stack; @@ -63,8 +61,6 @@ public final class FinancialServicesConfigParser { private final Map configuration = new HashMap<>(); private final Map> fsExecutors = new HashMap<>(); private final Map> authorizeSteps = new HashMap<>(); - private final Map> allowedScopes = new HashMap<>(); - private final Map> allowedAPIs = new HashMap<>(); private SecretResolver secretResolver; private OMElement rootElement; private static FinancialServicesConfigParser parser; @@ -132,8 +128,6 @@ private void buildConfiguration() { readChildElements(rootElement, nameStack); buildFSExecutors(); buildConsentAuthSteps(); - buildAllowedScopes(); - buildAllowedSubscriptions(); } catch (IOException | XMLStreamException | OMException e) { throw new FinancialServicesRuntimeException("Error occurred while building configuration from " + "financial-services.xml", e); @@ -297,74 +291,6 @@ private void readChildElements(OMElement serverConfig, Stack nameStack) } } - private void buildAllowedScopes() { - OMElement gatewayElement = rootElement.getFirstChildWithName( - new QName(FinancialServicesConstants.FS_CONFIG_QNAME, FinancialServicesConstants.GATEWAY_CONFIG_TAG)); - - if (gatewayElement != null) { - OMElement tppManagementElement = gatewayElement.getFirstChildWithName( - new QName(FinancialServicesConstants.FS_CONFIG_QNAME, - FinancialServicesConstants.TPP_MANAGEMENT_CONFIG_TAG)); - - if (tppManagementElement != null) { - OMElement allowedScopesElement = tppManagementElement.getFirstChildWithName(new QName( - FinancialServicesConstants.FS_CONFIG_QNAME, - FinancialServicesConstants.ALLOWED_SCOPES_CONFIG_TAG)); - - // obtaining each scope under allowed scopes - Iterator environmentIterator = allowedScopesElement - .getChildrenWithLocalName(FinancialServicesConstants.SCOPE_CONFIG_TAG); - - while (environmentIterator.hasNext()) { - OMElement scopeElem = (OMElement) environmentIterator.next(); - String scopeName = scopeElem.getAttributeValue(new QName("name")); - String rolesStr = scopeElem.getAttributeValue(new QName("roles")); - if (StringUtils.isNotEmpty(rolesStr)) { - List rolesList = Arrays.stream(rolesStr.split(",")) - .map(String::trim) - .collect(Collectors.toList()); - allowedScopes.put(scopeName, rolesList); - } - } - } - } - } - - private void buildAllowedSubscriptions() { - - OMElement dcrElement = rootElement.getFirstChildWithName( - new QName(FinancialServicesConstants.FS_CONFIG_QNAME, FinancialServicesConstants.DCR_CONFIG_TAG)); - - if (dcrElement != null) { - OMElement regulatoryAPIs = dcrElement.getFirstChildWithName( - new QName(FinancialServicesConstants.FS_CONFIG_QNAME, - FinancialServicesConstants.REGULATORY_API_NAMES)); - - if (regulatoryAPIs != null) { - - // obtaining each regulatory API under allowed regulatory APIs - Iterator environmentIterator = regulatoryAPIs - .getChildrenWithLocalName(FinancialServicesConstants.REGULATORY_API); - - while (environmentIterator.hasNext()) { - OMElement regulatoryAPIElem = (OMElement) environmentIterator.next(); - String regulatoryAPIName = regulatoryAPIElem.getAttributeValue(new QName( - FinancialServicesConstants.API_NAME)); - String rolesStr = regulatoryAPIElem.getAttributeValue(new QName( - FinancialServicesConstants.API_ROLE)); - if (StringUtils.isNotEmpty(rolesStr)) { - List rolesList = Arrays.stream(rolesStr.split(",")) - .map(String::trim) - .collect(Collectors.toList()); - allowedAPIs.put(regulatoryAPIName, rolesList); - } else { - allowedAPIs.put(regulatoryAPIName, Collections.emptyList()); - } - } - } - } - } - /** * Method to obtain config key from stack. * @@ -440,14 +366,6 @@ public Map> getConsentAuthorizeSteps() { return Collections.unmodifiableMap(authorizeSteps); } - public Map> getAllowedScopes() { - return Collections.unmodifiableMap(allowedScopes); - } - - public Map> getAllowedAPIs() { - return Collections.unmodifiableMap(allowedAPIs); - } - public String getDataSourceName() { Optional source = getConfigurationFromKeyAsString(FinancialServicesConstants.JDBC_PERSISTENCE_CONFIG); diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationService.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationService.java index 374450ae..6fd0afcb 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationService.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationService.java @@ -18,7 +18,6 @@ package org.wso2.financial.services.accelerator.common.config; -import java.util.List; import java.util.Map; /** @@ -32,8 +31,4 @@ public interface FinancialServicesConfigurationService { public Map> getAuthorizeSteps(); - public Map> getAllowedScopes(); - - public Map> getAllowedAPIs(); - } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationServiceImpl.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationServiceImpl.java index 3f71e0fe..81d87e5f 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationServiceImpl.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/config/FinancialServicesConfigurationServiceImpl.java @@ -18,7 +18,6 @@ package org.wso2.financial.services.accelerator.common.config; -import java.util.List; import java.util.Map; /** @@ -45,14 +44,4 @@ public Map> getAuthorizeSteps() { return configParser.getConsentAuthorizeSteps(); } - - @Override - public Map> getAllowedScopes() { - return configParser.getAllowedScopes(); - } - - @Override - public Map> getAllowedAPIs() { - return configParser.getAllowedAPIs(); - } } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java index d1c7e915..b355b3ec 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/constant/FinancialServicesConstants.java @@ -29,7 +29,6 @@ public class FinancialServicesConstants { public static final String GATEWAY_CONFIG_TAG = "Gateway"; public static final String GATEWAY_EXECUTOR_CONFIG_TAG = "FinancialServicesGatewayExecutors"; public static final String EXECUTOR_CONFIG_TAG = "Executor"; - public static final String DCR_CONFIG_TAG = "DCR"; public static final String COMMON_IDENTITY_CACHE_ACCESS_EXPIRY = "Common.Identity.Cache.CacheAccessExpiry"; public static final String COMMON_IDENTITY_CACHE_MODIFY_EXPIRY = "Common.Identity.Cache.CacheModifiedExpiry"; public static final String JWKS_CONNECTION_TIMEOUT = "JWKS-Retriever.ConnectionTimeout"; @@ -38,30 +37,17 @@ public class FinancialServicesConstants { "DCR.RegistrationRequestParams.SoftwareEnvironmentIdentification.PropertyName"; public static final String DCR_SOFTWARE_ENV_IDENTIFICATION_VALUE_FOR_SANDBOX = "DCR.RegistrationRequestParams.SoftwareEnvironmentIdentification.PropertyValueForSandbox"; - public static final String REGULATORY_API_NAMES = "RegulatoryAPINames"; - public static final String API_NAME = "name"; - public static final String API_ROLE = "roles"; - public static final String REGULATORY_API = "API"; - public static final String JWT_HEAD = "head"; - public static final String JWT_BODY = "body"; - public static final String NEW_LINE = "[\r\n]"; public static final String JDBC_PERSISTENCE_CONFIG = "JDBCPersistenceManager.DataSource.Name"; public static final String DB_CONNECTION_VERIFICATION_TIMEOUT = "JDBCPersistenceManager.ConnectionVerificationTimeout"; public static final String CONSENT_CONFIG_TAG = "Consent"; - public static final String ALLOWED_SCOPES_CONFIG_TAG = "AllowedScopes"; - public static final String SCOPE_CONFIG_TAG = "Scope"; - public static final String TPP_MANAGEMENT_CONFIG_TAG = "TPPManagement"; public static final String CONNECTION_POOL_MAX_CONNECTIONS = "HTTPConnectionPool.MaxConnections"; public static final String CONNECTION_POOL_MAX_CONNECTIONS_PER_ROUTE = "HTTPConnectionPool.MaxConnectionsPerRoute"; public static final String IS_PSU_FEDERATED = "PSUFederatedAuthentication.Enabled"; public static final String PSU_FEDERATED_IDP_NAME = "PSUFederatedAuthentication.IDPName"; + public static final String IDEMPOTENCY_IS_ENABLED = "Consent.Idempotency.Enabled"; public static final String IDEMPOTENCY_ALLOWED_TIME = "Consent.Idempotency.AllowedTimeDuration"; - public static final String DOT_SEPARATOR = "."; - public static final String PRODUCTION = "PRODUCTION"; - public static final String SANDBOX = "SANDBOX"; - public static final String MANAGE_HANDLER = "Consent.ManageHandler"; public static final String AUTHORIZE_STEPS_CONFIG_TAG = "AuthorizeSteps"; public static final String STEP_CONFIG_TAG = "Step"; @@ -70,31 +56,37 @@ public class FinancialServicesConstants { public static final String CONSENT_VALIDATOR = "Consent.Validation.Validator"; public static final String ADMIN_HANDLER = "Consent.AdminHandler"; public static final String PRESERVE_CONSENT = "Consent.PreserveConsentLink"; - public static final String AUTH_SERVLET_EXTENSION = "Identity.AuthenticationWebApp.ServletExtension"; public static final String CONSENT_API_USERNAME = "Consent.ConsentAPICredentials.Username"; public static final String CONSENT_API_PASSWORD = "Consent.ConsentAPICredentials.Password"; + public static final String MAX_INSTRUCTED_AMOUNT = "Consent.Payments.MaximumInstructedAmount"; + + public static final String AUTH_SERVLET_EXTENSION = "Identity.AuthenticationWebApp.ServletExtension"; public static final String REQUEST_VALIDATOR = "Identity.Extensions.RequestObjectValidator"; public static final String RESPONSE_HANDLER = "Identity.Extensions.ResponseTypeHandler"; public static final String CLAIM_PROVIDER = "Identity.Extensions.ClaimProvider"; public static final String CONSENT_ID_CLAIM_NAME = "Identity.ConsentIDClaimName"; - public static final String MAX_INSTRUCTED_AMOUNT = "Consent.Payments.MaximumInstructedAmount"; public static final String REMOVE_USER_STORE_DOMAIN_FROM_SUBJECT = "Identity.TokenSubject.RemoveUserStoreDomainFromSubject"; public static final String REMOVE_TENANT_DOMAIN_FROM_SUBJECT = "Identity.TokenSubject.RemoveTenantDomainFromSubject"; + public static final String KEYSTORE_LOCATION_TAG = "Security.InternalKeyStore.Location"; + public static final String KEYSTORE_PASSWORD_TAG = "Security.InternalKeyStore.Password"; + public static final String SIGNING_ALIAS_TAG = "Security.InternalKeyStore.KeyAlias"; + public static final String SIGNING_KEY_PASSWORD = "Security.InternalKeyStore.KeyPassword"; + public static final String PUBLISHER_HOSTNAME = "PublisherURL"; public static final String REQUEST_ROUTER = "Gateway.RequestRouter"; public static final String GATEWAY_CACHE_EXPIRY = "Gateway.Cache.GatewayCache.CacheAccessExpiry"; public static final String GATEWAY_CACHE_MODIFIED_EXPIRY = "Gateway.Cache.GatewayCache.CacheModifiedExpiry"; public static final String CONSENT_VALIDATION_ENDPOINT = "Gateway.ConsentValidationEndpoint"; - public static final String KEYSTORE_LOCATION_TAG = "Security.InternalKeyStore.Location"; - public static final String KEYSTORE_PASSWORD_TAG = "Security.InternalKeyStore.Password"; - public static final String SIGNING_ALIAS_TAG = "Security.InternalKeyStore.KeyAlias"; - public static final String SIGNING_KEY_PASSWORD = "Security.InternalKeyStore.KeyPassword"; + public static final String VALIDATE_JWT = "Gateway.DCR.RequestJWTValidation"; + public static final String JWKS_ENDPOINT_NAME = "Gateway.DCR.JWKSEndpointName"; + public static final String SSA_CLIENT_NAME = "Gateway.DCR.SSAClientName"; + public static final String DCR_USE_SOFTWAREID_AS_APPNAME = "Gateway.DCR.UseSoftwareIdAsAppName"; //Event Notifications Constants - public static final String EVENT_NOTIFICATION_GENERATOR = "EventNotifications.NotificationGeneration." + - "NotificationGenerator"; + public static final String EVENT_NOTIFICATION_GENERATOR = + "EventNotifications.NotificationGeneration.NotificationGenerator"; public static final String TOKEN_ISSUER = "EventNotifications.NotificationGeneration.TokenIssuer"; public static final String MAX_SETS_TO_RETURN = "EventNotifications.NotificationGeneration.NumberOfSetsToReturn"; public static final String SIGNING_ALIAS = "EventNotifications.SigningAlias"; @@ -117,4 +109,11 @@ public class FinancialServicesConstants { "EventNotifications.Realtime.EventNotificationThreadPoolSize"; public static final String REALTIME_EVENT_NOTIFICATION_REQUEST_GENERATOR = "EventNotifications.Realtime.RequestGenerator"; + + public static final String JWT_HEAD = "head"; + public static final String JWT_BODY = "body"; + public static final String NEW_LINE = "[\r\n]"; + public static final String DOT_SEPARATOR = "."; + public static final String PRODUCTION = "PRODUCTION"; + public static final String SANDBOX = "SANDBOX"; } diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/JWTUtils.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/JWTUtils.java index 1b6bace0..b1e060ee 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/JWTUtils.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/main/java/org/wso2/financial/services/accelerator/common/util/JWTUtils.java @@ -39,7 +39,6 @@ import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; import com.nimbusds.jwt.proc.DefaultJWTProcessor; -import net.minidev.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -91,19 +90,17 @@ public class JWTUtils { * @return json object containing requested jwt part * @throws ParseException if an error occurs while parsing the jwt */ - public static JSONObject decodeRequestJWT(String jwtToken, String jwtPart) throws ParseException { - - JSONObject jsonObject = new JSONObject(); + public static String decodeRequestJWT(String jwtToken, String jwtPart) throws ParseException { JWSObject plainObject = JWSObject.parse(jwtToken); - if ("head".equals(jwtPart)) { - jsonObject = plainObject.getHeader().toJSONObject(); - } else if ("body".equals(jwtPart)) { - jsonObject = plainObject.getPayload().toJSONObject(); + if (FinancialServicesConstants.JWT_HEAD.equals(jwtPart)) { + return plainObject.getHeader().toString(); + } else if (FinancialServicesConstants.JWT_BODY.equals(jwtPart)) { + return plainObject.getPayload().toString(); } - return jsonObject; + return StringUtils.EMPTY; } @@ -121,7 +118,7 @@ public static JSONObject decodeRequestJWT(String jwtToken, String jwtPart) throw * object */ @Generated(message = "Excluding from code coverage since can not call this method due to external https call") - public static boolean validateJWTSignature(String jwtString, String jwksUri, String algorithm) + public static JWTClaimsSet validateJWTSignature(String jwtString, String jwksUri, String algorithm) throws ParseException, BadJOSEException, JOSEException, MalformedURLException { int defaultConnectionTimeout = 3000; @@ -155,8 +152,7 @@ public static boolean validateJWTSignature(String jwtString, String jwksUri, Str jwtProcessor.setJWSKeySelector(keySelector); // Process the token, set optional context parameters. SimpleSecurityContext securityContext = new SimpleSecurityContext(); - jwtProcessor.process((SignedJWT) jwt, securityContext); - return true; + return jwtProcessor.process((SignedJWT) jwt, securityContext); } /** diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/test/java/org/wso2/financial/services/accelerator/common/test/FSConfigParserTests.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/test/java/org/wso2/financial/services/accelerator/common/test/FSConfigParserTests.java index 007d5a5c..a1983a25 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/test/java/org/wso2/financial/services/accelerator/common/test/FSConfigParserTests.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.common/src/test/java/org/wso2/financial/services/accelerator/common/test/FSConfigParserTests.java @@ -26,12 +26,9 @@ import org.wso2.financial.services.accelerator.common.util.CarbonUtils; import java.io.File; -import java.util.List; import java.util.Map; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; /** * Test class for Config Parser functionality. @@ -85,12 +82,6 @@ public void testConfigParserInit() { assertEquals(stepsConfig.get("Retrieve").get(1), "org.wso2.financial.services.accelerator.common.test.CustomStep1"); - Map> apiMap = configParser.getAllowedAPIs(); - List roles = apiMap.get("DynamicClientRegistration"); - Assert.assertNotNull(apiMap); - Assert.assertNotNull(apiMap.get("DynamicClientRegistration")); - assertNotNull(apiMap.get("AccountandTransactionAPI")); - assertTrue(roles.contains("AISP")); } @Test(priority = 5) diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/exception/FSExecutorException.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/exception/FSExecutorException.java new file mode 100644 index 00000000..5a71cfb1 --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/exception/FSExecutorException.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.exception; + +/** + * Financial Services executor exception class. + */ +public class FSExecutorException extends Exception { + + private String errorCode; + private String errorPayload; + + public FSExecutorException(String message, String errorCode, String errorPayload) { + + super(message); + this.errorCode = errorCode; + this.errorPayload = errorPayload; + } + + public FSExecutorException(String message, Throwable cause) { + + super(message, cause); + } + + public FSExecutorException(String message) { + super(message); + } + + public FSExecutorException(Throwable cause, String errorCode, String errorPayload) { + + super(cause); + this.errorCode = errorCode; + this.errorPayload = errorPayload; + } + + public FSExecutorException(String message, Throwable cause, String errorCode, String errorPayload) { + + super(message, cause); + this.errorCode = errorCode; + this.errorPayload = errorPayload; + } + + public String getErrorCode() { + + return errorCode; + } + + public void setErrorCode(String errorCode) { + + this.errorCode = errorCode; + } + + public String getErrorPayload() { + + return errorPayload; + } + + public void setErrorPayload(String errorPayload) { + + this.errorPayload = errorPayload; + } +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/dcr/DCRExecutor.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/dcr/DCRExecutor.java new file mode 100644 index 00000000..b5afcaad --- /dev/null +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/executor/impl/dcr/DCRExecutor.java @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + *

+ * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.financial.services.accelerator.gateway.executor.impl.dcr; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; +import org.wso2.financial.services.accelerator.common.constant.FinancialServicesConstants; +import org.wso2.financial.services.accelerator.common.constant.FinancialServicesErrorCodes; +import org.wso2.financial.services.accelerator.common.util.Generated; +import org.wso2.financial.services.accelerator.common.util.JWTUtils; +import org.wso2.financial.services.accelerator.gateway.executor.core.FinancialServicesGatewayExecutor; +import org.wso2.financial.services.accelerator.gateway.executor.exception.FSExecutorException; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIRequestContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSAPIResponseContext; +import org.wso2.financial.services.accelerator.gateway.executor.model.FSExecutorError; +import org.wso2.financial.services.accelerator.gateway.internal.GatewayDataHolder; +import org.wso2.financial.services.accelerator.gateway.util.GatewayConstants; + +import java.net.MalformedURLException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Map; + +import javax.ws.rs.HttpMethod; + +/** + * Executor for DCR. + */ +public class DCRExecutor implements FinancialServicesGatewayExecutor { + + private static final Log log = LogFactory.getLog(DCRExecutor.class); + private static final Map configs = GatewayDataHolder.getInstance() + .getFinancialServicesConfigurationService().getConfigurations(); + + @Override + public void preProcessRequest(FSAPIRequestContext fsapiRequestContext) { + + if (fsapiRequestContext.isError()) { + return; + } + + boolean validateJWT = true; + if (configs.containsKey(FinancialServicesConstants.VALIDATE_JWT)) { + validateJWT = Boolean.parseBoolean(configs.get(FinancialServicesConstants.VALIDATE_JWT).toString()); + } + + if (validateJWT) { + String payload = fsapiRequestContext.getRequestPayload(); + try { + String httpMethod = fsapiRequestContext.getMsgInfo().getHttpMethod(); + if (HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod)) { + if (payload != null) { + //decode request jwt + String decodedRequest = JWTUtils.decodeRequestJWT(payload, "body"); + + //Check whether decodedRequest is null + if (decodedRequest == null) { + throw new FSExecutorException("invalid_client_metadata", + FinancialServicesErrorCodes.BAD_REQUEST_CODE, + "Provided jwt is malformed and cannot be decoded"); + } + + JSONObject decodedRequestObj = new JSONObject(decodedRequest); + JSONObject decodedSSA; + //Check whether the SSA exists and decode the SSA + if (decodedRequestObj.has(GatewayConstants.SOFTWARE_STATEMENT) && + decodedRequestObj.getString(GatewayConstants.SOFTWARE_STATEMENT) != null) { + String ssa = JWTUtils.decodeRequestJWT(decodedRequestObj + .getString(GatewayConstants.SOFTWARE_STATEMENT), "body"); + decodedSSA = new JSONObject(ssa); + } else { + //Throwing an exception whn SSA is not found + throw new FSExecutorException("invalid_client_metadata", + FinancialServicesErrorCodes.BAD_REQUEST_CODE, + "Required parameter software statement cannot be null"); + } + JWTClaimsSet requestClaims = validateRequestSignature(payload, decodedSSA); + String isDcrPayload = constructIsDcrPayload(requestClaims, decodedSSA); + + fsapiRequestContext.setModifiedPayload(isDcrPayload); + Map requestHeaders = fsapiRequestContext.getMsgInfo().getHeaders(); + requestHeaders.remove("Content-Type"); + Map addedHeaders = fsapiRequestContext.getAddedHeaders(); + addedHeaders.put(GatewayConstants.CONTENT_TYPE_TAG, GatewayConstants.JSON_CONTENT_TYPE); + fsapiRequestContext.setAddedHeaders(addedHeaders); + fsapiRequestContext.getMsgInfo().setHeaders(requestHeaders); + } else { + handleBadRequestError(fsapiRequestContext, "Malformed request found"); + } + } + } catch (ParseException e) { + log.error("Error occurred while decoding the provided jwt", e); + handleBadRequestError(fsapiRequestContext, "Malformed request JWT"); + } catch (BadJOSEException e) { + log.error("Error occurred while validating the signature", e); + handleBadRequestError(fsapiRequestContext, "Invalid request signature. " + e.getMessage()); + } catch (JOSEException | MalformedURLException e) { + log.error("Error occurred while validating the signature", e); + handleBadRequestError(fsapiRequestContext, "Invalid request signature"); + } catch (FSExecutorException e) { + log.error("Error occurred while validating the signature", e); + handleBadRequestError(fsapiRequestContext, e.getErrorPayload()); + } + } + } + + @Override + public void postProcessRequest(FSAPIRequestContext fsapiRequestContext) { + + } + + @Override + public void preProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + } + + @Override + public void postProcessResponse(FSAPIResponseContext fsapiResponseContext) { + + } + + private void handleBadRequestError(FSAPIRequestContext fsapiRequestContext, String message) { + + //catch errors and set to context + FSExecutorError error = new FSExecutorError("Bad request", + "invalid_client_metadata", message, "400"); + ArrayList executorErrors = fsapiRequestContext.getErrors(); + executorErrors.add(error); + fsapiRequestContext.setError(true); + fsapiRequestContext.setErrors(executorErrors); + + } + + @Generated(message = "Excluding from unit tests since there is an external http call") + private JWTClaimsSet validateRequestSignature(String payload, JSONObject decodedSSA) + throws ParseException, JOSEException, BadJOSEException, MalformedURLException, + FSExecutorException { + + String jwksEndpointName = configs.get(FinancialServicesConstants.JWKS_ENDPOINT_NAME).toString(); + //validate request signature + String jwksEndpoint = decodedSSA.getString(jwksEndpointName); + SignedJWT signedJWT = SignedJWT.parse(payload); + String alg = signedJWT.getHeader().getAlgorithm().getName(); + return JWTUtils.validateJWTSignature(payload, jwksEndpoint, alg); + } + + /** + * Convert the given JWT claims set to a JSON string. + * + * @param jwtClaimsSet The JWT claims set. + * + * @return The JSON string. + */ + @SuppressWarnings("unchecked") + public static String constructIsDcrPayload(JWTClaimsSet jwtClaimsSet, JSONObject decodedSSA) { + + JSONObject jsonObject = new JSONObject(jwtClaimsSet.getClaims()); + + // Convert the iat and exp claims into seconds + if (jwtClaimsSet.getIssueTime() != null) { + jsonObject.put(GatewayConstants.IAT, jwtClaimsSet.getIssueTime().getTime() / 1000); + } + if (jwtClaimsSet.getExpirationTime() != null) { + jsonObject.put(GatewayConstants.EXP, jwtClaimsSet.getExpirationTime().getTime() / 1000); + } + + jsonObject.put(GatewayConstants.CLIENT_NAME, getApplicationName(jwtClaimsSet, decodedSSA)); + jsonObject.put(GatewayConstants.JWKS_URI, decodedSSA.getString(configs + .get(FinancialServicesConstants.JWKS_ENDPOINT_NAME).toString())); + jsonObject.put(GatewayConstants.TOKEN_TYPE, GatewayConstants.JWT); + jsonObject.put(GatewayConstants.REQUIRE_SIGNED_OBJ, true); + jsonObject.put(GatewayConstants.TLS_CLIENT_CERT_ACCESS_TOKENS, true); + + return jsonObject.toString(); + } + + /** + * Retrieves the application name from the registration request. + * + * @param request registration or update request + * @param decodedSSA Decoded SSA + * @return The application name + */ + public static String getApplicationName(JWTClaimsSet request, JSONObject decodedSSA) { + boolean useSoftwareIdAsAppName = Boolean.parseBoolean(configs + .get(FinancialServicesConstants.DCR_USE_SOFTWAREID_AS_APPNAME).toString()); + if (useSoftwareIdAsAppName) { + // If the request does not contain a software statement, get the software Id directly from the request + if (StringUtils.isEmpty(request.getClaims().get(GatewayConstants.SOFTWARE_STATEMENT).toString())) { + return request.getClaims().get(GatewayConstants.SOFTWARE_STATEMENT).toString(); + } + return decodedSSA.getString(GatewayConstants.SOFTWARE_ID); + } + return getSafeApplicationName(decodedSSA + .getString(configs.get(FinancialServicesConstants.SSA_CLIENT_NAME).toString())); + } + + public static String getSafeApplicationName(String applicationName) { + + if (StringUtils.isEmpty(applicationName)) { + throw new IllegalArgumentException("Application name should be a valid string"); + } + + String sanitizedInput = applicationName.trim().replaceAll(GatewayConstants.DISALLOWED_CHARS_PATTERN, + GatewayConstants.SUBSTITUTE_STRING); + return StringUtils.abbreviate(sanitizedInput, GatewayConstants.ABBREVIATED_STRING_LENGTH); + + } +} diff --git a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java index 0575c608..44e9bd6b 100644 --- a/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java +++ b/financial-services-accelerator/components/org.wso2.financial.services.accelerator.gateway/src/main/java/org/wso2/financial/services/accelerator/gateway/util/GatewayConstants.java @@ -63,4 +63,19 @@ public class GatewayConstants { public static final String MESSAGE = "message"; public static final String DESCRIPTION = "description"; public static final String LINKS = "links"; + public static final String IAT = "iat"; + public static final String EXP = "exp"; + public static final String AUD = "aud"; + public static final String CLIENT_NAME = "client_name"; + public static final String JWKS_URI = "jwks_uri"; + public static final String TOKEN_TYPE = "token_type_extension"; + public static final String APP_OWNER = "ext_application_owner"; + public static final String REQUIRE_SIGNED_OBJ = "require_signed_request_object"; + public static final String TLS_CLIENT_CERT_ACCESS_TOKENS = "tls_client_certificate_bound_access_tokens"; + public static final String JWT = "JWT"; + public static final String SOFTWARE_STATEMENT = "software_statement"; + public static final String SOFTWARE_ID = "software_id"; + public static final String DISALLOWED_CHARS_PATTERN = "([~!#$;%^&*+={}\\s\\|\\\\<>\\\"\\'\\/,\\]\\[\\(\\)])"; + public static final String SUBSTITUTE_STRING = "_"; + public static final int ABBREVIATED_STRING_LENGTH = 70; }