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