From d38922aa6387083a868a9676d5bc4b28153af540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Ra=C4=8Dansk=C3=BD?= Date: Thu, 12 Jan 2023 09:32:31 +0100 Subject: [PATCH 1/2] Fix #151: Add RequestContextConverter (#156) (cherry picked from commit 4a1e1087f2b72b1b6cd69523565bd19255a31a1b) --- README.md | 14 +++ http-common/pom.xml | 36 ++++++++ .../http/common/request/RequestContext.java | 38 ++++++++ .../request/RequestContextConverter.java | 90 +++++++++++++++++++ .../request/RequestContextConverterTest.java | 75 ++++++++++++++++ pom.xml | 1 + 6 files changed, 254 insertions(+) create mode 100644 http-common/pom.xml create mode 100644 http-common/src/main/java/com/wultra/core/http/common/request/RequestContext.java create mode 100644 http-common/src/main/java/com/wultra/core/http/common/request/RequestContextConverter.java create mode 100644 http-common/src/test/java/com/wultra/core/http/common/request/RequestContextConverterTest.java diff --git a/README.md b/README.md index ae4379d..f64d5b4 100644 --- a/README.md +++ b/README.md @@ -370,3 +370,17 @@ Auditing with parameters and type of audit message: param.put("operation_id", operationId); audit.info("an access message", AuditDetail.builder().type("ACCESS").params(param).build()); ``` + + +## Wultra HTTP Common + +The `http-common` project provides common functionality for HTTP stack. + + +### Features + + +#### RequestContextConverter + +`RequestContextConverter` converts `HttpServletRequest` to a Wultra specific class `RequestContext`. +This context object contains _user agent_ and best-effort guess of the _client IP address_. diff --git a/http-common/pom.xml b/http-common/pom.xml new file mode 100644 index 0000000..a56f50a --- /dev/null +++ b/http-common/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + io.getlime.core + lime-java-core-parent + 1.7.0-SNAPSHOT + + + http-common + Common HTTP functionality + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + \ No newline at end of file diff --git a/http-common/src/main/java/com/wultra/core/http/common/request/RequestContext.java b/http-common/src/main/java/com/wultra/core/http/common/request/RequestContext.java new file mode 100644 index 0000000..28ceb4c --- /dev/null +++ b/http-common/src/main/java/com/wultra/core/http/common/request/RequestContext.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Wultra s.r.o. + * + * Licensed 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 com.wultra.core.http.common.request; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +/** + * Context of the HTTP request. + * + * @author Petr Dvorak, petr@wultra.com + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +@Builder +@Getter +@ToString +@EqualsAndHashCode +public class RequestContext { + + private String ipAddress; + private String userAgent; + +} diff --git a/http-common/src/main/java/com/wultra/core/http/common/request/RequestContextConverter.java b/http-common/src/main/java/com/wultra/core/http/common/request/RequestContextConverter.java new file mode 100644 index 0000000..5febbd6 --- /dev/null +++ b/http-common/src/main/java/com/wultra/core/http/common/request/RequestContextConverter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Wultra s.r.o. + * + * Licensed 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 com.wultra.core.http.common.request; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Converter for HTTP request context information. + * + * @author Petr Dvorak, petr@wultra.com + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +public final class RequestContextConverter { + + /** + * List of HTTP headers that may contain the actual IP address + * when hidden behind a proxy component. + */ + private static final List HTTP_HEADERS_IP_ADDRESS = Collections.unmodifiableList(Arrays.asList( + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR" + )); + + private static final String HTTP_HEADER_USER_AGENT = "User-Agent"; + + private RequestContextConverter() { + throw new IllegalStateException("Should not be instantiated"); + } + + /** + * Convert HTTP Servlet Request to request context representation. + * + * @param source HttpServletRequest instance. + * @return Request context data. + */ + public static RequestContext convert(final HttpServletRequest source) { + if (source == null) { + return null; + } + return RequestContext.builder() + .userAgent(source.getHeader(HTTP_HEADER_USER_AGENT)) + .ipAddress(getClientIpAddress(source)) + .build(); + } + + /** + * Obtain the best-effort guess of the client IP address. + * + * @param request HttpServletRequest instance. + * @return Best-effort information about the client IP address. + */ + private static String getClientIpAddress(final HttpServletRequest request) { + for (String header : HTTP_HEADERS_IP_ADDRESS) { + final String ip = request.getHeader(header); + if (isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip; + } + } + return request.getRemoteAddr(); + } + + private static boolean isNotBlank(final String value) { + return !(value == null || value.trim().isEmpty()); + } +} diff --git a/http-common/src/test/java/com/wultra/core/http/common/request/RequestContextConverterTest.java b/http-common/src/test/java/com/wultra/core/http/common/request/RequestContextConverterTest.java new file mode 100644 index 0000000..d500f17 --- /dev/null +++ b/http-common/src/test/java/com/wultra/core/http/common/request/RequestContextConverterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2023 Wultra s.r.o. + * + * Licensed 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 com.wultra.core.http.common.request; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Test for {@link RequestContextConverter}. + * + * @author Lubos Racansky, lubos.racansky@wultra.com + */ +class RequestContextConverterTest { + + @Test + void testNullRequest() { + final RequestContext result = RequestContextConverter.convert(null); + + assertNull(result); + } + + @Test + void testUserAgentNull() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final RequestContext result = RequestContextConverter.convert(request); + + assertNull(result.getUserAgent()); + } + + @Test + void testUserAgent() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"); + + final RequestContext result = RequestContextConverter.convert(request); + + assertEquals("Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0", result.getUserAgent()); + } + + @Test + void testIpAddress() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final RequestContext result = RequestContextConverter.convert(request); + + assertEquals("127.0.0.1", result.getIpAddress()); + } + + @Test + void testIpAddressProxy() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Proxy-Client-IP", "\t"); + request.addHeader("HTTP_X_FORWARDED_FOR", "unKNOWN"); + request.addHeader("HTTP_X_FORWARDED", "192.168.1.134"); + + final RequestContext result = RequestContextConverter.convert(request); + + assertEquals("192.168.1.134", result.getIpAddress()); + } +} diff --git a/pom.xml b/pom.xml index 1081327..7a1e552 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ audit-base + http-common rest-model-base rest-client-base From 6b84497eed94ae4b3abbb3582d99a6b3f4dba2a1 Mon Sep 17 00:00:00 2001 From: Jan Dusil Date: Sat, 2 Dec 2023 18:58:42 +0100 Subject: [PATCH 2/2] Fix #151: Add RequestContextConverter (backport) --- http-common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-common/pom.xml b/http-common/pom.xml index a56f50a..efc8008 100644 --- a/http-common/pom.xml +++ b/http-common/pom.xml @@ -7,7 +7,7 @@ io.getlime.core lime-java-core-parent - 1.7.0-SNAPSHOT + 1.6.2-SNAPSHOT http-common