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.Base64;
28 import org.reactivestreams.Publisher;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 import org.springframework.core.io.buffer.DataBuffer;
33 import org.springframework.http.server.reactive.ServerHttpRequest;
34 import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
35 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
36 import org.springframework.util.MultiValueMap;
37 import org.springframework.web.server.ServerWebExchange;
38 import org.springframework.web.server.WebFilter;
39 import org.springframework.web.server.WebFilterChain;
40 import reactor.core.publisher.Flux;
41 import reactor.core.publisher.Mono;
42 import reactor.util.context.Context;
43 import reactor.util.context.ContextView;
45 public class ReactiveEntryExitFilter implements WebFilter {
47 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
48 private static final String BEARER_PREFIX = "Bearer ";
49 private static final String FACILITY_KEY = "facility";
50 private static final String SUBJECT_KEY = "subject";
53 public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
55 // sets FACILITY_KEY and SUBJECT_KEY in MDC
56 auditLog(exchange.getRequest());
58 String subject = MDC.get(SUBJECT_KEY);
59 String facility = MDC.get(FACILITY_KEY);
61 ServerHttpRequest httpRequest = exchange.getRequest();
62 MultiValueMap<String, String> queryParams = httpRequest.getQueryParams();
63 logger.info("Request received with path: {}, and the Request Id: {}, with HTTP Method: {}", httpRequest.getPath(),
64 exchange.getRequest().getId(), exchange.getRequest().getMethod());
65 if (!queryParams.isEmpty())
66 logger.trace("For the request Id: {}, the Query parameters are: {}", exchange.getRequest().getId(), queryParams);
68 ServerHttpRequestDecorator loggingServerHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
72 public Flux<DataBuffer> getBody() {
73 return Flux.deferContextual(contextView ->
74 // Now, return the original body flux with a doFinally to clear MDC after processing
75 super.getBody().doOnNext(dataBuffer -> {
76 requestBody = dataBuffer.toString(StandardCharsets.UTF_8); // Read the bytes from the DataBuffer
77 logger.trace("For the request ID: {} the received request body: {}", exchange.getRequest().getId(), requestBody);
78 }).doOnEach(signal -> MDC.clear())
79 .doFinally(signal -> MDC.clear())); // Clear MDC after the request body is processed
82 ServerHttpResponseDecorator loggingServerHttpResponseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
84 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
85 return Mono.deferContextual(contextView ->
86 super.writeWith(Flux.from(body).doOnNext(dataBuffer -> {
87 String responseBody = dataBuffer.toString(StandardCharsets.UTF_8);
88 restoreFromContextToMdc(contextView);
89 logger.info("For the request ID: {} the Status code of the response: {}",
90 exchange.getRequest().getId(), getStatusCode());
91 logger.trace("For the request ID: {} the response is: {} ",
92 exchange.getRequest().getId(), responseBody);
93 })).doFinally(signalType -> MDC.clear())); // Clear MDC to prevent leakage
96 return chain.filter(exchange.mutate()
97 .request(loggingServerHttpRequestDecorator)
98 .response(loggingServerHttpResponseDecorator)
100 .contextWrite(Context.of(FACILITY_KEY, facility, SUBJECT_KEY, subject))
101 .doFinally(signalType -> MDC.clear());
104 private void auditLog(ServerHttpRequest request) {
105 String authHeader = request.getHeaders().getFirst("Authorization");
106 String subject = "n/av";
107 if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
108 String token = authHeader.substring(BEARER_PREFIX.length());
109 subject = this.getSubjectFromToken(token);
112 String facility = "log audit";
114 MDC.put(SUBJECT_KEY, subject);
115 MDC.put(FACILITY_KEY, facility);
118 private String getSubjectFromToken(String token) {
120 String[] chunks = token.split("\\.");
121 if (chunks.length < 2) {
122 logger.warn("Invalid JWT: Missing payload");
126 Base64.Decoder decoder = Base64.getUrlDecoder();
127 String payload = new String(decoder.decode(chunks[1]));
128 JsonObject jsonObject = JsonParser.parseString(payload).getAsJsonObject();
130 if (jsonObject.has("upn")) {
131 return sanitize(jsonObject.get("upn").getAsString());
132 } else if (jsonObject.has("preferred_username")) {
133 return sanitize(jsonObject.get("preferred_username").getAsString());
134 } else if (jsonObject.has("sub")) {
135 return sanitize(jsonObject.get("sub").getAsString());
137 } catch (Exception e) {
138 logger.warn("Failed to extract subject from token: {}", e.getMessage());
143 private String sanitize(String input) {
147 // Replace dangerous characters
148 return input.replaceAll("[\r\n]", "") // Remove newlines
149 .replaceAll("[{}()<>]", "") // Remove brackets
150 .replaceAll("[^\\p{Alnum}\\p{Punct}\\s]", ""); // Remove non-printable characters
153 private void restoreFromContextToMdc(ContextView context) {
154 context.getOrEmpty(FACILITY_KEY).ifPresent(value -> MDC.put(FACILITY_KEY, value.toString()));
155 context.getOrEmpty(SUBJECT_KEY).ifPresent(value -> MDC.put(SUBJECT_KEY, value.toString()));