View Javadoc

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.error("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.error("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.error("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 }