FELIX-4330 - Make SSL headers configurable:

- applied a slightly different approach as suggested by [~fmeschbe] by splitting the header
  name and value into 2 different configuration properties allowing any header variant to
  be supported;
- also made the header used to obtain forwarded client certificates configurable, as it 
  appears to have at least one other representation in the wild;
- added metatype description for the managed service to document the configuration properties.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1596230 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/sslfilter/pom.xml b/http/sslfilter/pom.xml
index 682c069..0f30f60 100644
--- a/http/sslfilter/pom.xml
+++ b/http/sslfilter/pom.xml
@@ -44,6 +44,13 @@
     <build>
         <plugins>
             <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
@@ -52,6 +59,13 @@
                         <Bundle-Activator>
                             org.apache.felix.http.sslfilter.internal.SslFilterActivator
                         </Bundle-Activator>
+                        <Import-Package>
+                            org.osgi.service.cm;resolution:=optional,
+                            *
+                        </Import-Package>
+                        <DynamicImport-Package>
+                            org.osgi.service.cm;version="[1.2,2)"
+                        </DynamicImport-Package>)
                     </instructions>
                 </configuration>
             </plugin>
@@ -77,7 +91,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.http.api</artifactId>
-            <version>2.3.0-SNAPSHOT</version>
+            <version>${felix.http.api.version}</version>
         </dependency>
 
         <!-- Test Dependencies -->
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
index 82f85f9..b9b7576 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
@@ -18,15 +18,24 @@
  */
 package org.apache.felix.http.sslfilter.internal;
 
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Dictionary;
 import java.util.Hashtable;
-import java.util.Map;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import javax.servlet.ServletException;
 
 import org.apache.felix.http.api.ExtHttpService;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
 import org.osgi.service.log.LogService;
 import org.osgi.util.tracker.ServiceTracker;
 
@@ -35,13 +44,56 @@
  */
 public class HttpServiceTracker extends ServiceTracker
 {
-    private final Map<ServiceReference, SslFilter> filters;
+    private final ConcurrentMap<ServiceReference, SslFilter> filters;
 
+    private ServiceRegistration configReceiver;
+
+    @SuppressWarnings("serial")
     public HttpServiceTracker(BundleContext context)
     {
         super(context, ExtHttpService.class.getName(), null);
 
-        this.filters = new HashMap<ServiceReference, SslFilter>();
+        this.filters = new ConcurrentHashMap<ServiceReference, SslFilter>();
+    }
+
+    @Override
+    public void open(boolean trackAllServices)
+    {
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_PID, SslFilter.PID);
+
+        this.configReceiver = super.context.registerService(ManagedService.class.getName(), new ServiceFactory()
+        {
+            public Object getService(Bundle bundle, ServiceRegistration registration)
+            {
+                return new ManagedService()
+                {
+                    public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException
+                    {
+                        configureFilters(properties);
+                    }
+                };
+            }
+            
+            public void ungetService(Bundle bundle, ServiceRegistration registration, Object service)
+            {
+                // Nop
+            }
+        }, props);
+
+        super.open(trackAllServices);
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+
+        if (this.configReceiver != null)
+        {
+            this.configReceiver.unregister();
+            this.configReceiver = null;
+        }
     }
 
     public Object addingService(ServiceReference reference)
@@ -54,7 +106,7 @@
             {
                 service.registerFilter(filter, ".*", new Hashtable(), 0, null);
 
-                this.filters.put(reference, filter);
+                this.filters.putIfAbsent(reference, filter);
 
                 SystemLogger.log(LogService.LOG_DEBUG, "SSL filter registered...");
             }
@@ -69,7 +121,7 @@
 
     public void removedService(ServiceReference reference, Object service)
     {
-        SslFilter filter = (SslFilter) this.filters.remove(reference);
+        SslFilter filter = this.filters.remove(reference);
         if (filter != null)
         {
             ((ExtHttpService) service).unregisterFilter(filter);
@@ -79,4 +131,13 @@
 
         super.removedService(reference, service);
     }
+
+    void configureFilters(@SuppressWarnings("rawtypes") final Dictionary properties) throws ConfigurationException
+    {
+        List<SslFilter> filters = new ArrayList<SslFilter>(this.filters.values());
+        for (SslFilter sslFilter : filters)
+        {
+            sslFilter.configure(properties);
+        }
+    }
 }
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
index 7460ea0..21ec51b 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
@@ -20,6 +20,7 @@
 
 import java.io.IOException;
 import java.security.cert.CertificateException;
+import java.util.Dictionary;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -29,33 +30,45 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 
+import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.log.LogService;
 
 public class SslFilter implements Filter
 {
-    // request header indicating an SSL endpoint proxy
-    private static final String X_FORWARD_SSL_HEADER = "X-Forwarded-SSL";
+    public static final String PID = "org.apache.felix.http.sslfilter.SslFilter";
 
-    // value indicating an SSL endpoint proxy
-    private static final String X_FORWARD_SSL_VALUE = "on";
+    private static final String DEFAULT_SSL_HEADER = "X-Forwarded-SSL";
+    private static final String DEFAULT_SSL_VALUE = "on";
+    private static final String DEFAULT_CERT_HEADER = "X-Forwarded-SSL-Certificate";
 
-    // request header indicating an SSL client certificate (if available)
-    private static final String X_FORWARD_SSL_CERTIFICATE_HEADER = "X-Forwarded-SSL-Certificate";
+    private static final String PROP_SSL_HEADER = "ssl-forward.header";
+    private static final String PROP_SSL_VALUE = "ssl-forward.value";
+    private static final String PROP_SSL_CERT_KEY = "ssl-forward-cert.header";
 
-    public void init(FilterConfig config)
+    private volatile ConfigHolder config;
+
+    SslFilter()
     {
-        // No explicit initialization needed...
+        this.config = new ConfigHolder(DEFAULT_SSL_HEADER, DEFAULT_SSL_VALUE, DEFAULT_CERT_HEADER);
+    }
+
+    public void destroy()
+    {
+        // No explicit destroy needed...
     }
 
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
     {
+        final ConfigHolder cfg = this.config;
+
         HttpServletRequest httpReq = (HttpServletRequest) req;
-        if (X_FORWARD_SSL_VALUE.equalsIgnoreCase(httpReq.getHeader(X_FORWARD_SSL_HEADER)))
+        if (cfg.sslValue.equalsIgnoreCase(httpReq.getHeader(cfg.sslHeader)))
         {
             try
             {
-                // In case this fails, we fall back to the original HTTP request, which is better than nothing...
-                httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(X_FORWARD_SSL_CERTIFICATE_HEADER));
+                // In case this fails, we fall back to the original HTTP
+                // request, which is better than nothing...
+                httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(cfg.certHeader));
             }
             catch (CertificateException e)
             {
@@ -78,8 +91,65 @@
         }
     }
 
-    public void destroy()
+    public void init(FilterConfig config)
     {
-        // No explicit destroy needed...
+        // make sure there is some configuration
+    }
+
+    void configure(@SuppressWarnings("rawtypes") final Dictionary properties) throws ConfigurationException
+    {
+        String certHeader = DEFAULT_CERT_HEADER;
+        String sslHeader = DEFAULT_SSL_HEADER;
+        String sslValue = DEFAULT_SSL_VALUE;
+
+        if (properties != null)
+        {
+            certHeader = getOptionalString(properties, PROP_SSL_CERT_KEY);
+            sslHeader = getMandatoryString(properties, PROP_SSL_HEADER);
+            sslValue = getMandatoryString(properties, PROP_SSL_VALUE);
+        }
+
+        this.config = new ConfigHolder(sslHeader, sslValue, certHeader);
+
+        SystemLogger.log(LogService.LOG_INFO, "SSL filter (re)configured with: " + "SSL forward header = '" + sslHeader + "'; SSL forward value = '" + sslValue + "'; SSL certificate header = '"
+            + certHeader + "'.");
+    }
+
+    private String getOptionalString(@SuppressWarnings("rawtypes") Dictionary properties, String key) throws ConfigurationException
+    {
+        Object raw = properties.get(key);
+        if (raw == null || "".equals(((String) raw).trim()))
+        {
+            return null;
+        }
+        if (!(raw instanceof String))
+        {
+            throw new ConfigurationException(key, "invalid value");
+        }
+        return ((String) raw).trim();
+    }
+
+    private String getMandatoryString(@SuppressWarnings("rawtypes") Dictionary properties, String key) throws ConfigurationException
+    {
+        String value = getOptionalString(properties, key);
+        if (value == null)
+        {
+            throw new ConfigurationException(key, "missing value");
+        }
+        return value;
+    }
+
+    static class ConfigHolder
+    {
+        final String certHeader;
+        final String sslHeader;
+        final String sslValue;
+
+        public ConfigHolder(String sslHeader, String sslValue, String certHeader)
+        {
+            this.sslHeader = sslHeader;
+            this.sslValue = sslValue;
+            this.certHeader = certHeader;
+        }
     }
 }
diff --git a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
new file mode 100644
index 0000000..8c22736
--- /dev/null
+++ b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
@@ -0,0 +1,24 @@
+# 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
+#
+# 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.
+name=Apache Felix Http Service SSL Filter
+description=Configuration for the Http Service SSL Filter. Please consult the documentation of your proxy for the actual headers and values to use. 
+ssl-forward.header.name=SSL forward header
+ssl-forward.header.description=HTTP Request header name that indicates a request is a SSL request terminated at a proxy between the client and the originating server. The default value is 'X-Forwarded-SSL' as is customarily used in the wild. Other commonly used names are\: 'X-Forwarded-Proto' (Amazon ELB), 'X-Forwarded-Protocol' (alternative), and 'Front-End-Https' (Microsoft IIS).
+ssl-forward.value.name=SSL forward value
+ssl-forward.value.description=HTTP Request header value that indicates a request is a SSL request terminated at a proxy. The default value is 'on'. Another commonly used value is 'https'.
+ssl-forward-cert.header.name=SSL client header
+ssl-forward-cert.header.description=HTTP Request header name that contains the client certificate forwarded by a proxy. The default value is 'X-Forwarded-SSL-Certificate'. Another commonly used value is 'X-Forwarded-SSL-Client-Cert'.
diff --git a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
new file mode 100644
index 0000000..c3bc0b5
--- /dev/null
+++ b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0" localization="OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter">
+    <OCD id="org.apache.felix.http.sslfilter.SslFilter" name="%name" description="%description">
+        <AD id="ssl-forward.header" name="%ssl-forward.header.name" description="%ssl-forward.header.description"
+        	type="String" default="X-Forwarded-SSL" required="true" />
+        <AD id="ssl-forward.value" name="%ssl-forward.value.name" description="%ssl-forward.value.description"
+        	type="String" default="on" required="true" />
+        <AD id="ssl-forward-cert.header" name="%ssl-forward-cert.header.name" description="%ssl-forward-cert.header.description"
+        	type="String" default="X-Forwarded-SSL-Certificate" required="false"/>
+    </OCD>
+    <Designate pid="org.apache.felix.http.sslfilter.SslFilter">
+        <Object ocdref="org.apache.felix.http.sslfilter.SslFilter"/>
+    </Designate>
+</metatype:MetaData>
+<!--
+    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
+    
+    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.
+-->