This is an automated email from the ASF dual-hosted git repository. radhikakundam pushed a commit to branch atlas-2.5 in repository https://gitbox.apache.org/repos/asf/atlas.git
commit 9a752f125617a71e0c85f68aab907b48a8fb3ac7 Author: pareshd <[email protected]> AuthorDate: Fri Apr 25 01:59:39 2025 +0530 ATLAS-5019: Add header in atlas login.jsp page (#335) (cherry picked from commit 008f7548752dafa25a7f4ba336b89be2557e0926) --- .../web/filters/AtlasAuthenticationFilter.java | 15 +-- .../web/filters/AtlasCSRFPreventionFilter.java | 2 +- .../filters/AtlasKnoxSSOAuthenticationFilter.java | 5 +- .../org/apache/atlas/web/filters/HeadersUtil.java | 77 +++++++++++---- .../atlas/web/servlets/AtlasHttpServlet.java | 22 ++--- .../apache/atlas/web/filters/HeaderUtilsTest.java | 108 +++++++++++++++++++++ 6 files changed, 178 insertions(+), 51 deletions(-) diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationFilter.java b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationFilter.java index e95828f4a..652af3a86 100644 --- a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationFilter.java +++ b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasAuthenticationFilter.java @@ -155,10 +155,6 @@ public class AtlasAuthenticationFilter extends AuthenticationFilter { throw new ServletException(e); } - if (configuration != null) { - headerProperties = ConfigurationConverter.getProperties(configuration.subset("atlas.headers")); - } - String tokenValidityStr = null; if (configuration != null) { @@ -416,16 +412,7 @@ public class AtlasAuthenticationFilter extends AuthenticationFilter { String action = httpRequest.getParameter("action"); String doAsUser = request.getParameter("doAs"); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_FRAME_OPTIONS_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_CONTENT_TYPE_OPTIONS_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_XSS_PROTECTION_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.STRICT_TRANSPORT_SEC_KEY); - - if (headerProperties != null) { - for (String headerKey : headerProperties.stringPropertyNames()) { - responseWrapper.setHeader(headerKey, headerProperties.getProperty(headerKey)); - } - } + HeadersUtil.setSecurityHeaders(responseWrapper); if (logoutHandler != null && supportTrustedProxy && StringUtils.isNotEmpty(doAsUser) && StringUtils.equals(action, RestUtil.TIMEOUT_ACTION)) { if (existingAuth != null) { diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasCSRFPreventionFilter.java b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasCSRFPreventionFilter.java index e62cde382..7dc12acc3 100644 --- a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasCSRFPreventionFilter.java +++ b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasCSRFPreventionFilter.java @@ -106,7 +106,7 @@ public class AtlasCSRFPreventionFilter implements Filter { final HttpServletResponse httpResponse = (HttpServletResponse) response; AtlasResponseRequestWrapper responseWrapper = new AtlasResponseRequestWrapper(httpResponse); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_FRAME_OPTIONS_KEY); + HeadersUtil.setSecurityHeaders(responseWrapper); if (isCSRF_ENABLED) { handleHttpInteraction(new ServletFilterHttpInteraction(httpRequest, httpResponse, chain)); diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasKnoxSSOAuthenticationFilter.java b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasKnoxSSOAuthenticationFilter.java index ac8de40c8..a5dca7aae 100644 --- a/webapp/src/main/java/org/apache/atlas/web/filters/AtlasKnoxSSOAuthenticationFilter.java +++ b/webapp/src/main/java/org/apache/atlas/web/filters/AtlasKnoxSSOAuthenticationFilter.java @@ -166,10 +166,7 @@ public class AtlasKnoxSSOAuthenticationFilter implements Filter { HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; AtlasResponseRequestWrapper responseWrapper = new AtlasResponseRequestWrapper(httpResponse); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_FRAME_OPTIONS_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_CONTENT_TYPE_OPTIONS_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.X_XSS_PROTECTION_KEY); - HeadersUtil.setHeaderMapAttributes(responseWrapper, HeadersUtil.STRICT_TRANSPORT_SEC_KEY); + HeadersUtil.setSecurityHeaders(responseWrapper); if (!ssoEnabled) { filterChain.doFilter(servletRequest, servletResponse); diff --git a/webapp/src/main/java/org/apache/atlas/web/filters/HeadersUtil.java b/webapp/src/main/java/org/apache/atlas/web/filters/HeadersUtil.java index cc9863112..dbec3cdbf 100644 --- a/webapp/src/main/java/org/apache/atlas/web/filters/HeadersUtil.java +++ b/webapp/src/main/java/org/apache/atlas/web/filters/HeadersUtil.java @@ -17,28 +17,39 @@ */ package org.apache.atlas.web.filters; +import com.google.common.annotations.VisibleForTesting; +import org.apache.atlas.ApplicationProperties; import org.apache.atlas.AtlasConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.Properties; public class HeadersUtil { - public static final String X_FRAME_OPTIONS_KEY = "X-Frame-Options"; - public static final String X_CONTENT_TYPE_OPTIONS_KEY = "X-Content-Type-Options"; - public static final String X_XSS_PROTECTION_KEY = "X-XSS-Protection"; - public static final String STRICT_TRANSPORT_SEC_KEY = "Strict-Transport-Security"; - public static final String CONTENT_SEC_POLICY_KEY = "Content-Security-Policy"; - public static final String X_FRAME_OPTIONS_VAL = "DENY"; - public static final String X_CONTENT_TYPE_OPTIONS_VAL = "nosniff"; - public static final String X_XSS_PROTECTION_VAL = "1; mode=block"; - public static final String STRICT_TRANSPORT_SEC_VAL = "max-age=31536000; includeSubDomains"; - public static final String CONTENT_SEC_POLICY_VAL = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data:; connect-src 'self'; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline';font-src 'self' data:"; - public static final String SERVER_KEY = "Server"; - public static final String USER_AGENT_KEY = "User-Agent"; - public static final String USER_AGENT_VALUE = "Mozilla"; - public static final String X_REQUESTED_WITH_KEY = "X-REQUESTED-WITH"; - public static final String X_REQUESTED_WITH_VALUE = "XMLHttpRequest"; - public static final int SC_AUTHENTICATION_TIMEOUT = 419; + public static final Logger LOG = LoggerFactory.getLogger(HeadersUtil.class); + + public static final String X_FRAME_OPTIONS_KEY = "X-Frame-Options"; + public static final String X_CONTENT_TYPE_OPTIONS_KEY = "X-Content-Type-Options"; + public static final String X_XSS_PROTECTION_KEY = "X-XSS-Protection"; + public static final String STRICT_TRANSPORT_SEC_KEY = "Strict-Transport-Security"; + public static final String CONTENT_SEC_POLICY_KEY = "Content-Security-Policy"; + public static final String X_FRAME_OPTIONS_VAL = "DENY"; + public static final String X_CONTENT_TYPE_OPTIONS_VAL = "nosniff"; + public static final String X_XSS_PROTECTION_VAL = "1; mode=block"; + public static final String STRICT_TRANSPORT_SEC_VAL = "max-age=31536000; includeSubDomains"; + public static final String CONTENT_SEC_POLICY_VAL = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data:; connect-src 'self'; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline';font-src 'self' data:"; + public static final String SERVER_KEY = "Server"; + public static final String USER_AGENT_KEY = "User-Agent"; + public static final String USER_AGENT_VALUE = "Mozilla"; + public static final String X_REQUESTED_WITH_KEY = "X-REQUESTED-WITH"; + public static final String X_REQUESTED_WITH_VALUE = "XMLHttpRequest"; + public static final int SC_AUTHENTICATION_TIMEOUT = 419; + public static final String CONFIG_PREFIX_HTTP_RESPONSE_HEADER = "atlas.headers"; private static final Map<String, String> HEADER_MAP = new HashMap<>(); @@ -50,22 +61,48 @@ public class HeadersUtil { return HEADER_MAP.get(header); } + public static Map<String, String> getAllHeaders() { + return new HashMap<>(HEADER_MAP); + } + public static void setHeaderMapAttributes(AtlasResponseRequestWrapper responseWrapper, String headerKey) { responseWrapper.setHeader(headerKey, HEADER_MAP.get(headerKey)); } public static void setSecurityHeaders(AtlasResponseRequestWrapper responseWrapper) { - for (Map.Entry<String, String> entry : HEADER_MAP.entrySet()) { - responseWrapper.setHeader(entry.getKey(), entry.getValue()); - } + HEADER_MAP.forEach((key, value) -> responseWrapper.setHeader(key, value)); } - static { + @VisibleForTesting + public static void initializeHttpResponseHeaders(Properties configuredHeaders) { + HEADER_MAP.clear(); + HEADER_MAP.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS_VAL); HEADER_MAP.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS_VAL); HEADER_MAP.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION_VAL); HEADER_MAP.put(STRICT_TRANSPORT_SEC_KEY, STRICT_TRANSPORT_SEC_VAL); HEADER_MAP.put(CONTENT_SEC_POLICY_KEY, CONTENT_SEC_POLICY_VAL); HEADER_MAP.put(SERVER_KEY, AtlasConfiguration.HTTP_HEADER_SERVER_VALUE.getString()); + + if (configuredHeaders != null) { + configuredHeaders.stringPropertyNames().forEach(name -> HEADER_MAP.put(name, configuredHeaders.getProperty(name))); + } + } + + static { + Properties configuredHeaders = null; + + try { + Configuration baseConfig = ApplicationProperties.get(); + Configuration headerConfig = ApplicationProperties.getSubsetConfiguration(baseConfig, CONFIG_PREFIX_HTTP_RESPONSE_HEADER); + + configuredHeaders = Optional.ofNullable(headerConfig) + .map(ConfigurationConverter::getProperties) + .orElseGet(Properties::new); + } catch (Exception e) { + LOG.info("Failed to load custom headers: {}", e.getMessage()); + } + + initializeHttpResponseHeaders(configuredHeaders); } } diff --git a/webapp/src/main/java/org/apache/atlas/web/servlets/AtlasHttpServlet.java b/webapp/src/main/java/org/apache/atlas/web/servlets/AtlasHttpServlet.java index e42e6d37d..04566a378 100644 --- a/webapp/src/main/java/org/apache/atlas/web/servlets/AtlasHttpServlet.java +++ b/webapp/src/main/java/org/apache/atlas/web/servlets/AtlasHttpServlet.java @@ -17,11 +17,11 @@ */ package org.apache.atlas.web.servlets; +import org.apache.atlas.web.filters.AtlasResponseRequestWrapper; +import org.apache.atlas.web.filters.HeadersUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,22 +29,20 @@ import javax.servlet.http.HttpServletResponse; public class AtlasHttpServlet extends HttpServlet { public static final Logger LOG = LoggerFactory.getLogger(AtlasHttpServlet.class); - public static final String TEXT_HTML = "text/html"; - public static final String XFRAME_OPTION = "X-Frame-Options"; - public static final String DENY = "DENY"; - public static final String ALLOW = "ALLOW"; + public static final String TEXT_HTML = "text/html"; + public static final String ALLOW = "ALLOW"; protected void includeResponse(HttpServletRequest request, HttpServletResponse response, String template) { try { response.setContentType(TEXT_HTML); - response.setHeader(XFRAME_OPTION, DENY); + AtlasResponseRequestWrapper responseWrapper = new AtlasResponseRequestWrapper(response); + HeadersUtil.setSecurityHeaders(responseWrapper); - ServletContext context = getServletContext(); - RequestDispatcher rd = context.getRequestDispatcher(template); - - rd.include(request, response); + getServletContext() + .getRequestDispatcher(template) + .include(request, response); } catch (Exception e) { - LOG.error("Error in AtlasHttpServlet {}", template, e); + LOG.error("Failed to include template [{}] in AtlasHttpServlet", template, e); } } } diff --git a/webapp/src/test/java/org/apache/atlas/web/filters/HeaderUtilsTest.java b/webapp/src/test/java/org/apache/atlas/web/filters/HeaderUtilsTest.java new file mode 100644 index 000000000..a6113a52f --- /dev/null +++ b/webapp/src/test/java/org/apache/atlas/web/filters/HeaderUtilsTest.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.atlas.web.filters; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class HeaderUtilsTest { + private Map<String, String> originalHeaders; + + @Before + public void setUp() throws Exception { + originalHeaders = new HashMap<>(HeadersUtil.getAllHeaders()); + } + + @After + public void tearDown() throws Exception { + HeadersUtil.initializeHttpResponseHeaders(convertMapToProperties(originalHeaders)); + } + + private Properties convertMapToProperties(Map<String, String> map) { + Properties props = new Properties(); + + map.forEach(props::setProperty); + + return props; + } + + @Test + public void testLoadHeadersFromProperties() { + HeadersUtil.initializeHttpResponseHeaders(createPropertiesWithHeaders("X-Custom-Header-One", "ValueOne", "X-Custom-Header-Two", "ValueTwo")); + + assertEquals("ValueOne", HeadersUtil.getHeaderMap("X-Custom-Header-One")); + assertEquals("ValueTwo", HeadersUtil.getHeaderMap("X-Custom-Header-Two")); + } + + @Test + public void testGetHeaderMapReturnsNullForMissingKey() { + HeadersUtil.initializeHttpResponseHeaders(createPropertiesWithHeaders("X-Exists", "ExistsValue")); + + assertNull(HeadersUtil.getHeaderMap("X-Does-Not-Exist")); + } + + @Test + public void testSetSecurityHeadersSetsAllHeaders() { + HeadersUtil.initializeHttpResponseHeaders(createPropertiesWithHeaders("X-One", "Val1", "X-Two", "Val2")); + + AtlasResponseRequestWrapper mockWrapper = mock(AtlasResponseRequestWrapper.class); + HeadersUtil.setSecurityHeaders(mockWrapper); + + verify(mockWrapper).setHeader("X-One", "Val1"); + verify(mockWrapper).setHeader("X-Two", "Val2"); + } + + @Test + public void testSetHeaderMapAttributes() { + HeadersUtil.initializeHttpResponseHeaders(createPropertiesWithHeaders("X-Test", "HeaderTestValue")); + + AtlasResponseRequestWrapper mockWrapper = mock(AtlasResponseRequestWrapper.class); + HeadersUtil.setHeaderMapAttributes(mockWrapper, "X-Test"); + + verify(mockWrapper).setHeader("X-Test", "HeaderTestValue"); + } + + @Test + public void testDefaultHeadersArePresent() { + HeadersUtil.initializeHttpResponseHeaders(null); + + assertEquals("DENY", HeadersUtil.getHeaderMap(HeadersUtil.X_FRAME_OPTIONS_KEY)); + assertEquals("nosniff", HeadersUtil.getHeaderMap(HeadersUtil.X_CONTENT_TYPE_OPTIONS_KEY)); + assertEquals("1; mode=block", HeadersUtil.getHeaderMap(HeadersUtil.X_XSS_PROTECTION_KEY)); + } + + private Properties createPropertiesWithHeaders(String... headers) { + Properties props = new Properties(); + + for (int i = 0; i < headers.length / 2; i++) { + props.setProperty(headers[i * 2], headers[(i *2) + 1]); + } + + return props; + } +}
