Wayf changes for new metadata, plus more caching options.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / wayf / WayfService.java
1 /*
2  * Copyright [2005] [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.wayf;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.URLEncoder;
22 import java.util.Collection;
23 import java.util.Date;
24
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.ServletException;
27 import javax.servlet.UnavailableException;
28 import javax.servlet.http.HttpServlet;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31
32 import org.apache.log4j.Logger;
33 import org.xml.sax.SAXException;
34
35 import edu.internet2.middleware.shibboleth.common.ShibResource;
36 import edu.internet2.middleware.shibboleth.common.ShibResource.ResourceNotAvailableException;
37 import edu.internet2.middleware.shibboleth.metadata.Metadata;
38 import edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadata;
39 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
40
41 /**
42  * A servlet implementation of the Shibboleth WAYF service. Allows a browser user to select from among a group of origin
43  * sites. User selection is optionally cached and the user is forwarded to the HandleService appropriate to his
44  * selection.
45  * 
46  * @author Walter Hoehn wassa@columbia.edu
47  */
48 public class WayfService extends HttpServlet {
49
50         private String wayfConfigFileLocation;
51         private String siteConfigFileLocation;
52         private WayfConfig config;
53         private Metadata metadata;
54         private WayfCacheOptions wSessionOptions = new WayfCacheOptions();
55         private WayfCacheOptions wPermOptions = new WayfCacheOptions();
56         private static Logger log = Logger.getLogger(WayfService.class.getName());
57         
58         /**
59          * @see GenericServlet#init()
60          */
61         public void init() throws ServletException {
62
63                 super.init();
64                 log.info("Initializing WAYF.");
65                 loadInitParams();
66                 log.info("Loading configuration from file.");
67                 configure();
68
69                 log.info("Initializing site metadata & watchdog");
70                 try {
71                         metadata = new XMLMetadata(siteConfigFileLocation);
72                 } catch (ResourceNotAvailableException e) {
73                         log.error("Sites file watchdog could not be initialized: " + e);
74                         throw new ServletException(e);
75                 } catch (MetadataException e) {
76                         log.error("Sites files could not be parsed" + e);
77                         throw new ServletException(e);
78                 }
79
80                 // Setup Cacheing options
81                 wSessionOptions.setDomain(config.getCacheDomain());
82                 wPermOptions.setDomain(config.getCacheDomain());
83                 wPermOptions.setExpiration(config.getCacheExpiration());
84
85                 initViewConfig();
86                 log.info("WAYF initialization completed.");
87         }
88
89         /**
90          * Populates WayfConfig from file contents.
91          */
92         private void configure() throws UnavailableException {
93
94                 try {
95                         InputStream is = new ShibResource(wayfConfigFileLocation, this.getClass()).getInputStream();
96                         WayfConfigDigester digester = new WayfConfigDigester();
97                         digester.setValidating(true);
98                         config = (WayfConfig) digester.parse(is);
99
100                 } catch (SAXException se) {
101                         log.fatal("Error parsing WAYF configuration file.", se);
102                         throw new UnavailableException("Error parsing WAYF configuration file.");
103                 } catch (IOException ioe) {
104                         log.fatal("Error reading WAYF configuration file.", ioe);
105                         throw new UnavailableException("Error reading WAYF configuration file.");
106                 }
107         }
108
109         /**
110          * Setup application-wide beans for view
111          */
112         private void initViewConfig() {
113
114                 getServletContext().setAttribute("supportContact", config.getSupportContact());
115                 getServletContext().setAttribute("helpText", config.getHelpText());
116                 getServletContext().setAttribute("searchResultEmptyText", config.getSearchResultEmptyText());
117                 getServletContext().setAttribute("logoLocation", config.getLogoLocation());
118         }
119
120         /**
121          * Reads parameters from web.xml <init-param /> construct.
122          */
123         private void loadInitParams() {
124
125                 wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
126                 if (wayfConfigFileLocation == null) {
127                         log.warn("No WAYFConfigFileLocation parameter found... using default location.");
128                         wayfConfigFileLocation = "/conf/wayfconfig.xml";
129                 }
130                 siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
131                 if (siteConfigFileLocation == null) {
132                         log.warn("No SiteonfigFileLocation parameter found... using default location.");
133                         siteConfigFileLocation = "/conf/metadata.xml";
134                 }
135
136         }
137
138         /**
139          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
140          */
141         public void doGet(HttpServletRequest req, HttpServletResponse res) {
142
143                 log.info("Handling WAYF request.");
144                 // Tell the browser not to cache the WAYF page
145                 res.setHeader("Cache-Control", "no-cache");
146                 res.setHeader("Pragma", "no-cache");
147                 res.setDateHeader("Expires", 0);
148
149                 // Decide how to route the request based on query string
150                 String requestType = req.getParameter("action");
151                 if (requestType == null) {
152                         requestType = "lookup";
153                 }
154                 try {
155                         if (requestType.equals("deleteFromCache")) {
156                                 log.debug("Deleting saved HS from cache");
157                                 WayfCacheFactory.getInstance(config.getCacheType(), wPermOptions).deleteHsFromCache(req, res);
158                                 handleLookup(req, res);
159                         } else if (WayfCacheFactory.getInstance(config.getCacheType()).hasCachedHS(req)) {
160                                 forwardToHS(req, res, WayfCacheFactory.getInstance(config.getCacheType()).getCachedHS(req));
161                         } else if (requestType.equals("search")) {
162                                 handleSearch(req, res);
163                         } else if (requestType.equals("selection")) {
164                                 handleSelection(req, res);
165                         } else {
166                                 handleLookup(req, res);
167                         }
168                 } catch (WayfException we) {
169                         handleError(req, res, we);
170                 }
171         }
172
173         /**
174          * Displays a WAYF selection page.
175          */
176         private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
177
178                 try {
179                         if ((getSHIRE(req) == null) || (getTarget(req) == null)) { throw new WayfException(
180                                         "Invalid or missing data from SHIRE"); 
181                         }
182
183                         req.setAttribute("sites", IdPSite.getIdPSites(metadata));
184                         req.setAttribute("shire", getSHIRE(req));
185                         req.setAttribute("target", getTarget(req));
186                         String providerId = getProviderId(req);
187                         if (providerId != null) {
188                                 req.setAttribute("providerId", providerId);
189                         }
190                         
191                         req.setAttribute("time", new Long(new Date().getTime() / 1000).toString()); // Unix Time
192                         req.setAttribute("requestURL", req.getRequestURI().toString());
193
194                         log.debug("Displaying WAYF selection page.");
195                         RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
196
197                         rd.forward(req, res);
198                 } catch (IOException ioe) {
199                         throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
200                 } catch (ServletException se) {
201                         throw new WayfException("Problem displaying WAYF UI." + se.toString());
202                 }
203         }
204
205         /**
206          * Looks for origin sites that match search terms supplied by the user
207          */
208         private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
209
210                 String parameter = req.getParameter("string"); 
211                 if (parameter != null) {
212                         Collection sites = IdPSite.seachForMatchingOrigins(metadata, parameter, config);
213                         if (sites.size() != 0) {
214                                 req.setAttribute("searchresults", sites);
215                         } else {
216                                 req.setAttribute("searchResultsEmpty", "true");
217                         }
218                 }
219                 handleLookup(req, res);
220         }
221
222         /**
223          * Registers a user's HS selection and forwards appropriately
224          */
225         private void handleSelection(HttpServletRequest req, HttpServletResponse res) throws WayfException {
226
227                 log.debug("Processing handle selection: " + req.getParameter("origin"));
228                 String handleService = null;
229                 try {
230                         //
231                         // If we have had a refresh between then and now the following will fail
232                         //
233                         handleService = metadata.lookup(req.getParameter("origin")).getIDPSSODescriptor(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS).getSingleSignOnServiceManager().getDefaultEndpoint().getLocation(); 
234                 } 
235                 catch (Exception ex) {
236                         log.error("Error dispatching to IdP", ex);
237                 }
238                 
239                 
240                 if (handleService == null) {
241                         handleLookup(req, res);
242                 } else {
243                         if ((req.getParameter("cache") != null)) {
244                                 if (req.getParameter("cache").equalsIgnoreCase("session")) {
245                                         WayfCacheFactory.getInstance(config.getCacheType(), wSessionOptions).addHsToCache(handleService, req, res);
246                                 }
247                                 else if (req.getParameter("cache").equalsIgnoreCase("perm")) {
248                                         WayfCacheFactory.getInstance(config.getCacheType(), wPermOptions).addHsToCache(handleService, req, res);
249                                 }
250                         }
251                         forwardToHS(req, res, handleService);
252                 }
253
254         }
255
256         /**
257          * Uses an HTTP Status 307 redirect to forward the user the HS.
258          * 
259          * @param handleService
260          *            The URL of the Shiboleth HS.
261          */
262         private void forwardToHS(HttpServletRequest req, HttpServletResponse res, String handleService)
263                         throws WayfException {
264
265                 log.info("Redirecting to selected Handle Service");
266                 try {
267                         StringBuffer buffer = new StringBuffer(handleService + "?target="
268                                         + URLEncoder.encode(getTarget(req), "UTF-8") + "&shire="
269                                         + URLEncoder.encode(getSHIRE(req), "UTF-8"));
270                         String providerId = getProviderId(req);
271                         log.debug("WALTER: (" + providerId + ").");
272                         if (providerId != null) {
273                                 buffer.append("&providerId=" + URLEncoder.encode(getProviderId(req), "UTF-8"));
274                         }
275                         buffer.append("&time=" + new Long(new Date().getTime() / 1000).toString()); // Unix Time
276                         res.sendRedirect(buffer.toString());
277                 } catch (IOException ioe) {
278                         throw new WayfException("Error forwarding to HS: " + ioe.toString());
279                 }
280
281         }
282
283         /**
284          * Handles all "recoverable" errors in WAYF processing by logging the error and forwarding the user to an
285          * appropriate error page.
286          * 
287          * @param we
288          *            The WayfException respective to the error being handled
289          */
290         private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
291
292                 log.error("WAYF Failure: " + we.toString());
293                 log.debug("Displaying WAYF error page.");
294                 req.setAttribute("errorText", we.toString());
295                 req.setAttribute("requestURL", req.getRequestURI().toString());
296                 RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
297
298                 try {
299                         rd.forward(req, res);
300                 } catch (IOException ioe) {
301                         log.error("Problem trying to display WAYF error page: " + ioe.toString());
302                 } catch (ServletException se) {
303                         log.error("Problem trying to display WAYF error page: " + se.toString());
304                 }
305         }
306
307         /**
308          * Retrieves the SHIRE from the request.
309          * 
310          * @throws WayfException
311          *             If the request does not contain a shire parameter.
312          */
313         private String getSHIRE(HttpServletRequest req) throws WayfException {
314
315                 String shire = (String) req.getAttribute("shire");
316                 if (req.getParameter("shire") != null) {
317                         shire = req.getParameter("shire");
318                 }
319                 if (shire == null) { throw new WayfException("Invalid data from SHIRE: No acceptance URL received."); }
320                 return shire;
321         }
322
323         /**
324          * Retrieves the user's target URL from the request.
325          * 
326          * @throws WayfException
327          *             If the request does not contain a target parameter
328          */
329         private String getTarget(HttpServletRequest req) throws WayfException {
330
331                 String target = (String) req.getAttribute("target");
332                 if (req.getParameter("target") != null) {
333                         target = req.getParameter("target");
334                 }
335                 if (target == null) { throw new WayfException("Invalid data from SHIRE: No target URL received."); }
336                 return target;
337         }
338
339         private String getProviderId(HttpServletRequest req) {
340
341                 if (req.getParameter("providerId") != null && !(req.getParameter("providerId").length() == 0)) {
342                         return req.getParameter("providerId");
343
344                 } else {
345                         String attr = (String) req.getAttribute("providerId");
346                         if (attr == null || attr.length() == 0) { return null; }
347                         return attr;
348                 }
349         }       
350 }