SDSS WAYF patch for multi-federation support
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / wayf / DiscoveryServiceHandler.java
1 package edu.internet2.middleware.shibboleth.wayf;
2
3 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
4
5 import java.io.IOException;
6 import java.net.URLEncoder;
7 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.Date;
10 import java.util.Hashtable;
11 import java.util.List;
12 import java.util.TreeSet;
13
14 import javax.servlet.RequestDispatcher;
15 import javax.servlet.ServletException;
16 import javax.servlet.http.HttpServletRequest;
17 import javax.servlet.http.HttpServletResponse;
18
19 import org.apache.log4j.Logger;
20 import org.w3c.dom.Element;
21 import org.w3c.dom.NodeList;
22
23
24 public class DiscoveryServiceHandler {
25
26         private final static Logger log = Logger.getLogger(DiscoveryServiceHandler.class.getName());
27
28         private final static String DEFAULT_JSP_FILE = "/wayf.jsp";
29         private final static String DEFAULT_ERROR_JSP_FILE = "/wayferror.jsp";
30         
31         private final String location;
32         private final boolean isDefault;
33         private final HandlerConfig config;
34         //
35         // hacked enum
36         //
37         
38         private final List /*<IdPSiteSet>*/ siteSets = new ArrayList /*<IdPSiteSet>*/ ();
39         
40         
41         protected DiscoveryServiceHandler(Element config, 
42                                                                       Hashtable /*<String, IdPSiteSet>*/ federations,
43                                                                       HandlerConfig defaultConfig) throws ShibbolethConfigurationException
44         {
45                 this.config = new HandlerConfig(config, defaultConfig);
46                 
47                 location = config.getAttribute("location");
48                 
49                 if (location == null || location.equals("")) {
50                         
51                         log.error("DiscoveryService must have a location specified");
52                         throw new ShibbolethConfigurationException("DiscoveryService must have a location specified");
53                         
54                 }
55
56                 String attribute = ((Element) config).getAttribute("default");
57                 if (attribute != null && !attribute.equals("")) {
58                         isDefault = Boolean.valueOf(attribute).booleanValue();
59                 } else {
60                         isDefault = true;
61                 }
62                 
63                 NodeList list = config.getElementsByTagName("Federation");
64                     
65             for (int i = 0; i < list.getLength(); i++ ) {
66                         
67                 attribute = ((Element) list.item(i)).getAttribute("identifier");
68                         
69                     IdPSiteSet siteset = (IdPSiteSet) federations.get(attribute);
70                     
71                     if (siteset == null) {
72                         log.error("Handler " + location + ": could not find metadata for identifier " + attribute);
73                         throw new ShibbolethConfigurationException("Handler " + location + ": could not find metadata for identifier " + attribute);
74                     }
75                     
76                     siteSets.add(siteset);
77             }
78
79             if (siteSets.size() == 0) {
80                         //
81                         // No Federations explicitly named
82                         //
83                         siteSets.addAll(federations.values());
84                 }
85         }
86         
87         
88         //
89         // Standard Beany Methods
90         //
91         /**
92          * Returns the 'Name' of the service - the path used to identify the ServiceHandler 
93          */
94         
95         public String getLocation() {
96                 return location;
97         }
98
99         public boolean isDefault() {
100                 return isDefault;
101         }
102
103         public void doGet(HttpServletRequest req, HttpServletResponse res) {
104                 // Decide how to route the request based on query string
105                 String requestType = req.getParameter("action");
106                 if (requestType == null) {
107                         requestType = "lookup";
108                 }
109                 try {
110
111                         SamlIdPCookie cookie;
112                         
113                         if (config.getHandleCookie() == HandlerConfig.CLEAR_COOKIE) {
114                                 
115                                 //
116                                 // Mark the cookie for deletion (unless we reset it later)
117                                 
118                                 SamlIdPCookie.deleteCookie(req, res);
119                                 
120                                 //
121                                 // And create an empty cookie
122                                 //
123                                 cookie = new SamlIdPCookie(req, res, config.getCacheDomain());
124                                 
125                         } else {
126
127                                 cookie = SamlIdPCookie.getIdPCookie(req, res, config.getCacheDomain());
128                         }
129
130                         
131                         String searchSP = null;
132                         
133                         if (config.getLookupSp()) {
134                                 searchSP = getProviderId(req);
135                         }
136                         
137                         List /*<IdPSite>*/ cookieList = cookie.getIdPList(siteSets, searchSP);
138                         
139                         //
140                         // Put if we have been told to
141                         //
142                         if (((config.getHandleCookie() == HandlerConfig.ALWAYS_FOLLOW_COOKIE) && (cookieList.size() > 0)) ||
143                                 ((config.getHandleCookie() == HandlerConfig.FOLLOW_SINGLE_COOKIE) && (cookieList.size() == 1))) {
144                                 
145                                 IdPSite site = (IdPSite) cookieList.get(0);
146                                 
147                                 //
148                                 // Move name to front of cookie, and write back specifying a time of
149                                 // zero (which will leave the time untouched)
150                                 //
151                                 cookie.addIdPName(site.getName(), config.getCacheExpiration());
152                                 
153                                 forwardToIdP(req, res, site);
154                         } else if (requestType.equals("search")) {
155                                 
156                                 handleSearch(req, res, cookieList);
157                                 
158                         } else if (requestType.equals("selection")) {
159                                 
160                                 String origin = req.getParameter("origin");
161                                 log.debug("Processing handle selection: " + origin);
162                                 
163                                 IdPSite site = IdPSiteSet.IdPforSP(siteSets, origin, searchSP);
164                                 
165                                 if (site == null) {
166                                         handleLookup(req, res, cookieList);
167                                 } else {
168                                         
169                                         //
170                                         // Write back cache - if we were asked to
171                                         //
172                                         
173                                         if ((req.getParameter("cache") != null)) {
174                         if (req.getParameter("cache").equalsIgnoreCase("session")) {
175                             cookie.addIdPName(origin, -1);
176                         } else if (req.getParameter("cache").equalsIgnoreCase("perm")) {
177                             cookie.addIdPName(origin, config.getCacheExpiration());
178                         }
179                     }
180                                         forwardToIdP(req, res, site);
181                                 }
182                         } else {
183                                 handleLookup(req, res, cookieList);
184                         }
185                 } catch (WayfException we) {
186                         handleError(req, res, we);
187                 }
188
189         }
190         
191         /**
192          * Displays a WAYF selection page.
193          * 
194          */
195         private void handleLookup(HttpServletRequest req, HttpServletResponse res, List/*<IdPSite>*/ cookieList) throws WayfException {
196
197                 try {
198                         if ((getSHIRE(req) == null) || (getTarget(req) == null)) { throw new WayfException(
199                                         "Invalid or missing data from SHIRE"); 
200                         }
201
202                         if (cookieList.size() > 0) {
203                                 req.setAttribute("cookieList", cookieList);
204                         }
205                         req.setAttribute("shire", getSHIRE(req));
206                         req.setAttribute("target", getTarget(req));
207                         String providerId = getProviderId(req);
208                         if (providerId != null) {
209                                 req.setAttribute("providerId", providerId);
210                         }
211
212                         Collection /*<IdPSiteSetEntry>*/ siteLists = null;
213                         if (config.getProvideListOfLists()) {
214                                 siteLists = new ArrayList /*<IdPSiteSetEntry>*/(siteSets.size());
215                         }
216                         
217                         Collection /*<IdPSite>*/ sites = null;
218                         if (config.getProvideList()) {
219                                 sites = new TreeSet/*<IdPSite>*/();
220                         }
221         
222                         String searchSP = null; 
223                         
224                         if (config.getLookupSp()) {
225                                 searchSP = providerId;
226                         }
227                         
228                         IdPSiteSet.getSiteLists(siteSets, searchSP, siteLists, sites);
229                         
230                         req.setAttribute("sites", sites);
231                         req.setAttribute("siteLists", siteLists);
232                         
233                         if (siteLists != null && siteLists.size() == 1) {
234                                 req.setAttribute("singleSiteList", new Object());
235                         }
236
237                         req.setAttribute("time", new Long(new Date().getTime() / 1000).toString()); // Unix Time
238                         req.setAttribute("requestURL", req.getRequestURI().toString());
239
240                         log.debug("Displaying WAYF selection page.");
241                         RequestDispatcher rd = req.getRequestDispatcher(config.getJspFile());
242
243                         rd.forward(req, res);
244                 } catch (IOException ioe) {
245                         throw new WayfException("Problem displaying WAYF UI.\n" + ioe.getMessage());
246                 } catch (ServletException se) {
247                         throw new WayfException("Problem displaying WAYF UI.\n" +  se.getMessage());
248                 }
249         }
250
251         /**
252          * Looks for origin sites that match search terms supplied by the user
253          * 
254          */
255         private void handleSearch(HttpServletRequest req, HttpServletResponse res, List /*<IdPSite>*/ cookieList) throws WayfException {
256
257                 String parameter = req.getParameter("string"); 
258                 if (parameter != null) {
259                         Collection/*<IdPSite>*/ sites = IdPSiteSet.seachForMatchingOrigins(siteSets, getProviderId(req), parameter, config);
260                         if (sites.size() != 0) {
261                                 req.setAttribute("searchresults", sites);
262                         } else {
263                                 req.setAttribute("searchResultsEmpty", "true");
264                         }
265                 }
266                 handleLookup(req, res, cookieList);
267         }
268
269         /**
270          * Uses an HTTP Status 307 redirect to forward the user the HS.
271          * 
272          * @param site - The Idp.
273          */
274         private void forwardToIdP(HttpServletRequest req, HttpServletResponse res, IdPSite site)
275                         throws WayfException {
276
277                 String handleService = site.getAddressFor(); 
278                                 
279                 if (handleService != null ) {
280
281                         log.info("Redirecting to selected Handle Service: " + handleService);
282                         try {
283                                 StringBuffer buffer = new StringBuffer(handleService + "?target="
284                                                 + URLEncoder.encode(getTarget(req), "UTF-8") + "&shire="
285                                                 + URLEncoder.encode(getSHIRE(req), "UTF-8"));
286                                 String providerId = getProviderId(req);
287                                 log.debug("WALTER: (" + providerId + ").");
288                                 if (providerId != null) {
289                                         buffer.append("&providerId=" + URLEncoder.encode(getProviderId(req), "UTF-8"));
290                                 }
291                                 buffer.append("&time=" + new Long(new Date().getTime() / 1000).toString()); // Unix Time
292                                 res.sendRedirect(buffer.toString());
293                         } catch (IOException ioe) {
294                                 //
295                                 // That failed.  Purge the cache or we will go around again...
296                                 //
297                                 SamlIdPCookie.deleteCookie(req, res);
298                                 throw new WayfException("Error forwarding to HS: \n" + ioe.getMessage());
299                         }
300                 } else {
301                         log.error("Error finding to IdP: " + site.getDisplayName());
302                         handleLookup(req, res, null);
303                 }
304         }
305
306         /**
307          * Handles all "recoverable" errors in WAYF processing by logging the error and forwarding the user to an
308          * appropriate error page.
309          * 
310          * @param we
311          *            The WayfException respective to the error being handled
312          */
313         private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
314
315                 log.error("WAYF Failure: " + we.toString());
316                 log.debug("Displaying WAYF error page.");
317                 req.setAttribute("errorText", we.toString());
318                 req.setAttribute("requestURL", req.getRequestURI().toString());
319                 RequestDispatcher rd = req.getRequestDispatcher(config.getErrorJspFile());
320
321                 try {
322                         rd.forward(req, res);
323                 } catch (IOException ioe) {
324                         log.error("Problem trying to display WAYF error page: " + ioe.toString());
325                 } catch (ServletException se) {
326                         log.error("Problem trying to display WAYF error page: " + se.toString());
327                 }
328         }
329
330         /**
331          * Retrieves the SHIRE from the request.
332          * 
333          * @throws WayfException
334          *             If the request does not contain a shire parameter.
335          */
336         private String getSHIRE(HttpServletRequest req) throws WayfException {
337
338                 String shire = (String) req.getAttribute("shire");
339                 if (req.getParameter("shire") != null) {
340                         shire = req.getParameter("shire");
341                 }
342                 if (shire == null) { throw new WayfException("Invalid data from SHIRE: No acceptance URL received."); }
343                 return shire;
344         }
345
346         /**
347          * Retrieves the user's target URL from the request.
348          * 
349          * @throws WayfException
350          *             If the request does not contain a target parameter
351          */
352         private String getTarget(HttpServletRequest req) throws WayfException {
353
354                 String target = (String) req.getAttribute("target");
355                 if (req.getParameter("target") != null) {
356                         target = req.getParameter("target");
357                 }
358                 if (target == null) { throw new WayfException("Invalid data from SHIRE: No target URL received."); }
359                 return target;
360         }
361
362         private String getProviderId(HttpServletRequest req) {
363
364                 String param = req.getParameter("providerId");
365                 if (param != null && !(param.length() == 0)) {
366                         return req.getParameter("providerId");
367
368                 } else {
369                         String attr = (String) req.getAttribute("providerId");
370                         if (attr == null || attr.length() == 0) { return null; }
371                         return attr;
372                 }
373         }
374 }
375