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