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.provider;
19 import java.net.Inet4Address;
20 import java.net.Inet6Address;
21 import java.net.InetAddress;
22 import java.net.UnknownHostException;
23 import java.util.BitSet;
24 import java.util.List;
25 import java.util.concurrent.CopyOnWriteArrayList;
27 import javax.servlet.ServletRequest;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
35 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
38 * IP Address authentication handler.
40 * This "authenticates" a user based on their IP address. It operates in either default deny or default allow mode, and
41 * evaluates a given request against a list of blocked or permitted IPs. It supports both IPv4 and IPv6.
43 * If an Authentication Context Class or DeclRef URI is not specified, it will default to
44 * "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol".
46 public class IPAddressLoginHandler extends AbstractLoginHandler {
49 private final Logger log = LoggerFactory.getLogger(IPAddressLoginHandler.class);
51 /** The username to use for IP-address "authenticated" users. */
52 private String username;
54 /** Are the IPs in ipList a permitted list or a deny list. */
55 private boolean defaultDeny;
57 /** The list of denied or permitted IPs. */
58 private List<IPEntry> ipList;
61 * Set the permitted IP addresses.
63 * If <code>defaultDeny</code> is <code>true</code> then only the IP addresses in <code>ipList</code> will be
64 * "authenticated." If <code>defaultDeny</code> is <code>false</code>, then all IP addresses except those in
65 * <code>ipList</code> will be authenticated.
67 * @param entries A list of IP addresses (with CIDR masks).
68 * @param defaultDeny Does <code>ipList</code> contain a deny or permit list.
70 public void setEntries(final List<String> entries, boolean defaultDeny) {
72 this.defaultDeny = defaultDeny;
73 ipList = new CopyOnWriteArrayList<IPEntry>();
75 for (String addr : entries) {
77 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(
79 } catch (UnknownHostException ex) {
80 log.warn("IPAddressHandler: Error parsing IP entry \"" + addr + "\". Ignoring.");
86 public boolean supportsPassive() {
91 public boolean supportsForceAuthentication() {
96 * Get the username for all IP-address authenticated users.
98 * @return The username for IP-address authenticated users.
100 public String getUsername() {
105 * Set the username to use for all IP-address authenticated users.
107 * @param name The username for IP-address authenticated users.
109 public void setUsername(String name) {
114 public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
117 handleDefaultDeny(httpRequest, httpResponse);
119 handleDefaultAllow(httpRequest, httpResponse);
122 AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
125 protected void handleDefaultDeny(HttpServletRequest request, HttpServletResponse response) {
127 boolean ipAllowed = searchIpList(request);
130 log.debug("Authenticated user by IP address");
131 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
135 protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
137 boolean ipDenied = searchIpList(request);
140 log.debug("Authenticated user by IP address");
141 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
146 * Search the list of InetAddresses for the client's address.
148 * @param request The ServletReqeust
150 * @return <code>true</code> if the client's address is in <code>ipList</code>
152 private boolean searchIpList(ServletRequest request) {
154 boolean found = false;
157 InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
158 BitSet addrbits = byteArrayToBitSet(addr.getAddress());
160 for (IPEntry entry : ipList) {
162 BitSet netaddr = entry.getNetworkAddress();
163 BitSet netmask = entry.getNetmask();
165 addrbits.and(netmask);
166 if (addrbits.equals(netaddr)) {
172 } catch (UnknownHostException ex) {
173 log.error("Error resolving hostname.", ex);
181 * Converts a byte array to a BitSet.
183 * The supplied byte array is assumed to have the most signifigant bit in element 0.
185 * @param bytes the byte array with most signifigant bit in element 0.
189 protected BitSet byteArrayToBitSet(final byte[] bytes) {
191 BitSet bits = new BitSet();
193 for (int i = 0; i < bytes.length * 8; i++) {
194 if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
203 * Encapsulates a network address and a netmask on ipList.
205 protected class IPEntry {
207 /** The network address. */
208 private final BitSet networkAddress;
211 private final BitSet netmask;
214 * Construct a new IPEntry given a network address in CIDR format.
216 * @param entry A CIDR-formatted network address/netmask
218 * @throws UnknownHostException If entry is malformed.
220 public IPEntry(String entry) throws UnknownHostException {
222 // quick sanity checks
223 if (entry == null || entry.length() == 0) {
224 throw new UnknownHostException("entry is null.");
227 int cidrOffset = entry.indexOf("/");
228 if (cidrOffset == -1) {
229 log.warn("Invalid entry \"" + entry + "\" -- it lacks a netmask component.");
230 throw new UnknownHostException("entry lacks a netmask component.");
233 // ensure that only one "/" is present.
234 if (entry.indexOf("/", cidrOffset + 1) != -1) {
235 log.warn("Invalid entry \"" + entry + "\" -- too many \"/\" present.");
236 throw new UnknownHostException("entry has too many netmask components.");
239 String networkString = entry.substring(0, cidrOffset);
240 String netmaskString = entry.substring(cidrOffset + 1, entry.length());
242 InetAddress tempAddr = InetAddress.getByName(networkString);
243 networkAddress = byteArrayToBitSet(tempAddr.getAddress());
245 int masklen = Integer.parseInt(netmaskString);
248 if (tempAddr instanceof Inet4Address) {
250 } else if (tempAddr instanceof Inet6Address) {
253 throw new UnknownHostException("Unable to determine Inet protocol version");
256 // ensure that the netmask isn't too large
257 if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
258 throw new UnknownHostException("Netmask is too large for an IPv4 address: " + masklen);
259 } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
260 throw new UnknownHostException("Netmask is too large for an IPv6 address: " + masklen);
263 netmask = new BitSet(addrlen);
264 netmask.set(addrlen - masklen, addrlen, true);
268 * Get the network address.
270 * @return the network address.
272 public BitSet getNetworkAddress() {
273 return networkAddress;
279 * @return the netmask.
281 public BitSet getNetmask() {