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