Merge remote branch 'tags/2.3.4'
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / session / IdPSessionFilter.java
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 }