2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 package edu.internet2.middleware.shibboleth.wayf;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.net.URLEncoder;
56 import javax.servlet.RequestDispatcher;
57 import javax.servlet.ServletException;
58 import javax.servlet.UnavailableException;
59 import javax.servlet.http.HttpServlet;
60 import javax.servlet.http.HttpServletRequest;
61 import javax.servlet.http.HttpServletResponse;
63 import org.apache.log4j.Logger;
64 import org.xml.sax.SAXException;
67 * A servlet implementation of the Shibboleth WAYF service. Allows a browser user to
68 * select from among a group of origin sites. User selection is optionally cached
69 * and the user is forwarded to the HandleService appropriate to his selection.
71 * @author Walter Hoehn wassa@columbia.edu
73 public class WayfService extends HttpServlet {
75 private String wayfConfigFileLocation;
76 private String siteConfigFileLocation;
77 private WayfConfig config;
78 private WayfOrigins originConfig;
79 private WayfCacheOptions wOptions = new WayfCacheOptions();
80 private static Logger log = Logger.getLogger(WayfService.class.getName());
83 * @see GenericServlet#init()
85 public void init() throws ServletException {
88 log.info("Initializing WAYF.");
90 log.info("Loading configuration from file.");
93 //Setup Cacheing options
94 wOptions.setDomain(config.getCacheDomain());
95 wOptions.setExpiration(config.getCacheExpiration());
98 log.info("WAYF initialization completed.");
102 * Populates WayfConfig and WayfOrigins objects from file contents.
104 private void configure() throws UnavailableException {
106 InputStream is = getServletContext().getResourceAsStream(wayfConfigFileLocation);
107 WayfConfigDigester digester = new WayfConfigDigester(getServletContext());
108 InputStream siteIs = getServletContext().getResourceAsStream(siteConfigFileLocation);
109 OriginSitesDigester siteDigester = new OriginSitesDigester(getServletContext());
112 digester.setValidating(true);
113 config = (WayfConfig) digester.parse(is);
115 } catch (SAXException se) {
116 log.fatal("Error parsing WAYF configuration file.", se);
117 throw new UnavailableException("Error parsing WAYF configuration file.");
118 } catch (IOException ioe) {
119 log.fatal("Error reading WAYF configuration file.", ioe);
120 throw new UnavailableException("Error reading WAYF configuration file.");
124 siteDigester.setValidating(true);
125 originConfig = (WayfOrigins) siteDigester.parse(siteIs);
127 } catch (SAXException se) {
128 log.fatal("Error parsing site file.", se);
129 throw new UnavailableException("Error parsing site file.");
130 } catch (IOException ioe) {
131 log.fatal("Error reading site file.", ioe);
132 throw new UnavailableException("Error reading site file.");
137 * Setup application-wide beans for view
139 private void initViewConfig() {
140 getServletContext().setAttribute("originsets", originConfig.getOriginSets());
141 getServletContext().setAttribute("supportContact", config.getSupportContact());
142 getServletContext().setAttribute("helpText", config.getHelpText());
143 getServletContext().setAttribute("searchResultEmptyText", config.getSearchResultEmptyText());
144 getServletContext().setAttribute("logoLocation", config.getLogoLocation());
148 * Reads parameters from web.xml <init-param /> construct.
150 private void loadInitParams() {
152 wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
153 if (wayfConfigFileLocation == null) {
154 log.warn("No WAYFConfigFileLocation parameter found... using default location.");
155 wayfConfigFileLocation = "/WEB-INF/conf/wayfconfig.xml";
157 siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
158 if (siteConfigFileLocation == null) {
159 log.warn("No SiteonfigFileLocation parameter found... using default location.");
160 siteConfigFileLocation = "/WEB-INF/conf/sites.xml";
166 * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
168 public void doGet(HttpServletRequest req, HttpServletResponse res) {
170 log.info("Handling WAYF request.");
171 //Tell the browser not to cache the WAYF page
172 res.setHeader("Cache-Control", "no-cache");
173 res.setHeader("Pragma", "no-cache");
174 res.setDateHeader("Expires", 0);
176 //Decide how to route the request based on query string
177 String requestType = req.getParameter("action");
178 if (requestType == null) {
179 requestType = "lookup";
182 if (requestType.equals("deleteFromCache")) {
183 log.debug("Deleting saved HS from cache");
184 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).deleteHsFromCache(req, res);
185 handleLookup(req, res);
186 } else if (WayfCacheFactory.getInstance(config.getCacheType()).hasCachedHS(req)) {
190 WayfCacheFactory.getInstance(config.getCacheType()).getCachedHS(req));
191 } else if (requestType.equals("search")) {
192 handleSearch(req, res);
193 } else if (requestType.equals("selection")) {
194 handleSelection(req, res);
196 handleLookup(req, res);
198 } catch (WayfException we) {
199 handleError(req, res, we);
204 * Displays a WAYF selection page.
206 private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
208 if ((getSHIRE(req) == null) || (getTarget(req) == null)) {
209 throw new WayfException("Invalid or missing data from SHIRE");
211 req.setAttribute("shire", getSHIRE(req));
212 req.setAttribute("target", getTarget(req));
213 req.setAttribute("encodedShire", URLEncoder.encode(getSHIRE(req)));
214 req.setAttribute("encodedTarget", URLEncoder.encode(getTarget(req)));
215 req.setAttribute("requestURL", req.getRequestURI().toString());
217 log.debug("Displaying WAYF selection page.");
218 RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
220 rd.forward(req, res);
221 } catch (IOException ioe) {
222 throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
223 } catch (ServletException se) {
224 throw new WayfException("Problem displaying WAYF UI." + se.toString());
229 * Looks for origin sites that match search terms supplied by the user
231 private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
233 if (req.getParameter("string") != null) {
234 Origin[] origins = originConfig.seachForMatchingOrigins(req.getParameter("string"), config);
235 if (origins.length != 0) {
236 req.setAttribute("searchresults", origins);
238 req.setAttribute("searchResultsEmpty", "true");
241 handleLookup(req, res);
245 * Registers a user's HS selection and forwards appropriately
247 private void handleSelection(HttpServletRequest req, HttpServletResponse res) throws WayfException {
249 log.debug("Processing handle selection: " + req.getParameter("origin"));
250 String handleService = originConfig.lookupHSbyName(req.getParameter("origin"));
251 if (handleService == null) {
252 handleLookup(req, res);
254 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).addHsToCache(handleService, req, res);
255 forwardToHS(req, res, handleService);
261 * Uses an HTTP Status 307 redirect to forward the user the HS.
262 * @param handleService The URL of the Shiboleth HS.
264 private void forwardToHS(HttpServletRequest req, HttpServletResponse res, String handleService)
265 throws WayfException {
267 String shire = getSHIRE(req);
268 String target = getTarget(req);
269 log.info("Redirecting to selected Handle Service");
274 + URLEncoder.encode(target)
276 + URLEncoder.encode(shire));
277 } catch (IOException ioe) {
278 throw new WayfException("Error forwarding to HS: " + ioe.toString());
284 * Handles all "recoverable" errors in WAYF processing by logging the error
285 * and forwarding the user to an appropriate error page.
286 * @param we The WayfException respective to the error being handled
288 private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
290 log.error("WAYF Failure: " + we.toString());
291 log.debug("Displaying WAYF error page.");
292 req.setAttribute("errorText", we.toString());
293 req.setAttribute("requestURL", req.getRequestURI().toString());
294 RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
297 rd.forward(req, res);
298 } catch (IOException ioe) {
299 log.error("Problem trying to display WAYF error page: " + ioe.toString());
300 } catch (ServletException se) {
301 log.error("Problem trying to display WAYF error page: " + se.toString());
305 * Retrieves the SHIRE from the request.
306 * @throws WayfException If the request does not contain a shire parameter.
308 private String getSHIRE(HttpServletRequest req) throws WayfException {
310 String shire = (String) req.getAttribute("shire");
311 if (req.getParameter("shire") != null) {
312 shire = req.getParameter("shire");
315 throw new WayfException("Invalid data from SHIRE: No acceptance URL received.");
320 * Retrieves the user's target URL from the request.
321 * @throws WayfException If the request does not contain a target parameter
323 private String getTarget(HttpServletRequest req) throws WayfException {
325 String target = (String) req.getAttribute("target");
326 if (req.getParameter("target") != null) {
327 target = req.getParameter("target");
329 if (target == null) {
330 throw new WayfException("Invalid data from SHIRE: No target URL received.");