2 * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package edu.internet2.middleware.shibboleth.idp.authn.impl;
19 import java.net.InetAddress;
20 import java.net.UnknownHostException;
21 import java.util.List;
22 import java.util.BitSet;
23 import java.util.concurrent.CopyOnWriteArrayList;
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28 import javax.servlet.ServletRequest;
30 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationHandler;
31 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
32 import java.net.Inet4Address;
33 import java.net.Inet6Address;
35 import org.apache.log4j.Logger;
37 import org.joda.time.DateTime;
40 * IP Address authentication handler.
42 * This "authenticates" a user based on their IP address. It operates in either
43 * default deny or default allow mode, and evaluates a given request against a
44 * list of blocked or permitted IPs. It supports both IPv4 and IPv6.
46 public class IPAddressHandler implements AuthenticationHandler {
49 * Encapsulates a network address and a netmask on ipList.
51 protected class IPEntry {
53 /** The network address. */
54 private final BitSet networkAddress;
57 private final BitSet netmask;
60 * Construct a new IPEntry given a network address in CIDR format.
63 * A CIDR-formatted network address/netmask
65 * @throws UnknownHostException
66 * If entry is malformed.
68 public IPEntry(String entry) throws UnknownHostException {
70 // quick sanity checks
71 if (entry == null || entry.length() == 0) {
72 throw new UnknownHostException("entry is null.");
75 int cidrOffset = entry.indexOf("/");
76 if (cidrOffset == -1) {
77 log.error("IPAddressHandler: invalid entry \"" + entry
78 + "\" -- it lacks a netmask component.");
79 throw new UnknownHostException(
80 "entry lacks a netmask component.");
83 // ensure that only one "/" is present.
84 if (entry.indexOf("/", cidrOffset + 1) != -1) {
85 log.error("IPAddressHandler: invalid entry \"" + entry
86 + "\" -- too many \"/\" present.");
87 throw new UnknownHostException(
88 "entry has too many netmask components.");
91 String networkString = entry.substring(0, cidrOffset);
92 String netmaskString = entry.substring(cidrOffset + 1, entry
95 InetAddress tempAddr = InetAddress.getByName(networkString);
96 networkAddress = byteArrayToBitSet(tempAddr.getAddress());
98 int masklen = Integer.parseInt(netmaskString);
99 int addrlen = networkAddress.length();
101 // ensure that the netmask isn't too large
102 if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
103 throw new UnknownHostException(
104 "IPAddressHandler: Netmask is too large for an IPv4 address: "
106 } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
107 throw new UnknownHostException(
108 "IPAddressHandler: Netmask is too large for an IPv6 address: "
112 netmask = new BitSet(addrlen);
113 netmask.set(addrlen - masklen, addrlen, true);
117 * Get the network address.
119 * @return the network address.
121 public BitSet getNetworkAddress() {
122 return networkAddress;
128 * @return the netmask.
130 public BitSet getNetmask() {
135 private static final Logger log = Logger.getLogger(IPAddressHandler.class);
137 /** the URI of the AuthnContextDeclRef or the AuthnContextClass */
138 private String authnMethodURI;
140 /** The return location */
141 private String returnLocation;
143 /** Are the IPs in ipList a permitted list or a deny list */
144 private boolean defaultDeny;
146 /** The list of denied or permitted IPs */
147 private List<IPEntry> ipList;
149 /** Creates a new instance of IPAddressHandler */
150 public IPAddressHandler() {
154 * Set the permitted IP addresses.
156 * If <code>defaultDeny</code> is <code>true</code> then only the IP
157 * addresses in <code>ipList</code> will be "authenticated." If
158 * <code>defaultDeny</code> is <code>false</code>, then all IP
159 * addresses except those in <code>ipList</code> will be authenticated.
162 * A list of IP addresses (with CIDR masks).
164 * Does <code>ipList</code> contain a deny or permit list.
166 public void setEntries(final List<String> entries, boolean defaultDeny) {
168 this.defaultDeny = defaultDeny;
169 ipList = new CopyOnWriteArrayList<IPEntry>();
171 for (String addr : entries) {
174 .add(new edu.internet2.middleware.shibboleth.idp.authn.impl.IPAddressHandler.IPEntry(
176 } catch (UnknownHostException ex) {
177 log.error("IPAddressHandler: Error parsing entry \"" + addr
184 public void setReturnLocation(String location) {
185 this.returnLocation = location;
189 public boolean supportsPassive() {
194 public boolean supportsForceAuthentication() {
199 public void logout(final HttpServletRequest request,
200 final HttpServletResponse response, final String principal) {
202 RequestDispatcher dispatcher = request
203 .getRequestDispatcher(returnLocation);
204 // dispatcher.forward(request, response);
208 public void login(final HttpServletRequest request,
209 final HttpServletResponse response, final LoginContext loginCtx) {
211 loginCtx.setAuthenticationAttempted();
212 loginCtx.setAuthenticationInstant(new DateTime());
215 handleDefaultDeny(request, response, loginCtx);
217 handleDefaultAllow(request, response, loginCtx);
221 protected void handleDefaultDeny(HttpServletRequest request,
222 HttpServletResponse response, LoginContext loginCtx) {
224 boolean ipAllowed = searchIpList(request);
227 loginCtx.setAuthenticationOK(true);
229 loginCtx.setAuthenticationOK(false);
231 .setAuthenticationFailureMessage("User's IP is not in the permitted list.");
235 protected void handleDefaultAllow(HttpServletRequest request,
236 HttpServletResponse response, LoginContext loginCtx) {
238 boolean ipDenied = searchIpList(request);
241 loginCtx.setAuthenticationOK(false);
243 .setAuthenticationFailureMessage("Users's IP is in the deny list.");
245 loginCtx.setAuthenticationOK(true);
250 * Search the list of InetAddresses for the client's address.
255 * @return <code>true</code> if the client's address is in
256 * <code>this.ipList</code>
258 private boolean searchIpList(final ServletRequest request) {
260 boolean found = false;
263 InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
264 BitSet addrbits = byteArrayToBitSet(addr.getAddress());
266 for (IPEntry entry : ipList) {
268 BitSet netaddr = entry.getNetworkAddress();
269 BitSet netmask = entry.getNetmask();
271 addrbits.and(netmask);
272 if (addrbits.equals(netaddr)) {
278 } catch (UnknownHostException ex) {
279 log.error("Error resolving hostname: ", ex);
287 * Converts a byte array to a BitSet.
289 * The supplied byte array is assumed to have the most signifigant bit in
293 * the byte array with most signifigant bit in element 0.
297 protected static BitSet byteArrayToBitSet(final byte[] bytes) {
299 BitSet bits = new BitSet();
301 for (int i = 0; i < bytes.length * 8; i++) {
302 if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {