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 URI of the AuthnContextDeclRef or the AuthnContextClass. */
52 private String authnMethodURI = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol";
54 /** The username to use for IP-address "authenticated" users. */
55 private String username;
57 /** Are the IPs in ipList a permitted list or a deny list. */
58 private boolean defaultDeny;
60 /** The list of denied or permitted IPs. */
61 private List<IPEntry> ipList;
64 * Set the permitted IP addresses.
66 * If <code>defaultDeny</code> is <code>true</code> then only the IP addresses in <code>ipList</code> will be
67 * "authenticated." If <code>defaultDeny</code> is <code>false</code>, then all IP addresses except those in
68 * <code>ipList</code> will be authenticated.
70 * @param entries A list of IP addresses (with CIDR masks).
71 * @param defaultDeny Does <code>ipList</code> contain a deny or permit list.
73 public void setEntries(final List<String> entries, boolean defaultDeny) {
75 this.defaultDeny = defaultDeny;
76 ipList = new CopyOnWriteArrayList<IPEntry>();
78 for (String addr : entries) {
80 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(
82 } catch (UnknownHostException ex) {
83 log.error("IPAddressHandler: Error parsing entry \"" + addr + "\". Ignoring.");
89 public boolean supportsPassive() {
94 public boolean supportsForceAuthentication() {
99 * Get the username for all IP-address authenticated users.
101 * @return The username for IP-address authenticated users.
103 public String getUsername() {
108 * Set the username to use for all IP-address authenticated users.
110 * @param name The username for IP-address authenticated users.
112 public void setUsername(String name) {
117 public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
120 handleDefaultDeny(httpRequest, httpResponse);
122 handleDefaultAllow(httpRequest, httpResponse);
125 AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
128 protected void handleDefaultDeny(HttpServletRequest request, HttpServletResponse response) {
130 boolean ipAllowed = searchIpList(request);
133 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
137 protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
139 boolean ipDenied = searchIpList(request);
142 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
147 * Search the list of InetAddresses for the client's address.
149 * @param request The ServletReqeust
151 * @return <code>true</code> if the client's address is in <code>ipList</code>
153 private boolean searchIpList(ServletRequest request) {
155 boolean found = false;
158 InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
159 BitSet addrbits = byteArrayToBitSet(addr.getAddress());
161 for (IPEntry entry : ipList) {
163 BitSet netaddr = entry.getNetworkAddress();
164 BitSet netmask = entry.getNetmask();
166 addrbits.and(netmask);
167 if (addrbits.equals(netaddr)) {
173 } catch (UnknownHostException ex) {
174 log.error("IPAddressHandler: Error resolving hostname.", ex);
182 * Converts a byte array to a BitSet.
184 * The supplied byte array is assumed to have the most signifigant bit in element 0.
186 * @param bytes the byte array with most signifigant bit in element 0.
190 protected BitSet byteArrayToBitSet(final byte[] bytes) {
192 BitSet bits = new BitSet();
194 for (int i = 0; i < bytes.length * 8; i++) {
195 if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
204 * Encapsulates a network address and a netmask on ipList.
206 protected class IPEntry {
208 /** The network address. */
209 private final BitSet networkAddress;
212 private final BitSet netmask;
215 * Construct a new IPEntry given a network address in CIDR format.
217 * @param entry A CIDR-formatted network address/netmask
219 * @throws UnknownHostException If entry is malformed.
221 public IPEntry(String entry) throws UnknownHostException {
223 // quick sanity checks
224 if (entry == null || entry.length() == 0) {
225 throw new UnknownHostException("entry is null.");
228 int cidrOffset = entry.indexOf("/");
229 if (cidrOffset == -1) {
230 log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- it lacks a netmask component.");
231 throw new UnknownHostException("entry lacks a netmask component.");
234 // ensure that only one "/" is present.
235 if (entry.indexOf("/", cidrOffset + 1) != -1) {
236 log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- too many \"/\" present.");
237 throw new UnknownHostException("entry has too many netmask components.");
240 String networkString = entry.substring(0, cidrOffset);
241 String netmaskString = entry.substring(cidrOffset + 1, entry.length());
243 InetAddress tempAddr = InetAddress.getByName(networkString);
244 networkAddress = byteArrayToBitSet(tempAddr.getAddress());
246 int masklen = Integer.parseInt(netmaskString);
247 int addrlen = networkAddress.length();
249 // ensure that the netmask isn't too large
250 if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
251 throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv4 address: " + masklen);
252 } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
253 throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv6 address: " + masklen);
256 netmask = new BitSet(addrlen);
257 netmask.set(addrlen - masklen, addrlen, true);
261 * Get the network address.
263 * @return the network address.
265 public BitSet getNetworkAddress() {
266 return networkAddress;
272 * @return the netmask.
274 public BitSet getNetmask() {