https://bugs.internet2.edu/jira/browse/SIDP-281
[java-idp.git] / src / main / java / 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.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;
26
27 import javax.servlet.ServletRequest;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
35 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
36
37 /**
38  * IP Address authentication handler.
39  * 
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.
42  * 
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".
45  */
46 public class IPAddressLoginHandler extends AbstractLoginHandler {
47
48     /** Class logger. */
49     private final Logger log = LoggerFactory.getLogger(IPAddressLoginHandler.class);
50
51     /** The username to use for IP-address "authenticated" users. */
52     private String username;
53
54     /** Are the IPs in ipList a permitted list or a deny list. */
55     private boolean defaultDeny;
56
57     /** The list of denied or permitted IPs. */
58     private List<IPEntry> ipList;
59
60     /**
61      * Set the permitted IP addresses.
62      * 
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.
66      * 
67      * @param entries A list of IP addresses (with CIDR masks).
68      * @param defaultDeny Does <code>ipList</code> contain a deny or permit list.
69      */
70     public void setEntries(final List<String> entries, boolean defaultDeny) {
71
72         this.defaultDeny = defaultDeny;
73         ipList = new CopyOnWriteArrayList<IPEntry>();
74
75         for (String addr : entries) {
76             try {
77                 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(
78                         addr));
79             } catch (UnknownHostException ex) {
80                 log.warn("IPAddressHandler: Error parsing IP entry \"" + addr + "\". Ignoring.");
81             }
82         }
83     }
84
85     /** {@inheritDoc} */
86     public boolean supportsPassive() {
87         return true;
88     }
89
90     /** {@inheritDoc} */
91     public boolean supportsForceAuthentication() {
92         return true;
93     }
94
95     /**
96      * Get the username for all IP-address authenticated users.
97      * 
98      * @return The username for IP-address authenticated users.
99      */
100     public String getUsername() {
101         return username;
102     }
103
104     /**
105      * Set the username to use for all IP-address authenticated users.
106      * 
107      * @param name The username for IP-address authenticated users.
108      */
109     public void setUsername(String name) {
110         username = name;
111     }
112
113     /** {@inheritDoc} */
114     public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
115
116         if (defaultDeny) {
117             handleDefaultDeny(httpRequest, httpResponse);
118         } else {
119             handleDefaultAllow(httpRequest, httpResponse);
120         }
121
122         AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
123     }
124
125     protected void handleDefaultDeny(HttpServletRequest request, HttpServletResponse response) {
126
127         boolean ipAllowed = searchIpList(request);
128
129         if (ipAllowed) {
130             log.debug("Authenticated user by IP address");
131             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
132         }
133     }
134
135     protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
136
137         boolean ipDenied = searchIpList(request);
138
139         if (!ipDenied) {
140             log.debug("Authenticated user by IP address");
141             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
142         }
143     }
144
145     /**
146      * Search the list of InetAddresses for the client's address.
147      * 
148      * @param request The ServletReqeust
149      * 
150      * @return <code>true</code> if the client's address is in <code>ipList</code>
151      */
152     private boolean searchIpList(ServletRequest request) {
153
154         boolean found = false;
155
156         try {
157             InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
158             BitSet addrbits = byteArrayToBitSet(addr.getAddress());
159
160             for (IPEntry entry : ipList) {
161
162                 BitSet netaddr = entry.getNetworkAddress();
163                 BitSet netmask = entry.getNetmask();
164
165                 addrbits.and(netmask);
166                 if (addrbits.equals(netaddr)) {
167                     found = true;
168                     break;
169                 }
170             }
171
172         } catch (UnknownHostException ex) {
173             log.error("Error resolving hostname.", ex);
174             return false;
175         }
176
177         return found;
178     }
179
180     /**
181      * Converts a byte array to a BitSet.
182      * 
183      * The supplied byte array is assumed to have the most signifigant bit in element 0.
184      * 
185      * @param bytes the byte array with most signifigant bit in element 0.
186      * 
187      * @return the BitSet
188      */
189     protected BitSet byteArrayToBitSet(final byte[] bytes) {
190
191         BitSet bits = new BitSet();
192
193         for (int i = 0; i < bytes.length * 8; i++) {
194             if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
195                 bits.set(i);
196             }
197         }
198
199         return bits;
200     }
201
202     /**
203      * Encapsulates a network address and a netmask on ipList.
204      */
205     protected class IPEntry {
206
207         /** The network address. */
208         private final BitSet networkAddress;
209
210         /** The netmask. */
211         private final BitSet netmask;
212
213         /**
214          * Construct a new IPEntry given a network address in CIDR format.
215          * 
216          * @param entry A CIDR-formatted network address/netmask
217          * 
218          * @throws UnknownHostException If entry is malformed.
219          */
220         public IPEntry(String entry) throws UnknownHostException {
221
222             // quick sanity checks
223             if (entry == null || entry.length() == 0) {
224                 throw new UnknownHostException("entry is null.");
225             }
226
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.");
231             }
232
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.");
237             }
238
239             String networkString = entry.substring(0, cidrOffset);
240             String netmaskString = entry.substring(cidrOffset + 1, entry.length());
241
242             InetAddress tempAddr = InetAddress.getByName(networkString);
243             networkAddress = byteArrayToBitSet(tempAddr.getAddress());
244
245             int masklen = Integer.parseInt(netmaskString);
246
247             int addrlen;
248             if (tempAddr instanceof Inet4Address) {
249                 addrlen = 32;
250             } else if (tempAddr instanceof Inet6Address) {
251                 addrlen = 128;
252             }else{
253                 throw new UnknownHostException("Unable to determine Inet protocol version");
254             }
255
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);
261             }
262
263             netmask = new BitSet(addrlen);
264             netmask.set(addrlen - masklen, addrlen, true);
265         }
266
267         /**
268          * Get the network address.
269          * 
270          * @return the network address.
271          */
272         public BitSet getNetworkAddress() {
273             return networkAddress;
274         }
275
276         /**
277          * Get the netmask.
278          * 
279          * @return the netmask.
280          */
281         public BitSet getNetmask() {
282             return netmask;
283         }
284     }
285 }