Begin adding support for metadata reloading in WAYF.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / wayf / WayfService.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
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.
22  * 
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
28  * 
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.
33  * 
34  * 
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.
48  */
49
50 package edu.internet2.middleware.shibboleth.wayf;
51
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.net.URLEncoder;
55
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;
62
63 import org.apache.log4j.Logger;
64 import org.xml.sax.SAXException;
65
66 import edu.internet2.middleware.shibboleth.common.ShibResource;
67
68 /**
69  * A servlet implementation of the Shibboleth WAYF service.  Allows a browser user to 
70  * select from among a group of origin sites.  User selection is optionally cached 
71  * and the user is forwarded to the HandleService appropriate to his selection.
72  *
73  * @author Walter Hoehn wassa&#064;columbia.edu
74  */
75 public class WayfService extends HttpServlet {
76
77         private String wayfConfigFileLocation;
78         private String siteConfigFileLocation;
79         private WayfConfig config;
80         private WayfOrigins originConfig;
81         private WayfCacheOptions wOptions = new WayfCacheOptions();
82         private static Logger log = Logger.getLogger(WayfService.class.getName());
83
84         /**
85          * @see GenericServlet#init()
86          */
87         public void init() throws ServletException {
88
89                 super.init();
90                 log.info("Initializing WAYF.");
91                 loadInitParams();
92                 log.info("Loading configuration from file.");
93                 configure();
94                 
95                 //Setup Cacheing options
96                 wOptions.setDomain(config.getCacheDomain());
97                 wOptions.setExpiration(config.getCacheExpiration());
98                 
99                 initViewConfig();
100                 log.info("WAYF initialization completed.");
101         }
102
103         /**
104          * Populates WayfConfig and WayfOrigins objects from file contents.
105          */
106         private void configure() throws UnavailableException {
107                 try {
108                         InputStream is = new ShibResource(wayfConfigFileLocation, this.getClass()).getInputStream();
109                         WayfConfigDigester digester = new WayfConfigDigester();
110                         digester.setValidating(true);
111                         config = (WayfConfig) digester.parse(is);
112
113                 } catch (SAXException se) {
114                         log.fatal("Error parsing WAYF configuration file.", se);
115                         throw new UnavailableException("Error parsing WAYF configuration file.");
116                 } catch (IOException ioe) {
117                         log.fatal("Error reading WAYF configuration file.", ioe);
118                         throw new UnavailableException("Error reading WAYF configuration file.");
119                 }
120
121                 loadSiteConfig();
122         }
123         
124         private void loadSiteConfig() throws UnavailableException {
125                 try {
126                         InputStream siteIs = getServletContext().getResourceAsStream(siteConfigFileLocation);
127                         OriginSitesDigester siteDigester = new OriginSitesDigester();
128                         siteDigester.setValidating(true);
129                         originConfig = (WayfOrigins) siteDigester.parse(siteIs);
130
131                 } catch (SAXException se) {
132                         log.fatal("Error parsing site file.", se);
133                         throw new UnavailableException("Error parsing site file.");
134                 } catch (IOException ioe) {
135                         log.fatal("Error reading site file.", ioe);
136                         throw new UnavailableException("Error reading site file.");
137                 }
138         }
139
140         /**
141          * Setup application-wide beans for view
142          */
143         private void initViewConfig() {
144                 getServletContext().setAttribute("originsets", getOrigins().getOriginSets());
145                 getServletContext().setAttribute("supportContact", config.getSupportContact());
146                 getServletContext().setAttribute("helpText", config.getHelpText());
147                 getServletContext().setAttribute("searchResultEmptyText", config.getSearchResultEmptyText());
148                 getServletContext().setAttribute("logoLocation", config.getLogoLocation());
149         }
150
151         /**
152          * Reads parameters from web.xml <init-param /> construct.
153          */
154         private void loadInitParams() {
155
156                 wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
157                 if (wayfConfigFileLocation == null) {
158                         log.warn("No WAYFConfigFileLocation parameter found... using default location.");
159                         wayfConfigFileLocation = "/conf/wayfconfig.xml";
160                 }
161                 siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
162                 if (siteConfigFileLocation == null) {
163                         log.warn("No SiteonfigFileLocation parameter found... using default location.");
164                         siteConfigFileLocation = "/sites.xml";
165                 }
166
167         }
168
169         /**
170          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
171          */
172         public void doGet(HttpServletRequest req, HttpServletResponse res) {
173
174                 log.info("Handling WAYF request.");
175                 //Tell the browser not to cache the WAYF page
176                 res.setHeader("Cache-Control", "no-cache");
177                 res.setHeader("Pragma", "no-cache");
178                 res.setDateHeader("Expires", 0);
179
180                 //Decide how to route the request based on query string
181                 String requestType = req.getParameter("action");
182                 if (requestType == null) {
183                         requestType = "lookup";
184                 }
185                 try {
186                         if (requestType.equals("deleteFromCache")) {
187                                 log.debug("Deleting saved HS from cache");
188                                 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).deleteHsFromCache(req, res);
189                                 handleLookup(req, res);
190                         } else if (WayfCacheFactory.getInstance(config.getCacheType()).hasCachedHS(req)) {
191                                 forwardToHS(
192                                         req,
193                                         res,
194                                         WayfCacheFactory.getInstance(config.getCacheType()).getCachedHS(req));
195                         } else if (requestType.equals("search")) {
196                                 handleSearch(req, res);
197                         } else if (requestType.equals("selection")) {
198                                 handleSelection(req, res);
199                         } else {
200                                 handleLookup(req, res);
201                         }
202                 } catch (WayfException we) {
203                         handleError(req, res, we);
204                 }
205         }
206
207         /**
208          * Displays a WAYF selection page.
209          */
210         private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
211
212                 try {
213                         if ((getSHIRE(req) == null) || (getTarget(req) == null)) {
214                                 throw new WayfException("Invalid or missing data from SHIRE");
215                         }
216                         req.setAttribute("shire", getSHIRE(req));
217                         req.setAttribute("target", getTarget(req));
218                         req.setAttribute("encodedShire", URLEncoder.encode(getSHIRE(req), "UTF-8"));
219                         req.setAttribute("encodedTarget", URLEncoder.encode(getTarget(req), "UTF-8"));
220                         req.setAttribute("requestURL", req.getRequestURI().toString());
221
222                         log.debug("Displaying WAYF selection page.");
223                         RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
224
225                         rd.forward(req, res);
226                 } catch (IOException ioe) {
227                         throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
228                 } catch (ServletException se) {
229                         throw new WayfException("Problem displaying WAYF UI." + se.toString());
230                 }
231         }
232         
233         /**
234          * Looks for origin sites that match search terms supplied by the user
235          */
236         private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
237
238                 if (req.getParameter("string") != null) {
239                         Origin[] origins = getOrigins().seachForMatchingOrigins(req.getParameter("string"), config);
240                         if (origins.length != 0) {
241                                 req.setAttribute("searchresults", origins);
242                         } else {
243                                 req.setAttribute("searchResultsEmpty", "true");
244                         }
245                 }
246                 handleLookup(req, res);
247         }
248
249         /**
250          * Registers a user's HS selection and forwards appropriately
251          */
252         private void handleSelection(HttpServletRequest req, HttpServletResponse res) throws WayfException {
253
254                 log.debug("Processing handle selection: " + req.getParameter("origin"));
255                 String handleService = getOrigins().lookupHSbyName(req.getParameter("origin"));
256                 if (handleService == null) {
257                         handleLookup(req, res);
258                 } else {
259                         if ((req.getParameter("cache") != null)
260                                 && req.getParameter("cache").equalsIgnoreCase("TRUE")) {
261                                 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).addHsToCache(handleService, req, res);
262                         }
263                         forwardToHS(req, res, handleService);
264                 }
265
266         }
267
268         /**
269          * Uses an HTTP Status 307 redirect to forward the user the HS.
270          * @param handleService The URL of the Shiboleth HS.
271          */
272         private void forwardToHS(HttpServletRequest req, HttpServletResponse res, String handleService)
273                 throws WayfException {
274
275                 String shire = getSHIRE(req);
276                 String target = getTarget(req);
277                 log.info("Redirecting to selected Handle Service");
278                 try {
279                         res.sendRedirect(
280                                 handleService
281                                         + "?target="
282                                         + URLEncoder.encode(target, "UTF-8")
283                                         + "&shire="
284                                         + URLEncoder.encode(shire, "UTF-8"));
285                 } catch (IOException ioe) {
286                         throw new WayfException("Error forwarding to HS: " + ioe.toString());
287                 }
288
289         }
290
291         /**
292          * Handles all "recoverable" errors in WAYF processing by logging the error
293          * and forwarding the user to an appropriate error page.
294          * @param we The WayfException respective to the error being handled
295          */
296         private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
297
298                 log.error("WAYF Failure: " + we.toString());
299                 log.debug("Displaying WAYF error page.");
300                 req.setAttribute("errorText", we.toString());
301                 req.setAttribute("requestURL", req.getRequestURI().toString());
302                 RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
303
304                 try {
305                         rd.forward(req, res);
306                 } catch (IOException ioe) {
307                         log.error("Problem trying to display WAYF error page: " + ioe.toString());
308                 } catch (ServletException se) {
309                         log.error("Problem trying to display WAYF error page: " + se.toString());
310                 }
311         }
312         /**
313          * Retrieves the SHIRE from the request.
314          * @throws WayfException If the request does not contain a shire parameter.
315          */
316         private String getSHIRE(HttpServletRequest req) throws WayfException {
317
318                 String shire = (String) req.getAttribute("shire");
319                 if (req.getParameter("shire") != null) {
320                         shire = req.getParameter("shire");
321                 }
322                 if (shire == null) {
323                         throw new WayfException("Invalid data from SHIRE: No acceptance URL received.");
324                 }
325                 return shire;
326         }
327         /**
328          * Retrieves the user's target URL from the request.
329          * @throws WayfException If the request does not contain a target parameter
330          */
331         private String getTarget(HttpServletRequest req) throws WayfException {
332
333                 String target = (String) req.getAttribute("target");
334                 if (req.getParameter("target") != null) {
335                         target = req.getParameter("target");
336                 }
337                 if (target == null) {
338                         throw new WayfException("Invalid data from SHIRE: No target URL received.");
339                 }
340                 return target;
341         }
342         
343         private WayfOrigins getOrigins() {
344                 synchronized (originConfig) {
345                         return originConfig;
346                 }
347         }
348         
349         private void reloadOriginMetadata() {
350
351                 WayfOrigins safetyCache = getOrigins();
352                 try {
353                         synchronized (originConfig) {
354                                 loadSiteConfig();
355                         }
356                         getServletContext().setAttribute("originsets", getOrigins().getOriginSets());
357                 } catch (UnavailableException e) {
358                         log.error("Failed to load updated origin site metadata: " + e);
359                         synchronized (originConfig) {
360                                 originConfig = safetyCache;
361                         }
362                 }
363         }
364
365 }
366