Skip to content

Commit

Permalink
Merge pull request #24 from imesh94/fix/infosec-data-publishing
Browse files Browse the repository at this point in the history
Add changes to identify external traffic for infosec data publishing
  • Loading branch information
anjuchamantha authored Oct 28, 2024
2 parents c98f342 + 9339886 commit 41255c1
Show file tree
Hide file tree
Showing 13 changed files with 502 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -812,4 +812,26 @@ public int performConfigIntegerValueCheck(String key, int defaultValue) {
}
return defaultValue;
}

/**
* Get external traffic header name.
* This header should be set by the load balancer to identify the external traffic.
*
* @return String
*/
public String getExternalTrafficHeaderName() {

return ((String) getConfigElementFromKey(CommonConstants.EXTERNAL_TRAFFIC_HEADER_NAME)).trim();
}

/**
* Get external traffic expected header value.
* If this value is set in the header identified by the header name, the traffic is considered as external.
*
* @return String
*/
public String getExternalTrafficExpectedValue() {

return ((String) getConfigElementFromKey(CommonConstants.EXTERNAL_TRAFFIC_EXPECTED_VALUE)).trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public class CommonConstants {
public static final String VALIDATE_ACCOUNTS_ON_RETRIEVAL = "BNR.ValidateAccountsOnRetrieval";
public static final String ENABLE_CONSENT_REVOCATION = "BNR.EnableConsentRevocation";
public static final String CUSTOMER_TYPE_SELECTION_METHOD = "BNR.CustomerTypeSelectionMethod";
public static final String EXTERNAL_TRAFFIC_HEADER_NAME = "ExternalTraffic.HeaderName";
public static final String EXTERNAL_TRAFFIC_EXPECTED_VALUE = "ExternalTraffic.ExpectedValue";

// Http related constants
public static final String POST_METHOD = "POST";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,20 @@ public void testGetBNRCustomerTypeSelectionMethod() {
Assert.assertEquals(openBankingCDSConfigParser.getBNRCustomerTypeSelectionMethod(), "profile_selection");
}

@Test(priority = 8)
public void testGetExternalTrafficHeaderName() {
String dummyConfigFile = absolutePathForTestResources + "/open-banking-cds.xml";
OpenBankingCDSConfigParser openBankingCDSConfigParser = OpenBankingCDSConfigParser.getInstance(dummyConfigFile);
Assert.assertEquals(openBankingCDSConfigParser.getExternalTrafficHeaderName(), "X-External-Traffic");
}

@Test(priority = 8)
public void testGetExternalTrafficExpectedValue() {
String dummyConfigFile = absolutePathForTestResources + "/open-banking-cds.xml";
OpenBankingCDSConfigParser openBankingCDSConfigParser = OpenBankingCDSConfigParser.getInstance(dummyConfigFile);
Assert.assertEquals(openBankingCDSConfigParser.getExternalTrafficExpectedValue(), "true");
}

private void injectEnvironmentVariable(String key, String value)
throws ReflectiveOperationException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,8 @@
<DisclosureOptionsManagement>
<Enable>true</Enable>
</DisclosureOptionsManagement>
<ExternalTraffic>
<HeaderName>X-External-Traffic</HeaderName>
<ExpectedValue>true</ExpectedValue>
</ExternalTraffic>
</Server>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openbanking.cds.identity.filter;

import org.wso2.openbanking.cds.identity.utils.CDSIdentityConstants;

import javax.servlet.ServletRequest;

/**
* Authorize Data Publishing Filter.
* Implements custom logic related to publishing /authorize request data.
*/
public class AuthorizeDataPublishingFilter extends InfoSecDataPublishingFilter {

@Override
public boolean shouldPublishCurrentRequestData(ServletRequest request) {

// If the sessionDataKey query parameter is present, it is an internal redirect and should not be published.
return request.getParameter(CDSIdentityConstants.SESSION_DATA_KEY_PARAMETER) == null &&
super.shouldPublishCurrentRequestData(request);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* <p>
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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
Expand All @@ -26,7 +26,9 @@
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.openbanking.cds.common.config.OpenBankingCDSConfigParser;
import org.wso2.openbanking.cds.common.data.publisher.CDSDataPublishingService;
import org.wso2.openbanking.cds.common.utils.CommonConstants;
import org.wso2.openbanking.cds.identity.filter.constants.CDSFilterConstants;

import java.io.IOException;
Expand Down Expand Up @@ -54,6 +56,11 @@
public class InfoSecDataPublishingFilter implements Filter {

private static final Log LOG = LogFactory.getLog(InfoSecDataPublishingFilter.class);
private final Map<String, Object> configMap = OpenBankingCDSConfigParser.getInstance().getConfiguration();
private final String externalTrafficHeaderName = (String) configMap.get(CommonConstants
.EXTERNAL_TRAFFIC_HEADER_NAME);
private final String expectedExternalTrafficHeaderValue = (String) configMap.get(CommonConstants
.EXTERNAL_TRAFFIC_EXPECTED_VALUE);

@Override
public void init(FilterConfig filterConfig) {
Expand Down Expand Up @@ -83,7 +90,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
public void publishReportingData(HttpServletRequest request, HttpServletResponse response) {

if (Boolean.parseBoolean((String) OpenBankingConfigParser.getInstance().getConfiguration()
.get(DataPublishingConstants.DATA_PUBLISHING_ENABLED))) {
.get(DataPublishingConstants.DATA_PUBLISHING_ENABLED)) && shouldPublishCurrentRequestData(request)) {

String messageId = UUID.randomUUID().toString();

Expand All @@ -94,6 +101,9 @@ public void publishReportingData(HttpServletRequest request, HttpServletResponse
// publish api endpoint latency data
Map<String, Object> latencyData = generateLatencyDataMap(request, messageId);
CDSDataPublishingService.getCDSDataPublishingService().publishApiLatencyData(latencyData);
} else {
LOG.debug("Data publishing is disabled or the request is not an external request. Infosec data " +
"publishing skipped.");
}
}

Expand Down Expand Up @@ -245,4 +255,15 @@ private String extractClientId(HttpServletRequest request) {
public void destroy() {
}

/**
* Check whether data should be published for the current request.
*
* @return boolean
*/
public boolean shouldPublishCurrentRequestData(ServletRequest request) {

// If the request is internal traffic, no need to publish data
return expectedExternalTrafficHeaderValue.equalsIgnoreCase(
((HttpServletRequest) request).getHeader(externalTrafficHeaderName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ public class CDSIdentityConstants {
public static final String CODE_RESPONSE_TYPE = "code";
public static final String JWT_RESPONSE_MODE = "jwt";
public static final String UNSUPPORTED_RESPONSE_TYPE_ERROR = "unsupported_response_type";
public static final String SESSION_DATA_KEY_PARAMETER = "sessionDataKey";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/**
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openbanking.cds.identity.filter;

import com.wso2.openbanking.accelerator.common.config.OpenBankingConfigParser;
import com.wso2.openbanking.accelerator.common.exception.OpenBankingException;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.wso2.openbanking.cds.common.config.OpenBankingCDSConfigParser;
import org.wso2.openbanking.cds.common.data.publisher.CDSDataPublishingService;
import org.wso2.openbanking.cds.common.utils.CommonConstants;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.FilterChain;

import static org.mockito.Mockito.verify;
import static org.wso2.openbanking.cds.identity.filter.util.TestConstants.EXTERNAL_TRAFFIC_HEADER;
import static org.wso2.openbanking.cds.identity.filter.util.TestConstants.SESSION_DATA_KEY;

/**
* Test class for AuthorizeDataPublishingFilter Data Publishing Filter.
*/
@PowerMockIgnore("jdk.internal.reflect.*")
@PrepareForTest({OpenBankingCDSConfigParser.class, OpenBankingConfigParser.class, CDSDataPublishingService.class})
public class AuthorizeDataPublishingFilterTests extends PowerMockTestCase {

private OpenBankingCDSConfigParser openBankingCDSConfigParserMock;
private OpenBankingConfigParser openBankingConfigParserMock;

MockHttpServletRequest request;
MockHttpServletResponse response;
FilterChain filterChain;
AuthorizeDataPublishingFilter filter;
Map<String, Object> cdsConfigs = new HashMap<>();
Map<String, Object> configs = new HashMap<>();

@BeforeClass
public void init() throws OpenBankingException {

cdsConfigs.put(CommonConstants.EXTERNAL_TRAFFIC_HEADER_NAME, "X-External-Traffic");
cdsConfigs.put(CommonConstants.EXTERNAL_TRAFFIC_EXPECTED_VALUE, "true");
configs.put("DataPublishing.Enabled", "true");

openBankingCDSConfigParserMock = PowerMockito.mock(OpenBankingCDSConfigParser.class);
PowerMockito.mockStatic(OpenBankingCDSConfigParser.class);
PowerMockito.when(OpenBankingCDSConfigParser.getInstance()).thenReturn(openBankingCDSConfigParserMock);
PowerMockito.when(openBankingCDSConfigParserMock.getConfiguration()).thenReturn(cdsConfigs);

filter = Mockito.spy(AuthorizeDataPublishingFilter.class);
}

@BeforeMethod
public void beforeMethod() {

request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
filterChain = Mockito.spy(FilterChain.class);

openBankingConfigParserMock = PowerMockito.mock(OpenBankingConfigParser.class);
PowerMockito.mockStatic(OpenBankingConfigParser.class);
PowerMockito.when(OpenBankingConfigParser.getInstance()).thenReturn(openBankingConfigParserMock);
PowerMockito.when(openBankingConfigParserMock.getConfiguration()).thenReturn(configs);
}

@Test(description = "Test that data is published when X-External-Traffic header is true and " +
"session data key parameter is absent")
public void testDataPublishedWhenExternalTrafficHeaderPresentAndSessionDataKeyAbsent() throws Exception {

CDSDataPublishingService cdsDataPublishingServiceMock = PowerMockito.mock(CDSDataPublishingService.class);
PowerMockito.mockStatic(CDSDataPublishingService.class);
PowerMockito.when(CDSDataPublishingService.getCDSDataPublishingService()).thenReturn(
cdsDataPublishingServiceMock);

Mockito.doReturn(new HashMap<>()).when(filter).generateInvocationDataMap(Mockito.any(), Mockito.any(),
Mockito.any());
Mockito.doReturn(new HashMap<>()).when(filter).generateLatencyDataMap(Mockito.any(), Mockito.any());

request.addHeader(EXTERNAL_TRAFFIC_HEADER, "true");

filter.doFilter(request, response, filterChain);

// Verify that data is published
verify(cdsDataPublishingServiceMock).publishApiInvocationData(Mockito.anyMap());
verify(cdsDataPublishingServiceMock).publishApiLatencyData(Mockito.anyMap());
}

@Test(description = "Test that data is not published when X-External-Traffic header contains unexpected value " +
"and session data key parameter is present")
public void testDataNotPublishedWhenExternalTrafficHeaderIsNotTrueAndSessionDataKeyPresent()
throws Exception {

CDSDataPublishingService cdsDataPublishingServiceMock = PowerMockito.mock(CDSDataPublishingService.class);
PowerMockito.mockStatic(CDSDataPublishingService.class);
PowerMockito.when(CDSDataPublishingService.getCDSDataPublishingService()).thenReturn(
cdsDataPublishingServiceMock);

Mockito.doReturn(new HashMap<>()).when(filter).generateInvocationDataMap(Mockito.any(), Mockito.any(),
Mockito.any());
Mockito.doReturn(new HashMap<>()).when(filter).generateLatencyDataMap(Mockito.any(), Mockito.any());

request.addHeader(EXTERNAL_TRAFFIC_HEADER, "false");
request.setParameter(SESSION_DATA_KEY, UUID.randomUUID().toString());

filter.doFilter(request, response, filterChain);

// Verify that data is NOT published
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiInvocationData(Mockito.anyMap());
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiLatencyData(Mockito.anyMap());
}

@Test(description = "Test that data is not published when X-External-Traffic header is true and " +
"session data key attribute is present")
public void testDataNotPublishedWhenExternalTrafficHeaderAndSessionDataKeyPresent() throws
Exception {

CDSDataPublishingService cdsDataPublishingServiceMock = PowerMockito.mock(CDSDataPublishingService.class);
PowerMockito.mockStatic(CDSDataPublishingService.class);
PowerMockito.when(CDSDataPublishingService.getCDSDataPublishingService()).thenReturn(
cdsDataPublishingServiceMock);

Mockito.doReturn(new HashMap<>()).when(filter).generateInvocationDataMap(Mockito.any(), Mockito.any(),
Mockito.any());
Mockito.doReturn(new HashMap<>()).when(filter).generateLatencyDataMap(Mockito.any(), Mockito.any());

request.addHeader(EXTERNAL_TRAFFIC_HEADER, "true");
request.setParameter(SESSION_DATA_KEY, UUID.randomUUID().toString());

filter.doFilter(request, response, filterChain);

// Verify that data is NOT published
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiInvocationData(Mockito.anyMap());
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiLatencyData(Mockito.anyMap());
}

@Test(description = "Test that data is not published when X-External-Traffic header is absent and " +
"session data key parameter is present")
public void testDataNotPublishedWhenExternalTrafficHeaderAbsentAndSessionDataKeyPresent() throws
Exception {

CDSDataPublishingService cdsDataPublishingServiceMock = PowerMockito.mock(CDSDataPublishingService.class);
PowerMockito.mockStatic(CDSDataPublishingService.class);
PowerMockito.when(CDSDataPublishingService.getCDSDataPublishingService()).thenReturn(
cdsDataPublishingServiceMock);

Mockito.doReturn(new HashMap<>()).when(filter).generateInvocationDataMap(Mockito.any(), Mockito.any(),
Mockito.any());
Mockito.doReturn(new HashMap<>()).when(filter).generateLatencyDataMap(Mockito.any(), Mockito.any());

request.setParameter(SESSION_DATA_KEY, UUID.randomUUID().toString());

filter.doFilter(request, response, filterChain);

// Verify that data is NOT published
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiInvocationData(Mockito.anyMap());
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiLatencyData(Mockito.anyMap());
}

@Test(description = "Test that data is not published when both X-External-Traffic header " +
"and sessionDataKey absent")
public void testDataNotPublishedWhenExternalTrafficHeaderAndSessionDataKeyAbsent() throws Exception {

CDSDataPublishingService cdsDataPublishingServiceMock = PowerMockito.mock(CDSDataPublishingService.class);
PowerMockito.mockStatic(CDSDataPublishingService.class);
PowerMockito.when(CDSDataPublishingService.getCDSDataPublishingService()).
thenReturn(cdsDataPublishingServiceMock);

Mockito.doReturn(new HashMap<>()).when(filter).generateInvocationDataMap(Mockito.any(), Mockito.any(),
Mockito.any());
Mockito.doReturn(new HashMap<>()).when(filter).generateLatencyDataMap(Mockito.any(), Mockito.any());

filter.doFilter(request, response, filterChain);

// Verify that data is NOT published
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiInvocationData(Mockito.anyMap());
verify(cdsDataPublishingServiceMock, Mockito.never()).publishApiLatencyData(Mockito.anyMap());
}

}
Loading

0 comments on commit 41255c1

Please sign in to comment.