View Javadoc

    1   /*
    2    * Licensed to the University Corporation for Advanced Internet Development,
    3    * Inc. (UCAID) under one or more contributor license agreements.  See the
    4    * NOTICE file distributed with this work for additional information regarding
    5    * copyright ownership. The UCAID licenses this file to You under the Apache
    6    * License, Version 2.0 (the "License"); you may not use this file except in
    7    * compliance with the License.  You may obtain a copy of the License at
    8    *
    9    *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package edu.internet2.middleware.shibboleth.idp.session;
    19
    20  import java.io.IOException;
    21  import java.security.GeneralSecurityException;
    22  import java.security.MessageDigest;
    23  import java.util.Arrays;
    24
    25  import javax.servlet.Filter;
    26  import javax.servlet.FilterChain;
    27  import javax.servlet.FilterConfig;
    28  import javax.servlet.ServletException;
    29  import javax.servlet.ServletRequest;
    30  import javax.servlet.ServletResponse;
    31  import javax.servlet.http.Cookie;
    32  import javax.servlet.http.HttpServletRequest;
    33
    34  import org.joda.time.DateTime;
    35  import org.opensaml.ws.transport.http.HTTPTransportUtils;
    36  import org.opensaml.xml.util.Base64;
    37  import org.opensaml.xml.util.DatatypeHelper;
    38  import org.slf4j.Logger;
    39  import org.slf4j.LoggerFactory;
    40  import org.slf4j.MDC;
    41
    42  import edu.internet2.middleware.shibboleth.common.session.SessionManager;
    43  import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
    44
    45  /**
    46   * A filter that adds the current users {@link Session} the request, if the user has a session.
    47   */
    48  public class IdPSessionFilter implements Filter {
    49
    50      /** Class Logger. */
    51      private final Logger log = LoggerFactory.getLogger(IdPSessionFilter.class);
    52
    53      /** Whether the client must always come back from the same address. */
    54      private boolean consistentAddress;
    55
    56      /** IdP session manager. */
    57      private SessionManager<Session> sessionManager;
    58
    59      /** {@inheritDoc} */
    60      public void destroy() {
    61
    62      }
    63
    64      /** {@inheritDoc} */
    65      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
    66              ServletException {
    67          HttpServletRequest httpRequest = (HttpServletRequest) request;
    68
    69          MDC.put("JSESSIONID", httpRequest.getSession().getId());
    70          MDC.put("clientIP", httpRequest.getRemoteAddr());
    71
    72          Cookie sessionCookie = getIdPSessionCookie(httpRequest);
    73          Session idpSession = getUserSession(sessionCookie, httpRequest);
    74          if (idpSession != null) {
    75              log.trace("Updating IdP session activity time and adding session object to the request");
    76              idpSession.setLastActivityInstant(new DateTime());
    77              MDC.put("idpSessionId", idpSession.getSessionID());
    78              httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
    79          }
    80
    81          filterChain.doFilter(request, response);
    82      }
    83
    84      /** {@inheritDoc} */
    85      public void init(FilterConfig filterConfig) throws ServletException {
    86          String sessionManagerId = filterConfig.getInitParameter("sessionManagedId");
    87          if (DatatypeHelper.isEmpty(sessionManagerId)) {
    88              sessionManagerId = "shibboleth.SessionManager";
    89          }
    90
    91          sessionManager = (SessionManager<Session>) filterConfig.getServletContext().getAttribute(sessionManagerId);
    92
    93          String consistentAddressParam = filterConfig.getInitParameter("ensureConsistentClientAddress");
    94          if (DatatypeHelper.isEmpty(consistentAddressParam)) {
    95              consistentAddress = true;
    96          } else {
    97              consistentAddress = Boolean.parseBoolean(consistentAddressParam);
    98          }
    99      }
    100
    101     /**
    102      * Gets the IdP session cookie from the current request, if the user currently has a session.
    103      *
    104      * @param httpRequest current HTTP request
    105      *
    106      * @return the user's current IdP session cookie, if they have a current session, otherwise null
    107      */
    108     protected Cookie getIdPSessionCookie(HttpServletRequest httpRequest) {
    109         log.trace("Attempting to retrieve IdP session cookie.");
    110         Cookie[] requestCookies = httpRequest.getCookies();
    111
    112         if (requestCookies != null) {
    113             for (Cookie requestCookie : requestCookies) {
    114                 if (DatatypeHelper.safeEquals(requestCookie.getName(), AuthenticationEngine.IDP_SESSION_COOKIE_NAME)) {
    115                     log.trace("Found IdP session cookie.");
    116                     return requestCookie;
    117                 }
    118             }
    119         }
    120
    121         return null;
    122     }
    123
    124     /**
    125      * Gets the user session associated with a session cookie.
    126      *
    127      * @param sessionCookie the session cookie
    128      * @param httpRequest the current HTTP request
    129      *
    130      * @return the session associated with the cookie or null if there is no currently assoicated session
    131      */
    132     protected Session getUserSession(Cookie sessionCookie, HttpServletRequest httpRequest) {
    133         if (sessionCookie == null || DatatypeHelper.isEmpty(sessionCookie.getValue())) {
    134             return null;
    135         }
    136
    137         // index 0: remote address
    138         // index 1: session ID
    139         // index 2: Base64(HMAC(index 0 + index 1))
    140         String cookieValue = HTTPTransportUtils.urlDecode(sessionCookie.getValue());
    141         String[] valueComponents = cookieValue.split("\\|");
    142         if (valueComponents.length != 3) {
    143             log.warn("IdP session cookie has an improperly formated value: {}", cookieValue);
    144             return null;
    145         }
    146
    147         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
    148         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
    149         byte[] signatureBytes = Base64.decode(valueComponents[2]);
    150
    151         String sessionId = new String(sessionIdBytes);
    152         Session userSession = sessionManager.getSession(sessionId);
    153
    154         if (userSession != null) {
    155             if (isCookieValid(httpRequest, remoteAddressBytes, sessionIdBytes, signatureBytes, userSession
    156                     .getSessionSecret())) {
    157                 return userSession;
    158             }
    159         } else {
    160             log.debug("No session associated with session ID {} - session must have timed out", valueComponents[1]);
    161         }
    162         return null;
    163     }
    164
    165     /**
    166      * Validates the session cookie. This validates that the cookie came from the same IP address to which it was given,
    167      * if consistent address checking is enabled, and that cookie data hasn't been changed.
    168      *
    169      * @param httpRequest incoming HTTP request
    170      * @param remoteAddressBytes remote address from the cookie value
    171      * @param sessionIdBytes session ID from the cookie value
    172      * @param signatureBytes signature from the cookie value
    173      * @param sessionSecret secrete associated with the user's session
    174      *
    175      * @return true if the information in the cookie is valid, false if not
    176      */
    177     protected boolean isCookieValid(HttpServletRequest httpRequest, byte[] remoteAddressBytes, byte[] sessionIdBytes,
    178             byte[] signatureBytes, byte[] sessionSecret) {
    179         if (consistentAddress) {
    180             String remoteAddress = new String(remoteAddressBytes);
    181             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
    182                 log.error("Client sent a cookie from address {} but the cookie was issued to address {}", httpRequest
    183                         .getRemoteAddr(), remoteAddress);
    184                 return false;
    185             }
    186         }
    187
    188         try {
    189             MessageDigest digester = MessageDigest.getInstance("SHA");
    190             digester.update(sessionSecret);
    191             digester.update(remoteAddressBytes);
    192             digester.update(sessionIdBytes);
    193             if (!Arrays.equals(digester.digest(), signatureBytes)) {
    194                 log.error("Session cookie has been tampered with, its signature no longer matches expected value");
    195                 return false;
    196             }
    197         } catch (GeneralSecurityException e) {
    198             log.error("Unable to compute signature over session cookie material", e);
    199         }
    200
    201         return true;
    202     }
    203 }