2418755d19542796b9ea307f2fb1426a8ab7d3b3
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / provider / IPAddressLoginHandler.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.authn.provider;
18
19 import java.io.IOException;
20 import java.net.Inet4Address;
21 import java.net.Inet6Address;
22 import java.net.InetAddress;
23 import java.net.UnknownHostException;
24 import java.util.BitSet;
25 import java.util.List;
26 import java.util.concurrent.CopyOnWriteArrayList;
27
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.apache.log4j.Logger;
35 import org.joda.time.DateTime;
36
37 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
38 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
39 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
40
41 /**
42  * IP Address authentication handler.
43  * 
44  * This "authenticates" a user based on their IP address. It operates in either default deny or default allow mode, and
45  * evaluates a given request against a list of blocked or permitted IPs. It supports both IPv4 and IPv6.
46  * 
47  * If an Authentication Context Class or DeclRef URI is not specified, it will default to
48  * "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol".
49  */
50 public class IPAddressLoginHandler extends AbstractLoginHandler {
51
52     /** Class logger. */
53     private final Logger log = Logger.getLogger(IPAddressLoginHandler.class);
54
55     /** The URI of the AuthnContextDeclRef or the AuthnContextClass. */
56     private String authnMethodURI = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol";
57
58     /** The username to use for IP-address "authenticated" users. */
59     private String username;
60
61     /** Are the IPs in ipList a permitted list or a deny list. */
62     private boolean defaultDeny;
63
64     /** The list of denied or permitted IPs. */
65     private List<IPEntry> ipList;
66
67     /**
68      * Set the permitted IP addresses.
69      * 
70      * If <code>defaultDeny</code> is <code>true</code> then only the IP addresses in <code>ipList</code> will be
71      * "authenticated." If <code>defaultDeny</code> is <code>false</code>, then all IP addresses except those in
72      * <code>ipList</code> will be authenticated.
73      * 
74      * @param entries A list of IP addresses (with CIDR masks).
75      * @param defaultDeny Does <code>ipList</code> contain a deny or permit list.
76      */
77     public void setEntries(final List<String> entries, boolean defaultDeny) {
78
79         this.defaultDeny = defaultDeny;
80         ipList = new CopyOnWriteArrayList<IPEntry>();
81
82         for (String addr : entries) {
83             try {
84                 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(addr));
85             } catch (UnknownHostException ex) {
86                 log.error("IPAddressHandler: Error parsing entry \"" + addr + "\". Ignoring.");
87             }
88         }
89     }
90
91     /** {@inheritDoc} */
92     public boolean supportsPassive() {
93         return true;
94     }
95
96     /** {@inheritDoc} */
97     public boolean supportsForceAuthentication() {
98         return true;
99     }
100
101     /**
102      * Get the username for all IP-address authenticated users.
103      * 
104      * @return The username for IP-address authenticated users.
105      */
106     public String getUsername() {
107         return username;
108     }
109
110     /**
111      * Set the username to use for all IP-address authenticated users.
112      * 
113      * @param name The username for IP-address authenticated users.
114      */
115     public void setUsername(String name) {
116         username = name;
117     }
118
119     /** {@inheritDoc} */
120     public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
121
122         if (defaultDeny) {
123             handleDefaultDeny(httpRequest, httpResponse);
124         } else {
125             handleDefaultAllow(httpRequest, httpResponse);
126         }
127
128         AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
129     }
130
131     protected void handleDefaultDeny(HttpServletRequest request, HttpServletResponse response) {
132
133         boolean ipAllowed = searchIpList(request);
134
135         if (ipAllowed) {
136             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
137         }
138     }
139
140     protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
141
142         boolean ipDenied = searchIpList(request);
143
144         if (!ipDenied) {
145             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
146         }
147     }
148
149     /**
150      * Search the list of InetAddresses for the client's address.
151      * 
152      * @param request The ServletReqeust
153      * 
154      * @return <code>true</code> if the client's address is in <code>ipList</code>
155      */
156     private boolean searchIpList(ServletRequest request) {
157
158         boolean found = false;
159
160         try {
161             InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
162             BitSet addrbits = byteArrayToBitSet(addr.getAddress());
163
164             for (IPEntry entry : ipList) {
165
166                 BitSet netaddr = entry.getNetworkAddress();
167                 BitSet netmask = entry.getNetmask();
168
169                 addrbits.and(netmask);
170                 if (addrbits.equals(netaddr)) {
171                     found = true;
172                     break;
173                 }
174             }
175
176         } catch (UnknownHostException ex) {
177             log.error("IPAddressHandler: Error resolving hostname.", ex);
178             return false;
179         }
180
181         return found;
182     }
183
184     /**
185      * Converts a byte array to a BitSet.
186      * 
187      * The supplied byte array is assumed to have the most signifigant bit in element 0.
188      * 
189      * @param bytes the byte array with most signifigant bit in element 0.
190      * 
191      * @return the BitSet
192      */
193     protected BitSet byteArrayToBitSet(final byte[] bytes) {
194
195         BitSet bits = new BitSet();
196
197         for (int i = 0; i < bytes.length * 8; i++) {
198             if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
199                 bits.set(i);
200             }
201         }
202
203         return bits;
204     }
205
206     /**
207      * Encapsulates a network address and a netmask on ipList.
208      */
209     protected class IPEntry {
210
211         /** The network address. */
212         private final BitSet networkAddress;
213
214         /** The netmask. */
215         private final BitSet netmask;
216
217         /**
218          * Construct a new IPEntry given a network address in CIDR format.
219          * 
220          * @param entry A CIDR-formatted network address/netmask
221          * 
222          * @throws UnknownHostException If entry is malformed.
223          */
224         public IPEntry(String entry) throws UnknownHostException {
225
226             // quick sanity checks
227             if (entry == null || entry.length() == 0) {
228                 throw new UnknownHostException("entry is null.");
229             }
230
231             int cidrOffset = entry.indexOf("/");
232             if (cidrOffset == -1) {
233                 log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- it lacks a netmask component.");
234                 throw new UnknownHostException("entry lacks a netmask component.");
235             }
236
237             // ensure that only one "/" is present.
238             if (entry.indexOf("/", cidrOffset + 1) != -1) {
239                 log.error("IPAddressHandler: invalid entry \"" + entry + "\" -- too many \"/\" present.");
240                 throw new UnknownHostException("entry has too many netmask components.");
241             }
242
243             String networkString = entry.substring(0, cidrOffset);
244             String netmaskString = entry.substring(cidrOffset + 1, entry.length());
245
246             InetAddress tempAddr = InetAddress.getByName(networkString);
247             networkAddress = byteArrayToBitSet(tempAddr.getAddress());
248
249             int masklen = Integer.parseInt(netmaskString);
250             int addrlen = networkAddress.length();
251
252             // ensure that the netmask isn't too large
253             if ((tempAddr instanceof Inet4Address) && (masklen > 32)) {
254                 throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv4 address: " + masklen);
255             } else if ((tempAddr instanceof Inet6Address) && masklen > 128) {
256                 throw new UnknownHostException("IPAddressHandler: Netmask is too large for an IPv6 address: " + masklen);
257             }
258
259             netmask = new BitSet(addrlen);
260             netmask.set(addrlen - masklen, addrlen, true);
261         }
262
263         /**
264          * Get the network address.
265          * 
266          * @return the network address.
267          */
268         public BitSet getNetworkAddress() {
269             return networkAddress;
270         }
271
272         /**
273          * Get the netmask.
274          * 
275          * @return the netmask.
276          */
277         public BitSet getNetmask() {
278             return netmask;
279         }
280     }
281 }