View Javadoc

1   /*
2    * Copyright 2008 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.session;
18  
19  import java.io.IOException;
20  import java.security.GeneralSecurityException;
21  import java.security.MessageDigest;
22  import java.util.Arrays;
23  
24  import javax.servlet.Filter;
25  import javax.servlet.FilterChain;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  import javax.servlet.http.Cookie;
31  import javax.servlet.http.HttpServletRequest;
32  
33  import org.joda.time.DateTime;
34  import org.opensaml.ws.transport.http.HTTPTransportUtils;
35  import org.opensaml.xml.util.Base64;
36  import org.opensaml.xml.util.DatatypeHelper;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import org.slf4j.MDC;
40  
41  import edu.internet2.middleware.shibboleth.common.session.SessionManager;
42  import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
43  
44  /**
45   * A filter that adds the current users {@link Session} the request, if the user has a session.
46   */
47  public class IdPSessionFilter implements Filter {
48  
49      /** Class Logger. */
50      private final Logger log = LoggerFactory.getLogger(IdPSessionFilter.class);
51  
52      /** Whether the client must always come back from the same address. */
53      private boolean consistentAddress;
54  
55      /** IdP session manager. */
56      private SessionManager<Session> sessionManager;
57  
58      /** {@inheritDoc} */
59      public void destroy() {
60  
61      }
62  
63      /** {@inheritDoc} */
64      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
65              ServletException {
66          HttpServletRequest httpRequest = (HttpServletRequest) request;
67  
68          Cookie sessionCookie = getIdPSessionCookie(httpRequest);
69          Session idpSession = validateCookie(sessionCookie, httpRequest);
70          if (idpSession != null) {
71              log.trace("Updating IdP session activity time and adding session object to the request");
72              idpSession.setLastActivityInstant(new DateTime());
73              MDC.put("idpSessionId", idpSession.getSessionID());
74              httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
75          }
76  
77          filterChain.doFilter(request, response);
78      }
79  
80      /** {@inheritDoc} */
81      public void init(FilterConfig filterConfig) throws ServletException {
82          String sessionManagerId = filterConfig.getInitParameter("sessionManagedId");
83          if (DatatypeHelper.isEmpty(sessionManagerId)) {
84              sessionManagerId = "shibboleth.SessionManager";
85          }
86  
87          sessionManager = (SessionManager<Session>) filterConfig.getServletContext().getAttribute(sessionManagerId);
88  
89          String consistentAddressParam = filterConfig.getInitParameter("ensureConsistentClientAddress");
90          if (DatatypeHelper.isEmpty(consistentAddressParam)) {
91              consistentAddress = true;
92          } else {
93              consistentAddress = Boolean.parseBoolean(consistentAddressParam);
94          }
95      }
96  
97      /**
98       * Gets the IdP session cookie from the current request, if the user currently has a session.
99       * 
100      * @param httpRequest current HTTP request
101      * 
102      * @return the user's current IdP session cookie, if they have a current session, otherwise null
103      */
104     protected Cookie getIdPSessionCookie(HttpServletRequest httpRequest) {
105         log.trace("Attempting to retrieve IdP session cookie.");
106         Cookie[] requestCookies = httpRequest.getCookies();
107 
108         if (requestCookies != null) {
109             for (Cookie requestCookie : requestCookies) {
110                 if (DatatypeHelper.safeEquals(requestCookie.getName(), AuthenticationEngine.IDP_SESSION_COOKIE_NAME)) {
111                     log.trace("Found IdP session cookie.");
112                     return requestCookie;
113                 }
114             }
115         }
116 
117         return null;
118     }
119 
120     /**
121      * Validates the given session cookie against the associated session.
122      * 
123      * @param sessionCookie the session cookie
124      * @param httpRequest the current HTTP request
125      * 
126      * @return the session against which the cookie was validated
127      */
128     protected Session validateCookie(Cookie sessionCookie, HttpServletRequest httpRequest) {
129         if (sessionCookie == null) {
130             return null;
131         }
132 
133         // index 0: remote address
134         // index 1: session ID
135         // index 2: Base64(HMAC(index 0 + index 1))
136         String[] valueComponents = HTTPTransportUtils.urlDecode(sessionCookie.getValue()).split("\\|");
137         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
138         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
139         byte[] signatureBytes = Base64.decode(valueComponents[2]);
140 
141         if (consistentAddress) {
142             String remoteAddress = new String(remoteAddressBytes);
143             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
144                 log.error("Client sent a cookie from addres {} but the cookie was issued to address {}", httpRequest
145                         .getRemoteAddr(), remoteAddress);
146                 return null;
147             }
148         }
149 
150         String sessionId = new String(sessionIdBytes);
151         Session userSession = sessionManager.getSession(sessionId);
152 
153         if (userSession != null) {
154             try {
155                 MessageDigest digester = MessageDigest.getInstance("SHA");
156                 digester.update(userSession.getSessionSecret());
157                 digester.update(remoteAddressBytes);
158                 digester.update(sessionIdBytes);
159                 if (!Arrays.equals(digester.digest(), signatureBytes)) {
160                     log.error("Session cookie signature did not match, the session cookie has been tampered with");
161                     return null;
162                 }
163             } catch (GeneralSecurityException e) {
164                 log.error("Unable to computer over session cookie material", e);
165             }
166         } else {
167             log.debug("No session associated with session ID {} - session must have timed out",
168                             valueComponents[1]);
169         }
170         return userSession;
171     }
172 }