Added configurability for domain and expiration to cookie cache
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / wayf / WayfService.java
1 package edu.internet2.middleware.shibboleth.wayf;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.net.URLEncoder;
6
7 import javax.servlet.RequestDispatcher;
8 import javax.servlet.ServletException;
9 import javax.servlet.UnavailableException;
10 import javax.servlet.http.HttpServlet;
11 import javax.servlet.http.HttpServletRequest;
12 import javax.servlet.http.HttpServletResponse;
13
14 import org.apache.log4j.Logger;
15 import org.xml.sax.SAXException;
16
17 /**
18  * A servlet implementation of the Shibboleth WAYF service.  Allows a browser user to 
19  * select from among a group of origin sites.  User selection is optionally cached 
20  * and the user is forwarded to the HandleService appropriate to his selection.
21  *
22  * @author Walter Hoehn wassa@columbia.edu
23  */
24 public class WayfService extends HttpServlet {
25
26         private String wayfConfigFileLocation;
27         private String siteConfigFileLocation;
28         private WayfConfig config;
29         private WayfOrigins originConfig;
30         private WayfCacheOptions wOptions = new WayfCacheOptions();
31         private static Logger log = Logger.getLogger(WayfService.class.getName());
32
33         /**
34          * @see GenericServlet#init()
35          */
36         public void init() throws ServletException {
37
38                 super.init();
39                 log.info("Initializing WAYF.");
40                 loadInitParams();
41                 log.info("Loading configuration from file.");
42                 configure();
43                 
44                 //Setup Cacheing options
45                 wOptions.setDomain(config.getCacheDomain());
46                 wOptions.setExpiration(config.getCacheExpiration());
47                 
48                 initViewConfig();
49                 log.info("WAYF initialization completed.");
50         }
51
52         /**
53          * Populates WayfConfig and WayfOrigins objects from file contents.
54          */
55         private void configure() throws UnavailableException {
56
57                 InputStream is = getServletContext().getResourceAsStream(wayfConfigFileLocation);
58                 WayfConfigDigester digester = new WayfConfigDigester(getServletContext());
59                 InputStream siteIs = getServletContext().getResourceAsStream(siteConfigFileLocation);
60                 OriginSitesDigester siteDigester = new OriginSitesDigester(getServletContext());
61
62                 try {
63                         digester.setValidating(true);
64                         config = (WayfConfig) digester.parse(is);
65
66                 } catch (SAXException se) {
67                         log.fatal("Error parsing WAYF configuration file.", se);
68                         throw new UnavailableException("Error parsing WAYF configuration file.");
69                 } catch (IOException ioe) {
70                         log.fatal("Error reading WAYF configuration file.", ioe);
71                         throw new UnavailableException("Error reading WAYF configuration file.");
72                 }
73
74                 try {
75                         siteDigester.setValidating(true);
76                         originConfig = (WayfOrigins) siteDigester.parse(siteIs);
77
78                 } catch (SAXException se) {
79                         log.fatal("Error parsing site file.", se);
80                         throw new UnavailableException("Error parsing site file.");
81                 } catch (IOException ioe) {
82                         log.fatal("Error reading site file.", ioe);
83                         throw new UnavailableException("Error reading site file.");
84                 }
85         }
86         
87         /**
88          * Setup application-wide beans for view
89          */
90         private void initViewConfig() {
91                 getServletContext().setAttribute("originsets", originConfig.getOriginSets());
92                 getServletContext().setAttribute("supportContact", config.getSupportContact());
93                 getServletContext().setAttribute("helpText", config.getHelpText());
94                 getServletContext().setAttribute("searchResultEmptyText", config.getSearchResultEmptyText());
95                 getServletContext().setAttribute("logoLocation", config.getLogoLocation());
96         }
97
98         /**
99          * Reads parameters from web.xml <init-param /> construct.
100          */
101         private void loadInitParams() {
102
103                 wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
104                 if (wayfConfigFileLocation == null) {
105                         log.warn("No WAYFConfigFileLocation parameter found... using default location.");
106                         wayfConfigFileLocation = "/WEB-INF/conf/wayfconfig.xml";
107                 }
108                 siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
109                 if (siteConfigFileLocation == null) {
110                         log.warn("No SiteonfigFileLocation parameter found... using default location.");
111                         siteConfigFileLocation = "/WEB-INF/conf/sites.xml";
112                 }
113
114         }
115
116         /**
117          * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
118          */
119         public void doGet(HttpServletRequest req, HttpServletResponse res) {
120
121                 log.info("Handling WAYF request.");
122                 //Tell the browser not to cache the WAYF page
123                 res.setHeader("Cache-Control", "no-cache");
124                 res.setHeader("Pragma", "no-cache");
125                 res.setDateHeader("Expires", 0);
126
127                 //Decide how to route the request based on query string
128                 String requestType = req.getParameter("action");
129                 if (requestType == null) {
130                         requestType = "lookup";
131                 }
132                 try {
133                         if (requestType.equals("deleteFromCache")) {
134                                 log.debug("Deleting saved HS from cache");
135                                 WayfCacheFactory.getInstance(config.getCacheType(), wOptions).deleteHsFromCache(req, res);
136                                 handleLookup(req, res);
137                         } else if (WayfCacheFactory.getInstance(config.getCacheType()).hasCachedHS(req)) {
138                                 forwardToHS(
139                                         req,
140                                         res,
141                                         WayfCacheFactory.getInstance(config.getCacheType()).getCachedHS(req));
142                         } else if (requestType.equals("search")) {
143                                 handleSearch(req, res);
144                         } else if (requestType.equals("selection")) {
145                                 handleSelection(req, res);
146                         } else {
147                                 handleLookup(req, res);
148                         }
149                 } catch (WayfException we) {
150                         handleError(req, res, we);
151                 }
152         }
153
154         /**
155          * Displays a WAYF selection page.
156          */
157         private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
158
159                 if ((getSHIRE(req) == null) || (getTarget(req) == null)) {
160                         throw new WayfException("Invalid or missing data from SHIRE");
161                 }
162                 req.setAttribute("shire", getSHIRE(req));
163                 req.setAttribute("target", getTarget(req));
164                 req.setAttribute("encodedShire", URLEncoder.encode(getSHIRE(req)));
165                 req.setAttribute("encodedTarget", URLEncoder.encode(getTarget(req)));
166
167                 log.debug("Displaying WAYF selection page.");
168                 RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
169                 try {
170                         rd.forward(req, res);
171                 } catch (IOException ioe) {
172                         throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
173                 } catch (ServletException se) {
174                         throw new WayfException("Problem displaying WAYF UI." + se.toString());
175                 }
176         }
177         
178         /**
179          * Looks for origin sites that match search terms supplied by the user
180          */
181         private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
182
183                 if (req.getParameter("string") != null) {
184                         Origin[] origins = originConfig.seachForMatchingOrigins(req.getParameter("string"), config);
185                         if (origins.length != 0) {
186                                 req.setAttribute("searchresults", origins);
187                         } else {
188                                 req.setAttribute("searchResultsEmpty", "true");
189                         }
190                 }
191                 handleLookup(req, res);
192         }
193
194         /**
195          * Registers a user's HS selection and forwards appropriately
196          */
197         private void handleSelection(HttpServletRequest req, HttpServletResponse res) throws WayfException {
198
199                 String handleService = originConfig.lookupHSbyName(req.getParameter("origin"));
200                 if (handleService == null) {
201                         handleLookup(req, res);
202                 } else {
203                         WayfCacheFactory.getInstance(config.getCacheType(), wOptions).addHsToCache(handleService, req, res);
204                         forwardToHS(req, res, handleService);
205                 }
206
207         }
208
209         /**
210          * Uses an HTTP Status 307 redirect to forward the user the HS.
211          * @param handleService The URL of the Shiboleth HS.
212          */
213         private void forwardToHS(HttpServletRequest req, HttpServletResponse res, String handleService)
214                 throws WayfException {
215
216                 String shire = getSHIRE(req);
217                 String target = getTarget(req);
218                 log.info("Redirecting to selected Handle Service");
219                 try {
220                         res.sendRedirect(
221                                 handleService
222                                         + "?target="
223                                         + URLEncoder.encode(target)
224                                         + "&shire="
225                                         + URLEncoder.encode(shire));
226                 } catch (IOException ioe) {
227                         throw new WayfException("Error forwarding to HS: " + ioe.toString());
228                 }
229
230         }
231
232         /**
233          * Handles all "recoverable" errors in WAYF processing by logging the error
234          * and forwarding the user to an appropriate error page.
235          * @param we The WayfException respective to the error being handled
236          */
237         private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
238
239                 log.error("WAYF Failure: " + we.toString());
240                 log.debug("Displaying WAYF error page.");
241                 req.setAttribute("errorText", we.toString());
242                 req.setAttribute("requestURL", req.getRequestURI().toString());
243                 RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
244
245                 try {
246                         rd.forward(req, res);
247                 } catch (IOException ioe) {
248                         log.error("Problem trying to display WAYF error page: " + ioe.toString());
249                 } catch (ServletException se) {
250                         log.error("Problem trying to display WAYF error page: " + se.toString());
251                 }
252         }
253         /**
254          * Retrieves the SHIRE from the request.
255          * @throws WayfException If the request does not contain a shire parameter.
256          */
257         private String getSHIRE(HttpServletRequest req) throws WayfException {
258
259                 String shire = (String) req.getAttribute("shire");
260                 if (req.getParameter("shire") != null) {
261                         shire = req.getParameter("shire");
262                 }
263                 if (shire == null) {
264                         throw new WayfException("Invalid data from SHIRE: No acceptance URL received.");
265                 }
266                 return shire;
267         }
268         /**
269          * Retrieves the user's target URL from the request.
270          * @throws WayfException If the request does not contain a target parameter
271          */
272         private String getTarget(HttpServletRequest req) throws WayfException {
273
274                 String target = (String) req.getAttribute("target");
275                 if (req.getParameter("target") != null) {
276                         target = req.getParameter("target");
277                 }
278                 if (target == null) {
279                         throw new WayfException("Invalid data from SHIRE: No target URL received.");
280                 }
281                 return target;
282         }
283
284 }