001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http.servlet;
018
019import java.util.regex.Pattern;
020
021import javax.servlet.FilterConfig;
022import javax.servlet.ServletRequest;
023import javax.servlet.http.HttpServletRequest;
024import javax.servlet.http.HttpServletRequestWrapper;
025
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Sets {@link ServletRequest#isSecure()} to <code>true</code> if
031 * {@link ServletRequest#getRemoteAddr()} matches one of the <code>securedRemoteAddresses</code> of
032 * this filter.
033 * <p>
034 * This filter is often used in combination with {@link XForwardedRequestWrapperFactory} to get the
035 * remote address of the client even if the request goes through load balancers (e.g. F5 Big IP,
036 * Nortel Alteon) or proxies (e.g. Apache mod_proxy_http)
037 * <p>
038 * <strong>Configuration parameters:</strong>
039 * <table border="1">
040 * <caption>Configuration parameters</caption>
041 * <tr>
042 * <th>XForwardedFilter property</th>
043 * <th>Description</th>
044 * <th>Format</th>
045 * <th>Default value</th>
046 * </tr>
047 * <tr>
048 * <td>securedRemoteAddresses</td>
049 * <td>IP addresses for which {@link ServletRequest#isSecure()} must return <code>true</code></td>
050 * <td>Comma delimited list of regular expressions (in the syntax supported by the
051 * {@link java.util.regex.Pattern} library)</td>
052 * <td>Class A, B and C <a href="http://en.wikipedia.org/wiki/Private_network">private network IP
053 * address blocks</a> : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3},
054 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3},
055 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3}</td>
056 * </tr>
057 * </table>
058 * Note : the default configuration is can usually be used as internal servers are often trusted.
059 * </p>
060 * <p>
061 * <strong>Sample with secured remote addresses limited to 192.168.0.10 and 192.168.0.11</strong>
062 * </p>
063 * <p>
064 * SecuredRemoteAddressFilter configuration sample :
065 * </p>
066 * 
067 * <code><pre>
068 * &lt;filter&gt;
069 *    &lt;filter-name&gt;SecuredRemoteAddressFilter&lt;/filter-name&gt;
070 *    &lt;filter-class&gt;fr.xebia.servlet.filter.SecuredRemoteAddressFilter&lt;/filter-class&gt;
071 *    &lt;init-param&gt;
072 *       &lt;param-name&gt;securedRemoteAddresses&lt;/param-name&gt;&lt;param-value&gt;192\.168\.0\.10, 192\.168\.0\.11&lt;/param-value&gt;
073 *    &lt;/init-param&gt;
074 * &lt;/filter&gt;
075 * 
076 * &lt;filter-mapping&gt;
077 *    &lt;filter-name&gt;SecuredRemoteAddressFilter&lt;/filter-name&gt;
078 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
079 *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
080 * &lt;/filter-mapping&gt;</pre></code>
081 * <p>
082 * A request with <code>{@link ServletRequest#getRemoteAddr()} = 192.168.0.10 or 192.168.0.11</code>
083 * will be seen as <code>{@link ServletRequest#isSecure()} == true</code> even if
084 * <code>{@link HttpServletRequest#getScheme()} == "http"</code>.
085 * </p>
086 * 
087 * @author <a href="mailto:cyrille@cyrilleleclerc.com">Cyrille Le Clerc</a>
088 * @author Juergen Donnerstag
089 */
090public class SecuredRemoteAddressRequestWrapperFactory extends AbstractRequestWrapperFactory
091{
092        /** Logger */
093        private static final Logger log = LoggerFactory.getLogger(SecuredRemoteAddressRequestWrapperFactory.class);
094
095        private final static String SECURED_REMOTE_ADDRESSES_PARAMETER = "securedRemoteAddresses";
096
097        public static class Config
098        {
099                /** @see #setSecuredRemoteAdresses(String) */
100                private Pattern[] securedRemoteAddresses = new Pattern[] {
101                                Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"),
102                                Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"),
103                                Pattern.compile("172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}"),
104                                Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"),
105                                Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") };
106
107                /**
108                 * Comma delimited list of secured remote addresses. Expressed with regular expressions.
109                 * <p>
110                 * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3},
111                 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3},
112                 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3}
113                 * 
114                 * @param comaDelimitedSecuredRemoteAddresses
115                 */
116                public void setSecuredRemoteAdresses(final String comaDelimitedSecuredRemoteAddresses)
117                {
118                        securedRemoteAddresses = commaDelimitedListToPatternArray(comaDelimitedSecuredRemoteAddresses);
119                }
120        }
121
122        // Filter Config
123        private Config config = new Config();
124
125        /**
126         * Construct.
127         */
128        public SecuredRemoteAddressRequestWrapperFactory()
129        {
130        }
131
132        /**
133         * @return SecuredRemoteAddress and XForwarded filter specific config
134         */
135        public final Config getConfig()
136        {
137                return config;
138        }
139
140        /**
141         * The Wicket application might want to provide its own config
142         * 
143         * @param config
144         */
145        public final void setConfig(final Config config)
146        {
147                this.config = config;
148        }
149
150        @Override
151        public HttpServletRequest getWrapper(final HttpServletRequest request)
152        {
153                HttpServletRequest xRequest = super.getWrapper(request);
154
155                if (log.isDebugEnabled())
156                {
157                        log.debug("Incoming request uri=" + request.getRequestURI() + " with originalSecure='" +
158                                request.isSecure() + "', remoteAddr='" + request.getRemoteAddr() +
159                                "' will be seen with newSecure='" + xRequest.isSecure() + "'");
160                }
161
162                return xRequest;
163        }
164
165        @Override
166        public boolean needsWrapper(final HttpServletRequest request)
167        {
168                return !request.isSecure() &&
169                        matchesOne(request.getRemoteAddr(), config.securedRemoteAddresses) == false;
170        }
171
172        /**
173         * If incoming remote address matches one of the declared IP pattern, wraps the incoming
174         * {@link HttpServletRequest} to override {@link HttpServletRequest#isSecure()} to set it to
175         * <code>true</code>.
176         */
177        @Override
178        public HttpServletRequest newRequestWrapper(final HttpServletRequest request)
179        {
180                return new HttpServletRequestWrapper(request)
181                {
182                        @Override
183                        public boolean isSecure()
184                        {
185                                return true;
186                        }
187                };
188        }
189
190        /**
191         * @param filterConfig
192         */
193        public void init(final FilterConfig filterConfig)
194        {
195                String comaDelimitedSecuredRemoteAddresses = filterConfig.getInitParameter(SECURED_REMOTE_ADDRESSES_PARAMETER);
196                if (comaDelimitedSecuredRemoteAddresses != null)
197                {
198                        config.setSecuredRemoteAdresses(comaDelimitedSecuredRemoteAddresses);
199                }
200        }
201}