Repository: cxf
Updated Branches:
  refs/heads/3.1.x-fixes 6eb9cd56b -> b86ac6d0d


[CXF-7070] Avoiding logging the well-known senstive header values by default, 
patch from Andy McCright applied with a minor update, This closes #178


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/b86ac6d0
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/b86ac6d0
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/b86ac6d0

Branch: refs/heads/3.1.x-fixes
Commit: b86ac6d0d12c25ca8a50e8e4fcc494db258387f1
Parents: 6eb9cd5
Author: Sergey Beryozkin <sberyoz...@gmail.com>
Authored: Thu Oct 13 12:50:34 2016 +0100
Committer: Sergey Beryozkin <sberyoz...@gmail.com>
Committed: Thu Oct 13 12:52:06 2016 +0100

----------------------------------------------------------------------
 .../org/apache/cxf/transport/http/Headers.java  |  98 ++++++++++------
 .../apache/cxf/transport/http/HeadersTest.java  | 111 ++++++++++++++++---
 2 files changed, 156 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/b86ac6d0/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
----------------------------------------------------------------------
diff --git 
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java 
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
index 23b7975..7ec9f4a 100644
--- 
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
+++ 
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
@@ -61,8 +61,8 @@ public class Headers {
      * Each header value is added as a separate HTTP header, example, given A 
header with 'a' and 'b'
      * values, two A headers will be added as opposed to a single A header 
with the "a,b" value. 
      */
-    public static final String ADD_HEADERS_PROPERTY = 
"org.apache.cxf.http.add-headers";             
-       
+    public static final String ADD_HEADERS_PROPERTY = 
"org.apache.cxf.http.add-headers";
+
     public static final String PROTOCOL_HEADERS_CONTENT_TYPE = 
Message.CONTENT_TYPE.toLowerCase();
     public static final String HTTP_HEADERS_SETCOOKIE = "Set-Cookie";
     public static final String HTTP_HEADERS_LINK = "Link";
@@ -70,7 +70,10 @@ public class Headers {
     private static final String SET_EMPTY_REQUEST_CT_PROPERTY = 
"set.content.type.for.empty.request";
     private static final TimeZone TIME_ZONE_GMT = TimeZone.getTimeZone("GMT");
     private static final Logger LOG = LogUtils.getL7dLogger(Headers.class);
-    
+
+    private static final List<String> SENSITIVE_HEADERS = 
Arrays.asList("Authorization", "Proxy-Authorization");
+    private static final List<String> SENSITIVE_HEADER_MARKER = 
Arrays.asList("***");
+    private static final String ALLOW_LOGGING_SENSITIVE_HEADERS = 
"allow.logging.sensitive.headers";
     /**
      * Known HTTP headers whose values have to be represented as individual 
HTTP headers
      */
@@ -108,6 +111,25 @@ public class Headers {
         return name + "/" + version;
     }
     
+    /**
+     * Returns a traceable string representation of the passed-in headers map.
+     * The value for any keys in the map that are in the 
<code>SENSITIVE_HEADERS</code>
+     * array will be filtered out of the returned string.
+     * Note that this method is expensive as it will copy the map (except for 
the
+     * filtered keys), so it should be used sparingly - i.e. only when debug is
+     * enabled.
+     */
+    static String toString(Map<String, List<String>> headers, boolean 
logSensitiveHeaders) {
+        Map<String, List<String>> filteredHeaders = new TreeMap<String, 
List<String>>(String.CASE_INSENSITIVE_ORDER);
+        filteredHeaders.putAll(headers);
+        if (!logSensitiveHeaders) {
+            for (String filteredKey : SENSITIVE_HEADERS) {
+                filteredHeaders.put(filteredKey, SENSITIVE_HEADER_MARKER);
+            }
+        }
+        return filteredHeaders.toString();
+    }
+
     public Map<String, List<String>> headerMap() {
         return headers;
     }
@@ -189,7 +211,7 @@ public class Headers {
                     createMutableList(policy.getReferer()));
         }
     }
-    
+
     void setFromServerPolicy(HTTPServerPolicy policy) {
         if (policy.isSetCacheControl()) {
             headers.put("Cache-Control",
@@ -217,9 +239,8 @@ public class Headers {
         } else if (policy.isSetKeepAliveParameters()) {
             headers.put("Keep-Alive", 
createMutableList(policy.getKeepAliveParameters()));
         }
-        
-    
-        
+
+
     /*
      * TODO - hook up these policies
     <xs:attribute name="SuppressClientSendErrors" type="xs:boolean" 
use="optional" default="false">
@@ -241,8 +262,8 @@ public class Headers {
         headers.put("Proxy-Authorization",
                 createMutableList(authorization));
     }
-    
-    
+
+
     /**
      * While extracting the Message.PROTOCOL_HEADERS property from the Message,
      * this call ensures that the Message.PROTOCOL_HEADERS property is
@@ -254,7 +275,7 @@ public class Headers {
      */
     public static Map<String, List<String>> getSetProtocolHeaders(final 
Message message) {
         Map<String, List<String>> headers =
-            CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));  
      
+            CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
         if (null == headers) {
             headers = new TreeMap<String, 
List<String>>(String.CASE_INSENSITIVE_ORDER);
         } else if (headers instanceof HashMap) {
@@ -281,26 +302,31 @@ public class Headers {
     private static List<String> createMutableList(String val) {
         return new ArrayList<String>(Arrays.asList(new String[] {val}));
     }
-    
+
     /**
      * This procedure logs the PROTOCOL_HEADERS from the 
      * Message at the specified logging level.
      * 
+     * @param logger     The Logger to log to.
      * @param level   The Logging Level.
      * @param headers The Message protocol headers.
      */
-    void logProtocolHeaders(Level level) {
-        if (LOG.isLoggable(level)) {
-            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
-                List<String> headerList = entry.getValue();
+    static void logProtocolHeaders(Logger logger, Level level, 
+                                   Map<String, List<String>> headersMap,
+                                   boolean logSensitiveHeaders) {
+        if (logger.isLoggable(level)) {
+            for (Map.Entry<String, List<String>> entry : 
headersMap.entrySet()) {
+                String key = entry.getKey();
+                boolean sensitive = !logSensitiveHeaders && 
SENSITIVE_HEADERS.contains(key);
+                List<String> headerList = sensitive ? SENSITIVE_HEADER_MARKER 
: entry.getValue();
                 for (String value : headerList) {
-                    LOG.log(level, entry.getKey() + ": " 
+                    logger.log(level, key + ": " 
                         + (value == null ? "<null>" : value.toString()));
                 }
             }
         }
     }
-    
+
     /**
      * Set content type and protocol headers (Message.PROTOCOL_HEADERS) 
headers into the URL
      * connection. 
@@ -331,7 +357,6 @@ public class Headers {
                     // otherwise if it is GET then just drop it
                     dropContentType = true;
                 }
-                
             }
             if (!dropContentType) {
                 String ct = emptyRequest && !contentTypeSet ? "*/*" : 
determineContentType();
@@ -342,7 +367,7 @@ public class Headers {
         }
          
         transferProtocolHeadersToURLConnection(connection);
-        logProtocolHeaders(Level.FINE);
+        logProtocolHeaders(LOG, Level.FINE, headers, logSensitiveHeaders());
     }
 
     public String determineContentType() {
@@ -369,7 +394,7 @@ public class Headers {
         }
         return ct;
     }
-    
+
     /**
      * This procedure sets the URLConnection request properties
      * from the PROTOCOL_HEADERS in the message.
@@ -380,7 +405,7 @@ public class Headers {
         for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
             String header = entry.getKey();
             List<String> headerList = entry.getValue();
-            
+
             if (HttpHeaderHelper.CONTENT_TYPE.equalsIgnoreCase(header)) {
                 continue;
             }
@@ -404,7 +429,7 @@ public class Headers {
             connection.addRequestProperty("User-Agent", USER_AGENT);
         }
     }
-    
+
     /**
      * Copy the request headers into the message.
      * 
@@ -431,14 +456,19 @@ public class Headers {
             headers.put(Message.CONTENT_TYPE, 
Collections.singletonList(req.getContentType()));
         }
         if (LOG.isLoggable(Level.FINE)) {
-            LOG.log(Level.FINE, "Request Headers: " + headers.toString());
+            LOG.log(Level.FINE, "Request Headers: " + toString(headers, 
+                                                               
logSensitiveHeaders()));
         }
     }
 
+    private boolean logSensitiveHeaders() {
+        // Not allowed by default
+        return 
PropertyUtils.isTrue(message.getContextualProperty(ALLOW_LOGGING_SENSITIVE_HEADERS));
+    }
     private String getContentTypeFromMessage() {
         final String ct  = (String)message.get(Message.CONTENT_TYPE);
         final String enc = (String)message.get(Message.ENCODING);
-        
+
         if (null != ct 
             && null != enc
             && ct.indexOf("charset=") == -1
@@ -448,7 +478,7 @@ public class Headers {
             return ct;
         }
     }
-    
+
     // Assumes that response body is not available only
     // if Content-Length is available and set to 0
     private boolean isResponseBodyAvailable() {
@@ -465,7 +495,7 @@ public class Headers {
         }
         return true;
     }
-    
+
     /**
      * Copy the response headers into the response.
      * 
@@ -485,7 +515,7 @@ public class Headers {
         for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
             String header = entry.getKey();
             List<?> headerList = entry.getValue();
-            
+
             if (addHeaders || HTTP_HEADERS_SINGLE_VALUE_ONLY.contains(header)) 
{
                 for (int i = 0; i < headerList.size(); i++) {
                     Object headerObject = headerList.get(i);
@@ -500,18 +530,16 @@ public class Headers {
                     if (headerObject != null) {
                         sb.append(headerObjectToString(headerObject));
                     }
-                    
+
                     if (i + 1 < headerList.size()) {
                         sb.append(',');
                     }
                 }
                 response.setHeader(header, sb.toString());
             }
-
-            
         }
     }
-    
+
     private String headerObjectToString(Object headerObject) {
         if (headerObject.getClass() == String.class) {
             // Most likely 
@@ -521,7 +549,7 @@ public class Headers {
             // so that the below code may be pushed back to the JAX-RS 
             // front-end where non String header objects are more likely 
             // to be set. Though the below code may be generally useful
-            
+
             String headerString;
             if (headerObject instanceof Date) {
                 headerString = toHttpDate((Date)headerObject);
@@ -533,7 +561,7 @@ public class Headers {
             return headerString;
         }
     }
-    
+
     void removeContentType() {
         headers.remove(PROTOCOL_HEADERS_CONTENT_TYPE);
     }
@@ -551,12 +579,12 @@ public class Headers {
         dateFormat.setTimeZone(TIME_ZONE_GMT);
         return dateFormat;
     }
-    
+
     public static String toHttpDate(Date date) {
         SimpleDateFormat format = getHttpDateFormat();
         return format.format(date);
     }
-    
+
     public static String toHttpLanguage(Locale locale) {
         StringBuilder sb = new StringBuilder();
         sb.append(locale.getLanguage());

http://git-wip-us.apache.org/repos/asf/cxf/blob/b86ac6d0/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HeadersTest.java
----------------------------------------------------------------------
diff --git 
a/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HeadersTest.java
 
b/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HeadersTest.java
index 25be560..bf5cf84 100755
--- 
a/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HeadersTest.java
+++ 
b/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HeadersTest.java
@@ -23,6 +23,10 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -31,27 +35,28 @@ import org.apache.cxf.message.Message;
 import org.apache.cxf.message.MessageImpl;
 import org.easymock.EasyMock;
 import org.easymock.IMocksControl;
-import org.junit.After;
+
+import org.junit.AfterClass;
 import org.junit.Assert;
-import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
  *
  */
 public class HeadersTest extends Assert {
-    private IMocksControl control;
-    
-    @Before
-    public void setUp() {
+    private static IMocksControl control;
+
+    @BeforeClass
+    public static void setUpClass() {
         control = EasyMock.createNiceControl();
     }
-    
-    @After
-    public void tearDown() {
+
+    @AfterClass
+    public static void tearDown() {
         control.verify();
     }
-    
+
     @Test
     public void setHeadersTest() throws Exception {
         String[] headerNames = {"Content-Type", "authorization", "soapAction"};
@@ -60,7 +65,7 @@ public class HeadersTest extends Assert {
         for (int i = 0; i < headerNames.length; i++) {
             inmap.put(headerNames[i], Arrays.asList(headerValues[i]));
         }
-        
+
         HttpServletRequest req = control.createMock(HttpServletRequest.class);
         
EasyMock.expect(req.getHeaderNames()).andReturn(Collections.enumeration(inmap.keySet()));
         for (int i = 0; i < headerNames.length; i++) {
@@ -68,20 +73,20 @@ public class HeadersTest extends Assert {
                 andReturn(Collections.enumeration(inmap.get(headerNames[i])));
         }
         
EasyMock.expect(req.getContentType()).andReturn(headerValues[0]).anyTimes();
-        
+
         control.replay();
 
         Message message = new MessageImpl();
         message.put(AbstractHTTPDestination.HTTP_REQUEST, req);
-        
+
         Headers headers = new Headers(message);
         headers.copyFromRequest(req);
-        
+
         Map<String, List<String>> protocolHeaders = 
             CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
-        
+
         assertTrue("unexpected size", protocolHeaders.size() == 
headerNames.length);
-        
+
         assertEquals("unexpected header", 
protocolHeaders.get("Content-Type").get(0), headerValues[0]);
         assertEquals("unexpected header", 
protocolHeaders.get("content-type").get(0), headerValues[0]);
         assertEquals("unexpected header", 
protocolHeaders.get("CONTENT-TYPE").get(0), headerValues[0]);
@@ -91,11 +96,81 @@ public class HeadersTest extends Assert {
         assertEquals("unexpected header", 
protocolHeaders.get("authorization").get(0), headerValues[1]);
         assertEquals("unexpected header", 
protocolHeaders.get("AUTHORIZATION").get(0), headerValues[1]);
         assertEquals("unexpected header", 
protocolHeaders.get("authoriZATION").get(0), headerValues[1]);
-        
+
         assertEquals("unexpected header", 
protocolHeaders.get("SOAPAction").get(0), headerValues[2]);
         assertEquals("unexpected header", 
protocolHeaders.get("soapaction").get(0), headerValues[2]);
         assertEquals("unexpected header", 
protocolHeaders.get("SOAPACTION").get(0), headerValues[2]);
         assertEquals("unexpected header", 
protocolHeaders.get("soapAction").get(0), headerValues[2]);
-        
+
+    }
+
+    @Test
+    public void sensitiveHeadersTest() {
+        Map<String, List<String>> headerMap = new HashMap<String, 
List<String>>();
+        headerMap.put("Authorization", Arrays.asList("FAIL"));
+        headerMap.put("Proxy-Authorization", Arrays.asList("FAIL"));
+        headerMap.put("Content-Type", Arrays.asList("application/xml"));
+        headerMap.put("Accept", Arrays.asList("text/plain"));
+
+        String loggedString = Headers.toString(headerMap, false);
+        assertFalse("The value of a sensitive header could be logged: " + 
loggedString, loggedString.contains("FAIL"));
+        assertTrue("The value of a non-sensitive header would not be logged: " 
+ loggedString, 
+                   loggedString.contains("application/xml") && 
loggedString.contains("text/plain"));
+        assertTrue("Expected header keys were not logged: " + loggedString, 
+                   loggedString.contains("Authorization") && 
loggedString.contains("Proxy-Authorization")
+                   && loggedString.contains("Accept") && 
loggedString.contains("Content-Type"));
+    }
+
+    @Test
+    public void logProtocolHeadersTest() {
+        Map<String, List<String>> headerMap = new HashMap<String, 
List<String>>();
+        headerMap.put("Normal-Header", Arrays.asList("normal"));
+        headerMap.put("Multivalue-Header", Arrays.asList("first", "second"));
+        headerMap.put("Authorization", Arrays.asList("myPassword"));
+        headerMap.put("Null-Header", Arrays.asList((String)null));
+
+        //Set up test logger
+        Logger logger = Logger.getAnonymousLogger();
+        // remove all "normal" handlers and just use our custom handler for 
testing
+        logger.setUseParentHandlers(false);
+        logger.setLevel(Level.INFO);
+        for (Handler h : logger.getHandlers()) {
+            logger.removeHandler(h); 
+        }
+        logger.addHandler(new Handler() {
+
+            @Override
+            public void publish(LogRecord record) {
+                String msg = record.getMessage();
+                if (msg.startsWith("Normal-Header")) {
+                    assertTrue("Unexpected output for normal header - expected 
Normal-Header: normal, received " + msg,
+                               "Normal-Header: normal".equals(msg));
+                } else if (msg.startsWith("Multivalue-Header")) {
+                    assertTrue("Unexpected output for multi-value header - 
expected Multivalue-Header: first or " 
+                        + "Multivalue-Header: second, received: " + msg,
+                        "Multivalue-Header: first".equals(msg) || 
"Multivalue-Header: second".equals(msg));
+                } else if (msg.startsWith("Authorization")) {
+                    assertTrue("Unexpected output for sensitive header - 
expected Authorization: ***, received " + msg,
+                               "Authorization: ***".equals(msg));
+                } else if (msg.startsWith("Null-Header")) {
+                    assertTrue("Unexpected output for null header - expected 
Null-Header: <null>, received " + msg,
+                               "Null-Header: <null>".equals(msg));
+                } else {
+                    fail("Unexpected header logged: " + msg);
+                }
+
+            }
+
+            @Override
+            public void flush() {
+                // no-op
+            }
+
+            @Override
+            public void close() throws SecurityException {
+                // no-op
+            } });
+
+        Headers.logProtocolHeaders(logger, Level.INFO, headerMap, false);
     }
 }

Reply via email to