1cf82b59d0ee3fe9b220a46c31a59129f1a637cd
[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                 try {
122                         InputStream siteIs = getServletContext().getResourceAsStream(siteConfigFileLocation);
123                         OriginSitesDigester siteDigester = new OriginSitesDigester();
124                         siteDigester.setValidating(true);
125                         originConfig = (WayfOrigins) siteDigester.parse(siteIs);
126
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.");
133                 }
134         }
135         
136         /**
137          * Setup application-wide beans for view
138          */
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());
145         }
146
147         /**
148          * Reads parameters from web.xml <init-param /> construct.
149          */
150         private void loadInitParams() {
151
152                 wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
153                 if (wayfConfigFileLocation == null) {
154                         log.warn("No WAYFConfigFileLocation parameter found... using default location.");
155                         wayfConfigFileLocation = "/conf/wayfconfig.xml";
156                 }
157                 siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
158                 if (siteConfigFileLocation == null) {
159                         log.warn("No SiteonfigFileLocation parameter found... using default location.");
160                         siteConfigFileLocation = "/sites.xml";
161                 }
162
163         }
164
165         /**
166          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
167          */
168         public void doGet(HttpServletRequest req, HttpServletResponse res) {
169
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);
175
176                 //Decide how to route the request based on query string
177                 String requestType = req.getParameter("action");
178                 if (requestType == null) {
179                         requestType = "lookup";
180                 }
181                 try {
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)) {
187                                 forwardToHS(
188                                         req,
189                                         res,
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);
195                         } else {
196                                 handleLookup(req, res);
197                         }
198                 } catch (WayfException we) {
199                         handleError(req, res, we);
200                 }
201         }
202
203         /**
204          * Displays a WAYF selection page.
205          */
206         private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
207
208                 try {
209                         if ((getSHIRE(req) == null) || (getTarget(req) == null)) {
210                                 throw new WayfException("Invalid or missing data from SHIRE");
211                         }
212                         req.setAttribute("shire", getSHIRE(req));
213                         req.setAttribute("target", getTarget(req));
214                         req.setAttribute("encodedShire", URLEncoder.encode(getSHIRE(req), "UTF-8"));
215                         req.setAttribute("encodedTarget", URLEncoder.encode(getTarget(req), "UTF-8"));
216                         req.setAttribute("requestURL", req.getRequestURI().toString());
217
218                         log.debug("Displaying WAYF selection page.");
219                         RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
220
221                         rd.forward(req, res);
222                 } catch (IOException ioe) {
223                         throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
224                 } catch (ServletException se) {
225                         throw new WayfException("Problem displaying WAYF UI." + se.toString());
226                 }
227         }
228         
229         /**
230          * Looks for origin sites that match search terms supplied by the user
231          */
232         private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
233
234                 if (req.getParameter("string") != null) {
235                         Origin[] origins = originConfig.seachForMatchingOrigins(req.getParameter("string"), config);
236                         if (origins.length != 0) {
237                                 req.setAttribute("searchresults", origins);
238                         } else {
239                                 req.setAttribute("searchResultsEmpty", "true");
240                         }
241                 }
242                 handleLookup(req, res);
243         }
244
245         /**
246          * Registers a user's HS selection and forwards appropriately
247          */
248         private void handleSelection(HttpServletRequest req, HttpServletResponse res) throws WayfException {
249
250                 log.debug("Processing handle selection: " + req.getParameter("origin"));
251                 String handleService = originConfig.lookupHSbyName(req.getParameter("origin"));
252                 if (handleService == null) {
253                         handleLookup(req, res);
254                 } else {
255                         if ((req.getParameter("cache") != null)
256                                 && req.getParameter("cache").equalsIgnoreCase("TRUE")) {
257                                 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).addHsToCache(handleService, req, res);
258                         }
259                         forwardToHS(req, res, handleService);
260                 }
261
262         }
263
264         /**
265          * Uses an HTTP Status 307 redirect to forward the user the HS.
266          * @param handleService The URL of the Shiboleth HS.
267          */
268         private void forwardToHS(HttpServletRequest req, HttpServletResponse res, String handleService)
269                 throws WayfException {
270
271                 String shire = getSHIRE(req);
272                 String target = getTarget(req);
273                 log.info("Redirecting to selected Handle Service");
274                 try {
275                         res.sendRedirect(
276                                 handleService
277                                         + "?target="
278                                         + URLEncoder.encode(target, "UTF-8")
279                                         + "&shire="
280                                         + URLEncoder.encode(shire, "UTF-8"));
281                 } catch (IOException ioe) {
282                         throw new WayfException("Error forwarding to HS: " + ioe.toString());
283                 }
284
285         }
286
287         /**
288          * Handles all "recoverable" errors in WAYF processing by logging the error
289          * and forwarding the user to an appropriate error page.
290          * @param we The WayfException respective to the error being handled
291          */
292         private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
293
294                 log.error("WAYF Failure: " + we.toString());
295                 log.debug("Displaying WAYF error page.");
296                 req.setAttribute("errorText", we.toString());
297                 req.setAttribute("requestURL", req.getRequestURI().toString());
298                 RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
299
300                 try {
301                         rd.forward(req, res);
302                 } catch (IOException ioe) {
303                         log.error("Problem trying to display WAYF error page: " + ioe.toString());
304                 } catch (ServletException se) {
305                         log.error("Problem trying to display WAYF error page: " + se.toString());
306                 }
307         }
308         /**
309          * Retrieves the SHIRE from the request.
310          * @throws WayfException If the request does not contain a shire parameter.
311          */
312         private String getSHIRE(HttpServletRequest req) throws WayfException {
313
314                 String shire = (String) req.getAttribute("shire");
315                 if (req.getParameter("shire") != null) {
316                         shire = req.getParameter("shire");
317                 }
318                 if (shire == null) {
319                         throw new WayfException("Invalid data from SHIRE: No acceptance URL received.");
320                 }
321                 return shire;
322         }
323         /**
324          * Retrieves the user's target URL from the request.
325          * @throws WayfException If the request does not contain a target parameter
326          */
327         private String getTarget(HttpServletRequest req) throws WayfException {
328
329                 String target = (String) req.getAttribute("target");
330                 if (req.getParameter("target") != null) {
331                         target = req.getParameter("target");
332                 }
333                 if (target == null) {
334                         throw new WayfException("Invalid data from SHIRE: No target URL received.");
335                 }
336                 return target;
337         }
338
339 }