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 = getUserSession(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      * Gets the user session associated with a session cookie.
122      * 
123      * @param sessionCookie the session cookie
124      * @param httpRequest the current HTTP request
125      * 
126      * @return the session associated with the cookie or null if there is no currently assoicated session
127      */
128     protected Session getUserSession(Cookie sessionCookie, HttpServletRequest httpRequest) {
129         if (sessionCookie == null || DatatypeHelper.isEmpty(sessionCookie.getValue())) {
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 cookieValue = HTTPTransportUtils.urlDecode(sessionCookie.getValue());
137         String[] valueComponents = cookieValue.split("\\|");
138         if (valueComponents.length != 3) {
139             log.warn("IdP session cookie has an improperly formated value: {}", cookieValue);
140             return null;
141         }
142 
143         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
144         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
145         byte[] signatureBytes = Base64.decode(valueComponents[2]);
146 
147         String sessionId = new String(sessionIdBytes);
148         Session userSession = sessionManager.getSession(sessionId);
149 
150         if (userSession != null) {
151             if (isCookieValid(httpRequest, remoteAddressBytes, sessionIdBytes, signatureBytes, userSession
152                     .getSessionSecret())) {
153                 return userSession;
154             }
155         } else {
156             log.debug("No session associated with session ID {} - session must have timed out", valueComponents[1]);
157         }
158         return null;
159     }
160 
161     /**
162      * Validates the session cookie. This validates that the cookie came from the same IP address to which it was given,
163      * if consistent address checking is enabled, and that cookie data hasn't been changed.
164      * 
165      * @param httpRequest incoming HTTP request
166      * @param remoteAddressBytes remote address from the cookie value
167      * @param sessionIdBytes session ID from the cookie value
168      * @param signatureBytes signature from the cookie value
169      * @param sessionSecret secrete associated with the user's session
170      * 
171      * @return true if the information in the cookie is valid, false if not
172      */
173     protected boolean isCookieValid(HttpServletRequest httpRequest, byte[] remoteAddressBytes, byte[] sessionIdBytes,
174             byte[] signatureBytes, byte[] sessionSecret) {
175         if (consistentAddress) {
176             String remoteAddress = new String(remoteAddressBytes);
177             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
178                 log.error("Client sent a cookie from addres {} but the cookie was issued to address {}", httpRequest
179                         .getRemoteAddr(), remoteAddress);
180                 return false;
181             }
182         }
183 
184         try {
185             MessageDigest digester = MessageDigest.getInstance("SHA");
186             digester.update(sessionSecret);
187             digester.update(remoteAddressBytes);
188             digester.update(sessionIdBytes);
189             if (!Arrays.equals(digester.digest(), signatureBytes)) {
190                 log.error("Session cookie has been tampered with, its signature no longer matches expected value");
191                 return false;
192             }
193         } catch (GeneralSecurityException e) {
194             log.error("Unable to compute signature over session cookie material", e);
195         }
196 
197         return true;
198     }
199 }