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 }