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 }