Skip to content

Commit

Permalink
fix: Add missing encoded characters filter for services (#3701)
Browse files Browse the repository at this point in the history
* Fixed encoded slashes filter and added missing encoded characters filter

Signed-off-by: Elena Kubantseva <[email protected]>

* Added and fixed some tests

Signed-off-by: Elena Kubantseva <[email protected]>

* Added description to the filter

Signed-off-by: Elena Kubantseva <[email protected]>

* Addressed review comments

Signed-off-by: Elena Kubantseva <[email protected]>

* changed default value for enableUrlEncodedCharacters

Signed-off-by: Elena Kubantseva <[email protected]>

* removed unneeded annotations

Signed-off-by: Elena Kubantseva <[email protected]>

* removed public from tests

Signed-off-by: Elena Kubantseva <[email protected]>

---------

Signed-off-by: Elena Kubantseva <[email protected]>
Co-authored-by: Pavel Jareš <[email protected]>
  • Loading branch information
arxioly and pj892031 authored Aug 27, 2024
1 parent eb98b13 commit b5f945d
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ private EurekaMetadataDefinition() {
public static final String SERVICE_DESCRIPTION = "apiml.service.description";
public static final String SERVICE_EXTERNAL_URL = "apiml.service.externalUrl";
public static final String SERVICE_SUPPORTING_CLIENT_CERT_FORWARDING = "apiml.service.supportClientCertForwarding";
public static final String ENABLE_URL_ENCODED_CHARACTERS = "apiml.enableUrlEncodedCharacters";
public static final String APIML_ID = "apiml.service.apimlId";

public static final String API_INFO = "apiml.apiInfo";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.buf.EncodedSolidusHandling;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class UrlTomcatCustomizer implements TomcatConnectorCustomizer {

@Override
public void customize(Connector connector) {
connector.setAllowBackslash(true);
connector.setEncodedSolidusHandling(EncodedSolidusHandling.PASS_THROUGH.getValue());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.gateway.filters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import org.zowe.apiml.message.core.Message;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import reactor.core.publisher.Flux;

import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class AbstractEncodedCharactersFilterFactory extends AbstractGatewayFilterFactory<Object> {

private final MessageService messageService;
private final ObjectMapper mapper;
private final LocaleContextResolver localeContextResolver;
private final String messageKey;
private final WebSessionManager sessionManager = new DefaultWebSessionManager();
private final ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();

@InjectApimlLogger
private final ApimlLogger apimlLog = ApimlLogger.empty();

protected abstract boolean shouldFilter(String uri);

/**
* Filters requests by checking for encoded characters in the URI.
* If encoded characters are not allowed and found, returns a BAD_REQUEST response.
* Otherwise, proceeds with the filter chain.
*
* @return GatewayFilter
*/
@Override
public GatewayFilter apply(Object routeId) {
return ((exchange, chain) -> {
String uri = exchange.getRequest().getURI().toString();

if (!shouldFilter(uri)) {
return chain.filter(exchange);
}

var serverWebExchange = new DefaultServerWebExchange(exchange.getRequest(), exchange.getResponse(), sessionManager, serverCodecConfigurer, localeContextResolver);
serverWebExchange.getResponse().setRawStatusCode(SC_BAD_REQUEST);
serverWebExchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_VALUE);

Message message = messageService.createMessage(messageKey, uri);
try {
DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(mapper.writeValueAsBytes(message.mapToView()));
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
} catch (JsonProcessingException e) {
apimlLog.log("org.zowe.apiml.security.errorWritingResponse", e.getMessage());
throw new RuntimeException(e);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.gateway.filters;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.zowe.apiml.message.core.MessageService;

/**
* This filter should run on all requests for services, which do not have enabled encoded characters in URL
* <p>
* Special characters encoding is enabled on Tomcat so this filter takes over responsibility
* for filtering them.
* Encoded characters in URL are allowed by default.
*/

@Component
public class ForbidEncodedCharactersFilterFactory extends AbstractEncodedCharactersFilterFactory {

private static final char[] PROHIBITED_CHARACTERS = {'%', ';', '\\'};

public ForbidEncodedCharactersFilterFactory(MessageService messageService, ObjectMapper mapper, LocaleContextResolver localeContextResolver) {
super(messageService, mapper, localeContextResolver, "org.zowe.apiml.gateway.requestContainEncodedCharacter");
}

@Override
protected boolean shouldFilter(String uri) {
return StringUtils.containsAny(uri, PROHIBITED_CHARACTERS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,79 +10,30 @@

package org.zowe.apiml.gateway.filters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import org.zowe.apiml.message.core.Message;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import reactor.core.publisher.Flux;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
* This filter checks if encoded slashes in the URI are allowed based on configuration.
* If not allowed and encoded slashes are present, it returns a BAD_REQUEST response.
*/
@Component
@RequiredArgsConstructor
public class ForbidEncodedSlashesFilterFactory extends AbstractGatewayFilterFactory<Object> {
@ConditionalOnProperty(name = "apiml.service.allowEncodedSlashes", havingValue = "false", matchIfMissing = true)
public class ForbidEncodedSlashesFilterFactory extends AbstractEncodedCharactersFilterFactory {

private final MessageService messageService;
private final ObjectMapper mapper;
private static final String ENCODED_SLASH = "%2f";

private final LocaleContextResolver localeContextResolver;
private final WebSessionManager sessionManager = new DefaultWebSessionManager();
private final ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();

@InjectApimlLogger
private final ApimlLogger apimlLog = ApimlLogger.empty();
public ForbidEncodedSlashesFilterFactory(MessageService messageService, ObjectMapper mapper, LocaleContextResolver localeContextResolver) {
super(messageService, mapper, localeContextResolver, "org.zowe.apiml.gateway.requestContainEncodedSlash");
}

/**
* Filters requests to check for encoded slashes in the URI.
* If encoded slashes are not allowed and found, returns a BAD_REQUEST response.
* Otherwise, proceeds with the filter chain.
*
* @return Allowed Encoded slashes filter.
*/
@Override
public GatewayFilter apply(Object routeId) {
return ((exchange, chain) -> {
String uri = exchange.getRequest().getURI().toString();
String decodedUri = URLDecoder.decode(uri, StandardCharsets.UTF_8);

if (uri.equals(decodedUri)) {
return chain.filter(exchange);
}

var serverWebExchange = new DefaultServerWebExchange(exchange.getRequest(), exchange.getResponse(), sessionManager, serverCodecConfigurer, localeContextResolver);
serverWebExchange.getResponse().setRawStatusCode(SC_BAD_REQUEST);
serverWebExchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_VALUE);

Message message = messageService.createMessage("org.zowe.apiml.gateway.requestContainEncodedSlash", uri);
try {
DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(mapper.writeValueAsBytes(message.mapToView()));
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
} catch (JsonProcessingException e) {
apimlLog.log("org.zowe.apiml.security.errorWritingResponse", e.getMessage());
throw new RuntimeException(e);
}
});
protected boolean shouldFilter(String uri) {
return StringUtils.containsIgnoreCase(uri, ENCODED_SLASH);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@
import org.zowe.apiml.util.StringUtils;
import reactor.core.publisher.Flux;

import java.util.*;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.zowe.apiml.constants.EurekaMetadataDefinition.APIML_ID;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.ENABLE_URL_ENCODED_CHARACTERS;
import static org.zowe.apiml.constants.EurekaMetadataDefinition.SERVICE_SUPPORTING_CLIENT_CERT_FORWARDING;

@Service
Expand Down Expand Up @@ -133,6 +139,14 @@ List<FilterDefinition> getPostRoutingFilters(ServiceInstance serviceInstance) {
forwardClientCertFilter.setName("ForwardClientCertFilterFactory");
serviceRelated.add(forwardClientCertFilter);
}
//Allow encoded characters by default
if (!Optional.ofNullable(serviceInstance.getMetadata().get(ENABLE_URL_ENCODED_CHARACTERS))
.map(Boolean::parseBoolean)
.orElse(true)) {
FilterDefinition forbidEncodedCharactersFilter = new FilterDefinition();
forbidEncodedCharactersFilter.setName("ForbidEncodedCharactersFilterFactory");
serviceRelated.add(forbidEncodedCharactersFilter);
}

FilterDefinition pageRedirectionFilter = new FilterDefinition();
pageRedirectionFilter.setName("PageRedirectionFilterFactory");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

package org.zowe.apiml.gateway.config;

import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.buf.EncodedSolidusHandling;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class UrlTomcatCustomizerTest {

private final Connector connector = new Connector();

@Test
void givenConnector_whenCustomize_thenCustomized() {
UrlTomcatCustomizer urlTomcatCustomizer = new UrlTomcatCustomizer();
urlTomcatCustomizer.customize(connector);
assertTrue(connector.getAllowBackslash());
assertEquals(EncodedSolidusHandling.PASS_THROUGH.getValue(), connector.getEncodedSolidusHandling());
}
}
Loading

0 comments on commit b5f945d

Please sign in to comment.