Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add changes to identify external traffic for infosec data publishing #24

Merged
merged 7 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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