View Javadoc

1   /**
2    * Copyright [2006] [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  package edu.internet2.middleware.shibboleth.wayf;
17  
18  import java.io.IOException;
19  import java.io.UnsupportedEncodingException;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.net.URLDecoder;
23  import java.net.URLEncoder;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Comparator;
27  import java.util.Date;
28  import java.util.HashSet;
29  import java.util.Hashtable;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.TreeSet;
35  
36  import javax.servlet.RequestDispatcher;
37  import javax.servlet.ServletException;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  import org.opensaml.saml2.common.Extensions;
42  import org.opensaml.saml2.metadata.EntityDescriptor;
43  import org.opensaml.saml2.metadata.RoleDescriptor;
44  import org.opensaml.saml2.metadata.SPSSODescriptor;
45  import org.opensaml.samlext.idpdisco.DiscoveryResponse;
46  import org.opensaml.xml.XMLObject;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  import org.w3c.dom.Element;
50  import org.w3c.dom.NodeList;
51  
52  import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
53  import edu.internet2.middleware.shibboleth.wayf.plugins.Plugin;
54  import edu.internet2.middleware.shibboleth.wayf.plugins.PluginContext;
55  import edu.internet2.middleware.shibboleth.wayf.plugins.PluginMetadataParameter;
56  import edu.internet2.middleware.shibboleth.wayf.plugins.WayfRequestHandled;
57  
58  /**
59    * Specific handler for each version of the Discovery Service. 
60    */
61  public class DiscoveryServiceHandler {
62  
63      /*
64       * Protcol parameters - Old.
65       */
66      /**
67       * Shire is the SP Assertion Consumer endpoint.
68       */
69      private static final String SHIRE_PARAM_NAME = "shire";
70      /**
71       * TargetName is where we are trying to get to.
72       */
73      private static final String TARGET_PARAM_NAME = "target";
74      /**
75       * time is to do with replay attack.
76       */
77      private static final String TIME_PARAM_NAME = "time";
78      /**
79       * This is the ID (in the metadata) of the SP.
80       */
81      private static final String PROVIDERID_PARAM_NAME = "providerId";
82      
83      /**
84       * The entityDescriptor for the SP (if present).
85       */
86      private static final String PROVIDERID_OBJECT_PARAM_NAME = "providerObject"; 
87      /*
88       * Protocol parameters - New
89       */
90      /**
91       * The SP id.
92       */
93      private static final String ENTITYID_PARAM_NAME = "entityID";
94      /**
95       * Where to send the request back to.
96       */
97      private static final String RETURN_PARAM_NAME = "return";
98      /**
99       * "return" is an invalid attribute, so we use returnX.
100      */
101     private static final String RETURN_ATTRIBUTE_NAME = "returnX";
102     /**
103      * Alternatively the index of where to send the address back to.
104      */
105     private static final String RETURN_INDEX_NAME = "returnIndex";
106     
107     /**
108      * What value to put the ID of the selected metadata into.
109      */
110     private static final String RETURNID_PARAM_NAME = "returnIDParam";
111     
112     /**
113      * What returnIDParam defaults to.
114      */
115     private static final String RETURNID_DEFAULT_VALUE = "entityID";
116     /**
117      * Whether we are allowed to interact.
118      */
119     private static final String ISPASSIVE_PARAM_NAME = "isPassive";
120     
121     /**
122      * Whether we understand this or not.
123      */
124     private static final String POLICY_PARAM_NAME = "policy";
125 
126     /**
127      * The only policy we know about.
128      */
129     private static final String KNOWN_POLICY_NAME 
130         = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discoveryprotocol:single";
131     
132     /**
133      * Mandatory Serialization constant.
134      */
135     private static final  Logger LOG = LoggerFactory.getLogger(DiscoveryServiceHandler.class.getName());
136 
137     /**
138      * The location defines the last part of the URL which distinguished this handler. 
139      */
140     private final String location;
141     
142     /**
143      * If isDefault is true then if there is a mismatch then this handler is used.  
144      */
145     private final boolean isDefault;
146     
147     /**
148      * Config handles detailed behavior.
149      */
150     private final HandlerConfig config;
151     
152     /**
153      * The list of all the metadata providers that this discovery handler believes in.
154      */
155     private final List <IdPSiteSet> siteSets;
156     
157     /**
158      * The list of all the plugins that this hanlder has had configured.
159      */
160     private final List <Plugin> plugins;
161     
162     /**
163      * Constructor to create and configure the handler.
164      * @param config - DOM Element with configuration information.
165      * @param federations - Supplies all known providers which will be included if so configured.
166      * @param plugins - Supplies all known plugins which will be included if configured in. 
167      * @param defaultConfig - The default configurations.
168      * @throws ShibbolethConfigurationException - if we find something odd in the config file. 
169      */
170     protected DiscoveryServiceHandler(Element config, 
171                                       Hashtable <String, IdPSiteSet> federations,
172                                       Hashtable <String, Plugin> plugins, 
173                                       HandlerConfig defaultConfig) throws ShibbolethConfigurationException
174     {
175         siteSets = new ArrayList <IdPSiteSet>(federations.size());
176         this.plugins = new ArrayList <Plugin>(plugins.size());
177 
178         //
179         // Collect the Configuration from the XML
180         //
181         
182         this.config = new HandlerConfig(config, defaultConfig);
183         
184         location = config.getAttribute("location");
185         
186         if (location == null || location.equals("")) {
187                 
188                 LOG.error("DiscoveryService must have a location specified");
189                 throw new ShibbolethConfigurationException("DiscoveryService must have a location specified");  
190         }
191         
192         //
193         // Is this the default WAYF?
194         //
195         
196         String attribute = config.getAttribute("default");
197         if (attribute != null && !attribute.equals("")) {
198                 isDefault = Boolean.valueOf(attribute).booleanValue();
199         } else {
200                 isDefault = false;
201         }
202         
203         //
204         // Which federations (sitesets) do we care about?
205         //
206         
207         NodeList list = config.getElementsByTagName("Federation");
208                 
209         for (int i = 0; i < list.getLength(); i++ ) {
210                     
211             attribute = ((Element) list.item(i)).getAttribute("identifier");
212                     
213                 IdPSiteSet siteset = federations.get(attribute);
214                 
215                 if (siteset == null) {
216                     LOG.error("Handler " + location + ": could not find metadata for <Federation> with identifier " + attribute + ".");
217                     throw new ShibbolethConfigurationException(
218                            "Handler " + location + ": could not find metadata for  <Federation> identifier " + attribute + ".");
219                 }
220                 
221                 siteSets.add(siteset);
222         }
223 
224         if (siteSets.size() == 0) {
225             //
226             // No Federations explicitly named pick em all
227             //
228             siteSets.addAll(federations.values());
229         }
230         
231         //
232         // Now, which plugins?
233         //
234 
235         list = config.getElementsByTagName("PluginInstance");
236         
237         for (int i = 0; i < list.getLength(); i++ ) {
238                     
239             attribute = ((Element) list.item(i)).getAttribute("identifier");
240                     
241                 Plugin plugin = plugins.get(attribute);
242                 
243                 if (plugin == null) {
244                     LOG.error("Handler " + location + ": could not find plugin for identifier " + attribute);
245                     throw new ShibbolethConfigurationException(
246                               "Handler " + location + ": could not find plugin for identifier " + attribute);
247                 }
248                 
249                 this.plugins.add(plugin);
250         }
251 
252         //
253         // So now tell every IdPSite about every plugin.
254         //
255         // Note that there is only one idpsite per metadatafile per WAYF and that the discovery 
256         // services share them, so the data explosion is only number(IdpSites) * number(Plugins) not
257         // number(IdpSites) * number(Plugins) * number(DiscoverHandlers)
258         
259         for (IdPSiteSet site: siteSets) {
260             for (Plugin plugin: this.plugins) {
261                 site.addPlugin(plugin);
262             }
263         }
264     }
265     
266     
267     //
268     // Standard Beany Methods
269     //
270     /**
271      * The 'Name' of the service. the path used to identify the ServiceHandler.
272      * @return the path used to identify the ServiceHandler.
273      */
274     
275     protected String getLocation() {
276         return location;
277     }
278 
279     /**
280      * Whether this is the default service.
281      * @return is it?
282      */
283     protected boolean isDefault() {
284         return isDefault;
285     }
286     
287     //
288     // Now the bits that deal with the user request
289     //
290 
291     public void doGet(HttpServletRequest req, HttpServletResponse res) {
292         
293         String policy = req.getParameter(POLICY_PARAM_NAME);
294         
295         if (null != policy && !KNOWN_POLICY_NAME.equals(policy)) {
296             //
297             // unknown policy
298             //
299             LOG.error("Unknown policy " + policy);
300             handleError(req, res, "Unknown policy " + policy);
301             return;
302         }
303         
304         //
305         // Decide how to route the request based on query string
306         //
307         String requestType = req.getParameter("action");
308         
309         if (requestType == null || requestType.equals("")) {
310             requestType = "lookup";
311         }
312         
313         try {
314 
315             if (requestType.equals("search")) {
316                     
317                 String parameter = req.getParameter("string"); 
318                 if (parameter != null && parameter.equals("")) {
319                         parameter = null;
320                 }
321                 handleLookup(req, res, parameter);
322                     
323             } else if (requestType.equals("selection")) {
324                     
325                 handleSelection(req, res);
326             } else {
327                 handleLookup(req, res, null);
328             }
329         } catch (WayfException we) {
330             LOG.error("Error processing DS request:", we);
331             handleError(req, res, we.getLocalizedMessage());
332         } catch (WayfRequestHandled we) {
333             //
334             // Yuck - a sucess path involving an exception
335             //
336         }
337 
338     }
339         
340     /**
341      * When the WAYF user has selected something we look it up, tell the plugins and then dispatch to the Idp.
342      *  
343      * @param req - standard J2EE stuff
344      * @param res - standard J2EE stuff
345      * @throws WayfRequestHandled - if one of the plugins has done the dispatch
346      * @throws WayfException - if we had an errors
347      */
348     private void handleSelection(HttpServletRequest req, 
349                                  HttpServletResponse res) throws WayfRequestHandled, WayfException 
350      {
351             
352         String idpName = req.getParameter("origin");
353         LOG.debug("Processing handle selection: " + idpName);
354 
355         String sPName = getSPId(req);
356 
357         if (idpName == null || idpName.equals("")) {
358             handleLookup(req, res, null);
359             return;
360         }
361 
362         if (getValue(req, SHIRE_PARAM_NAME) == null) {
363             //
364             // 2.0 protocol
365             //
366             setupReturnAddress(sPName, req);
367         }
368         //
369         // Notify plugins
370         //
371         IdPSite site = null; 
372         
373         for (Plugin plugin:plugins) {
374             for (IdPSiteSet idPSiteSet: siteSets) {
375                 PluginMetadataParameter param = idPSiteSet.paramFor(plugin);
376                 plugin.selected(req, res, param, idpName);
377                 if (site == null && idPSiteSet.containsIdP(idpName)) {
378                     site = idPSiteSet.getSite(idpName);
379                 }
380             }
381         }
382         
383         if (site == null) {
384             handleLookup(req, res, null);
385         } else {
386             forwardRequest(req, res, site);
387         }
388     }
389 
390 
391     /**
392      * This sets up the parameter RETURN_ATTRIBUTE_NAME with the return address 
393      * harvested from the reqest.
394      * <ul><le>If a "return" parameter is present we check in the metadata for spoofing 
395      *         and then set up from there </le>
396      *     <le>If "returnID" is specified we get this from the metadata</le>
397      *     <le>If nothing is provided we get the default from the metadata (if provided)</le>
398      *     <le>Otherwise we whine</le>
399      * </ul>     
400      * @param spName - the name of the Service provider.
401      * @param req - The request.
402      * @throws WayfException - if we spot spoofing or there is no defaumlt 
403      */
404     private void setupReturnAddress(String spName, HttpServletRequest req) throws WayfException{
405         
406         DiscoveryResponse[] discoveryServices;
407         Set<XMLObject> objects = new HashSet<XMLObject>();
408         String defaultName = null;
409         boolean foundSPName = false;
410         
411         for (IdPSiteSet metadataProvider:siteSets) {
412             
413             //
414             // Only do work if the SP makes sense
415             //
416 
417             if (metadataProvider.containsSP(spName)) {
418                
419                 //
420                 // The name makes sense so let's get the entity and from that
421                 // all of its roles
422                 //
423                 foundSPName = true;
424                 EntityDescriptor entity = metadataProvider.getEntity(spName);
425                 List<RoleDescriptor> roles = entity.getRoleDescriptors();
426                 
427                 for (RoleDescriptor role:roles) {
428                     
429                     //
430                     // Check every role
431                     //
432                     
433                     if (role instanceof SPSSODescriptor) {
434                         
435                         //
436                         // And grab hold of all the extensions for SPSSO descriptors
437                         //
438                         
439                         Extensions exts = role.getExtensions();
440                         if (exts != null) {
441                             objects.addAll(exts.getOrderedChildren());
442                         }
443                     }
444                 }
445             }
446         }
447         if (!foundSPName) {
448             LOG.error("Could not locate SP " + spName + " in metadata");
449         }
450         
451         //
452         // Now, let's strip out everything which isn't a DiscoveryService
453         //
454         
455         discoveryServices = new DiscoveryResponse[objects.size()];
456         int dsCount = 0;
457         
458         for (XMLObject obj:objects) {
459             if (obj instanceof DiscoveryResponse) {
460                 DiscoveryResponse ds = (DiscoveryResponse) obj;
461                 discoveryServices[dsCount++] = ds;
462                 if (ds.isDefault() || null == defaultName) {
463                     defaultName = ds.getLocation();
464                 }
465             }
466         }
467         
468         //
469         // Now process the return parameters.  The name is either a parameter
470         // called RETURN_PARAM_NAME or an attributes called RETURN_ATTRIBUTE_NAME
471         //
472         String returnName = req.getParameter(RETURN_PARAM_NAME);
473         
474         if (returnName == null || returnName.length() == 0) {
475             returnName = getValue(req, RETURN_ATTRIBUTE_NAME);
476         }
477         
478         //
479         // Return index is only ever a parameter
480         //
481         
482         String returnIndex = req.getParameter(RETURN_INDEX_NAME);
483         
484         if (returnName != null && returnName.length() != 0) {
485             //
486             // Given something so we have to police it.
487             //
488             String nameNoParam = returnName;
489             URL providedReturnURL;
490             int index = nameNoParam.indexOf('?');
491             boolean found = false;
492             
493             if (index >= 0) {
494                 nameNoParam = nameNoParam.substring(0,index);
495             }
496             
497             try {
498                 providedReturnURL = new URL(nameNoParam);                
499             } catch (MalformedURLException e) {
500                 throw new WayfException("Couldn't parse provided return name " + nameNoParam, e);
501             }
502             
503             
504             for (DiscoveryResponse disc: discoveryServices) {
505                 if (equalsURL(disc, providedReturnURL)) {
506                     found = true;
507                     break;
508                 }
509             }
510             if (!found) {
511                 throw new WayfException("Couldn't find endpoint " + nameNoParam + " in metadata");
512             }
513         } else if (returnIndex != null && returnIndex.length() != 0) {
514             
515             int index; 
516             try {
517                 index = Integer.parseInt(returnIndex);
518             } catch (NumberFormatException e) {
519                 throw new WayfException("Couldn't convert " + returnIndex + " into an index");
520             }
521             //
522             // So look through to find the endpoint with the correct index
523             //
524             boolean found = false;
525             
526             for (DiscoveryResponse disc: discoveryServices) {
527                 if (index  == disc.getIndex()) {
528                     found = true;
529                     returnName = disc.getLocation();
530                     break;
531                 }
532             }
533             if (!found) {
534                 throw new WayfException("Couldn't not find endpoint " + returnIndex + "in metadata");
535             }
536         } else {
537             //
538             // No name, not index, so we want the default
539             //
540             returnName = defaultName;
541         }
542         //
543         // So by now returnName has the correct value, either harvested from or
544         // policed against the metadata
545         //
546         req.setAttribute(RETURN_ATTRIBUTE_NAME, returnName);
547     }
548 
549     /**
550      * Helper function to see whather the provided endpoint in the metadata matches the 
551      * provided return URL in the request.
552      * 
553      * @param discovery
554      * @param providedName
555      * @return
556      */
557     private static boolean equalsURL(DiscoveryResponse discovery, URL providedName) {
558         
559         //
560         // Nothing provided - no match
561         //
562         if (null == discovery) {
563             return false;
564         }
565         
566         URL discoveryName;
567         try {
568             discoveryName = new URL(discovery.getLocation());
569         } catch (MalformedURLException e) {
570             //
571             // Something bad happened.  Log it (it is only of interest to the sysadmin, not to the user)
572             //
573             LOG.warn("Found invalid discovery end point : " + discovery.getLocation(), e);
574             return false;
575         }
576         
577         return providedName.equals(discoveryName);
578         
579     }
580 
581     /**
582      * Displays a Discovery Service selection page, having first consulted the plugins as needed.
583      * @param req Describes the request
584      * @param res Describes the response
585      * @param searchName What are we looking for?
586      * 
587      * @throws WayfRequestHandled if a plugin has dealt with the request
588      * @throws WayfException in case of an error.
589      */
590     private void handleLookup(HttpServletRequest req, 
591                               HttpServletResponse res, 
592                               String searchName) throws WayfException, WayfRequestHandled {
593         
594         String shire = getValue(req, SHIRE_PARAM_NAME);
595         String providerId = getSPId(req);
596         EntityDescriptor sp = null;
597         boolean twoZeroProtocol = (shire == null);
598         boolean isPassive = (twoZeroProtocol && 
599                              "true".equalsIgnoreCase(getValue(req, ISPASSIVE_PARAM_NAME)));
600 
601         Collection <IdPSiteSetEntry> siteLists = null;
602         Collection<IdPSite> searchResults = null;
603         
604         if (config.getProvideListOfLists()) {
605             siteLists = new ArrayList <IdPSiteSetEntry>(siteSets.size());
606         }
607 
608         Collection <IdPSite> sites = null;
609         Comparator<IdPSite> comparator = new IdPSite.Compare(req);
610        
611         if (config.getProvideList()) {
612             sites = new TreeSet<IdPSite>(comparator);
613         }
614 
615         if (searchName != null && !searchName.equals("")) {
616             searchResults = new TreeSet<IdPSite>(comparator);
617         }
618 
619         LOG.debug("Processing Idp Lookup for : " + providerId);
620 
621         //
622         // Iterate over all the sitesets and if they know about the SP pass them to the plugins
623         // and then add them too the list
624         //
625 
626         PluginContext[] ctx = new PluginContext[plugins.size()];
627         List<IdPSite> hintList = new ArrayList<IdPSite>();
628         
629         if (twoZeroProtocol) {
630             setupReturnAddress(providerId, req);
631         }
632         //
633         // By having siteLists and sites as parameters we only iterate over 
634         // the metadata arrays once.
635         //
636         try {   
637             for (IdPSiteSet metadataProvider:siteSets) {
638                 
639                 //
640                 // Only do work if the SP makes sense
641                 //
642 
643                 if (metadataProvider.containsSP(providerId) || !config.getLookupSp()) {
644 
645                     Collection <IdPSite> search = null;
646 
647                     if (null == sp) {
648                         sp = metadataProvider.getEntity(providerId);
649                     }
650                     
651                     if (searchResults != null) {
652                         search = new TreeSet<IdPSite>(comparator);
653                     }
654 
655                     Map <String, IdPSite> theseSites = metadataProvider.getIdPSites(searchName, config, search);
656                     
657                     //
658                     // Consult the plugins
659                     //
660                     for (int i = 0; i < plugins.size(); i++) {
661                             
662                         Plugin plugin = plugins.get(i);
663                         
664                         if (searchResults == null) {
665                             //
666                             // This was a search
667                             //
668                             ctx[i] = plugin.lookup(req, 
669                                                    res, 
670                                                    metadataProvider.paramFor(plugin), 
671                                                    theseSites, 
672                                                    ctx[i], 
673                                                    hintList);
674                         } else {
675                             ctx[i] = plugin.search(req, 
676                                                    res, 
677                                                    metadataProvider.paramFor(plugin), 
678                                                    searchName, 
679                                                    theseSites, 
680                                                    ctx[i], 
681                                                    searchResults, 
682                                                    hintList);
683                         }
684                     }
685                     
686                     if (null == theseSites || theseSites.isEmpty()) {
687                         continue;
688                     }
689                     
690                     //
691                         
692                     // Accumulate any per-metadata provider information
693                     // 
694             
695                     Collection<IdPSite> values = new TreeSet<IdPSite>(comparator);
696                     if (null != theseSites) {
697                         values.addAll(theseSites.values());
698                     }
699                     
700                     if (siteLists != null) {
701                         siteLists.add(new IdPSiteSetEntry(metadataProvider,values));
702                     }
703                             
704                     if (sites != null) {
705                         sites.addAll(values);
706                     }
707                     
708                     if (searchResults != null) {
709                         searchResults.addAll(search);
710                     }
711                 }
712             }
713             
714             if (isPassive) {
715                 //
716                 // No GUI intervention.
717                 //
718                 if (0 != hintList.size()) {
719                     //
720                     // We found a candidate, hand it back
721                     //
722                     forwardRequest(req, res, hintList.get(0));
723                 } else {
724                     forwardRequest(req, res, null);
725                 }   
726                 return;
727             }
728             
729             //
730             // Now set up all the funky stuff that the JSP needs.  Firstly the protocol
731             // specific parameters which will come back to us
732             //
733             
734             if (twoZeroProtocol) {
735                 //
736                 // The return address was set up in setupReturnAddress
737                 //
738                 String returnString = (String) req.getAttribute(RETURN_ATTRIBUTE_NAME);
739                 if (null == returnString || 0 == returnString.length()) {
740                     throw new WayfException("Parameter " + RETURN_PARAM_NAME + " not supplied");
741                 }
742 
743                 String returnId = getValue(req, RETURNID_PARAM_NAME);
744                 if (null == returnId || 0 == returnId.length()) {
745                     returnId = RETURNID_DEFAULT_VALUE;
746                 }
747                 //
748                 // Return *means* something so we cannot use it as an attribute
749                 //
750                 req.setAttribute(RETURN_ATTRIBUTE_NAME, returnString);
751                 req.setAttribute(RETURNID_PARAM_NAME, returnId);
752                 req.setAttribute(ENTITYID_PARAM_NAME, providerId);
753                 
754             } else {
755                 String target = getValue(req, TARGET_PARAM_NAME);
756                 if (null == target || 0 == target.length()) {
757                     throw new WayfException("Could not extract target from provided parameters");
758                 }
759                 req.setAttribute(SHIRE_PARAM_NAME, shire);
760                 req.setAttribute(TARGET_PARAM_NAME, target);
761                 req.setAttribute(PROVIDERID_PARAM_NAME, providerId);
762                 //
763                 // Time is in unix format
764                 //
765                 req.setAttribute("time", new Long(new Date().getTime() / 1000).toString()); 
766                 
767             }
768             
769             //
770             // Setup the stuff that the GUI wants.  
771             //
772             setDisplayLanguage(sites, req);
773             req.setAttribute("sites", sites);
774             if (null != siteLists) {
775                 for (IdPSiteSetEntry siteSetEntry:siteLists) {
776                     setDisplayLanguage(siteSetEntry.getSites(), req);
777                 }
778             }
779             
780             req.setAttribute(PROVIDERID_OBJECT_PARAM_NAME, sp);
781                 
782             req.setAttribute("siteLists", siteLists);
783             req.setAttribute("requestURL", req.getRequestURI().toString());
784 
785             if (searchResults != null) {
786                 if (searchResults.size() != 0) {
787                     setDisplayLanguage(searchResults, req);
788                     req.setAttribute("searchresults", searchResults);
789                 } else {
790                     req.setAttribute("searchResultsEmpty", "true");
791                 }
792             }
793 
794             if (hintList.size() > 0) {
795                 setDisplayLanguage(hintList, req);
796                 req.setAttribute("cookieList", hintList);
797             }
798 
799             LOG.debug("Displaying WAYF selection page.");
800             RequestDispatcher rd = req.getRequestDispatcher(config.getJspFile());
801 
802             //
803             // And off to the jsp
804             //
805             rd.forward(req, res);
806         } catch (IOException ioe) {
807             LOG.error("Problem displaying WAYF UI.\n" +  ioe.getMessage());
808             throw new WayfException("Problem displaying WAYF UI", ioe);
809         } catch (ServletException se) {
810             LOG.error("Problem displaying WAYF UI.\n" +  se.getMessage());
811             throw new WayfException("Problem displaying WAYF UI", se);
812         }
813     }
814 
815     /**
816      * Prior to display we set the display language from the
817      * browser. There is probably a proper way to do this using
818      * jsp, but I want to keep the API between JSP and java the same 1.3->2.0
819      * @param sites - the sites we need to impact
820      * @param req - from whiuch we get the locale
821      */
822     private void setDisplayLanguage(Collection<IdPSite> sites, HttpServletRequest req) {
823         
824         if (null == sites) {
825             return;
826         }
827         Locale locale = req.getLocale();
828         if (null == locale) {
829             Locale.getDefault();
830         }
831         String lang = locale.getLanguage();
832 
833         for (IdPSite site : sites) {
834             site.setDisplayLanguage(lang);
835         }
836     }
837 
838 
839     /**
840      * Uses an HTTP Status 307 redirect to forward the user to the IdP or the SP.
841      * 
842      * @param req request under consideration
843      * @param res response under construction
844      * @param site The Idp
845      * @throws WayfException if bad things happen.
846      */
847     public static void forwardRequest(HttpServletRequest req, HttpServletResponse res, IdPSite site)
848                     throws WayfException {
849 
850         String shire = getValue(req, SHIRE_PARAM_NAME);
851         String providerId = getSPId(req);
852         boolean twoZeroProtocol = (shire == null);
853 
854         if (!twoZeroProtocol) {
855             String handleService = site.getAddressForWAYF(); 
856             if (handleService != null ) {
857 
858                 String target = getValue(req, TARGET_PARAM_NAME);
859                 if (null == target || 0 == target.length()) {
860                     throw new WayfException("Could not extract target from provided parameters");
861                 }
862 
863                 LOG.info("Redirecting to selected Handle Service: " + handleService);
864                 try {
865                     StringBuffer buffer = new StringBuffer(handleService +  
866                        "?" + TARGET_PARAM_NAME + "=");
867                     buffer.append(URLEncoder.encode(target, "UTF-8"));
868                     buffer.append("&" + SHIRE_PARAM_NAME + "=");
869                     buffer.append(URLEncoder.encode(shire, "UTF-8"));
870                     buffer.append("&" + PROVIDERID_PARAM_NAME + "=");
871                     buffer.append(URLEncoder.encode(providerId, "UTF-8"));
872                          
873                     //
874                     // Time is as per U**X
875                     //
876                     buffer.append("&" +  TIME_PARAM_NAME + "=");
877                     buffer.append(new Long(new Date().getTime() / 1000).toString());
878                     res.sendRedirect(buffer.toString());
879                 } catch (IOException ioe) {
880                     //
881                     // That failed.  
882                     //
883                     throw new WayfException("Error forwarding to IdP: \n" + ioe.getMessage());
884                 }
885             } else {
886                 String s = "Error finding to IdP: " + site.getDisplayName(req); 
887                 LOG.error(s);
888                 throw new WayfException(s);
889             }
890         } else {
891             String returnUrl = (String) req.getAttribute(RETURN_ATTRIBUTE_NAME);
892             
893             if (null == returnUrl || 0 == returnUrl.length()) {
894                 throw new WayfException("Could not find return parameter");
895             }
896             try {
897                 returnUrl = URLDecoder.decode(returnUrl, "UTF-8");
898             } catch (UnsupportedEncodingException e) {
899                 throw new WayfException("Did not understand parameter ", e);
900             }
901             String redirect;
902             if (site != null) {
903                 StringBuffer buffer = new StringBuffer(returnUrl);
904                 //
905                 // If we were given anybody to lookup, construct the URL
906                 //
907                 String returnParam = getValue(req, RETURNID_PARAM_NAME);
908                 if (null == returnParam || 0 == returnParam.length()) {
909                     returnParam = RETURNID_DEFAULT_VALUE;
910                 }              
911                 //
912                 // Do we add a '?' or a '&' for the parameters
913                 //
914 
915                 if (returnUrl.indexOf('?') >= 0) {
916                     //
917                     // there is a parameter already.  Add a '&'
918                     //
919                     buffer.append("&" + returnParam + "=");
920                 } else {
921                     //
922                     // No parameter.  Use ?
923                     //
924                     buffer.append("?" + returnParam + "=");
925                 }
926                 buffer.append(site.getName());
927                 redirect =  buffer.toString();
928             } else {
929                 //
930                 // Just send it back
931                 //
932                 redirect = returnUrl;
933             }
934             
935             LOG.debug("Dispatching to " + redirect);
936             
937             try {
938                 res.sendRedirect(redirect);
939             } catch (IOException ioe) {
940                 //
941                 // That failed.  
942                 //
943                 throw new WayfException("Error forwarding back to Sp: \n" + ioe.getMessage());
944             }         
945         }
946     }
947 
948     /**
949      * Handles all "recoverable" errors in WAYF processing by logging the error and forwarding the user to an
950      * appropriate error page.
951      * 
952      * @param req request under consideration
953      * @param res response under construction
954      * @param message - what so say
955      */
956     private void handleError(HttpServletRequest req, HttpServletResponse res, String message) {
957 
958         LOG.debug("Displaying WAYF error page.");
959         req.setAttribute("errorText", message);
960         req.setAttribute("requestURL", req.getRequestURI().toString());
961         RequestDispatcher rd = req.getRequestDispatcher(config.getErrorJspFile());
962 
963         try {
964             rd.forward(req, res);
965         } catch (IOException ioe) {
966             LOG.error("Problem trying to display WAYF error page: " + ioe.toString());
967         } catch (ServletException se) {
968             LOG.error("Problem trying to display WAYF error page: " + se.toString());
969         }
970     }
971 
972     /**
973      * Gets the value for the parameter either from the parameter or from jsp.
974      * @param req - the request.
975      * @param name - the name of the parameter.
976      * @return - result
977      */
978     private static String getValue(HttpServletRequest req, String name) {
979 
980         
981         String value = req.getParameter(name); 
982         if (value != null) {
983             return value;
984         }
985         return (String) req.getAttribute(name);
986     }
987 
988     private static String getSPId(HttpServletRequest req) throws WayfException {
989 
990         //
991         // Try first with 2.0 version
992         //
993         String param = req.getParameter(ENTITYID_PARAM_NAME);
994         if (param != null && !(param.length() == 0)) {
995             return param;
996         } 
997         
998         param = (String) req.getAttribute(ENTITYID_PARAM_NAME);
999         if (param != null && !(param.length() == 0)) {
1000             return param;
1001         }       
1002         //
1003         // So Try with 1.3 version
1004         //
1005         param = req.getParameter(PROVIDERID_PARAM_NAME);
1006         if (param != null && !(param.length() == 0)) {
1007             return param;
1008         } 
1009         
1010         param = (String) req.getAttribute(PROVIDERID_PARAM_NAME);
1011         if (param != null && !(param.length() == 0)) {
1012             return param;
1013         } 
1014         throw new WayfException("Could not locate SP identifier in parameters");
1015     }   
1016 }