001/* 002 * Copyright (c) 2000-2003 Yale University. All rights reserved. 003 * 004 * THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED 005 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 006 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY 007 * DISCLAIMED. IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE 008 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 009 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF 010 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR 011 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 012 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 013 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 014 * SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH 015 * DAMAGE. 016 * 017 * Redistribution and use of this software in source or binary forms, 018 * with or without modification, are permitted, provided that the 019 * following conditions are met: 020 * 021 * 1. Any redistribution must include the above copyright notice and 022 * disclaimer and this list of conditions in any related documentation 023 * and, if feasible, in the redistributed software. 024 * 025 * 2. Any redistribution must include the acknowledgment, "This product 026 * includes software developed by Yale University," in any related 027 * documentation and, if feasible, in the redistributed software. 028 * 029 * 3. The names "Yale" and "Yale University" must not be used to endorse 030 * or promote products derived from this software. 031 */ 032 033package edu.yale.its.tp.cas.client.filter; 034 035import java.io.*; 036import java.net.*; 037import java.util.*; 038import javax.servlet.*; 039import javax.servlet.http.*; 040import edu.yale.its.tp.cas.client.*; 041import edu.yale.its.tp.cas.client.filter.*; 042import javax.xml.parsers.ParserConfigurationException; 043import org.xml.sax.SAXException; 044 045/** 046 * <p> 047 * Protects web-accessible resources with CAS. 048 * </p> 049 * <p> 050 * The following filter initialization parameters are declared in <code>web.xml</code>: 051 * </p> 052 * <ul> 053 * <li><code>edu.yale.its.tp.cas.client.filter.loginUrl</code>: URL to login page on CAS server. (Required)</li> 054 * <li><code>edu.yale.its.tp.cas.client.filter.validateUrl</code>: URL to validation URL on CAS server. (Required)</li> 055 * <li><code>edu.yale.its.tp.cas.client.filter.serviceUrl</code>: URL of this service. (Required if 056 * <code>serverName</code> is not specified)</li> 057 * <li><code>edu.yale.its.tp.cas.client.filter.serverName</code>: full hostname with port number (e.g. 058 * <code>www.foo.com:8080</code>). Port number isn't required if it is standard (80 for HTTP, 443 for HTTPS). (Required 059 * if <code>serviceUrl</code> is not specified)</li> 060 * <li><code>edu.yale.its.tp.cas.client.filter.authorizedProxy</code>: whitespace-delimited list of valid proxies 061 * through which authentication may have proceeded. One one proxy must match. (Optional. If nothing is specified, the 062 * filter will only accept service tickets – not proxy tickets.)</li> 063 * <li><code>edu.yale.its.tp.cas.client.filter.renew</code>: value of CAS "renew" parameter. Bypasses single sign-on and 064 * requires user to provide CAS with his/her credentials again. (Optional. If nothing is specified, this defaults to 065 * false.)</li> 066 * <li><code>edu.yale.its.tp.cas.client.filter.wrapRequest</code>: wrap the <code>HttpServletRequest</code> object, 067 * overriding the <code>getRemoteUser()</code> method. When set to "true", <code>request.getRemoteUser()</code> will 068 * return the username of the currently logged-in CAS user. (Optional. If nothing is specified, this defaults to false.) 069 * </li> 070 * </ul> 071 * <p> 072 * The logged-in username is set in the session attribute defined by the value of <code>CAS_FILTER_USER</code> and may 073 * be accessed from within your application either by setting <code>wrapRequest</code> and calling 074 * <code>request.getRemoteUser()</code>, or by calling <code>session.getAttribute(CASFilter.CAS_FILTER_USER)</code>. 075 * </p> 076 * 077 * @author Shawn Bayern 078 */ 079public class CASFilter implements Filter { 080 081 // ********************************************************************* 082 // Constants 083 084 /** Session attribute in which the username is stored */ 085 public final static String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user"; 086 087 // ********************************************************************* 088 // Configuration state 089 090 private String casLogin, casValidate, casAuthorizedProxy, casServiceUrl, casRenew, casServerName; 091 092 private boolean wrapRequest; 093 094 // ********************************************************************* 095 // Initialization 096 097 public void init(FilterConfig config) throws ServletException { 098 casLogin = config.getInitParameter("edu.yale.its.tp.cas.client.filter.loginUrl"); 099 casValidate = config.getInitParameter("edu.yale.its.tp.cas.client.filter.validateUrl"); 100 casServiceUrl = config.getInitParameter("edu.yale.its.tp.cas.client.filter.serviceUrl"); 101 casAuthorizedProxy = config.getInitParameter("edu.yale.its.tp.cas.client.filter.authorizedProxy"); 102 casRenew = config.getInitParameter("edu.yale.its.tp.cas.client.filter.renew"); 103 casServerName = config.getInitParameter("edu.yale.its.tp.cas.client.filter.serverName"); 104 wrapRequest = Boolean.valueOf(config.getInitParameter("edu.yale.its.tp.cas.client.filter.wrapRequest")).booleanValue(); 105 } 106 107 // ********************************************************************* 108 // Filter processing 109 110 public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws ServletException, 111 IOException { 112 113 // make sure we've got an HTTP request 114 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) 115 throw new ServletException("CASFilter protects only HTTP resources"); 116 117 // Wrap the request if desired 118 if (wrapRequest) { 119 request = new CASFilterRequestWrapper((HttpServletRequest) request); 120 } 121 122 HttpSession session = ((HttpServletRequest) request).getSession(); 123 124 // if our attribute's already present, don't do anything 125 if (session != null && session.getAttribute(CAS_FILTER_USER) != null) { 126 fc.doFilter(request, response); 127 return; 128 } 129 130 // otherwise, we need to authenticate via CAS 131 String ticket = request.getParameter("ticket"); 132 133 // no ticket? abort request processing and redirect 134 if (ticket == null || ticket.equals("")) { 135 if (casLogin == null) { 136 throw new ServletException("When CASFilter protects pages that do not receive a 'ticket' " 137 + "parameter, it needs a edu.yale.its.tp.cas.client.filter.loginUrl " + "filter parameter"); 138 } 139 ((HttpServletResponse) response).sendRedirect(casLogin + "?service=" 140 + getService((HttpServletRequest) request) 141 + ((casRenew != null && !casRenew.equals("")) ? "&renew=" + casRenew : "")); 142 143 // abort chain 144 return; 145 } 146 147 // Yay, ticket! Validate it. 148 String user = getAuthenticatedUser((HttpServletRequest) request); 149 if (user == null) 150 throw new ServletException("Unexpected CAS authentication error"); 151 152 // Store the authenticated user in the session 153 if (session != null) // probably unncessary 154 session.setAttribute(CAS_FILTER_USER, user); 155 156 // continue processing the request 157 fc.doFilter(request, response); 158 } 159 160 // ********************************************************************* 161 // Destruction 162 163 public void destroy() { 164 } 165 166 // ********************************************************************* 167 // Utility methods 168 169 /** 170 * Converts a ticket parameter to a username, taking into account an optionally configured trusted proxy in the tier 171 * immediately in front of us. 172 */ 173 private String getAuthenticatedUser(HttpServletRequest request) throws ServletException { 174 ProxyTicketValidator pv = null; 175 try { 176 pv = new ProxyTicketValidator(); 177 pv.setCasValidateUrl(casValidate); 178 pv.setServiceTicket(request.getParameter("ticket")); 179 pv.setService(getService(request)); 180 pv.setRenew(Boolean.valueOf(casRenew).booleanValue()); 181 pv.validate(); 182 if (!pv.isAuthenticationSuccesful()) 183 throw new ServletException("CAS authentication error: " + pv.getErrorCode() + ": " 184 + pv.getErrorMessage()); 185 if (pv.getProxyList().size() != 0) { 186 // ticket was proxied 187 if (casAuthorizedProxy == null) { 188 throw new ServletException("this page does not accept proxied tickets"); 189 } else { 190 boolean authorized = false; 191 String proxy = (String) pv.getProxyList().get(0); 192 StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy); 193 while (casProxies.hasMoreTokens()) { 194 if (proxy.equals(casProxies.nextToken())) { 195 authorized = true; 196 break; 197 } 198 } 199 if (!authorized) { 200 throw new ServletException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'"); 201 } 202 } 203 } 204 return pv.getUser(); 205 } catch (SAXException ex) { 206 String xmlResponse = ""; 207 if (pv != null) 208 xmlResponse = pv.getResponse(); 209 throw new ServletException(ex + " " + xmlResponse); 210 } catch (ParserConfigurationException ex) { 211 throw new ServletException(ex); 212 } catch (IOException ex) { 213 throw new ServletException(ex); 214 } 215 } 216 217 /** 218 * Returns either the configured service or figures it out for the current request. The returned service is 219 * URL-encoded. 220 */ 221 private String getService(HttpServletRequest request) throws ServletException { 222 // ensure we have a server name or service name 223 if (casServerName == null && casServiceUrl == null) 224 throw new ServletException("need one of the following configuration " 225 + "parameters: edu.yale.its.tp.cas.client.filter.serviceUrl or " 226 + "edu.yale.its.tp.cas.client.filter.serverName"); 227 228 // use the given string if it's provided 229 if (casServiceUrl != null) 230 return URLEncoder.encode(casServiceUrl); 231 else 232 // otherwise, return our best guess at the service 233 return Util.getService(request, casServerName); 234 } 235}