2 * ========================LICENSE_START=================================
4 * ======================================================================
5 * Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
6 * ======================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ========================LICENSE_END===================================
21 package org.onap.ccsdk.oran.a1policymanagementservice.util.v3;
23 import com.google.gson.JsonObject;
24 import com.google.gson.JsonParser;
25 import java.lang.invoke.MethodHandles;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Base64;
30 import java.util.List;
32 import org.reactivestreams.Publisher;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import org.springframework.core.io.buffer.DataBuffer;
37 import org.springframework.http.server.reactive.ServerHttpRequest;
38 import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
39 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
40 import org.springframework.util.MultiValueMap;
41 import org.springframework.web.server.ServerWebExchange;
42 import org.springframework.web.server.WebFilter;
43 import org.springframework.web.server.WebFilterChain;
44 import org.springframework.web.util.pattern.PathPattern;
45 import reactor.core.publisher.Flux;
46 import reactor.core.publisher.Mono;
47 import reactor.util.context.Context;
48 import reactor.util.context.ContextView;
50 public class ReactiveEntryExitFilter implements WebFilter {
52 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
53 private static final String BEARER_PREFIX = "Bearer ";
54 private static final String FACILITY_KEY = "facility";
55 private static final String SUBJECT_KEY = "subject";
57 private final List<PathPattern> excludedPatterns = new ArrayList<>();
59 public ReactiveEntryExitFilter excludePathPatterns(PathPattern... patterns) {
60 excludedPatterns.addAll(Arrays.asList(patterns));
65 public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
67 // Skip processing for excluded patterns
68 for (PathPattern pattern : excludedPatterns) {
69 if (pattern.matches(exchange.getRequest().getPath().pathWithinApplication())) {
70 return chain.filter(exchange);
74 // sets FACILITY_KEY and SUBJECT_KEY in MDC
75 auditLog(exchange.getRequest());
77 String subject = MDC.get(SUBJECT_KEY);
78 String facility = MDC.get(FACILITY_KEY);
80 ServerHttpRequest httpRequest = exchange.getRequest();
81 MultiValueMap<String, String> queryParams = httpRequest.getQueryParams();
82 logger.info("Request received with path: {}, and the Request Id: {}, with HTTP Method: {}", httpRequest.getPath(),
83 exchange.getRequest().getId(), exchange.getRequest().getMethod());
84 if (!queryParams.isEmpty())
85 logger.trace("For the request Id: {}, the Query parameters are: {}", exchange.getRequest().getId(), queryParams);
87 ServerHttpRequestDecorator loggingServerHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
91 public Flux<DataBuffer> getBody() {
92 return Flux.deferContextual(contextView ->
93 // Now, return the original body flux with a doFinally to clear MDC after processing
94 super.getBody().doOnNext(dataBuffer -> {
95 requestBody = dataBuffer.toString(StandardCharsets.UTF_8); // Read the bytes from the DataBuffer
96 logger.trace("For the request ID: {} the received request body: {}", exchange.getRequest().getId(), requestBody);
97 }).doOnEach(signal -> MDC.clear())
98 .doFinally(signal -> MDC.clear())); // Clear MDC after the request body is processed
101 ServerHttpResponseDecorator loggingServerHttpResponseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
103 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
104 return Mono.deferContextual(contextView ->
105 super.writeWith(Flux.from(body).doOnNext(dataBuffer -> {
106 String responseBody = dataBuffer.toString(StandardCharsets.UTF_8);
107 restoreFromContextToMdc(contextView);
108 logger.info("For the request ID: {} the Status code of the response: {}",
109 exchange.getRequest().getId(), getStatusCode());
110 logger.trace("For the request ID: {} the response is: {} ",
111 exchange.getRequest().getId(), responseBody);
112 })).doFinally(signalType -> MDC.clear())); // Clear MDC to prevent leakage
115 return chain.filter(exchange.mutate()
116 .request(loggingServerHttpRequestDecorator)
117 .response(loggingServerHttpResponseDecorator)
119 .contextWrite(Context.of(FACILITY_KEY, facility, SUBJECT_KEY, subject))
120 .doFinally(signalType -> MDC.clear());
123 private void auditLog(ServerHttpRequest request) {
124 String authHeader = request.getHeaders().getFirst("Authorization");
125 String subject = "n/av";
126 if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
127 String token = authHeader.substring(BEARER_PREFIX.length());
128 subject = this.getSubjectFromToken(token);
131 String facility = "log audit";
133 MDC.put(SUBJECT_KEY, subject);
134 MDC.put(FACILITY_KEY, facility);
137 private String getSubjectFromToken(String token) {
139 String[] chunks = token.split("\\.");
140 if (chunks.length < 2) {
141 logger.warn("Invalid JWT: Missing payload");
145 Base64.Decoder decoder = Base64.getUrlDecoder();
146 String payload = new String(decoder.decode(chunks[1]));
147 JsonObject jsonObject = JsonParser.parseString(payload).getAsJsonObject();
149 if (jsonObject.has("upn")) {
150 return sanitize(jsonObject.get("upn").getAsString());
151 } else if (jsonObject.has("preferred_username")) {
152 return sanitize(jsonObject.get("preferred_username").getAsString());
153 } else if (jsonObject.has("sub")) {
154 return sanitize(jsonObject.get("sub").getAsString());
156 } catch (Exception e) {
157 logger.warn("Failed to extract subject from token: {}", e.getMessage());
162 private String sanitize(String input) {
166 // Replace dangerous characters
167 return input.replaceAll("[\r\n]", "") // Remove newlines
168 .replaceAll("[{}()<>]", "") // Remove brackets
169 .replaceAll("[^\\p{Alnum}\\p{Punct}\\s]", ""); // Remove non-printable characters
172 private void restoreFromContextToMdc(ContextView context) {
173 context.getOrEmpty(FACILITY_KEY).ifPresent(value -> MDC.put(FACILITY_KEY, value.toString()));
174 context.getOrEmpty(SUBJECT_KEY).ifPresent(value -> MDC.put(SUBJECT_KEY, value.toString()));