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 }