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  
33  import org.opensaml.DefaultBootstrap;
34  import org.opensaml.xml.parse.BasicParserPool;
35  import org.opensaml.xml.util.DatatypeHelper;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.w3c.dom.Document;
39  import org.w3c.dom.Element;
40  import org.w3c.dom.NodeList;
41  
42  import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
43  import edu.internet2.middleware.shibboleth.wayf.plugins.Plugin;
44  
45  /**
46   * A servlet implementation of the Shibboleth WAYF service. Allows a browser
47   * user to select from among a group of origin sites. User selection is
48   * optionally cached and the user is forwarded to the HandleService appropriate
49   * to his selection.
50   */
51  public class WayfService extends HttpServlet {
52  
53      /** Required constant for serializaton. */
54      private static final long serialVersionUID = 5244503011625804940L;
55      
56      /** Handle for outputting error and other messages. */
57      private static final Logger LOG = LoggerFactory.getLogger(WayfService.class.getName());
58  
59      /** Where to get the configuration. */
60      private String wayfConfigFileLocation;
61      
62      /** Logging service. */
63      private LogbackLoggingService logService;
64  
65      /** All the different Discovery Services we deal with. */
66      private List <DiscoveryServiceHandler> discoveryServices = new ArrayList <DiscoveryServiceHandler>();
67      
68      /**
69       * Initialize the Discovery Service.
70       * 
71       * @throws ServletException in the case of something bad happening
72       *  
73       * @see GenericServlet#init()
74       */
75      public void init() throws ServletException {
76  
77          super.init();
78          
79          wayfConfigFileLocation = getServletContext().getInitParameter("WAYFConfigFileLocation");
80          if (wayfConfigFileLocation == null) {
81              wayfConfigFileLocation = getServletConfig().getInitParameter("WAYFConfigFileLocation");
82          }
83          if (wayfConfigFileLocation == null) {
84              wayfConfigFileLocation = "/wayfconfig.xml";
85          }
86  
87  
88          try {
89              //
90              // Initialize logging
91              //
92              String wayfLogfile = getServletContext().getInitParameter("WAYFLogConfig");
93              if (null == wayfLogfile) {
94                  wayfLogfile = getServletConfig().getInitParameter("WAYFLogConfig");
95              }
96              long pollingFrequency = 1000*60*5;
97              
98              String wayfLogfilePollFrequency = getServletContext().getInitParameter("WAYFLogConfigPollFrequency");
99              if (null == wayfLogfilePollFrequency) {
100                 wayfLogfilePollFrequency = getServletConfig().getInitParameter("WAYFLogConfigPollFrequency");              
101             }
102             if(!DatatypeHelper.isEmpty(wayfLogfilePollFrequency)){
103                 pollingFrequency = Long.parseLong(wayfLogfilePollFrequency);
104             }
105             if (wayfLogfile != null) {
106                 logService = new LogbackLoggingService(wayfLogfile, pollingFrequency);
107             }
108 
109             LOG.info("Logging initiated");
110             
111             //
112             // Initialize OpenSAML 2 library
113             //
114             DefaultBootstrap.bootstrap();   
115         
116             BasicParserPool parser = new BasicParserPool();
117             parser.setNamespaceAware(true);
118             Document doc;
119             try {
120                 doc = parser.parse(new FileInputStream(wayfConfigFileLocation));
121             } catch (FileNotFoundException e) {
122                 LOG.error("Could not parse " + wayfConfigFileLocation, e);
123                 throw new ShibbolethConfigurationException("Could not parse " + wayfConfigFileLocation, e);
124             }            
125             NodeList itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS, 
126                                                                                     "Default");
127             
128             HandlerConfig defaultHandlerConfig;
129             
130             if (itemElements.getLength() == 1) {
131                     
132                 Element element = (Element) itemElements.item(0);
133                 String attribute = element.getAttribute("location");
134                 
135                 if (attribute != null && !attribute.equals("")) {
136                         
137                     LOG.error("<Default> element cannot contain a location attribute");
138                     throw new ShibbolethConfigurationException("<Default> element cannot contain a location attribute");
139                         
140                 }
141     
142                 attribute = element.getAttribute("default");
143                 
144                 if (attribute != null && !attribute.equals("")) {
145     
146                     LOG.error("<Default> element cannot contain a default attribute");
147                     throw new ShibbolethConfigurationException("<Default> element cannot contain a default attribute");
148                     
149                 }
150     
151                 itemElements = element.getElementsByTagName("Federation");
152                 
153                 if (itemElements.getLength() != 0) {
154                         
155                     LOG.error("<Default> element cannot contain <Federation> elements");
156                     throw new ShibbolethConfigurationException("<Default> element cannot contain <Federation> elements");
157     
158                 }
159                                         
160                 defaultHandlerConfig = new HandlerConfig(element, new HandlerConfig());
161         
162             } else if (itemElements.getLength() == 0) {
163     
164                     defaultHandlerConfig = new HandlerConfig();
165             
166             } else {
167                     LOG.error("Must specify exactly one <Default> element");
168                     throw new ShibbolethConfigurationException("Must specify exactly one <Default> element");
169             }
170                                           
171             //
172             // Load metadata
173             //
174             Hashtable <String, IdPSiteSet> siteSets = new Hashtable <String, IdPSiteSet>();
175     
176             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
177                             "MetadataProvider");
178             
179             for (int i = 0; i < itemElements.getLength(); i++) {
180                     
181                 Element element = (Element) itemElements.item(i);
182                 
183                 IdPSiteSet siteset = new IdPSiteSet(element, parser, defaultHandlerConfig.getWarnOnBadBinding());
184                 
185                 siteSets.put(siteset.getIdentifier(), siteset);
186             }
187             if (siteSets.size() < 1) {
188                 LOG.error("No Metadata Provider metadata loaded.");
189                 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
190             }
191             //
192             // Load plugins
193             //
194             
195             Hashtable <String, Plugin> plugins = new Hashtable <String, Plugin>();
196     
197             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
198                             "Plugin");
199             
200             for (int i = 0; i < itemElements.getLength(); i++) {
201                     
202                 Plugin plugin;
203                 
204                 Element element = (Element) itemElements.item(i);
205                 
206                 String identifier = element.getAttribute("identifier");
207         
208                 if (null == identifier || identifier.equals("")) {
209                         LOG.error("Could not load plugin with no identifier");
210                         continue;
211                 }
212                 
213                 String className = element.getAttribute("type");
214                 if (null == className || className.equals("")) {
215                         LOG.error("Plugin " + identifier + " did not have a valid type");
216                 }
217                 //
218                 // So try to get hold of the plugin
219                 //
220                 try {
221                     Class<Plugin> pluginClass = (Class<Plugin>) Class.forName(className);
222                     Class[] classParams = {Element.class};
223                     Constructor<Plugin> pluginConstructor = pluginClass.getConstructor(classParams);
224                     Object[] constructorParams = {element};
225                     
226                     plugin = pluginConstructor.newInstance(constructorParams);
227                         
228                 } catch (Exception e) {
229                     LOG.error("Plugin " + identifier + " could not be loaded ", e);
230                     continue;
231                 } 
232                 
233                 plugins.put(identifier, plugin);
234             }
235             
236             
237             //
238             // Load service handlers
239             //
240             itemElements = doc.getDocumentElement().getElementsByTagNameNS(XMLConstants.CONFIG_NS,
241                             "DiscoveryServiceHandler");
242             
243             for (int i = 0; i < itemElements.getLength(); i++) {
244                     
245                 discoveryServices.add(new DiscoveryServiceHandler((Element)itemElements.item(i), 
246                                       siteSets, 
247                                       plugins, 
248                                       defaultHandlerConfig));
249     
250             }
251     
252         } catch (Exception e) {
253         //
254         // All other exceptions are from the parsing
255         //
256         if (LOG != null) {
257                 LOG.error("Error parsing DS configuration file.", e);
258         }
259         throw new ServletException("Error parsing DS configuration file.", e);
260     }
261 
262     LOG.info("DS initialization completed.");
263 }
264 
265     /**
266      * Handle an HTTP GET.  Just passes out to the appropriate handler.
267      * @param req described the request.
268      * @param res contains the response.
269      * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
270      */
271     public void doGet(HttpServletRequest req, HttpServletResponse res) {
272             
273         LOG.info("Handling DS request.");
274         // Tell the browser not to cache the WAYF page
275         res.setHeader("Cache-Control", "no-cache");
276         res.setHeader("Pragma", "no-cache");
277         res.setDateHeader("Expires", 0);
278 
279         DiscoveryServiceHandler serviceHandler = lookupServiceHandler(req); 
280         
281         serviceHandler.doGet(req, res);
282            
283     }
284 
285     /**
286      * Given a request (an HTTP Get) find the apropriate DiscoveryService (from the name).
287      * @param req desribed the request
288      * @return the apropriate DiscoveryService.
289      */
290     private DiscoveryServiceHandler lookupServiceHandler(HttpServletRequest req) {
291 
292         Iterator<DiscoveryServiceHandler> it = discoveryServices.iterator();
293         String requestURL = req.getRequestURL().toString(); 
294         DiscoveryServiceHandler defaultHandler = null;
295         
296         while (it.hasNext()) {
297             DiscoveryServiceHandler handler = it.next();
298             
299             if (requestURL.matches(handler.getLocation())) {
300                 return handler;
301             }
302             if (defaultHandler == null || handler.isDefault()) {
303                 defaultHandler = handler;
304             }
305         }
306         LOG.warn("Could not find Discovery service Handler for " + requestURL);
307         return defaultHandler;
308     }    
309 }