Decrease log level of message - SIDP-318
[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 URI of the AuthnContextDeclRef or the AuthnContextClass. */
52     private String authnMethodURI = "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol";
53
54     /** The username to use for IP-address "authenticated" users. */
55     private String username;
56
57     /** Are the IPs in ipList a permitted list or a deny list. */
58     private boolean defaultDeny;
59
60     /** The list of denied or permitted IPs. */
61     private List<IPEntry> ipList;
62
63     /**
64      * Set the permitted IP addresses.
65      * 
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.
69      * 
70      * @param entries A list of IP addresses (with CIDR masks).
71      * @param defaultDeny Does <code>ipList</code> contain a deny or permit list.
72      */
73     public void setEntries(final List<String> entries, boolean defaultDeny) {
74
75         this.defaultDeny = defaultDeny;
76         ipList = new CopyOnWriteArrayList<IPEntry>();
77
78         for (String addr : entries) {
79             try {
80                 ipList.add(new edu.internet2.middleware.shibboleth.idp.authn.provider.IPAddressLoginHandler.IPEntry(
81                         addr));
82             } catch (UnknownHostException ex) {
83                 log.warn("IPAddressHandler: Error parsing IP entry \"" + addr + "\". Ignoring.");
84             }
85         }
86     }
87
88     /** {@inheritDoc} */
89     public boolean supportsPassive() {
90         return true;
91     }
92
93     /** {@inheritDoc} */
94     public boolean supportsForceAuthentication() {
95         return true;
96     }
97
98     /**
99      * Get the username for all IP-address authenticated users.
100      * 
101      * @return The username for IP-address authenticated users.
102      */
103     public String getUsername() {
104         return username;
105     }
106
107     /**
108      * Set the username to use for all IP-address authenticated users.
109      * 
110      * @param name The username for IP-address authenticated users.
111      */
112     public void setUsername(String name) {
113         username = name;
114     }
115
116     /** {@inheritDoc} */
117     public void login(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
118
119         if (defaultDeny) {
120             handleDefaultDeny(httpRequest, httpResponse);
121         } else {
122             handleDefaultAllow(httpRequest, httpResponse);
123         }
124
125         AuthenticationEngine.returnToAuthenticationEngine(httpRequest, httpResponse);
126     }
127
128     protected void handleDefaultDeny(HttpServletRequest request, HttpServletResponse response) {
129
130         boolean ipAllowed = searchIpList(request);
131
132         if (ipAllowed) {
133             log.debug("Authenticated user by IP address");
134             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
135         }
136     }
137
138     protected void handleDefaultAllow(HttpServletRequest request, HttpServletResponse response) {
139
140         boolean ipDenied = searchIpList(request);
141
142         if (!ipDenied) {
143             log.debug("Authenticated user by IP address");
144             request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
145         }
146     }
147
148     /**
149      * Search the list of InetAddresses for the client's address.
150      * 
151      * @param request The ServletReqeust
152      * 
153      * @return <code>true</code> if the client's address is in <code>ipList</code>
154      */
155     private boolean searchIpList(ServletRequest request) {
156
157         boolean found = false;
158
159         try {
160             InetAddress addr = InetAddress.getByName(request.getRemoteAddr());
161             BitSet addrbits = byteArrayToBitSet(addr.getAddress());
162
163             for (IPEntry entry : ipList) {
164
165                 BitSet netaddr = entry.getNetworkAddress();
166                 BitSet netmask = entry.getNetmask();
167
168                 addrbits.and(netmask);
169                 if (addrbits.equals(netaddr)) {
170                     found = true;
171                     break;
172                 }
173             }
174
175         } catch (UnknownHostException ex) {
176             log.error("Error resolving hostname.", ex);
177             return false;
178         }
179
180         return found;
181     }
182
183     /**
184      * Converts a byte array to a BitSet.
185      * 
186      * The supplied byte array is assumed to have the most signifigant bit in element 0.
187      * 
188      * @param bytes the byte array with most signifigant bit in element 0.
189      * 
190      * @return the BitSet
191      */
192     protected BitSet byteArrayToBitSet(final byte[] bytes) {
193
194         BitSet bits = new BitSet();
195
196         for (int i = 0; i < bytes.length * 8; i++) {
197             if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
198                 bits.set(i);
199             }
200         }
201
202         return bits;
203     }
204
205     /**
206      * Encapsulates a network address and a netmask on ipList.
207      */
208     protected class IPEntry {
209
210         /** The network address. */
211         private final BitSet networkAddress;
212
213         /** The netmask. */
214         private final BitSet netmask;
215
216         /**
217          * Construct a new IPEntry given a network address in CIDR format.
218          * 
219          * @param entry A CIDR-formatted network address/netmask
220          * 
221          * @throws UnknownHostException If entry is malformed.
222          */
223         public IPEntry(String entry) throws UnknownHostException {
224
225             // quick sanity checks
226             if (entry == null || entry.length() == 0) {
227                 throw new UnknownHostException("entry is null.");
228             }
229
230             int cidrOffset = entry.indexOf("/");
231             if (cidrOffset == -1) {
232                 log.warn("Invalid entry \"" + entry + "\" -- it lacks a netmask component.");
233                 throw new UnknownHostException("entry lacks a netmask component.");
234             }
235
236             // ensure that only one "/" is present.
237             if (entry.indexOf("/", cidrOffset + 1) != -1) {
238                 log.warn("Invalid entry \"" + entry + "\" -- too many \"/\" present.");
239                 throw new UnknownHostException("entry has too many netmask components.");
240             }
241
242             String networkString = entry.substring(0, cidrOffset);
243             String netmaskString = entry.substring(cidrOffset + 1, entry.length());
244
245             InetAddress tempAddr = InetAddress.getByName(networkString);
246             networkAddress = byteArrayToBitSet(tempAddr.getAddress());
247
248             int masklen = Integer.parseInt(netmaskString);
249             int addrlen = networkAddress.length();
250
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);
256             }
257
258             netmask = new BitSet(addrlen);
259             netmask.set(addrlen - masklen, addrlen, true);
260         }
261
262         /**
263          * Get the network address.
264          * 
265          * @return the network address.
266          */
267         public BitSet getNetworkAddress() {
268             return networkAddress;
269         }
270
271         /**
272          * Get the netmask.
273          * 
274          * @return the netmask.
275          */
276         public BitSet getNetmask() {
277             return netmask;
278         }
279     }
280 }