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 IP 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 log.debug("Authenticated user by IP address");
134 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
138 protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
140 boolean ipDenied = searchIpList(request);
143 log.debug("Authenticated user by IP address");
144 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
149 * Search the list of InetAddresses for the client's address.
151 * @param request The ServletReqeust
153 * @return <code>true</code> if the client's address is in <code>ipList</code>
155 private boolean searchIpList(ServletRequest request) {
157 boolean found = false;
160 InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
161 BitSet addrbits = byteArrayToBitSet(addr.getAddress());
163 for (IPEntry entry : ipList) {
165 BitSet netaddr = entry.getNetworkAddress();
166 BitSet netmask = entry.getNetmask();
168 addrbits.and(netmask);
169 if (addrbits.equals(netaddr)) {
175 } catch (UnknownHostException ex) {
176 log.error("Error resolving hostname.", ex);
184 * Converts a byte array to a BitSet.
186 * The supplied byte array is assumed to have the most signifigant bit in element 0.
188 * @param bytes the byte array with most signifigant bit in element 0.
192 protected BitSet byteArrayToBitSet(final byte[] bytes) {
194 BitSet bits = new BitSet();
196 for (int i = 0; i < bytes.length * 8; i++) {
197 if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
206 * Encapsulates a network address and a netmask on ipList.
208 protected class IPEntry {
210 /** The network address. */
211 private final BitSet networkAddress;
214 private final BitSet netmask;
217 * Construct a new IPEntry given a network address in CIDR format.
219 * @param entry A CIDR-formatted network address/netmask
221 * @throws UnknownHostException If entry is malformed.
223 public IPEntry(String entry) throws UnknownHostException {
225 // quick sanity checks
226 if (entry == null || entry.length() == 0) {
227 throw new UnknownHostException("entry is null.");
230 int cidrOffset = entry.indexOf("/");
231 if (cidrOffset == -1) {
232 log.error("Invalid entry \"" + entry + "\" -- it lacks a netmask component.");
233 throw new UnknownHostException("entry lacks a netmask component.");
236 // ensure that only one "/" is present.
237 if (entry.indexOf("/", cidrOffset + 1) != -1) {
238 log.error("Invalid entry \"" + entry + "\" -- too many \"/\" present.");
239 throw new UnknownHostException("entry has too many netmask components.");
242 String networkString = entry.substring(0, cidrOffset);
243 String netmaskString = entry.substring(cidrOffset + 1, entry.length());
245 InetAddress tempAddr = InetAddress.getByName(networkString);
246 networkAddress = byteArrayToBitSet(tempAddr.getAddress());
248 int masklen = Integer.parseInt(netmaskString);
249 int addrlen = networkAddress.length();
251 // ensure that the netmask isn't too large
252 if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
253 throw new UnknownHostException("Netmask is too large for an IPv4 address: " + masklen);
254 } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
255 throw new UnknownHostException("Netmask is too large for an IPv6 address: " + masklen);
258 netmask = new BitSet(addrlen);
259 netmask.set(addrlen - masklen, addrlen, true);
263 * Get the network address.
265 * @return the network address.
267 public BitSet getNetworkAddress() {
268 return networkAddress;
274 * @return the netmask.
276 public BitSet getNetmask() {