SDSS WAYF patch for multi-federation support
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 12 Nov 2005 23:17:34 +0000 (23:17 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 12 Nov 2005 23:17:34 +0000 (23:17 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1908 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

19 files changed:
build.xml
src/conf/wayfconfig.xml
src/edu/internet2/middleware/shibboleth/idp/IdPProtocolSupport.java
src/edu/internet2/middleware/shibboleth/idp/IdPResponder.java
src/edu/internet2/middleware/shibboleth/metadata/MetadataProviderFactory.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/wayf/DiscoveryServiceHandler.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/wayf/HandlerConfig.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/wayf/IdPSite.java
src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSet.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSetEntry.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/wayf/SamlIdPCookie.java
src/edu/internet2/middleware/shibboleth/wayf/WayfConfig.java [deleted file]
src/edu/internet2/middleware/shibboleth/wayf/WayfException.java
src/edu/internet2/middleware/shibboleth/wayf/WayfService.java
src/edu/internet2/middleware/shibboleth/xml/SchemasResourceListImpl.java
src/schemas/wayfconfig.xsd
webAppConfig/wayf.xml
webApplication/wayf.jsp
webApplication/wayferror.jsp

index 8c5e1f9..447fc29 100755 (executable)
--- a/build.xml
+++ b/build.xml
        <target name="package-wayf" depends="compile" description="Creates the WAYF war and moves it to ${dist}">
         <copy file="${appconfig}/schemas.properties" todir="${approot}/WEB-INF/classes/conf"/>
         <copy file="${appconfig}/wayfconfig.xml" todir="${approot}/WEB-INF/classes/conf"/>
+       <copy file="${appconfig}/log4j.properties" tofile="${build}/log4j.properties" failonerror="false"/>
             
                <war warfile="${dist}/${distnameWayf}.war" webxml="${configroot}/wayf.xml" basedir="${approot}" update="no"/>
        </target>
index 50ebecf..5f85e6d 100644 (file)
 <?xml version="1.0"?>
 <WayfConfig 
-       xmlns="urn:mace:shibboleth:wayf:config:1.0" 
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       cacheExpiration="604800"        
-       supportContact="mailto:root@localhost" 
-       logoLocation="images/logo.jpg">
-       <HelpText>In order to fulfill the request for a web resource you have just attempted to access, information must be obtained from your identify provider. Please select the provider with which you are affiliated.</HelpText>
-       <SearchResultEmptyText>No provider was found that matches your search criteria, please try again.</SearchResultEmptyText>
-
-       <SearchIgnore>
-               <IgnoreText>Institution</IgnoreText>
-               <IgnoreText>University</IgnoreText>
-               <IgnoreText>State </IgnoreText>
-               <IgnoreText>School</IgnoreText>
-       </SearchIgnore>
+        xmlns="urn:mace:shibboleth:wayf:config:1.0" 
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
+
+<!-- The default behaviour of Service is controlled via the elements
+     and attributes below.  Non default behaviour is achieved by
+     specifying any or all of these in the specific
+     DiscoveryServiceHanlder element.
+
+     The handleCookie attribute is one of 'NeverFollow', 'AlwaysFollow',
+     'FollowSingle' or 'ClearCookie' and controls how the WAYF deals
+     with the _saml_idp cookie.
+
+     The cacheDomain attribute sets the domain that the cookie will be
+     written to.
+
+     The cacheExpiration attribute sets the expiration time of the
+     cookie (if the 'Remember for a week' selection is made).
+
+     The jspFile & errorJspFile attributes control the display
+
+     The provideList attribute controls whether a single list of all
+     possible IdPs is presented.  The default wayfs.jsp works best
+     if this is true if provideListofList is true.
+     
+     The provideListOfList attribute controls whether mutiple lists
+     are presented (one for each MetadatProvider).
+
+     The showUsableIdPs attribute controls the contents of the above
+     lists.  The single list (provideList=true) is trimmed by
+     excluding IdPs which do not share a metadata file with the SP.
+     The multiple lists (provideListOfList=true) is trimmed by
+     excluding all lists which do not specify the SP.
+     
+     The SearchIgnore element contains a list of words to be ignored while
+     performing a search.
+-->
+
+        <Default 
+            handleCookie="NeverFollow"
+            jspFile="/wayf.jsp"
+            errorJspFile="/wayferror.jsp"
+            provideList="false"
+            provideListOfList="true"
+            showUnusableIdPs="false"
+            cacheExpiration="604800" >
+            <SearchIgnore>
+                <IgnoreText>Institution</IgnoreText>
+                <IgnoreText>University</IgnoreText>
+                <IgnoreText>State </IgnoreText>
+                <IgnoreText>School</IgnoreText>
+            </SearchIgnore>
+        </Default>
+        
+<!-- The MetadataProvider is in a similar syntax to that used to
+     configure an IdP.  This means that plugins for the IdP can be
+     used interchangably between the IdP and WAYF.
+
+     The identifier element is used to uniquely distinguish the
+     metadata in a Federation element below -->
+
+        <MetadataProvider 
+                displayName="Put in User Friendly Name here"
+                identifier="FirstSite"
+                type="edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadata"
+                uri="file:///usr/local/sites.xml"/>
+
+<!-- If the WAYF is to handle data from more than one metadata source
+     then more metadataproviders can be provided, as below
+        
+       <MetadataProvider 
+                displayName="Another Name Here"
+                identifier="SecondSite"
+                type="edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadata"
+                uri="file:/usr/local/moresites.xml"/>
+-->
+
+<!-- Every handler has to be declared with a DiscoverServiceHandler
+     element.  As well as the attributes and elements described above,
+     each DiscoveryServiceHandler *MUST* have a location attribute.
+     
+     The default wayf.xml specifies that "/WAYF" and "/*.wayf" are the
+     possibilities for DiscoveryServices.  If a URL matches the above,
+     but does not match any location in a DiscoveryServiceHandler,
+     then the first handler for which the default attribute is set
+     true is invoked -->
+
+
+    <DiscoveryServiceHandler
+        location=".+/WAYF" 
+        default="true" />
+
+<!-- The ClearCache handler causes the cookie to be deleted.  The jsp shipped
+     with the WAYF refers to this handler -->
+        
+    <DiscoveryServiceHandler
+        location=".+/ClearCache.wayf"
+        handleCookie="ClearCookie" />
+
+<!-- Example of how to constrain a DiscoveryService to one (or more)
+     explicit metadata sources.  (The default is to use all metadata
+     sources)
+
+ <DiscoveryServiceHandler location=".+/SecondOnly.wayf" >
+
+        <Federation identifier="SecondSite"/>
+    </DiscoveryServiceHandler>  
+-->
+
 </WayfConfig>
index 1aefbf9..9d92d7c 100644 (file)
@@ -43,6 +43,7 @@ import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
 import edu.internet2.middleware.shibboleth.metadata.EntitiesDescriptor;
 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
 import edu.internet2.middleware.shibboleth.metadata.Metadata;
+import edu.internet2.middleware.shibboleth.metadata.MetadataProviderFactory;
 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
 
 /**
index 274d259..90d5246 100644 (file)
@@ -57,8 +57,6 @@ import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
 import edu.internet2.middleware.shibboleth.log.LoggingInitializer;
-import edu.internet2.middleware.shibboleth.metadata.Metadata;
-import edu.internet2.middleware.shibboleth.metadata.MetadataException;
 
 /**
  * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
@@ -412,29 +410,3 @@ public class IdPResponder extends HttpServlet {
 
 }
 
-class MetadataProviderFactory {
-
-       private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
-
-       public static Metadata loadProvider(Element e) throws MetadataException {
-
-               String className = e.getAttribute("type");
-               if (className == null || className.equals("")) {
-                       log.error("Metadata Provider requires specification of the attribute \"type\".");
-                       throw new MetadataException("Failed to initialize Metadata Provider.");
-               } else {
-                       try {
-                               Class[] params = {Class.forName("org.w3c.dom.Element"),};
-                               return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
-                       } catch (Exception loaderException) {
-                               log.error("Failed to load Metadata Provider implementation class: " + loaderException);
-                               Throwable cause = loaderException.getCause();
-                               while (cause != null) {
-                                       log.error("caused by: " + cause);
-                                       cause = cause.getCause();
-                               }
-                               throw new MetadataException("Failed to initialize Metadata Provider.");
-                       }
-               }
-       }
-}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/metadata/MetadataProviderFactory.java b/src/edu/internet2/middleware/shibboleth/metadata/MetadataProviderFactory.java
new file mode 100644 (file)
index 0000000..ba2148e
--- /dev/null
@@ -0,0 +1,47 @@
+package edu.internet2.middleware.shibboleth.metadata;
+
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+
+public class MetadataProviderFactory {
+
+       private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
+
+       public static Metadata loadProvider(Element e) throws MetadataException {
+
+               String className = e.getAttribute("type");
+               if (className == null || className.equals("")) {
+                       log.error("Metadata Provider requires specification of the attribute \"type\".");
+                       throw new MetadataException("Failed to initialize Metadata Provider.");
+               } else {
+                       try {
+                               Class[] params = {Class.forName("org.w3c.dom.Element"),};
+                               return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
+                       } catch (Exception loaderException) {
+                               log.error("Failed to load Metadata Provider implementation class: " + loaderException);
+                               Throwable cause = loaderException.getCause();
+                               while (cause != null) {
+                                       log.error("caused by: " + cause);
+                                       cause = cause.getCause();
+                               }
+                               throw new MetadataException("Failed to initialize Metadata Provider.");
+                       }
+               }
+       }
+}
diff --git a/src/edu/internet2/middleware/shibboleth/wayf/DiscoveryServiceHandler.java b/src/edu/internet2/middleware/shibboleth/wayf/DiscoveryServiceHandler.java
new file mode 100644 (file)
index 0000000..7a82c33
--- /dev/null
@@ -0,0 +1,375 @@
+package edu.internet2.middleware.shibboleth.wayf;
+
+import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+
+public class DiscoveryServiceHandler {
+
+       private final static Logger log = Logger.getLogger(DiscoveryServiceHandler.class.getName());
+
+       private final static String DEFAULT_JSP_FILE = "/wayf.jsp";
+       private final static String DEFAULT_ERROR_JSP_FILE = "/wayferror.jsp";
+       
+       private final String location;
+       private final boolean isDefault;
+       private final HandlerConfig config;
+       //
+       // hacked enum
+       //
+       
+       private final List /*<IdPSiteSet>*/ siteSets = new ArrayList /*<IdPSiteSet>*/ ();
+       
+       
+       protected DiscoveryServiceHandler(Element config, 
+                                                                     Hashtable /*<String, IdPSiteSet>*/ federations,
+                                                                     HandlerConfig defaultConfig) throws ShibbolethConfigurationException
+       {
+               this.config = new HandlerConfig(config, defaultConfig);
+               
+               location = config.getAttribute("location");
+               
+               if (location == null || location.equals("")) {
+                       
+                       log.error("DiscoveryService must have a location specified");
+                       throw new ShibbolethConfigurationException("DiscoveryService must have a location specified");
+                       
+               }
+
+               String attribute = ((Element) config).getAttribute("default");
+               if (attribute != null && !attribute.equals("")) {
+                       isDefault = Boolean.valueOf(attribute).booleanValue();
+               } else {
+                       isDefault = true;
+               }
+               
+               NodeList list = config.getElementsByTagName("Federation");
+                   
+           for (int i = 0; i < list.getLength(); i++ ) {
+                       
+               attribute = ((Element) list.item(i)).getAttribute("identifier");
+                       
+                   IdPSiteSet siteset = (IdPSiteSet) federations.get(attribute);
+                   
+                   if (siteset == null) {
+                       log.error("Handler " + location + ": could not find metadata for identifier " + attribute);
+                       throw new ShibbolethConfigurationException("Handler " + location + ": could not find metadata for identifier " + attribute);
+                   }
+                   
+                   siteSets.add(siteset);
+           }
+
+           if (siteSets.size() == 0) {
+                       //
+                       // No Federations explicitly named
+                       //
+                       siteSets.addAll(federations.values());
+               }
+       }
+       
+       
+       //
+       // Standard Beany Methods
+       //
+       /**
+        * Returns the 'Name' of the service - the path used to identify the ServiceHandler 
+        */
+       
+       public String getLocation() {
+               return location;
+       }
+
+       public boolean isDefault() {
+               return isDefault;
+       }
+
+       public void doGet(HttpServletRequest req, HttpServletResponse res) {
+               // Decide how to route the request based on query string
+               String requestType = req.getParameter("action");
+               if (requestType == null) {
+                       requestType = "lookup";
+               }
+               try {
+
+                       SamlIdPCookie cookie;
+                       
+                       if (config.getHandleCookie() == HandlerConfig.CLEAR_COOKIE) {
+                               
+                               //
+                               // Mark the cookie for deletion (unless we reset it later)
+                               
+                               SamlIdPCookie.deleteCookie(req, res);
+                               
+                               //
+                               // And create an empty cookie
+                               //
+                               cookie = new SamlIdPCookie(req, res, config.getCacheDomain());
+                               
+                       } else {
+
+                               cookie = SamlIdPCookie.getIdPCookie(req, res, config.getCacheDomain());
+                       }
+
+                       
+                       String searchSP = null;
+                       
+                       if (config.getLookupSp()) {
+                               searchSP = getProviderId(req);
+                       }
+                       
+                       List /*<IdPSite>*/ cookieList = cookie.getIdPList(siteSets, searchSP);
+                       
+                       //
+                       // Put if we have been told to
+                       //
+                       if (((config.getHandleCookie() == HandlerConfig.ALWAYS_FOLLOW_COOKIE) && (cookieList.size() > 0)) ||
+                               ((config.getHandleCookie() == HandlerConfig.FOLLOW_SINGLE_COOKIE) && (cookieList.size() == 1))) {
+                               
+                               IdPSite site = (IdPSite) cookieList.get(0);
+                               
+                               //
+                               // Move name to front of cookie, and write back specifying a time of
+                               // zero (which will leave the time untouched)
+                               //
+                               cookie.addIdPName(site.getName(), config.getCacheExpiration());
+                               
+                               forwardToIdP(req, res, site);
+                       } else if (requestType.equals("search")) {
+                               
+                               handleSearch(req, res, cookieList);
+                               
+                       } else if (requestType.equals("selection")) {
+                               
+                               String origin = req.getParameter("origin");
+                               log.debug("Processing handle selection: " + origin);
+                               
+                               IdPSite site = IdPSiteSet.IdPforSP(siteSets, origin, searchSP);
+                               
+                               if (site == null) {
+                                       handleLookup(req, res, cookieList);
+                               } else {
+                                       
+                                       //
+                                       // Write back cache - if we were asked to
+                                       //
+                                       
+                                       if ((req.getParameter("cache") != null)) {
+                        if (req.getParameter("cache").equalsIgnoreCase("session")) {
+                            cookie.addIdPName(origin, -1);
+                        } else if (req.getParameter("cache").equalsIgnoreCase("perm")) {
+                            cookie.addIdPName(origin, config.getCacheExpiration());
+                        }
+                    }
+                                       forwardToIdP(req, res, site);
+                               }
+                       } else {
+                               handleLookup(req, res, cookieList);
+                       }
+               } catch (WayfException we) {
+                       handleError(req, res, we);
+               }
+
+       }
+       
+       /**
+        * Displays a WAYF selection page.
+        * 
+        */
+       private void handleLookup(HttpServletRequest req, HttpServletResponse res, List/*<IdPSite>*/ cookieList) throws WayfException {
+
+               try {
+                       if ((getSHIRE(req) == null) || (getTarget(req) == null)) { throw new WayfException(
+                                       "Invalid or missing data from SHIRE"); 
+                       }
+
+                       if (cookieList.size() > 0) {
+                               req.setAttribute("cookieList", cookieList);
+                       }
+                       req.setAttribute("shire", getSHIRE(req));
+                       req.setAttribute("target", getTarget(req));
+                       String providerId = getProviderId(req);
+                       if (providerId != null) {
+                               req.setAttribute("providerId", providerId);
+                       }
+
+                       Collection /*<IdPSiteSetEntry>*/ siteLists = null;
+                       if (config.getProvideListOfLists()) {
+                               siteLists = new ArrayList /*<IdPSiteSetEntry>*/(siteSets.size());
+                       }
+                       
+                       Collection /*<IdPSite>*/ sites = null;
+                       if (config.getProvideList()) {
+                               sites = new TreeSet/*<IdPSite>*/();
+                       }
+       
+                       String searchSP = null; 
+                       
+                       if (config.getLookupSp()) {
+                               searchSP = providerId;
+                       }
+                       
+                       IdPSiteSet.getSiteLists(siteSets, searchSP, siteLists, sites);
+                       
+                       req.setAttribute("sites", sites);
+                       req.setAttribute("siteLists", siteLists);
+                       
+                       if (siteLists != null && siteLists.size() == 1) {
+                               req.setAttribute("singleSiteList", new Object());
+                       }
+
+                       req.setAttribute("time", new Long(new Date().getTime() / 1000).toString()); // Unix Time
+                       req.setAttribute("requestURL", req.getRequestURI().toString());
+
+                       log.debug("Displaying WAYF selection page.");
+                       RequestDispatcher rd = req.getRequestDispatcher(config.getJspFile());
+
+                       rd.forward(req, res);
+               } catch (IOException ioe) {
+                       throw new WayfException("Problem displaying WAYF UI.\n" + ioe.getMessage());
+               } catch (ServletException se) {
+                       throw new WayfException("Problem displaying WAYF UI.\n" +  se.getMessage());
+               }
+       }
+
+       /**
+        * Looks for origin sites that match search terms supplied by the user
+        * 
+        */
+       private void handleSearch(HttpServletRequest req, HttpServletResponse res, List /*<IdPSite>*/ cookieList) throws WayfException {
+
+               String parameter = req.getParameter("string"); 
+               if (parameter != null) {
+                       Collection/*<IdPSite>*/ sites = IdPSiteSet.seachForMatchingOrigins(siteSets, getProviderId(req), parameter, config);
+                       if (sites.size() != 0) {
+                               req.setAttribute("searchresults", sites);
+                       } else {
+                               req.setAttribute("searchResultsEmpty", "true");
+                       }
+               }
+               handleLookup(req, res, cookieList);
+       }
+
+       /**
+        * Uses an HTTP Status 307 redirect to forward the user the HS.
+        * 
+        * @param site - The Idp.
+        */
+       private void forwardToIdP(HttpServletRequest req, HttpServletResponse res, IdPSite site)
+                       throws WayfException {
+
+               String handleService = site.getAddressFor(); 
+                               
+               if (handleService != null ) {
+
+                       log.info("Redirecting to selected Handle Service: " + handleService);
+                       try {
+                               StringBuffer buffer = new StringBuffer(handleService + "?target="
+                                               + URLEncoder.encode(getTarget(req), "UTF-8") + "&shire="
+                                               + URLEncoder.encode(getSHIRE(req), "UTF-8"));
+                               String providerId = getProviderId(req);
+                               log.debug("WALTER: (" + providerId + ").");
+                               if (providerId != null) {
+                                       buffer.append("&providerId=" + URLEncoder.encode(getProviderId(req), "UTF-8"));
+                               }
+                               buffer.append("&time=" + new Long(new Date().getTime() / 1000).toString()); // Unix Time
+                               res.sendRedirect(buffer.toString());
+                       } catch (IOException ioe) {
+                               //
+                               // That failed.  Purge the cache or we will go around again...
+                               //
+                               SamlIdPCookie.deleteCookie(req, res);
+                               throw new WayfException("Error forwarding to HS: \n" + ioe.getMessage());
+                       }
+               } else {
+                       log.error("Error finding to IdP: " + site.getDisplayName());
+                       handleLookup(req, res, null);
+               }
+       }
+
+       /**
+        * Handles all "recoverable" errors in WAYF processing by logging the error and forwarding the user to an
+        * appropriate error page.
+        * 
+        * @param we
+        *            The WayfException respective to the error being handled
+        */
+       private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
+
+               log.error("WAYF Failure: " + we.toString());
+               log.debug("Displaying WAYF error page.");
+               req.setAttribute("errorText", we.toString());
+               req.setAttribute("requestURL", req.getRequestURI().toString());
+               RequestDispatcher rd = req.getRequestDispatcher(config.getErrorJspFile());
+
+               try {
+                       rd.forward(req, res);
+               } catch (IOException ioe) {
+                       log.error("Problem trying to display WAYF error page: " + ioe.toString());
+               } catch (ServletException se) {
+                       log.error("Problem trying to display WAYF error page: " + se.toString());
+               }
+       }
+
+       /**
+        * Retrieves the SHIRE from the request.
+        * 
+        * @throws WayfException
+        *             If the request does not contain a shire parameter.
+        */
+       private String getSHIRE(HttpServletRequest req) throws WayfException {
+
+               String shire = (String) req.getAttribute("shire");
+               if (req.getParameter("shire") != null) {
+                       shire = req.getParameter("shire");
+               }
+               if (shire == null) { throw new WayfException("Invalid data from SHIRE: No acceptance URL received."); }
+               return shire;
+       }
+
+       /**
+        * Retrieves the user's target URL from the request.
+        * 
+        * @throws WayfException
+        *             If the request does not contain a target parameter
+        */
+       private String getTarget(HttpServletRequest req) throws WayfException {
+
+               String target = (String) req.getAttribute("target");
+               if (req.getParameter("target") != null) {
+                       target = req.getParameter("target");
+               }
+               if (target == null) { throw new WayfException("Invalid data from SHIRE: No target URL received."); }
+               return target;
+       }
+
+       private String getProviderId(HttpServletRequest req) {
+
+               String param = req.getParameter("providerId");
+               if (param != null && !(param.length() == 0)) {
+                       return req.getParameter("providerId");
+
+               } else {
+                       String attr = (String) req.getAttribute("providerId");
+                       if (attr == null || attr.length() == 0) { return null; }
+                       return attr;
+               }
+       }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/wayf/HandlerConfig.java b/src/edu/internet2/middleware/shibboleth/wayf/HandlerConfig.java
new file mode 100644 (file)
index 0000000..bf0429f
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.wayf;
+
+import java.util.HashSet;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
+
+/**
+ * Class used by the DiscoveryServiceHandler to handle run time behaviour 
+ * 
+ * @author Walter Hoehn wassa&#064;columbia.edu
+ */
+
+public class HandlerConfig {
+
+       private static final Logger log = Logger.getLogger(HandlerConfig.class.getName());
+       public final static String configNameSpace = "urn:mace:shibboleth:wayf:config:1.0"; 
+
+       private final HashSet ignoredForMatch;
+       private final int cacheExpiration;
+       private final String cacheDomain;
+       private final String jspFile;
+       private final String errorJspFile;
+       private final boolean provideListOfLists;  // defaults false below
+       private final boolean provideList;         // defaults true below
+       private final boolean lookupSp;            // defaults true below 
+       private final int handleCookie;
+       //
+       // hacked enum
+       //
+       public final static int ALWAYS_FOLLOW_COOKIE = 9;
+       public final static int FOLLOW_SINGLE_COOKIE = 10;
+       public final static int NEVER_FOLLOW_COOKIE = 11;
+       public final static int CLEAR_COOKIE = 12;
+
+       public HandlerConfig() {
+               //
+               // 'Sensible' default values
+               //
+               cacheExpiration = 604800;
+               cacheDomain = "";
+               jspFile = "/wayf.jsp";
+               errorJspFile = "/wayfError.jsp";
+               provideList = true;
+               provideListOfLists = false;
+               lookupSp = true;
+               handleCookie = NEVER_FOLLOW_COOKIE;
+               ignoredForMatch = new HashSet();        
+       }
+       
+       
+       private String getValue(Element element, String what) throws ShibbolethConfigurationException
+       {
+               NodeList list = element.getElementsByTagName(what);
+           
+           if (list.getLength() > 0) {
+               if (list.getLength() > 1) {
+                       throw new ShibbolethConfigurationException("More than one <" + what + "/> element");
+               }
+                       
+               return list.item(0).getTextContent();
+           }
+           return null;
+       }
+       
+       /**
+        * 
+        * Parse the Supplied XML element into a new WayfConfig Object
+        * 
+        */
+       
+       public HandlerConfig(Element config, HandlerConfig defaultValue) throws ShibbolethConfigurationException {
+           
+           log.debug("Loading global configuration properties.");
+
+           String attribute   = config.getAttribute("cacheDomain");
+           if ((attribute != null) && (attribute != "")) {
+               cacheDomain = attribute;
+           } else {
+               cacheDomain = defaultValue.cacheDomain;
+           }
+               
+           attribute = config.getAttribute("cacheExpiration");
+           if ((attribute != null) && (attribute != "")) {
+               
+               try {
+
+                       cacheExpiration = Integer.parseInt(attribute);
+               } catch (NumberFormatException ex) {
+                       
+                               log.error("Invalid CacheExpiration value - " + attribute);
+                       throw new ShibbolethConfigurationException("Invalid CacheExpiration value - " + attribute, ex);
+                       
+               }
+           } else {
+               cacheExpiration = defaultValue.cacheExpiration;
+           }
+
+           NodeList list = config.getElementsByTagName("SearchIgnore");
+           
+           if (list.getLength() == 0) {
+               
+               ignoredForMatch = defaultValue.ignoredForMatch;
+
+           } else { 
+               
+               ignoredForMatch = new HashSet();        
+                   
+               for (int i = 0; i < list.getLength(); i++ ) {
+                       
+                       NodeList inner = ((Element) list.item(i)).getElementsByTagName("IgnoreText");
+                       
+                       for(int j = 0; j < inner.getLength(); j++) {
+                               
+                               addIgnoredForMatch(inner.item(j).getTextContent());
+                       }
+                   }
+           }
+
+           attribute = config.getAttribute("jspFile");
+               if (attribute != null && !attribute.equals("")) {
+                       jspFile = attribute;
+               } else {
+                       jspFile = defaultValue.jspFile;
+               }
+               
+               attribute = config.getAttribute("errorJspFile");
+               if (attribute != null && !attribute.equals("")) {
+                       errorJspFile = attribute;
+               } else {
+                       errorJspFile = defaultValue.errorJspFile;
+               }
+               
+               attribute = ((Element) config).getAttribute("provideList");
+               if (attribute != null && !attribute.equals("")) {
+                       provideList = Boolean.valueOf(attribute).booleanValue();
+               } else { 
+                       provideList = defaultValue.provideList;
+               }
+
+               attribute = ((Element) config).getAttribute("provideListOfList");
+               if (attribute != null && !attribute.equals("")) {
+                       provideListOfLists = Boolean.valueOf(attribute).booleanValue();
+               } else {
+                       provideListOfLists = defaultValue.provideListOfLists;
+               }
+               
+               attribute = ((Element) config).getAttribute("showUnusableIdPs");
+               if (attribute != null && !attribute.equals("")) {
+                       lookupSp = !Boolean.valueOf(attribute).booleanValue();
+               } else {
+                       lookupSp = defaultValue.lookupSp;
+               }
+
+               attribute = ((Element) config).getAttribute("handleCookie");
+               if (attribute == null || attribute.equals("")) {
+                       handleCookie = defaultValue.handleCookie;
+               } else if (attribute.equalsIgnoreCase("alwaysfollow")) {
+                       handleCookie = ALWAYS_FOLLOW_COOKIE;
+               } else if (attribute.equalsIgnoreCase("followsingle")) {
+                       handleCookie = FOLLOW_SINGLE_COOKIE;
+               } else if (attribute.equalsIgnoreCase("neverfollow")) {
+                       handleCookie = NEVER_FOLLOW_COOKIE;
+               } else if (attribute.equalsIgnoreCase("clearcookie")) {
+                       handleCookie = CLEAR_COOKIE;
+               } else {
+                       
+                       log.error("Invalid value " + attribute + " to HandleCookie");
+                       throw new ShibbolethConfigurationException("Invalid value " + attribute + " to HandleCookie");
+               }       
+       }
+       
+
+       /**
+        * Determines if a particular string token should be used for matching when a user searches for origins.
+        * 
+        * @param str
+        *            The string to lookup
+        */
+       public boolean isIgnoredForMatch(String str) {
+
+               if (ignoredForMatch.contains(str.toLowerCase())) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Sets the tokens that should be ignored when a user searches for an origin site.
+        * 
+        * @param s
+        *            The ignored tokens are passed as a single string, each separated by whitespace
+        */
+       private void addIgnoredForMatch(String s) {
+
+               ignoredForMatch.add(s.toLowerCase());
+       }
+
+       /**
+        * Returns the cacheDomain.
+        * 
+        * @return String
+        */
+       public String getCacheDomain() {
+
+               return cacheDomain;
+       }
+
+       /**
+        * Returns the cacheExpiration.
+        * 
+        * @return int
+        */
+       public int getCacheExpiration() {
+
+               return cacheExpiration;
+       }
+       
+       public String getJspFile() {
+               return jspFile;
+       }
+       
+       public String getErrorJspFile() {
+               return errorJspFile;
+       }
+       
+       public boolean getProvideListOfLists() {
+               return provideListOfLists;
+       }
+       
+       public boolean getProvideList() {
+               return provideList;
+       }
+       
+       public boolean getLookupSp() {  
+               return lookupSp;  
+       }
+       
+       public int getHandleCookie() {  
+               return handleCookie;  
+       }
+}
index 441c6db..f38822b 100644 (file)
@@ -55,6 +55,11 @@ public class IdPSite implements Comparable {
                        return org.getDisplayName();
                }
        }
+       
+       public boolean equals(Object obj)
+       {
+               return ((obj instanceof IdPSite) && (((IdPSite)obj).getName().equals(getName())));
+       }
 
        /**
         * Based on 1.2 Origin.isMatch.  There must have been a reason for it...
@@ -67,6 +72,9 @@ public class IdPSite implements Comparable {
         */
        
        public int compareTo(Object o) {
+               
+               if (equals(o)) return 0;
+               
                int result = getDisplayName().toLowerCase().compareTo(((IdPSite) o).getDisplayName().toLowerCase());
                if (result == 0) {
                        result = getDisplayName().compareTo(((IdPSite) o).getDisplayName());
@@ -74,7 +82,7 @@ public class IdPSite implements Comparable {
                return result;
        }
 
-       static private boolean isMatch(EntityDescriptor entity, String str, WayfConfig config) {
+       static private boolean isMatch(EntityDescriptor entity, String str, HandlerConfig config) {
                
                Enumeration input = new StringTokenizer(str);
                while (input.hasMoreElements()) {
@@ -107,9 +115,9 @@ public class IdPSite implements Comparable {
                return false;
        }
        
-       static public Collection seachForMatchingOrigins(Metadata metadata,
+       static public Collection /*<IdPSite>*/ seachForMatchingOrigins(Metadata metadata,
                                                                                                        String searchString, 
-                                                                                                       WayfConfig config)
+                                                                                                       HandlerConfig config)
        {
                TreeSet /*<IdPSite>*/ result = new TreeSet /*<IdPSite>*/ ();
                Iterator /*<EntityDescriptor>*/ entities = Entities(metadata);
@@ -128,7 +136,7 @@ public class IdPSite implements Comparable {
                return result;
        }
        
-       static public Collection getIdPSites(Metadata metadata)
+       static public Collection /*<IdPSite>*/ getIdPSites(Metadata metadata)
        {
                TreeSet /*<IdPSite>*/ result = new TreeSet /*<IdPSite>*/ ();
                Iterator /*<EntityDescriptor>*/ entities = Entities(metadata);
@@ -144,7 +152,13 @@ public class IdPSite implements Comparable {
                } // iterate over all entities
                return result;
        }
-
+       
+       /**
+        * Lookup
+        */
+       public String getAddressFor() {
+               return entity.getIDPSSODescriptor(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS).getSingleSignOnServiceManager().getDefaultEndpoint().getLocation();
+       }
        /**
         * entitiesIterator:
         * 
diff --git a/src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSet.java b/src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSet.java
new file mode 100644 (file)
index 0000000..a68d205
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package edu.internet2.middleware.shibboleth.wayf;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.opensaml.XML;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
+import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
+import edu.internet2.middleware.shibboleth.metadata.Metadata;
+import edu.internet2.middleware.shibboleth.metadata.MetadataException;
+import edu.internet2.middleware.shibboleth.metadata.MetadataProviderFactory;
+
+
+/**
+ * 
+ * @author Rod Widdowson
+ *
+ * Represents a collection of related sites - usually a federation.  When the WAYF
+ * looks to see which IdP sites to show, it trims the list so as to not show IdP's 
+ * which do not trust the SP.
+ *
+ * This class is opaque outside this file.  The three static methods getSitesLists,
+ * searchForMatchingOrigins and lookupIdP provide mechansims for accessing 
+ * collections of IdPSiteSets.
+ * 
+ */
+
+public class IdPSiteSet {
+       
+       private static Logger log = Logger.getLogger(IdPSiteSet.class.getName());
+
+       private final Metadata metadata;
+       private final String identifier;
+       private final String displayName;
+       
+       public IdPSiteSet(Element el) throws ShibbolethConfigurationException {
+               
+           identifier = el.getAttribute("identifier");
+           displayName = el.getAttribute("displayName");
+           
+           log.info("Loading Metadata for " + displayName);
+
+           try {
+                       metadata = MetadataProviderFactory.loadProvider(el);
+           } catch (MetadataException ex) {
+               log.error("Could not parse " + displayName, ex);
+               throw new ShibbolethConfigurationException("Could not parse " + displayName, ex);
+           }
+       }
+       
+       /**
+        * The metadata representing this Set
+        */
+       
+       private Metadata getMetadata()
+       {
+               return metadata;
+       }
+       
+       protected String getIdentifier() {
+               return identifier;
+       }
+
+       private String getDisplayName() {
+               return displayName;
+       }
+
+       /**
+        * We do not need to look at set if it doesn't know about the given SP.  However if
+        * no SP is given (as per 1.1) then we do need to look 
+        */
+
+       private boolean containsSP(String SPName) {
+
+               //
+               // Deal with the case where we do *not* want to search by
+               // SP (also handles the 1.1 case)
+               //
+               
+               if ((SPName == null) || (SPName.length() == 0)) {
+                       return true;
+               }
+               
+               EntityDescriptor e = metadata.lookup(SPName);
+               
+               if (e == null) {
+                       return false;
+               }
+               
+               return (e.getSPSSODescriptor(XML.SAML11_PROTOCOL_ENUM) != null);
+       }
+       
+       private EntityDescriptor IdPforName(String IdPName) {
+
+               if ((IdPName == null) || (IdPName.length() == 0)) {
+                       return null;
+               }
+               
+               return metadata.lookup(IdPName);
+               
+       }
+       
+       /**
+        * Iterate over all the sitesets and if they know about the SP add them to the 
+        * list and the list of lists.    
+        * 
+        * @param siteSets  All the site sets we know about
+        * 
+        * @param SPName The SP we are looking for (null or empty matches all sitesets)
+        * 
+        * @param siteLists If not null this is is populated with a set of sets of sites
+        *  
+        * @param sites.  If not Null this is populated with a set of sites.
+        */
+       public static void getSiteLists(Collection /*<IdPSiteSet>*/ siteSets,
+                                                                       String SPName,
+                                                                       Collection /*<IdPSiteSetEntry>*/ siteLists,
+                                                                       Collection /*<IdPSite>*/ sites) {
+               //
+               // By having siteLists and sites as parameters we only iterate over 
+               // the metadata arrays once.
+               //
+               
+               Iterator /*<IdPSiteSet>*/ it = siteSets.iterator();
+               
+               while (it.hasNext()) {
+                       IdPSiteSet set = (IdPSiteSet) it.next();
+                       
+                       if (set.containsSP(SPName)) {
+                               Collection c = IdPSite.getIdPSites(set.getMetadata());
+                               
+                               if (siteLists != null) {
+                                       siteLists.add(new IdPSiteSetEntry(set.getDisplayName(),c));
+                               }
+                               
+                               if (sites != null) {
+                                       sites.addAll(c);
+                               }
+                       }
+               }
+               
+       }
+       
+
+       
+       /**
+        * Give the set of siteSets, an SP name and a searchString, look for the name in all 
+        * the appropriate siteSets. 
+        *
+        */
+
+       public static Collection /*<IdPSite>*/ seachForMatchingOrigins(Collection/*<IdPSiteSet>*/ siteSets, String SPName, String parameter, HandlerConfig config) {
+
+               Collection/*<IdPSite>*/ result = null;
+               Iterator/*<IdPSiteSet>*/ it = siteSets.iterator();
+               
+               while (it.hasNext()) {
+                       IdPSiteSet set = (IdPSiteSet) it.next();
+                       
+                       if (set.containsSP(SPName)) {
+                               Collection/*<IdPSite>*/ c = IdPSite.seachForMatchingOrigins(set.getMetadata(),parameter, config);
+                               
+                               if (result == null) {
+                                       result = c;
+                               } else {
+                                       result.addAll(c);
+                               }
+                       }
+               }
+               
+               return result;
+       }
+
+       public static IdPSite IdPforSP(List /*<IdPSiteSet>*/ siteSets, String IdPName, String SPName) {
+
+               Iterator /*<IdPSiteSet>*/ it = siteSets.iterator();
+               
+               while (it.hasNext()) {
+                       IdPSiteSet set = (IdPSiteSet) it.next();
+                       
+                       if (set.containsSP(SPName)) {
+                               EntityDescriptor e = set.IdPforName(IdPName);
+                               
+                               if (e != null) {
+                                       return new IdPSite(e);
+                               }
+                       }
+               }
+               
+               return null;
+       }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSetEntry.java b/src/edu/internet2/middleware/shibboleth/wayf/IdPSiteSetEntry.java
new file mode 100644 (file)
index 0000000..c35b466
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package edu.internet2.middleware.shibboleth.wayf;
+
+import java.util.Collection;
+/**
+ * @author Rod Widdowson
+ * 
+ * This is just a container class for tieing together a set of IdPs to a name - this being what
+ * is sent to the JSP for display purposes.
+ */
+public class IdPSiteSetEntry {
+       
+       private final String name;
+       private final Collection/*<IdPSite>*/ sites;
+       
+       public IdPSiteSetEntry(String name, Collection/*<IdPSite>*/ sites) {
+               this.name = name;
+               this.sites = sites;
+       }
+       
+       public String getName() {
+               return name;
+       }
+       
+       public Collection/*<IdPSite>*/ getSites() {
+               return sites;
+       }
+
+}
index 36a180f..ef7f0da 100644 (file)
 
 package edu.internet2.middleware.shibboleth.wayf;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Iterator;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.log4j.Logger;
 import org.bouncycastle.util.encoders.Base64;
 
 /**
@@ -37,11 +42,12 @@ import org.bouncycastle.util.encoders.Base64;
 public class SamlIdPCookie  {
 
        private static final String COOKIE_NAME = "_saml_idp";
-
+       private static final Logger log = Logger.getLogger(SamlIdPCookie.class.getName());
+       
        private final HttpServletRequest req;
        private final HttpServletResponse res;
        private final String domain;
-       private final ArrayList /*<String>*/ idPList = new ArrayList/*<String>*/();
+       private final List /*<String>*/ idPList = new ArrayList/*<String>*/();
        
        /**
         * Constructs a <code>SamlIdPCookie</code> from the provided string (which is the raw data 
@@ -59,16 +65,25 @@ public class SamlIdPCookie  {
                int start;
                int end;
                
-               if (codedData == null || codedData.equals("")) {
+               if (codedData == null || codedData.equals(""))
+               {
+                       log.info("Empty cookie");
                        return;
                }
                //
-               // Because there are spaces in the data the cookie may be returned enclosed in quotes
+               // An earlier version saved the cookie without URL encoding it, hence there may be 
+               // speaces which in turn means we maybe quoted.  Strip any quotes.
                //
                if (codedData.charAt(0) == '"' && codedData.charAt(codedData.length()-1) == '"') {
                        codedData= codedData.substring(1,codedData.length()-1);
                }
                
+               try {
+                       codedData = URLDecoder.decode(codedData, "UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       log.error("could not decode cookie");
+                       return;
+               }
                
                start = 0;
                end = codedData.indexOf(' ', start);
@@ -114,17 +129,6 @@ public class SamlIdPCookie  {
 
                writeCookie(expiration);
        }
-
-       /**
-        * Return an iterator over the list of IdPNames 
-        * @param which
-        * @return
-        */
-
-       public Iterator/*<String>*/ iterator() {
-
-               return idPList.iterator();
-       }
        
        /**
         * Delete the <b>entire<\b> cookie contents
@@ -136,6 +140,8 @@ public class SamlIdPCookie  {
                if (cookie == null) { 
                        return; 
                }
+               
+               cookie.setPath("/");
                cookie.setMaxAge(0);
                res.addCookie(cookie);
        }
@@ -161,9 +167,9 @@ public class SamlIdPCookie  {
         * @param origin
         */
        
-       public void deleteIdPName(String origin) {
+       public void deleteIdPName(String origin, int expiration) {
                idPList.remove(origin);
-               writeCookie(0);
+               writeCookie(expiration);
        }
 
        private void writeCookie(int expiration)
@@ -174,6 +180,7 @@ public class SamlIdPCookie  {
                        //
                        // Nothing to write, so delete the cookie
                        //
+                       cookie.setPath("/");
                        cookie.setMaxAge(0);
                        res.addCookie(cookie);
                        return;
@@ -192,7 +199,13 @@ public class SamlIdPCookie  {
                        buffer.append(what).append(' ');
                }
                
-               String value = buffer.toString();
+               String value;
+               try {
+                       value = URLEncoder.encode(buffer.toString(), "UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       log.error("Could not encode cookie");
+                       return;
+               }
                
                if (cookie == null) { 
                        cookie = new Cookie(COOKIE_NAME, value);
@@ -202,9 +215,9 @@ public class SamlIdPCookie  {
                cookie.setComment("Used to cache selection of a user's Shibboleth IdP");
                cookie.setPath("/");
 
-               if (expiration > 0) {
-                       cookie.setMaxAge(expiration);
-               }
+
+               cookie.setMaxAge(expiration);
+               
                if (domain != null && domain != "") {
                        cookie.setDomain(domain);
                }
@@ -212,6 +225,27 @@ public class SamlIdPCookie  {
        
        }
 
+       /**
+        * Lookup to see whether there is an IdP for the given SP 
+        */
+       
+       public List /*<IdPSite>*/ getIdPList(List /*<IdPSiteSet>*/ siteSets, String SPName)
+       {
+               
+               Iterator /*<String>*/ it = idPList.iterator();
+               List /*<IdPSite>*/ result = new ArrayList /*<IdPSite>*/(idPList.size());
+               
+               while (it.hasNext()) {
+                       String idPName = (String) it.next();
+                       IdPSite site = IdPSiteSet.IdPforSP(siteSets, idPName, SPName);
+                       if (site != null){
+                               result.add(site);
+                       }
+               }
+               return result;
+       }
+
+
        private static Cookie getCookie(HttpServletRequest req) {
                
                Cookie[] cookies = req.getCookies();
diff --git a/src/edu/internet2/middleware/shibboleth/wayf/WayfConfig.java b/src/edu/internet2/middleware/shibboleth/wayf/WayfConfig.java
deleted file mode 100755 (executable)
index c76c80d..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package edu.internet2.middleware.shibboleth.wayf;
-
-import java.util.HashSet;
-
-import org.apache.log4j.Logger;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
-
-/**
- * Class used by the WAYF service to determine runtime options Most of the fields of this class should have reasonable
- * defaults.
- * 
- * @author Walter Hoehn wassa&#064;columbia.edu
- */
-
-public class WayfConfig {
-
-       private static Logger log = Logger.getLogger(WayfConfig.class.getName());
-
-       private String logoLocation = "images/internet2.gif";
-       private String supportContact = "mailto:shib-support@internet2.org";
-       private String helpText = "In order to fulfill the request for the  web resource you "
-                       + "have just chosen, information must first be obtained from "
-                       + "your home institution. Please select the institution with " + "which you are affiliated.";
-       private String searchResultEmptyText = "No institution found that matches your search "
-                       + "criteria, please try again.";
-       private HashSet ignoredForMatch = new HashSet();
-       private int cacheExpiration;
-       private String cacheDomain;
-
-       private String getValue(Element element, String what) throws ShibbolethConfigurationException
-       {
-               NodeList list = element.getElementsByTagName(what);
-           
-           if (list.getLength() > 0) {
-               if (list.getLength() > 1) {
-                       throw new ShibbolethConfigurationException("More than one <" + what + "/> element");
-               }
-                       
-               return list.item(0).getTextContent();
-           }
-           return null;
-       }
-       
-       /**
-        * 
-        * Parse the Supplied XML element into a new WayfConfig Object
-        * 
-        */
-       
-       public WayfConfig(Element config) throws ShibbolethConfigurationException {
-
-           if (!config.getTagName().equals("WayfConfig")) { 
-
-               throw new ShibbolethConfigurationException(
-                   "Unexpected configuration data.  <WayfConfig/> is needed."); 
-           }
-
-           log.debug("Loading global configuration properties.");
-
-           String raw = config.getAttribute("cacheDomain");
-
-           if ((raw != null) && (raw != "")) {
-               setCacheDomain(raw);
-           }
-               
-           raw = config.getAttribute("cacheExpiration");
-           if ((raw != null) && (raw != "")) {
-               
-               try {
-
-                       setCacheExpiration(Integer.parseInt(raw));
-               } catch (NumberFormatException ex) {
-                       
-                       throw new ShibbolethConfigurationException("Invalid CacheExpiration value - " + raw, ex);
-               }
-           }
-
-           raw = config.getAttribute("logoLocation");
-           if ((raw != null) && (raw != "")) {
-               
-               setLogoLocation(raw);
-           }
-           
-           raw = config.getAttribute("supportContact");
-           if ((raw != null) && (raw != "")) {
-               
-               setSupportContact(raw);
-           }
-           
-           raw = getValue(config, "HelpText");
-           
-           if ((raw != null) && (raw != "")) {
-                       
-               setHelpText(raw);
-           }
-
-           raw = getValue(config, "SearchResultEmptyText");
-           
-           if ((raw != null) && (raw != "")) {
-               
-               setSearchResultEmptyText(raw);
-           }
-           
-           NodeList list = config.getElementsByTagName("SearchIgnore");
-           
-           for (int i = 0; i < list.getLength(); i++ ) {
-               
-               NodeList inner = ((Element) list.item(i)).getElementsByTagName("IgnoreText");
-               
-               for(int j = 0; j < inner.getLength(); j++) {
-                       
-                       addIgnoredForMatch(inner.item(j).getTextContent());
-               }
-           }
-
-       }
-       
-       public WayfConfig()
-       {
-               super();
-       }
-
-       public String getSearchResultEmptyText() {
-
-               return searchResultEmptyText;
-       }
-
-       public void setSearchResultEmptyText(String searchResultEmptyText) {
-
-               this.searchResultEmptyText = searchResultEmptyText;
-       }
-
-       public String getHelpText() {
-
-               return helpText;
-       }
-
-       public void setHelpText(String helpText) {
-
-               this.helpText = helpText;
-       }
-
-       public String getSupportContact() {
-
-               return supportContact;
-       }
-
-       public void setSupportContact(String supportContact) {
-
-               this.supportContact = supportContact;
-       }
-
-       public String getLogoLocation() {
-
-               return logoLocation;
-       }
-
-       public void setLogoLocation(String logoLocation) {
-
-               this.logoLocation = logoLocation;
-       }
-
-       /**
-        * Determines if a particular string token should be used for matching when a user searches for origins.
-        * 
-        * @param str
-        *            The string to lookup
-        */
-       public boolean isIgnoredForMatch(String str) {
-
-               if (ignoredForMatch.contains(str.toLowerCase())) {
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Sets the tokens that should be ignored when a user searches for an origin site.
-        * 
-        * @param s
-        *            The ignored tokens are passed as a single string, each separated by whitespace
-        */
-       public void addIgnoredForMatch(String s) {
-
-               ignoredForMatch.add(s.toLowerCase());
-       }
-
-       /**
-        * Returns the cacheDomain.
-        * 
-        * @return String
-        */
-       public String getCacheDomain() {
-
-               return cacheDomain;
-       }
-
-       /**
-        * Returns the cacheExpiration.
-        * 
-        * @return int
-        */
-       public int getCacheExpiration() {
-
-               return cacheExpiration;
-       }
-
-       /**
-        * Sets the cacheDomain.
-        * 
-        * @param cacheDomain
-        *            The cacheDomain to set
-        */
-       public void setCacheDomain(String cacheDomain) {
-
-               this.cacheDomain = cacheDomain;
-       }
-
-       /**
-        * Sets the cacheExpiration.
-        * 
-        * @param cacheExpiration
-        *            The cacheExpiration to set
-        */
-       public void setCacheExpiration(int cacheExpiration) {
-
-               this.cacheExpiration = cacheExpiration;
-       }
-
-}
index 3ebe814..aef2074 100755 (executable)
@@ -28,4 +28,5 @@ public class WayfException extends Exception {
 
                super(message);
        }
+
 }
\ No newline at end of file
index 86ff4a6..3914f99 100755 (executable)
 package edu.internet2.middleware.shibboleth.wayf;
 
 import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.Collection;
-import java.util.Date;
+import java.util.ArrayList;
+import java.util.Hashtable;
 import java.util.Iterator;
-
+import java.util.List;
 import javax.servlet.GenericServlet;
-import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletException;
-import javax.servlet.UnavailableException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.log4j.Logger;
 import org.w3c.dom.Document;
-
-import edu.internet2.middleware.shibboleth.common.ShibResource.ResourceNotAvailableException;
-import edu.internet2.middleware.shibboleth.metadata.Metadata;
-import edu.internet2.middleware.shibboleth.metadata.MetadataException;
-import edu.internet2.middleware.shibboleth.metadata.provider.XMLMetadata;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
 import edu.internet2.middleware.shibboleth.xml.Parser;
 
 /**
@@ -49,347 +44,165 @@ import edu.internet2.middleware.shibboleth.xml.Parser;
  */
 public class WayfService extends HttpServlet {
 
-    private String wayfConfigFileLocation;
-
-    private String siteConfigFileLocation;
-
-    private WayfConfig config;
-
-    private Metadata metadata;
-
-    private static Logger log = Logger.getLogger(WayfService.class.getName());
-
-    /**
-     * @see GenericServlet#init()
-     */
-    public void init() throws ServletException {
-        super.init();
-
-        log.info("Initializing WAYF.");
-        loadInitParams();
-
-        log.info("Loading configuration from file.");
-        configure();
-
-        log.info("Initializing site metadata & watchdog");
-        try {
-            metadata = new XMLMetadata(siteConfigFileLocation);
-        } catch (ResourceNotAvailableException e) {
-            log.error("Sites file watchdog could not be initialized: " + e);
-            throw new ServletException(e);
-        } catch (MetadataException e) {
-            log.error("Sites files could not be parsed" + e);
-            throw new ServletException(e);
-        }
-
-        initViewConfig();
-        log.info("WAYF initialization completed.");
-    }
-
-    /**
-     * Populates WayfConfig from file contents.
-     */
-    private void configure() throws UnavailableException {
-        try {
-            Document doc = Parser.loadDom(wayfConfigFileLocation, true);
-            config = new WayfConfig(doc.getDocumentElement());
-        } catch (IOException e) {
-            log.fatal("Error Loading WAYF configuration file.", e);
-            throw new UnavailableException("Error parsing WAYF configuration file.");
-        } catch (Exception e) {
-            // All other exceptions are from the parsing
-            log.fatal("Error parsing WAYF configuration file.", e);
-            throw new UnavailableException("Error parsing WAYF configuration file.");
-        }
-    }
-
-    /**
-     * Setup application-wide beans for view
-     */
-    private void initViewConfig() {
-
-        getServletContext().setAttribute("supportContact", config.getSupportContact());
-        getServletContext().setAttribute("helpText", config.getHelpText());
-        getServletContext().setAttribute("searchResultEmptyText", config.getSearchResultEmptyText());
-        getServletContext().setAttribute("logoLocation", config.getLogoLocation());
-    }
-
-    /**
-     * Reads parameters from web.xml <init-param /> construct.
-     */
-    private void loadInitParams() {
-
-        wayfConfigFileLocation = getServletContext().getInitParameter("WAYFConfigFileLocation");
-        if (wayfConfigFileLocation == null) {
-            log.info("No WAYFConfigFileLocation paramter found in servlet context, checking in servlet config");
-            wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
-            if (wayfConfigFileLocation == null) {
-                log.warn("No WAYFConfigFileLocation parameter found... using default location.");
-                wayfConfigFileLocation = "/conf/wayfconfig.xml";
-            }
-        }
-
-        siteConfigFileLocation = getServletContext().getInitParameter("SiteConfigFileLocation");
-        if (siteConfigFileLocation == null) {
-            log.info("No SiteConfigFileLocation paramter found in servlet context, checking in servlet config");
-            siteConfigFileLocation = getServletConfig().getInitParameter("SiteConfigFileLocation");
-            if (siteConfigFileLocation == null) {
-                log.warn("No SiteonfigFileLocation parameter found... using default location.");
-                siteConfigFileLocation = "/conf/metadata.xml";
-            }
-        }
-    }
-
-    /**
-     * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
-     */
-    public void doGet(HttpServletRequest req, HttpServletResponse res) {
-
-        log.info("Handling WAYF request.");
-        // Tell the browser not to cache the WAYF page
-        res.setHeader("Cache-Control", "no-cache");
-        res.setHeader("Pragma", "no-cache");
-        res.setDateHeader("Expires", 0);
-
-        // Decide how to route the request based on query string
-        String requestType = req.getParameter("action");
-        if (requestType == null) {
-            requestType = "lookup";
-        }
-        try {
-            if (requestType.equals("deleteFromCache")) {
-                log.debug("Deleting saved HS from cache");
-                SamlIdPCookie.deleteCookie(req, res);
-                handleLookup(req, res);
-                return;
-            }
-
-            SamlIdPCookie cookie;
-            if (req.getParameter("nolookup") == null) {
-                cookie = SamlIdPCookie.getIdPCookie(req, res, config.getCacheDomain());
-            } else {
-                // For the test case, do not do a cache lookup, start as empty
-                cookie = new SamlIdPCookie(req, res, config.getCacheDomain());
-            }
-
-            if (requestType.equals("search")) {
-                handleSearch(req, res);
-            } else if (requestType.equals("selection")) {
-                String origin = req.getParameter("origin");
-                log.debug("Processing handle selection: " + origin);
-                if (origin == null) {
-                    handleLookup(req, res);
-                } else {
-                    if ((req.getParameter("cache") != null)) {
-                        if (req.getParameter("cache").equalsIgnoreCase("session")) {
-                            cookie.addIdPName(origin, 0);
-                        } else if (req.getParameter("cache").equalsIgnoreCase("perm")) {
-                            cookie.addIdPName(origin, config.getCacheExpiration());
-                        }
-                    }
-                    redirectToIdP(req, res, origin, cookie);
-                }
-            } else {
-                // Try for a cache hit
-                String idPName = null;
-                Iterator it = cookie.iterator();
-
-                //
-                // The cached data may contain several IdPs, some of which we do
-                // not know about
-                // so iterate down until we find one we do know about
-                //  
-                while (it.hasNext()) {
-                    idPName = (String) it.next();
-                    if (metadata.lookup(idPName) != null) {
-                        break;
-                    }
-                }
-
-                if (idPName != null) {
-                    //
-                    // move the name to the head of the list, preserving the
-                    // cache expiration
-                    //
-                    cookie.addIdPName(idPName, 0);
-                    redirectToIdP(req, res, idPName, cookie);
-                } else {
-                    handleLookup(req, res);
-                }
-            }
-        } catch (WayfException we) {
-            handleError(req, res, we);
-        }
-    }
-
-    /**
-     * Displays a WAYF selection page.
-     */
-    private void handleLookup(HttpServletRequest req, HttpServletResponse res) throws WayfException {
-
-        try {
-            if ((getSHIRE(req) == null) || (getTarget(req) == null)) {
-                throw new WayfException("Invalid or missing data from SHIRE");
-            }
-
-            req.setAttribute("sites", IdPSite.getIdPSites(metadata));
-            req.setAttribute("shire", getSHIRE(req));
-            req.setAttribute("target", getTarget(req));
-            String providerId = getProviderId(req);
-            if (providerId != null) {
-                req.setAttribute("providerId", providerId);
-            }
-
-            req.setAttribute("time", new Long(new Date().getTime() / 1000).toString()); // Unix
-            // Time
-            req.setAttribute("requestURL", req.getRequestURI().toString());
-
-            log.debug("Displaying WAYF selection page.");
-            RequestDispatcher rd = req.getRequestDispatcher("/wayf.jsp");
-
-            rd.forward(req, res);
-        } catch (IOException ioe) {
-            throw new WayfException("Problem displaying WAYF UI." + ioe.toString());
-        } catch (ServletException se) {
-            throw new WayfException("Problem displaying WAYF UI." + se.toString());
-        }
-    }
-
-    /**
-     * Looks for origin sites that match search terms supplied by the user
-     */
-    private void handleSearch(HttpServletRequest req, HttpServletResponse res) throws WayfException {
-
-        String parameter = req.getParameter("string");
-        if (parameter != null) {
-            Collection sites = IdPSite.seachForMatchingOrigins(metadata, parameter, config);
-            if (sites.size() != 0) {
-                req.setAttribute("searchresults", sites);
-            } else {
-                req.setAttribute("searchResultsEmpty", "true");
-            }
-        }
-        handleLookup(req, res);
-    }
-
-    /**
-     * Registers a user's HS selection and forwards appropriately
-     */
-    private void redirectToIdP(HttpServletRequest req, HttpServletResponse res, String idPName, SamlIdPCookie cookie)
-            throws WayfException {
-        String idPSSOEndPoint = null;
-        try {
-            //
-            // If we have had a refresh between then and now the following will
-            // fail
-            //
-            idPSSOEndPoint = metadata.lookup(idPName).getIDPSSODescriptor(
-                    edu.internet2.middleware.shibboleth.common.XML.SHIB_NS).getSingleSignOnServiceManager()
-                    .getDefaultEndpoint().getLocation();
-        } catch (Exception ex) {
-            //
-            // remove this entry (only) from the cache
-            //
-            cookie.deleteIdPName(idPName);
-            log.error("Error dispatching to IdP: ", ex);
-        }
-
-        if (idPSSOEndPoint != null) {
-            log.info("Redirecting to SSO at selected IdP: " + idPSSOEndPoint);
-            try {
-                StringBuffer buffer = new StringBuffer(idPSSOEndPoint).append("?target=");
-                buffer.append(URLEncoder.encode(getTarget(req), "UTF-8")).append("&shire=");
-                buffer.append(URLEncoder.encode(getSHIRE(req), "UTF-8"));
-                String providerId = getProviderId(req);
-                log.debug("WALTER: (" + providerId + ").");
-                if (providerId != null) {
-                    buffer.append("&providerId=").append(URLEncoder.encode(getProviderId(req), "UTF-8"));
-                }
-                buffer.append("&time=").append(new Long(new Date().getTime() / 1000).toString()); // Unix
-                // Time
-                res.sendRedirect(buffer.toString());
-            } catch (IOException ioe) {
-                //
-                // remove this entry (only) from the cache
-                //
-                cookie.deleteIdPName(idPName);
-                throw new WayfException("Error forwarding to IdP SSO endpoint: " + ioe.toString());
-            }
-        } else {
-            //
-            // We
-            handleLookup(req, res);
-        }
-    }
-
-    /**
-     * Handles all "recoverable" errors in WAYF processing by logging the error
-     * and forwarding the user to an appropriate error page.
-     * 
-     * @param we The WayfException respective to the error being handled
-     */
-    private void handleError(HttpServletRequest req, HttpServletResponse res, WayfException we) {
-
-        log.error("WAYF Failure: " + we.toString());
-        log.debug("Displaying WAYF error page.");
-        req.setAttribute("errorText", we.toString());
-        req.setAttribute("requestURL", req.getRequestURI().toString());
-        RequestDispatcher rd = req.getRequestDispatcher("/wayferror.jsp");
-
-        try {
-            rd.forward(req, res);
-        } catch (IOException ioe) {
-            log.error("Problem trying to display WAYF error page: " + ioe.toString());
-        } catch (ServletException se) {
-            log.error("Problem trying to display WAYF error page: " + se.toString());
-        }
-    }
-
-    /**
-     * Retrieves the SHIRE from the request.
-     * 
-     * @throws WayfException If the request does not contain a shire parameter.
-     */
-    private String getSHIRE(HttpServletRequest req) throws WayfException {
-
-        String shire = (String) req.getAttribute("shire");
-        if (req.getParameter("shire") != null) {
-            shire = req.getParameter("shire");
-        }
-        if (shire == null) {
-            throw new WayfException("Invalid data from SHIRE: No acceptance URL received.");
-        }
-        return shire;
-    }
-
-    /**
-     * Retrieves the user's target URL from the request.
-     * 
-     * @throws WayfException If the request does not contain a target parameter
-     */
-    private String getTarget(HttpServletRequest req) throws WayfException {
-
-        String target = (String) req.getAttribute("target");
-        if (req.getParameter("target") != null) {
-            target = req.getParameter("target");
-        }
-        if (target == null) {
-            throw new WayfException("Invalid data from SHIRE: No target URL received.");
-        }
-        return target;
-    }
-
-    private String getProviderId(HttpServletRequest req) {
-
-        if (req.getParameter("providerId") != null && !(req.getParameter("providerId").length() == 0)) {
-            return req.getParameter("providerId");
-
-        } else {
-            String attr = (String) req.getAttribute("providerId");
-            if (attr == null || attr.length() == 0) {
-                return null;
-            }
-            return attr;
-        }
-    }
+       private String wayfConfigFileLocation;
+       private static final Logger log = Logger.getLogger(WayfService.class.getName());
+       
+       private List /*<DiscoveryServiceHandler>*/ discoveryServices = new ArrayList /*<DiscoveryServiceHandler>*/();
+       
+       /**
+        * @see GenericServlet#init()
+        */
+       public void init() throws ServletException {
+
+               super.init();
+
+               wayfConfigFileLocation = getServletContext().getInitParameter("WAYFConfigFileLocation");
+               if (wayfConfigFileLocation == null) {
+                       wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
+               }
+               if (wayfConfigFileLocation == null) {
+                       wayfConfigFileLocation = "/conf/wayfconfig.xml";
+               }
+
+               try {
+                       
+                       Document doc = Parser.loadDom(wayfConfigFileLocation, true);
+                       
+                       NodeList itemElements = doc.getDocumentElement().getElementsByTagNameNS(HandlerConfig.configNameSpace, "Default");
+                       
+                       HandlerConfig defaultHandlerConfig;
+                       
+                       if (itemElements.getLength() == 1) {
+                               
+                               Element element = (Element) itemElements.item(0);
+                               String attribute = element.getAttribute("location");
+                               
+                               if (attribute != null && !attribute.equals("")) {
+                                       
+                                       log.error("<Default> element cannot contain a location attribute");
+                                       throw new ShibbolethConfigurationException("<Default> element cannot contain a location attribute");
+                                       
+                               }
+
+                               attribute = element.getAttribute("default");
+                               
+                               if (attribute != null && !attribute.equals("")) {
+
+                                       log.error("<Default> element cannot contain a default attribute");
+                                       throw new ShibbolethConfigurationException("<Default> element cannot contain a default attribute");
+                                       
+                               }
+
+                               itemElements = element.getElementsByTagName("Federation");
+                               
+                               if (itemElements.getLength() != 0) {
+                                       
+                                       log.error("<Default> element cannot contain <Federation> elements");
+                                       throw new ShibbolethConfigurationException("<Default> element cannot contain <Federation> elements");
+
+                               }
+                                                       
+                               defaultHandlerConfig = new HandlerConfig(element, new HandlerConfig());
+                       
+                       } else if (itemElements.getLength() == 0) {
+
+                               defaultHandlerConfig = new HandlerConfig();
+                       
+                       } else {
+                               log.error("Must specify exactly one <Default> element");
+                               throw new ShibbolethConfigurationException("Must specify exactly one <Default> element");
+                       }
+                                               
+                       //
+                       // Load metadata
+                       //
+                       Hashtable /*<String, IdPSiteSet>*/ siteSets = new Hashtable /*<String, IdPSiteSet>*/();
+
+                       itemElements = doc.getDocumentElement().getElementsByTagNameNS(HandlerConfig.configNameSpace,
+                                       "MetadataProvider");
+                       
+                       for (int i = 0; i < itemElements.getLength(); i++) {
+                               
+                               Element element = (Element) itemElements.item(i);
+                               
+                               IdPSiteSet siteset = new IdPSiteSet(element);
+                               
+                               siteSets.put(siteset.getIdentifier(), siteset);
+                       }
+                       if (siteSets.size() < 1) {
+                               log.error("No Metadata Provider metadata loaded.");
+                               throw new ShibbolethConfigurationException("Could not load SAML metadata.");
+                       }
+                       
+                       //
+                       // Load service handlers
+                       //
+                       itemElements = doc.getDocumentElement().getElementsByTagNameNS(HandlerConfig.configNameSpace,
+                                       "DiscoveryServiceHandler");
+                       
+                       for (int i = 0; i < itemElements.getLength(); i++) {
+                               
+                               discoveryServices.add(new DiscoveryServiceHandler((Element)itemElements.item(i), siteSets, defaultHandlerConfig));
+
+                       }
+                       //if ()
+
+               } catch (IOException e) {
+                       if (log != null) {
+                               log.fatal("Error Loading WAYF configuration file.", e);
+                       }
+                       throw new ServletException("Error Loading WAYF configuration file.", e);
+               } catch (Exception e) {
+                       //
+                       // All other exceptions are from the parsing
+                       //
+                       if (log != null) {
+                               log.fatal("Error parsing WAYF configuration file.", e);
+                       }
+                       throw new ServletException("Error parsing WAYF configuration file.", e);
+               }
+               
+               log.info("WAYF initialization completed.");
+       }
+
+       /**
+        * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
+        */
+       public void doGet(HttpServletRequest req, HttpServletResponse res) {
+
+               
+               log.info("Handling WAYF request.");
+               // Tell the browser not to cache the WAYF page
+               res.setHeader("Cache-Control", "no-cache");
+               res.setHeader("Pragma", "no-cache");
+               res.setDateHeader("Expires", 0);
+
+               DiscoveryServiceHandler serviceHandler = lookupServiceHandler(req); 
+               
+               serviceHandler.doGet(req, res);
+               
+       }
+
+       private DiscoveryServiceHandler lookupServiceHandler(HttpServletRequest req) {
+
+               Iterator/*<DiscoveryServiceHandler>*/ it = discoveryServices.iterator();
+               String requestURL = req.getRequestURL().toString(); 
+               DiscoveryServiceHandler defaultHandler = null;
+               
+               while (it.hasNext()) {
+                       DiscoveryServiceHandler handler = (DiscoveryServiceHandler) it.next();
+                       
+                       if (requestURL.matches(handler.getLocation())) {
+                               return handler;
+                       }
+                       if (defaultHandler == null || handler.isDefault()) {
+                               defaultHandler = handler;
+                       }
+               }
+               log.warn("Could not find Discovery service Handler for " + requestURL);
+               return defaultHandler;
+       }
+
+
+       
 }
index 1d39297..5423a81 100644 (file)
@@ -88,7 +88,7 @@ public class SchemasResourceListImpl extends SchemaStore {
                 continue;
             }
             String targetNamespace = ele.getAttribute("targetNamespace");
-            if (targetNamespace==null) {
+            if (targetNamespace==null || targetNamespace == "") {
                 log.error("Schema has no targetNamespace: "+resourceName);
                 continue;
             }
index 1bebc42..9ea64ed 100755 (executable)
@@ -1,26 +1,68 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Schema for the Shibboleth WAYF Service configuration file - Walter Hoehn - 06/14/2002 -->
 <xs:schema 
-       targetNamespace="urn:mace:shibboleth:wayf:config:1.0"
-       xmlns:xs="http://www.w3.org/2001/XMLSchema" 
-       elementFormDefault="qualified">
-        <xs:element name="WayfConfig" >
-                <xs:complexType>             
-                       <xs:sequence>
-                                <xs:element name="HelpText" type="xs:string" minOccurs="0" maxOccurs="1"/>
-                                <xs:element name="SearchResultEmptyText" type="xs:string" minOccurs="0" maxOccurs="1"/>
-                                <xs:element name="SearchIgnore" minOccurs="0" maxOccurs="1">
-                                               <xs:complexType>
-                                                                                       <xs:sequence>
-                                                                                               <xs:element name="IgnoreText" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
-                                                                                       </xs:sequence>
-                                                                               </xs:complexType>
-                                       </xs:element>
+    targetNamespace="urn:mace:shibboleth:wayf:config:1.0"
+    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+    xmlns:wc="urn:mace:shibboleth:wayf:config:1.0"
+    elementFormDefault="qualified">
+
+    <xs:simpleType name="handleCookieType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="AlwaysFollow"/>
+            <xs:enumeration value="FollowSingle"/>
+            <xs:enumeration value="NeverFollow"/>
+            <xs:enumeration value="ClearCookie"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="configValues">
+        <xs:sequence>
+            <xs:element name="SearchIgnore" minOccurs="0" maxOccurs="1">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element name="IgnoreText" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
                     </xs:sequence>
-                    <xs:attribute name="supportContact" type="xs:string" use="optional"/>
-                    <xs:attribute name="logoLocation" type="xs:string" use="optional"/>
-                    <xs:attribute name="cacheExpiration" type="xs:string" use="optional"/>
-                    <xs:attribute name="cacheDomain" type="xs:string" use="optional"/>
                 </xs:complexType>
-        </xs:element>
+            </xs:element>
+             <xs:element name="Federation" minOccurs="0" maxOccurs="unbounded">
+               <xs:complexType>
+                  <xs:attribute name="identifier" use="required"/>
+               </xs:complexType>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="location" type="xs:string" use="optional" />
+        <xs:attribute name="default" type="xs:boolean" use="optional" />
+        <xs:attribute name="jspFile" type="xs:string" use="optional" />
+        <xs:attribute name="errorJspFile" type="xs:string" use="optional" />
+        <xs:attribute name="provideListOfList" type="xs:boolean" use="optional" />
+        <xs:attribute name="provideList" type="xs:boolean" use="optional" />
+        <xs:attribute name="handleCookie" type="wc:handleCookieType" use="optional" />
+        <xs:attribute name="showUnusableIdPs" type="xs:boolean" use="optional" />
+        <xs:attribute name="cacheDomain" type="xs:string" use="optional"/>
+        <xs:attribute name="cacheExpiration" type="xs:string" use="optional" />
+    </xs:complexType>
+
+    <xs:element name="WayfConfig" >
+        <xs:complexType>         
+            <xs:sequence>
+                <xs:element name="Default" type="wc:configValues" minOccurs="0" maxOccurs="1" /> 
+                            
+                <xs:element name="MetadataProvider"  minOccurs="0" maxOccurs="unbounded">
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
+                        </xs:sequence>
+                        <xs:attribute name="type" type="xs:string" use="required" />
+                        <xs:attribute name="displayName" type="xs:string" use="required" />
+                        <xs:attribute name="identifier" type="xs:string" use="required" />
+                        <xs:attribute name="uri" type="xs:string" use="required" />
+                        <xs:anyAttribute namespace="##other" processContents="lax" />
+                    </xs:complexType>
+                </xs:element>
+
+                <xs:element name="DiscoveryServiceHandler" type="wc:configValues"  minOccurs="1" maxOccurs="unbounded" />
+
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
 </xs:schema>
index d97a746..93f714b 100755 (executable)
                <display-name>Shibboleth WAYF Service</display-name>
                <servlet-class>edu.internet2.middleware.shibboleth.wayf.WayfService</servlet-class>
                <init-param>
-                       <param-name>SiteConfigFileLocation</param-name>
-                       <param-value>/conf/sites.xml</param-value>
+                       <param-name>WAYFConfigFileLocation</param-name>
+                       <param-value>/conf/wayfconfig.xml</param-value>
                </init-param>
        </servlet>
 
+<!-- We specify two mappings - old style http:/host/shibboleth-wayf/WAYF/ and the new style
+     whereby the precise name influences the behavior.  See the configuration file for 
+     examples -->
+
        <servlet-mapping>
                <servlet-name>WAYF</servlet-name>
                <url-pattern>/WAYF</url-pattern>
        </servlet-mapping>
 
+       <servlet-mapping>
+               <servlet-name>WAYF</servlet-name>
+               <url-pattern>*.wayf</url-pattern>
+       </servlet-mapping>
+
        <mime-mapping>
                <extension>css</extension>
                <mime-type>text/css</mime-type>
index 2c2f0d6..6fed076 100755 (executable)
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
-       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html>
+
 <%@ taglib uri="/WEB-INF/tlds/struts-logic.tld" prefix="logic" %>
-       <%@ taglib uri="/WEB-INF/tlds/struts-bean.tld" prefix="bean" %>
-       <jsp:useBean id="requestURL" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="helpText" scope="application" class="java.lang.String"/>
-       <jsp:useBean id="supportContact" scope="application" class="java.lang.String"/>
-       <jsp:useBean id="shire" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="target" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="providerId" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="time" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="searchResultEmptyText" scope="application" class="java.lang.String"/>
-       <jsp:useBean id="logoLocation" scope="application" class="java.lang.String"/>
+<%@ taglib uri="/WEB-INF/tlds/struts-bean.tld" prefix="bean" %>
+
+<logic:present name="showComments" scope="Request">
+
+<!-- TO CONFIGURE THIS FOR A SPECIFIC SITE
+     =====================================
+
+     Before you deploy this jsp you need to look for CONFIG below.
+     These mark places where you should make changes. 
+
+     If you want to make more profound changes but only to the jsp,
+     you should read the sections marked PROGRAMMING NOTE below.-->
+
+<!-- PROGRAMMING NOTE
+
+     "requestURL" contains the URL that was specified to get the
+     WAYF going.  The jsp uses it mostly for submitting result back to
+     the WAYF and error reporting -->
+
+</logic:present>
+
+    <jsp:useBean id="requestURL" scope="request" class="java.lang.String"/>
+
+<logic:present name="showComments" scope="Request">
+
+<!-- PROGRAMMING NOTE
+
+     shire, target, provider and time are all part of the Shibboleth
+     protocol and need to be specified as parameters to the WAYF. -->
+
+</logic:present>
+
+
+    <jsp:useBean id="shire" scope="request" class="java.lang.String"/>
+    <jsp:useBean id="target" scope="request" class="java.lang.String"/>
+    <jsp:useBean id="providerId" scope="request" class="java.lang.String"/>
+    <jsp:useBean id="time" scope="request" class="java.lang.String"/>
+
+<logic:present name="showComments" scope="Request">
+<!-- PROGRAMMING NOTE
+
+     In addition to the above.  The WAYF also supplies the following to
+     the jsp.
+
+     "cookieList" If this exists it represents the contents of the
+         _saml_idp cookie (possibly filtered to remove IdPs which
+         cannot serve the SP).  It is a Collection of IdPSite objects,
+         which themselves have the following properties:
+
+       "name" The uri for the IdP, which needs to be returned to the
+              WAYF in the "origin" parameter.
+
+       "displayName" User friendly name (taken from its alias)
+
+       "addressFor" The (ungarnished) URL for the IdP. This could be
+              used to create a direct hyperlink to the IdP
+
+     "sites" If this exists it contains all the possible IdPs for for
+         the SP (possibly filtered).  It is a Collection of IdPSite
+         Objects which are described above.  This is only present if
+         provideList was defined true in the configuration.
+
+     "siteLists" If this exists it contains all the possible metadata
+         files which can service for the SP (possibly filtered).  It
+         is a collection of IdPSiteSetEntry Objects which have two
+         properties:
+
+         "name" This is the displayName from the Metadata element in
+            the WAYF configuration file
+
+         "sites" This represents the IdPs.  Again it is a collection
+            of IdPSite Objects
+
+         It is only present if provideListOfList was defined true in
+         the configuration.
+
+     "singleSiteList" if this is present, then there is only one
+         IdPSiteSetEntry Object in "siteLists".
+
+     "searchresultempty" If this is present then it means that a
+         search was performed, but no suitable IdPs were returned.
+
+     "searchresults" If this is present it represents the list of IdPs
+         which matched a previous search.  It is a Collection of
+         IdPSite Objects. -->
+
+<!-- PROGRAMMING NOTE
+
+     The jsp communicates back to the WAYF via the parameters listed
+     above, and:
+
+        "action" what the WAYF has to do.  Possible contents are:
+
+            "lookup" - refresh the screen.
+            "search" - perform a search on the contents parameter "string"
+            "selection" - redirect to the IdP with the uri "origin"
+
+        "cache" preserve any selection in the _saml_idp cookie. A
+            value of "session" makes the cookie last for the browser
+            session, "perm" gives it the lifetime specified in the
+            configuration file.  -->
+
+</logic:present>
+
 <head>
-       <link rel="stylesheet" title="normal" type="text/css" href="wayf.css" />
-       <title>InQueue: Identity Provider Selection</title>
-</head>
+    <link rel="stylesheet" title="normal" type="text/css"
+    href="wayf.css" /> <title>Identity Provider Selection</title>
+    </head>
 
 <body>
     <div class="head">
-       <h1>Select an identity provider</h1>
+        <h1>
+
+Select an identity provider
+
+        </h1>
     </div>
 
     <div class="selector">
-       <p class="text"><bean:write name="helpText" /></p>
-       <div class="list">
-
-           <h2>Choose from a list:</h2>
-
-           <form method="get" action="<bean:write name="requestURL" />">
-               <p>
-                   <input type="hidden" name="shire" value="<bean:write name="shire" />" />
-                   <input type="hidden" name="target" value="<bean:write name="target" />" />
-                   <logic:present name="providerId" scope="request">
-                           <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
-                   </logic:present>
-                   <logic:present name="time" scope="request">
-                               <input type="hidden" name="time" value="<bean:write name="time" />" />
-                   </logic:present>
-                   <input type="hidden" name="action" value="selection" />
-                   <select name="origin">      
-                       <logic:iterate id="site" name="sites">
-                           <option value="<jsp:getProperty name="site" property="name" />">
-                                       <jsp:getProperty name="site" property="displayName" />
-                           </option>
-                       </logic:iterate>
-                   </select>
-                   <input type="submit" value="Select" />
-                   <select name="cache">
-                       <option value="false"> Do not remember
-                       <option value="session" selected> Remember for session
-                       <option value="perm"> Remember for a week
-                   </select>
-               </p>
-           </form>
-
-           <div class="search">
-               <span class="option">or</span>
-
-               <h2>Search by keyword:</h2>
-
-               <form method="get" action="<bean:write name="requestURL" />">
-                   <p>
-                       <input type="hidden" name="shire" value="<bean:write name="shire" />" />
-                       <input type="hidden" name="target" value="<bean:write name="target" />" />
-                       <logic:present name="providerId" scope="request">
-                           <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
-                       </logic:present>
-                       <logic:present name="time" scope="request">
-                           <input type="hidden" name="time" value="<bean:write name="time" />" />
-                       </logic:present>
-                       <input type="hidden" name="action" value="search" />
-                       <input type="text" name="string" />
-                       <input type="submit" value="Search" />
-                   </p>
-               </form>
-
-               <logic:present name="searchResultsEmpty" scope="request">
-                   <p class="error"><bean:write name="searchResultEmptyText" /></p>
-               </logic:present>
-
-               <logic:present name="searchresults" scope="request">
-                   <h3>Search results:</h3>
-                   <form method="get" action="<bean:write name="requestURL" />">
-                       <ul>
-                           <logic:iterate id="currResult" name="searchresults">
-                               <li>
-                                   <input type="radio" name="origin" value="<jsp:getProperty name="currResult" property="name" />" />
-                                   <jsp:getProperty name="currResult" property="displayName" />
-                               </li>
-                           </logic:iterate>
-                       </ul>
-                       <p>
-                           <input type="hidden" name="shire" value="<bean:write name="shire" />" />
-                           <input type="hidden" name="target" value="<bean:write name="target" />" />
-                           <logic:present name="providerId" scope="request">
-                                       <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
-                           </logic:present>
-                           <logic:present name="time" scope="request">
-                                       <input type="hidden" name="time" value="<bean:write name="time" />" />
-                           </logic:present>
-                           <input type="hidden" name="action" value="selection" />
-                           <input type="submit" value="Select" />
-                           <select name="cache">
-                               <option value="false"> Do not remember
-                               <option value="session" selected> Remember for session
-                               <option value="perm"> Remember for a week
-                           </select>
-                       </p>
-                   </form>     
-               </logic:present>
-
-           </div>
-       </div>
-
-       <div class="footer">
-           <p class="text">Need assistance? Send mail to <a href="mailto:inqueue-support@internet2.edu">inqueue-support@internet2.edu</a> with description.</p>
-           <div class="logo"><img src="./images/internet2.gif" alt="InQueue" /></div>
-       </div>
-                       
+    <p class="text">
+
+<!--CONFIG-->
+
+The Service you are trying to reach requires that you
+authenticate with your home institution, please select it from the
+list below.
+
+    </p>
+    <logic:present name="cookieList" scope="request">
+
+        <h2>
+
+Recently used institutions:
+
+        </h2>   
+
+<logic:present name="showComments" scope="Request">
+
+<!-- PROGRAMMING NOTE
+     Generate a hyperlink back to the WAYF.  Note that we are
+     simulating the user having specified a permanent cookie -->
+
+</logic:present>
+
+        <logic:iterate id="site" name="cookieList">
+            <p  class="text">
+                <a href="<bean:write name="requestURL" />?target=<bean:write name="target" />&shire=<bean:write name="shire" />&providerId=<bean:write name="providerId" />&time=value=<bean:write name="time" />&cache=perm&action=selection&origin=<jsp:getProperty name="site" property="name" />">
+                    <jsp:getProperty name="site"
+                    property="displayName" />
+                </a>
+            </p>
+        </logic:iterate>
+
+<logic:present name="showComments" scope="Request">
+
+<!-- PROGRAMMING NOTE
+
+     We defined the ClearCache.Wayf service in wayfconfig.  So we know
+     it is here.  This will empty the cookie and loop -->
+
+</logic:present>
+
+        <form method="get" action="ClearCache.wayf" />
+            <input type="hidden" name="shire" value="<bean:write name="shire" />" />
+            <input type="hidden" name="target" value="<bean:write name="target" />" />
+            <logic:present name="providerId" scope="request">
+               <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
+            </logic:present>
+            <logic:present name="time" scope="request">
+                <input type="hidden" name="time" value="<bean:write name="time" />" />
+            </logic:present>
+            <input type="submit" value="Clear" />
+        </form>
+
+    </logic:present>
+
+    <div class="list">
+
+        <h2>
+
+Choose from a list:
+
+        </h2>
+
+        <logic:present name="sites" scope="request">
+        <logic:notPresent name="siteLists" scope="request">
+
+            <form method="get" action="<bean:write name="requestURL" />">
+                    <input type="hidden" name="shire" value="<bean:write name="shire" />" />
+                    <input type="hidden" name="target" value="<bean:write name="target" />" />
+                    <logic:present name="providerId" scope="request">
+                        <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
+                    </logic:present>
+                    <logic:present name="time" scope="request">
+                        <input type="hidden" name="time" value="<bean:write name="time" />" />
+                    </logic:present>
+                    <input type="hidden" name="action" value="selection" />
+                    <select name="origin">      
+                        <logic:iterate id="site" name="sites">
+                            <option value="<jsp:getProperty name="site" property="name" />">
+                                <jsp:getProperty name="site" property="displayName" />
+                            </option>
+                        </logic:iterate>
+                    </select>
+                    <input type="submit" value="Select" />
+                    <select name="cache">
+                        <option value="false"> Do not remember
+                        <option value="session" selected> Remember for session
+                        <option value="perm"> Remember for a week
+                    </select>
+            </form>
+        </logic:notPresent>
+        </logic:present>
+
+<logic:present name="showComments" scope="Request">
+
+<!-- PROGRAMMING NOTE
+     Build two tables side by side, one with the Federation names and 'ALL' (if apposite)
+     and the other will be dynamically populated with the members of that federation.
+
+     This needs to work in the face of no javascript, so we initially populate the 
+     Right hand list with all the IdPs.  The first Selection in the Left hand Table will
+     shrink this list
+
+     The 'lists of all IdPs' is derived from the one which java gives us (if it did)
+     otherwise it is derived by a double iteration through the List of Lists.  This
+     makes for complicated looking code, but it's dead simple really.
+
+ -->
+
+</logic:present>
+
+        <logic:present name="siteLists" scope="request">
+          <form method="get" action="<bean:write name="requestURL" />">
+            <input type="hidden" name="shire" value="<bean:write name="shire" />" />
+            <input type="hidden" name="target" value="<bean:write name="target" />" />
+
+            <logic:present name="providerId" scope="request">
+              <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
+            </logic:present>
+
+            <logic:present name="time" scope="request">
+              <input type="hidden" name="time" value="<bean:write name="time" />" />
+            </logic:present>
+
+            <table name="tab">
+               <th>Federation </th>
+               <th>Institution</th>
+               <tr>
+                 <td><select name="FedSelector" size="10" id="FedSelect" 
+                             onChange="changedFed(this.form.origin,
+                                                  this.form.FedSelector[this.form.FedSelector.selectedIndex].value);">
+                   <logic:iterate id="siteset" name="siteLists">
+                     <logic:present name="singleSiteList" scope="request">
+
+                       <!-- Only One site so select it -->
+
+                       <option value="<jsp:getProperty name="siteset" property="name"/>" SELECTED>
+                         <jsp:getProperty name="siteset" property="name"/>
+                       </option>
+                     </logic:present>
+                     <logic:notPresent name="singleSiteList" scope="request">
+                       <option value="<jsp:getProperty name="siteset" property="name"/>">
+                         <jsp:getProperty name="siteset" property="name"/>
+                       </option>
+                     </logic:notPresent>
+                   </logic:iterate>
+
+                   <logic:notPresent name="singleSiteList" scope="request">
+
+                     <!-- More than one site so select the 'All' -->
+
+                     <option value="ALL" selected>
+                         All Sites
+                     </option>
+                   </logic:notPresent>
+                </select></td>
+
+                 <td>
+                   <input type="hidden" name="action" value="selection" />
+                   <select name="origin" size="10" id="originIdp"> 
+                     <logic:present name="sites" scope="request">
+                       <logic:iterate id="site" name="sites">
+                         <option value="<jsp:getProperty name="site" property="name" />">
+                           <jsp:getProperty name="site" property="displayName" />
+                         </option>
+                       </logic:iterate>
+                     </logic:present>
+
+                     <logic:notPresent name="sites" scope="request">
+                       <logic:iterate id="siteset" name="siteLists">
+                         <logic:iterate id="site" name="siteset" property="sites">
+                           <option value="<jsp:getProperty name="site" property="name" />">
+                             <jsp:getProperty name="site" property="displayName" />
+                           </option>
+                         </logic:iterate>
+                       </logic:iterate>        
+                     </logic:notPresent>
+                   </select>
+                   
+                 </td>
+               </tr>
+             </table>
+             <p>
+               <input type="submit" value="Select" />
+               <select name="cache">
+                 <option value="false"> Do not remember
+                 <option value="session" selected> Remember for session
+                 <option value="perm"> Remember for a week
+               </select>
+             </p>
+           </form>
+        </logic:present>
+        </div>
+        <div class="search">
+            <span class="option">or</span>
+
+            <h2>
+
+Search by keyword:
+
+            </h2>
+
+            <form method="get" action="<bean:write name="requestURL" />">
+                <p>
+                    <input type="hidden" name="shire" value="<bean:write name="shire" />" />
+                    <input type="hidden" name="target" value="<bean:write name="target" />" />
+                    <logic:present name="providerId" scope="request">
+                        <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
+                    </logic:present>
+                    <logic:present name="time" scope="request">
+                        <input type="hidden" name="time" value="<bean:write name="time" />" />
+                    </logic:present>
+                    <input type="hidden" name="action" value="search" />
+                    <input type="text" name="string" />
+                    <input type="submit" value="Search" />
+                </p>
+            </form>
+
+            <logic:present name="searchResultsEmpty" scope="request">
+                <p class="error">
+
+No provider was found that matches your search criteria, please try again.
+
+                </p>
+            </logic:present>
+
+            <logic:present name="searchresults" scope="request">
+                <h3>
+
+Search results:
+
+                </h3>
+                <form method="get" action="<bean:write name="requestURL" />">
+                    <ul>
+                        <logic:iterate id="currResult" name="searchresults">
+                            <li>
+                                <input type="radio" name="origin" value="<jsp:getProperty name="currResult" property="name" />" />
+                                <jsp:getProperty name="currResult" property="displayName" />
+                            </li>
+                        </logic:iterate>
+                    </ul>
+                    <p>
+                        <input type="hidden" name="shire" value="<bean:write name="shire" />" />
+                        <input type="hidden" name="target" value="<bean:write name="target" />" />
+                        <logic:present name="providerId" scope="request">
+                            <input type="hidden" name="providerId" value="<bean:write name="providerId" />" />
+                        </logic:present>
+                        <logic:present name="time" scope="request">
+                            <input type="hidden" name="time" value="<bean:write name="time" />" />
+                        </logic:present>
+                        <input type="hidden" name="action" value="selection" />
+                        <input type="submit" value="Select" />
+                        <select name="cache">
+                            <option value="false"> Do not remember
+                            <option value="session" selected> Remember for session
+                            <option value="perm"> Remember for a week
+                        </select>
+                    </p>
+                </form>     
+            </logic:present>
+        </div>
+    </div>
+
+    <div class="footer">
+        <p class="text">
+<!--CONFIG-->
+Need assistance? Send mail to <a href="mailto:user@domain"> administrator's name</a> with description.
+        </p>
+        <div class="logo"><img src="images/internet2.gif" alt="InQueue" /></div>
+    </div>
+
+<logic:present name="showComments" scope="Request">
+
+<!--PROGRAMMING NOTE
+  
+  We need to program the on changed selector.  Note that option.InnterText only
+  works on IE, options.remove doesn't work on Firefox, and that
+  options.add doesn't work on Safari.  Hence the somewhat strange manipulations
+  to delete & populate the list of options.
+
+  X        is the select object for the right hand table
+  Selected is the name selected in the left hand table
+
+-->
+
+</logic:present>
+
+<logic:present name="siteLists" scope="request">
+<script language="javascript" type="text/javascript">
+<!--
+
+function changedFed(X, Selected) {
+
+  <logic:notPresent name="singleSiteList" scope="request">
+
+     while (X.length > 0) {
+        X.options[(X.length-1)] = null;
+     }
+  
+  
+    <logic:iterate id="siteset" name="siteLists">
+      if (Selected == "<jsp:getProperty name="siteset" property="name"/>") {
+        var opt;
+        <logic:iterate id="site" name="siteset" property="sites">
+          opt = new Option ("<jsp:getProperty name="site" property="displayName" />");
+          X.options[X.length] = opt;
+          opt.value = "<jsp:getProperty name="site" property="name" />";
+        </logic:iterate>
+      }
+    </logic:iterate>
+  
+      if (Selected == "ALL") {
+        var opt;
+  
+      <logic:present name="sites" scope="request">
+          <logic:iterate id="site" name="sites">
+            opt = new Option("<jsp:getProperty name="site" property="displayName" />");
+            X.options[X.length] = opt;
+            opt.value = "<jsp:getProperty name="site" property="name" />";
+          </logic:iterate>
+      </logic:present>
+  
+      <logic:notPresent name="sites" scope="request">
+          <logic:iterate id="siteset" name="siteLists">
+            <logic:iterate id="site" name="siteset" property="sites">
+              opt = new Option ("<jsp:getProperty name="site" property="displayName" />");
+              X.options[X.length] = opt;
+              opt.value = "<jsp:getProperty name="site" property="name" />";
+            </logic:iterate>
+          </logic:iterate>
+      </logic:notPresent>
+    }
+  
+  </logic:notPresent>
+   
+  
+}
+-->
+</script>
+</logic:present>
+  
 </body>
 </html>
+  
\ No newline at end of file
index a6770dc..5e72816 100755 (executable)
@@ -7,9 +7,7 @@
        
        <jsp:useBean id="requestURL" scope="application" class="java.lang.String"/>
        <jsp:useBean id="errorText" scope="request" class="java.lang.String"/>
-       <jsp:useBean id="supportContact" scope="application" class="java.lang.String"/>
-       <jsp:useBean id="logoLocation" scope="application" class="java.lang.String"/>
-       
+
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
        <link rel="stylesheet" type="text/css" href="main.css" />
 
 <body>
 <div class="head">
-<img src="<bean:write name="logoLocation" />" alt="Logo" />
+<img src="images/logo.jpg" alt="Logo" />
 <h1>Inter-institutional Access System Failure</h1>
 </div>
 
 <p>The inter-institutional access system experienced a technical failure.</p>
 
-<p>Please email <a href="mailto:<bean:write name="supportContact" />"><bean:write name="supportContact" /></a> and include the following error message:</p>
+<p>Please email <a href="mailto:user@domain"> administrator's name</a> and include the following error message:</p>
 
 <p class="error">WAYF failure at (<bean:write name="requestURL" />)</p>