View Javadoc

1   /*
2    * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.shibboleth.wayf;
18  
19  import java.io.FileInputStream;
20  import java.io.FileNotFoundException;
21  import java.lang.reflect.Constructor;
22  import java.util.ArrayList;
23  import java.util.Hashtable;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.servlet.GenericServlet;
28  import javax.servlet.ServletException;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.xml.namespace.QName;
33  
34  import org.opensaml.DefaultBootstrap;
35  import org.opensaml.xml.Configuration;
36  import org.opensaml.xml.XMLObjectBuilderFactory;
37  import org.opensaml.xml.io.UnmarshallerFactory;
38  import org.opensaml.xml.parse.BasicParserPool;
39  import org.opensaml.xml.util.DatatypeHelper;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.Element;
44  import org.w3c.dom.NodeList;
45  
46  import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
47  import edu.internet2.middleware.shibboleth.wayf.idpdisco.Description;
48  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DescriptionBuilder;
49  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DescriptionUnmarshaller;
50  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DiscoHints;
51  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DiscoHintsBuilder;
52  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DiscoHintsUnmarshaller;
53  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DisplayName;
54  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DisplayNameBuilder;
55  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DisplayNameUnmarshaller;
56  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DomainHint;
57  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DomainHintBuilder;
58  import edu.internet2.middleware.shibboleth.wayf.idpdisco.DomainHintUnmarshaller;
59  import edu.internet2.middleware.shibboleth.wayf.idpdisco.GeolocationHint;
60  import edu.internet2.middleware.shibboleth.wayf.idpdisco.GeolocationHintBuilder;
61  import edu.internet2.middleware.shibboleth.wayf.idpdisco.GeolocationHintUnmarshaller;
62  import edu.internet2.middleware.shibboleth.wayf.idpdisco.IPHint;
63  import edu.internet2.middleware.shibboleth.wayf.idpdisco.IPHintBuilder;
64  import edu.internet2.middleware.shibboleth.wayf.idpdisco.IPHintUnmarshaller;
65  import edu.internet2.middleware.shibboleth.wayf.idpdisco.InformationURL;
66  import edu.internet2.middleware.shibboleth.wayf.idpdisco.InformationURLBuilder;
67  import edu.internet2.middleware.shibboleth.wayf.idpdisco.InformationURLUnmarshaller;
68  import edu.internet2.middleware.shibboleth.wayf.idpdisco.Logo;
69  import edu.internet2.middleware.shibboleth.wayf.idpdisco.LogoBuilder;
70  import edu.internet2.middleware.shibboleth.wayf.idpdisco.LogoUnmarshaller;
71  import edu.internet2.middleware.shibboleth.wayf.idpdisco.PrivacyStatementURL;
72  import edu.internet2.middleware.shibboleth.wayf.idpdisco.PrivacyStatementURLBuilder;
73  import edu.internet2.middleware.shibboleth.wayf.idpdisco.PrivacyStatementURLUnmarshaller;
74  import edu.internet2.middleware.shibboleth.wayf.idpdisco.UIInfo;
75  import edu.internet2.middleware.shibboleth.wayf.idpdisco.UIInfoBuilder;
76  import edu.internet2.middleware.shibboleth.wayf.idpdisco.UIInfoUnmarshaller;
77  import edu.internet2.middleware.shibboleth.wayf.plugins.Plugin;
78  
79  /**
80   * A servlet implementation of the Shibboleth WAYF service. Allows a browser
81   * user to select from among a group of origin sites. User selection is
82   * optionally cached and the user is forwarded to the HandleService appropriate
83   * to his selection.
84   */
85  public class WayfService extends HttpServlet {
86  
87      /** Required constant for serializaton. */
88      private static final long serialVersionUID = 5244503011625804940L;
89      
90      /** Handle for outputting error and other messages. */
91      private static final Logger LOG = LoggerFactory.getLogger(WayfService.class.getName());
92  
93      /** Where to get the configuration. */
94      private String wayfConfigFileLocation;
95      
96      /** Logging service. */
97      private LogbackLoggingService logService;
98  
99      /** All the different Discovery Services we deal with. */
100     private List <DiscoveryServiceHandler> discoveryServices = new ArrayList <DiscoveryServiceHandler>();
101     
102     /**
103      * help for init.
104      */
105     private void setupOtherSamlTypes(){
106         QName response;
107         UnmarshallerFactory uFactory = Configuration.getUnmarshallerFactory();
108         XMLObjectBuilderFactory bFactory = Configuration.getBuilderFactory();
109         
110         //
111         // The UiInfo type
112         //
113         response = new QName(UIInfo.MDUI_NS, Description.DEFAULT_ELEMENT_LOCAL_NAME);
114         uFactory.registerUnmarshaller(response, new DescriptionUnmarshaller());
115         bFactory.registerBuilder(response, new DescriptionBuilder());
116         
117         response = new QName(UIInfo.MDUI_NS, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
118         uFactory.registerUnmarshaller(response, new DisplayNameUnmarshaller());
119         bFactory.registerBuilder(response, new DisplayNameBuilder());
120         
121         response = new QName(UIInfo.MDUI_NS, InformationURL.DEFAULT_ELEMENT_LOCAL_NAME);
122         uFactory.registerUnmarshaller(response, new InformationURLUnmarshaller());
123         bFactory.registerBuilder(response, new InformationURLBuilder());
124 
125         response = new QName(UIInfo.MDUI_NS, Logo.DEFAULT_ELEMENT_LOCAL_NAME);
126         uFactory.registerUnmarshaller(response, new LogoUnmarshaller());
127         bFactory.registerBuilder(response, new LogoBuilder());
128 
129         response = new QName(UIInfo.MDUI_NS, PrivacyStatementURL.DEFAULT_ELEMENT_LOCAL_NAME);
130         uFactory.registerUnmarshaller(response, new PrivacyStatementURLUnmarshaller());
131         bFactory.registerBuilder(response, new PrivacyStatementURLBuilder());
132 
133         response = new QName(UIInfo.MDUI_NS, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
134         uFactory.registerUnmarshaller(response, new UIInfoUnmarshaller());
135         bFactory.registerBuilder(response, new UIInfoBuilder());
136         
137         //
138         // The DiscoHint Types
139         //
140         response = new QName(DiscoHints.MDUI_NS, IPHint.DEFAULT_ELEMENT_LOCAL_NAME);
141         uFactory.registerUnmarshaller(response, new IPHintUnmarshaller());
142         bFactory.registerBuilder(response, new IPHintBuilder());
143         
144         response = new QName(DiscoHints.MDUI_NS, GeolocationHint.DEFAULT_ELEMENT_LOCAL_NAME);
145         uFactory.registerUnmarshaller(response, new GeolocationHintUnmarshaller());
146         bFactory.registerBuilder(response, new GeolocationHintBuilder());
147 
148         response = new QName(DiscoHints.MDUI_NS, DomainHint.DEFAULT_ELEMENT_LOCAL_NAME);
149         uFactory.registerUnmarshaller(response, new DomainHintUnmarshaller());
150         bFactory.registerBuilder(response, new DomainHintBuilder());
151 
152         response = new QName(DiscoHints.MDUI_NS, DiscoHints.DEFAULT_ELEMENT_LOCAL_NAME);
153         uFactory.registerUnmarshaller(response, new DiscoHintsUnmarshaller());
154         bFactory.registerBuilder(response, new DiscoHintsBuilder());
155     }
156     
157 
158     /**
159      * Initialize the Discovery Service.
160      * 
161      * @throws javax.servlet.ServletException in the case of something bad happening
162      *  
163      * @see GenericServlet#init()
164      */
165     public void init() throws ServletException {
166 
167         String loadMetadataExts = null;
168         
169         super.init();
170         
171         wayfConfigFileLocation = getServletContext().getInitParameter("WAYFConfigFileLocation");
172         if (wayfConfigFileLocation == null) {
173             wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
174         }
175         if (wayfConfigFileLocation == null) {
176             wayfConfigFileLocation = "/wayfconfig.xml";
177         }
178 
179         loadMetadataExts = getServletContext().getInitParameter("loadMetadataExts");
180         if (loadMetadataExts == null) {
181             loadMetadataExts = getServletConfig().getInitParameter("loadMetadataExts");
182         }
183 
184         try {
185             //
186             // Initialize logging
187             //
188             String wayfLogfile = getServletContext().getInitParameter("WAYFLogConfig");
189             if (null == wayfLogfile) {
190                 wayfLogfile = getServletConfig().getInitParameter("WAYFLogConfig");
191             }
192             long pollingFrequency = 1000*60*5;
193             
194             String wayfLogfilePollFrequency = getServletContext().getInitParameter("WAYFLogConfigPollFrequency");
195             if (null == wayfLogfilePollFrequency) {
196                 wayfLogfilePollFrequency = getServletConfig().getInitParameter("WAYFLogConfigPollFrequency");              
197             }
198             if(!DatatypeHelper.isEmpty(wayfLogfilePollFrequency)){
199                 pollingFrequency = Long.parseLong(wayfLogfilePollFrequency);
200             }
201             if (wayfLogfile != null) {
202                 logService = new LogbackLoggingService(wayfLogfile, pollingFrequency);
203             }
204 
205             LOG.info("Logging initiated");
206             
207             //
208             // Initialize OpenSAML 2 library
209             //
210             DefaultBootstrap.bootstrap();   
211         
212             BasicParserPool parser = new BasicParserPool();
213             parser.setNamespaceAware(true);
214             Document doc;
215             try {
216                 doc = parser.parse(new FileInputStream(wayfConfigFileLocation));
217             } catch (FileNotFoundException e) {
218                 LOG.error("Could not parse " + wayfConfigFileLocation, e);
219                 throw new ShibbolethConfigurationException("Could not parse " + wayfConfigFileLocation, e);
220             }            
221             NodeList itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS, 
222                                                                                     "Default");
223             
224             HandlerConfig defaultHandlerConfig;
225             
226             if (itemElements.getLength() == 1) {
227                     
228                 Element element = (Element) itemElements.item(0);
229                 String attribute = element.getAttribute("location");
230                 
231                 if (attribute != null && !attribute.equals("")) {
232                         
233                     LOG.error("<Default> element cannot contain a location attribute");
234                     throw new ShibbolethConfigurationException("<Default> element cannot contain a location attribute");
235                         
236                 }
237     
238                 attribute = element.getAttribute("default");
239                 
240                 if (attribute != null && !attribute.equals("")) {
241     
242                     LOG.error("<Default> element cannot contain a default attribute");
243                     throw new ShibbolethConfigurationException("<Default> element cannot contain a default attribute");
244                     
245                 }
246     
247                 itemElements = element.getElementsByTagName("Federation");
248                 
249                 if (itemElements.getLength() != 0) {
250                         
251                     LOG.error("<Default> element cannot contain <Federation> elements");
252                     throw new ShibbolethConfigurationException("<Default> element cannot contain <Federation> elements");
253     
254                 }
255                                         
256                 defaultHandlerConfig = new HandlerConfig(element, new HandlerConfig());
257         
258             } else if (itemElements.getLength() == 0) {
259     
260                     defaultHandlerConfig = new HandlerConfig();
261             
262             } else {
263                     LOG.error("Must specify exactly one <Default> element");
264                     throw new ShibbolethConfigurationException("Must specify exactly one <Default> element");
265             }
266             
267             //
268             // As a trial for V2, load metadata extensions - if enabled
269             //
270             if (loadMetadataExts != null) {
271                 LOG.debug("Setting up <UIInfo> and <DiscoHints> parsers - UNSUPPORTED OPERATION");
272                 setupOtherSamlTypes();
273             }
274                                           
275             //
276             // Load metadata
277             //
278             Hashtable <String, IdPSiteSet> siteSets = new Hashtable <String, IdPSiteSet>();
279     
280             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
281                             "MetadataProvider");
282             
283             for (int i = 0; i < itemElements.getLength(); i++) {
284                     
285                 Element element = (Element) itemElements.item(i);
286                 
287                 IdPSiteSet siteset = new IdPSiteSet(element, parser, defaultHandlerConfig.getWarnOnBadBinding());
288                 
289                 siteSets.put(siteset.getIdentifier(), siteset);
290             }
291             if (siteSets.size() < 1) {
292                 LOG.error("No Metadata Provider metadata loaded.");
293                 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
294             }
295             //
296             // Load plugins
297             //
298             
299             Hashtable <String, Plugin> plugins = new Hashtable <String, Plugin>();
300     
301             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
302                             "Plugin");
303             
304             for (int i = 0; i < itemElements.getLength(); i++) {
305                     
306                 Plugin plugin;
307                 
308                 Element element = (Element) itemElements.item(i);
309                 
310                 String identifier = element.getAttribute("identifier");
311         
312                 if (null == identifier || identifier.equals("")) {
313                         LOG.error("Could not load plugin with no identifier");
314                         continue;
315                 }
316                 
317                 String className = element.getAttribute("type");
318                 if (null == className || className.equals("")) {
319                         LOG.error("Plugin " + identifier + " did not have a valid type");
320                 }
321                 //
322                 // So try to get hold of the plugin
323                 //
324                 try {
325                     Class<Plugin> pluginClass = (Class<Plugin>) Class.forName(className);
326                     Class[] classParams = {Element.class};
327                     Constructor<Plugin> pluginConstructor = pluginClass.getConstructor(classParams);
328                     Object[] constructorParams = {element};
329                     
330                     plugin = pluginConstructor.newInstance(constructorParams);
331                         
332                 } catch (Exception e) {
333                     LOG.error("Plugin " + identifier + " could not be loaded ", e);
334                     continue;
335                 } 
336                 
337                 plugins.put(identifier, plugin);
338             }
339             
340             
341             //
342             // Load service handlers
343             //
344             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
345                             "DiscoveryServiceHandler");
346             
347             for (int i = 0; i < itemElements.getLength(); i++) {
348                     
349                 discoveryServices.add(new DiscoveryServiceHandler((Element)itemElements.item(i), 
350                                       siteSets, 
351                                       plugins, 
352                                       defaultHandlerConfig));
353     
354             }
355     
356         } catch (Exception e) {
357         //
358         // All other exceptions are from the parsing
359         //
360         if (LOG != null) {
361                 LOG.error("Error parsing DS configuration file.", e);
362         }
363         throw new ServletException("Error parsing DS configuration file.", e);
364     }
365 
366     LOG.info("DS initialization completed.");
367 }
368 
369     /**
370      * Handle an HTTP GET.  Just passes out to the appropriate handler.
371      * @param req described the request.
372      * @param res contains the response.
373      * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
374      */
375     public void doGet(HttpServletRequest req, HttpServletResponse res) {
376             
377         LOG.info("Handling DS request.");
378         // Tell the browser not to cache the WAYF page
379         res.setHeader("Cache-Control", "no-cache");
380         res.setHeader("Pragma", "no-cache");
381         res.setDateHeader("Expires", 0);
382 
383         DiscoveryServiceHandler serviceHandler = lookupServiceHandler(req); 
384         
385         serviceHandler.doGet(req, res);
386            
387     }
388 
389     /**
390      * Given a request (an HTTP Get) find the apropriate DiscoveryService (from the name).
391      * @param req desribed the request
392      * @return the apropriate DiscoveryService.
393      */
394     private DiscoveryServiceHandler lookupServiceHandler(HttpServletRequest req) {
395 
396         Iterator<DiscoveryServiceHandler> it = discoveryServices.iterator();
397         String requestURL = req.getRequestURL().toString(); 
398         DiscoveryServiceHandler defaultHandler = null;
399         
400         while (it.hasNext()) {
401             DiscoveryServiceHandler handler = it.next();
402             
403             if (requestURL.matches(handler.getLocation())) {
404                 return handler;
405             }
406             if (defaultHandler == null || handler.isDefault()) {
407                 defaultHandler = handler;
408             }
409         }
410         LOG.warn("Could not find Discovery service Handler for " + requestURL);
411         return defaultHandler;
412     }    
413 }