001    /* Copyright 2006-2009 the original author or authors.
002     *
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *      http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software
010     * distributed under the License is distributed on an "AS IS" BASIS,
011     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     * See the License for the specific language governing permissions and
013     * limitations under the License.
014     */
015    package org.codehaus.groovy.grails.plugins.springsecurity;
016    
017    import java.io.IOException;
018    import java.net.InetAddress;
019    import java.net.UnknownHostException;
020    import java.util.Arrays;
021    import java.util.Map;
022    
023    import javax.servlet.FilterChain;
024    import javax.servlet.ServletException;
025    import javax.servlet.http.HttpServletRequest;
026    import javax.servlet.http.HttpServletResponse;
027    
028    import org.apache.log4j.Logger;
029    import org.springframework.beans.factory.InitializingBean;
030    import org.springframework.beans.factory.annotation.Required;
031    import org.springframework.security.ui.FilterChainOrder;
032    import org.springframework.security.ui.SpringSecurityFilter;
033    import org.springframework.util.AntPathMatcher;
034    import org.springframework.util.Assert;
035    import org.springframework.util.StringUtils;
036    
037    /**
038     * Blocks access to protected resources based on IP address. Sends 404 rather than
039     * reporting error to hide visibility of the resources.
040     * <br/>
041     * Supports either Ant-style patterns (e.g. 10.**) or masked patterns
042     * (e.g. 192.168.1.0/24 or 202.24.0.0/14).
043     *
044     * @author <a href='mailto:beckwithb@studentsonly.com'>Burt Beckwith</a>
045     */
046    public class IpAddressFilter extends SpringSecurityFilter implements InitializingBean {
047    
048            private final Logger _log = Logger.getLogger(getClass());
049    
050            private final AntPathMatcher _pathMatcher = new AntPathMatcher();
051    
052            private Map<String, String> _restrictions;
053    
054            /**
055             * {@inheritDoc}
056             * @see org.springframework.security.ui.SpringSecurityFilter#doFilterHttp(
057             *      javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
058             *      javax.servlet.FilterChain)
059             */
060            @Override
061            protected void doFilterHttp(
062                              final HttpServletRequest request,
063                              final HttpServletResponse response,
064                              final FilterChain chain) throws IOException, ServletException {
065    
066                    if (!isAllowed(request.getRemoteAddr(), request.getRequestURI())) {
067                            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
068                            return;
069                    }
070    
071                    chain.doFilter(request, response);
072            }
073    
074            /**
075             * {@inheritDoc}
076             * @see org.springframework.security.ui.SpringSecurityFilter#getOrder()
077             */
078            public int getOrder() {
079                    return FilterChainOrder.LOGOUT_FILTER + 1;
080            }
081    
082            /**
083             * {@inheritDoc}
084             * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
085             */
086            public void afterPropertiesSet() {
087                    Assert.notNull(_restrictions, "ipRestrictions is required");
088            }
089    
090            /**
091             * Dependency injection for the ip/pattern restriction map.
092             * @param restrictions  the map
093             */
094            @Required
095            public void setIpRestrictions(final Map<String, String> restrictions) {
096                    _restrictions = restrictions;
097            }
098    
099            private boolean isAllowed(final String ip, final String requestURI) {
100    
101                    if ("127.0.0.1".equals(ip)) {
102                            return true;
103                    }
104    
105                    String reason = null;
106    
107                    for (Map.Entry<String, String> entry : _restrictions.entrySet()) {
108                            String uriPattern = entry.getKey();
109                            if (!_pathMatcher.match(uriPattern, requestURI)) {
110                                    continue;
111                            }
112    
113                            String ipPattern = entry.getValue();
114                            if (ipPattern.contains("/")) {
115                                    try {
116                                            if (!matchesUsingMask(ipPattern, ip)) {
117                                                    reason = ipPattern;
118                                                    break;
119                                            }
120                                    }
121                                    catch (UnknownHostException e) {
122                                            reason = e.getMessage();
123                                            break;
124                                    }
125                            }
126                            else if (!_pathMatcher.match(ipPattern, ip)) {
127                                    reason = ipPattern;
128                                    break;
129                            }
130                    }
131    
132                    if (reason != null) {
133                            _log.error("disallowed request " + requestURI + " from " + ip + ": " + reason);
134                            return false;
135                    }
136    
137                    return true;
138            }
139    
140            private boolean matchesUsingMask(final String ipPattern, final String ip) throws UnknownHostException {
141    
142                    String[] addressAndMask = StringUtils.split(ipPattern, "/");
143    
144                    InetAddress requiredAddress = parseAddress(addressAndMask[0]);
145                    InetAddress remoteAddress = parseAddress(ip);
146                    if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
147                            throw new IllegalArgumentException(
148                                            "IP Address in expression must be the same type as version returned by request");
149                    }
150    
151                    int maskBits = Integer.parseInt(addressAndMask[1]);
152                    if (maskBits == 0) {
153                            return remoteAddress.equals(requiredAddress);
154                    }
155    
156                    int oddBits = maskBits % 8;
157                    byte[] mask = new byte[maskBits / 8 + (oddBits == 0 ? 0 : 1)];
158    
159                    Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte)0xFF);
160    
161                    if (oddBits != 0) {
162                            int finalByte = (1 << oddBits) - 1;
163                            finalByte <<= 8 - oddBits;
164                            mask[mask.length - 1] = (byte) finalByte;
165                    }
166    
167                    byte[] remoteAddressBytes = remoteAddress.getAddress();
168                    byte[] requiredAddressBytes = requiredAddress.getAddress();
169                    for (int i = 0; i < mask.length; i++) {
170                            if ((remoteAddressBytes[i] & mask[i]) != (requiredAddressBytes[i] & mask[i])) {
171                                    return false;
172                            }
173                    }
174    
175                    return true;
176            }
177    
178            private InetAddress parseAddress(final String address) throws UnknownHostException {
179                    try {
180                            return InetAddress.getByName(address);
181                    }
182                    catch (UnknownHostException e) {
183                            _log.error("unable to parse " + address);
184                            throw e;
185                    }
186            }
187    }