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; }