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 }