/* * ============LICENSE_START========================================== * org.onap.music * =================================================================== * Copyright (c) 2017 AT&T Intellectual Property * =================================================================== * Modifications Copyright (C) 2019 IBM * =================================================================== * 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. * * ============LICENSE_END============================================= * ==================================================================== */ package org.onap.music.eelf.logging; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.onap.music.authentication.AuthorizationError; import org.onap.music.main.MusicUtil; import com.fasterxml.jackson.databind.ObjectMapper; /** * * This is the first filter in the chain to be executed before cadi * authentication. The priority has been set in MusicApplication * through filter registration bean * * The responsibility of this filter is to validate header values as per * contract and write it to MDC and http response header back. * * * @author sp931a * */ public class MusicLoggingServletFilter implements Filter { private EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(MusicLoggingServletFilter.class); // client transaction id, specific to client system, set in properties public static final String CONVERSATION_ID = MusicUtil.getConversationIdPrefix() + "ConversationId"; // can be used as correlation-id in case of callback, also this can be passed to // other services for tracking. public static final String MESSAGE_ID = MusicUtil.getMessageIdPrefix() + "MessageId"; // client id would be the unique client source-system-id, i;e VALET or CONDUCTOR // etc public static final String CLIENT_ID = MusicUtil.getClientIdPrefix() + "ClientId"; // unique transaction of the source system private static final String TRANSACTION_ID = MusicUtil.getTransIdPrefix() + "Transaction-Id"; public MusicLoggingServletFilter() throws ServletException { super(); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { logger.info(EELFLoggerDelegate.securityLogger, "In MusicLogginServletFilter doFilter start() :: [\"+MusicUtil.getTransIdRequired()+\",\"+MusicUtil.getConversationIdRequired()+\",\"+MusicUtil.getClientIdRequired()+\",\"+MusicUtil.getMessageIdRequired()"); HttpServletRequest httpRequest = null; HttpServletResponse httpResponse = null; Map headerMap = null; Map upperCaseHeaderMap = null; if (null != request && null != response) { httpRequest = (HttpServletRequest) request; httpResponse = (HttpServletResponse) response; headerMap = getHeadersInfo(httpRequest); // The custom header values automatically converted into lower case, not sure // why ? So i had to covert all keys to upper case // The response header back to client will have all custom header values as // upper case. upperCaseHeaderMap = headerMap.entrySet().stream() .collect(Collectors.toMap(entry -> entry.getKey().toUpperCase(), entry -> entry.getValue())); // Enable/disable keys are present in /opt/app/music/etc/music.properties if (MusicUtil.getTransIdRequired() && !upperCaseHeaderMap.containsKey(TRANSACTION_ID.toUpperCase())) { populateError(httpResponse, "Transaction id '" + TRANSACTION_ID + "' required on http header"); return; } else { populateMDCAndResponseHeader(upperCaseHeaderMap, TRANSACTION_ID, "transactionId", MusicUtil.getTransIdRequired(), httpResponse); } if (MusicUtil.getConversationIdRequired() && !upperCaseHeaderMap.containsKey(CONVERSATION_ID.toUpperCase())) { populateError(httpResponse, "Conversation Id '" + CONVERSATION_ID + "' required on http header"); return; } else { populateMDCAndResponseHeader(upperCaseHeaderMap, CONVERSATION_ID, "conversationId", MusicUtil.getConversationIdRequired(), httpResponse); } if (MusicUtil.getMessageIdRequired() && !upperCaseHeaderMap.containsKey(MESSAGE_ID.toUpperCase())) { populateError(httpResponse, "Message Id '" + MESSAGE_ID + "' required on http header"); return; } else { populateMDCAndResponseHeader(upperCaseHeaderMap, MESSAGE_ID, "messageId", MusicUtil.getMessageIdRequired(), httpResponse); } if (MusicUtil.getClientIdRequired() && !upperCaseHeaderMap.containsKey(CLIENT_ID.toUpperCase())) { populateError(httpResponse, "Client Id '" + CLIENT_ID + "' required on http header"); return; } else { populateMDCAndResponseHeader(upperCaseHeaderMap, CLIENT_ID, "clientId", MusicUtil.getClientIdRequired(), httpResponse); } } logger.info(EELFLoggerDelegate.securityLogger, "In MusicLogginServletFilter doFilter. Header values validated sucessfully"); chain.doFilter(request, response); } private void populateError(HttpServletResponse httpResponse, String errMsg) throws IOException { AuthorizationError authError = new AuthorizationError(); authError.setResponseCode(HttpServletResponse.SC_BAD_REQUEST); authError.setResponseMessage(errMsg); byte[] responseToSend = restResponseBytes(authError); httpResponse.setHeader("Content-Type", "application/json"); // ideally the http response code should be 200, as this is a biz validation // failure. For now, keeping it consistent with other places. httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); httpResponse.getOutputStream().write(responseToSend); } private void populateMDCAndResponseHeader(Map headerMap, String idKey, String mdcKey, boolean isRequired, HttpServletResponse httpResponse) { idKey = idKey.trim().toUpperCase(); // 1. setting the keys & value in MDC for future use 2.setting the values in // http response header back to client. if (isRequired && (headerMap.containsKey(idKey))) { EELFLoggerDelegate.mdcPut(mdcKey, headerMap.get(idKey)); httpResponse.addHeader(idKey, headerMap.get(idKey)); } else { // do nothing } } private Map getHeadersInfo(HttpServletRequest request) { Map map = new HashMap(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } private byte[] restResponseBytes(AuthorizationError eErrorResponse) throws IOException { String serialized = new ObjectMapper().writeValueAsString(eErrorResponse); return serialized.getBytes(); } }