Fixed a SAML metadata parsing bug.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / metadata / provider / XMLMetadataProvider.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the
6  * above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other
7  * materials provided with the distribution, if any, must include the following acknowledgment: "This product includes
8  * software developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2
9  * Project. Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2,
11  * nor the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please
13  * contact shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2,
14  * UCAID, or the University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name,
15  * without prior written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS
16  * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES,
17  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
18  * NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS
19  * WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED
20  * INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
23  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 package edu.internet2.middleware.shibboleth.metadata.provider;
28
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.security.NoSuchAlgorithmException;
32 import java.text.ParseException;
33 import java.text.SimpleDateFormat;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.Map;
40 import java.util.TimeZone;
41
42 import org.apache.log4j.Logger;
43 import org.apache.xml.security.encryption.EncryptionMethod;
44 import org.apache.xml.security.exceptions.XMLSecurityException;
45 import org.apache.xml.security.keys.KeyInfo;
46 import org.bouncycastle.util.encoders.Hex;
47 import org.opensaml.SAMLAttribute;
48 import org.opensaml.SAMLBinding;
49 import org.opensaml.SAMLBrowserProfile;
50 import org.opensaml.SAMLException;
51 import org.opensaml.XML;
52 import org.opensaml.artifact.Artifact;
53 import org.opensaml.artifact.SAMLArtifactType0001;
54 import org.opensaml.artifact.SAMLArtifactType0002;
55 import org.opensaml.artifact.Util;
56 import org.w3c.dom.Attr;
57 import org.w3c.dom.Element;
58 import org.w3c.dom.NamedNodeMap;
59 import org.w3c.dom.Node;
60 import org.w3c.dom.NodeList;
61
62 import edu.internet2.middleware.shibboleth.common.Constants;
63 import edu.internet2.middleware.shibboleth.metadata.*;
64
65 /**
66  * @author Scott Cantor
67  */
68 public class XMLMetadataProvider implements Metadata {
69
70         private static Logger log = Logger.getLogger(XMLMetadataProvider.class.getName());
71         private Map     /* <String,ArrayList<EntityDescriptor> > */ sites = new HashMap();
72     private Map /* <String,ArrayList<EntityDescriptor> > */ sources = new HashMap();
73     private XMLEntityDescriptor rootProvider = null;
74     private XMLEntitiesDescriptor rootGroup = null;
75
76         public XMLMetadataProvider(Element e) throws SAMLException {
77         if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EntitiesDescriptor"))
78             rootGroup=new XMLEntitiesDescriptor(e,this, Long.MAX_VALUE, null);
79         else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EntityDescriptor"))
80             rootProvider=new XMLEntityDescriptor(e,this, Long.MAX_VALUE, null);
81         else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"SiteGroup"))
82             rootGroup=new XMLEntitiesDescriptor(e,this, Long.MAX_VALUE, null);
83         else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"OriginSite"))
84             rootProvider=new XMLEntityDescriptor(e,this, Long.MAX_VALUE, null);
85         else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"DestinationSite"))
86             rootProvider=new XMLEntityDescriptor(e,this, Long.MAX_VALUE, null);
87         else {
88             log.error("Construction requires a valid SAML metadata file");
89             throw new MetadataException("Construction requires a valid SAML metadata file");
90         }
91         }
92
93         public EntityDescriptor lookup(String id) {
94                 ArrayList list = (ArrayList)sites.get(id);
95                 if (list != null) {
96                     long now = System.currentTimeMillis();
97             for (int i=0; i<list.size(); i++) {
98                 if (now < ((XMLEntityDescriptor)list.get(i)).getValidUntil())
99                     return (EntityDescriptor)list.get(i);
100             }
101         }
102                 return null;
103         }
104
105     public EntityDescriptor lookup(Artifact artifact) {
106         ArrayList list = null;
107         
108         if (artifact instanceof SAMLArtifactType0001) {
109             list = (ArrayList)sources.get(((SAMLArtifactType0001)artifact).getSourceId());
110         }
111         else if (artifact instanceof SAMLArtifactType0002) {
112             list = (ArrayList)sources.get(((SAMLArtifactType0002)artifact).getSourceLocation().toString());
113         }
114         else {
115             log.error("unsupported artifact type (" + artifact.getTypeCode().toString() + ")");
116         }
117
118         if (list != null) {
119             long now = System.currentTimeMillis();
120             for (int i=0; i<list.size(); i++) {
121                 if (now < ((XMLEntityDescriptor)list.get(i)).getValidUntil())
122                     return (EntityDescriptor)list.get(i);
123             }
124         }
125         return null;
126     }
127
128     class XMLEndpoint implements Endpoint {
129         private Element root = null;
130         private String binding = null;
131         private String location = null;
132         private String resploc = null;
133
134         XMLEndpoint(Element e) {
135             root = e;
136             binding = XML.assign(e.getAttributeNS(null,"Binding"));
137             location = XML.assign(e.getAttributeNS(null,"Location"));
138             resploc = XML.assign(e.getAttributeNS(null,"ResponseLocation"));
139         }
140         
141         XMLEndpoint(String binding, String location) {
142             this.binding = binding;
143             this.location = location;
144         }
145
146         public String getBinding() {
147             return binding;
148         }
149
150         public String getLocation() {
151             return location;
152         }
153
154         public String getResponseLocation() {
155             return resploc;
156         }
157         
158         public Element getElement() {
159             return root;
160         }
161     }
162
163     class XMLIndexedEndpoint extends XMLEndpoint implements IndexedEndpoint {
164         private int index = 0;
165         
166         XMLIndexedEndpoint(Element e) {
167             super(e);
168             index = Integer.parseInt(e.getAttributeNS(null,"index"));
169         }
170
171         public int getIndex() {
172             return index;
173         }
174     }
175     
176     class XMLEndpointManager implements EndpointManager {
177         private ArrayList endpoints = new ArrayList();
178         Endpoint soft = null;   // Soft default (not explicit)
179         Endpoint hard = null;   // Hard default (explicit)
180
181         public Iterator getEndpoints() {
182             return endpoints.iterator();
183         }
184         
185         public Endpoint getDefaultEndpoint() {
186             if (hard != null) return hard;
187             if (soft != null) return soft;
188             if (!endpoints.isEmpty()) return (Endpoint)endpoints.get(0);
189             return null;
190         }
191         
192         public Endpoint getEndpointByIndex(int index) {
193             for (int i=0; i < endpoints.size(); i++) {
194                 if (endpoints.get(i) instanceof IndexedEndpoint && index==((IndexedEndpoint)endpoints.get(i)).getIndex())
195                     return (Endpoint)endpoints.get(i);
196             }
197             return null;
198         }
199         
200         public Endpoint getEndpointByBinding(String binding) {
201             for (int i=0; i < endpoints.size(); i++) {
202                 if (binding.equals(((Endpoint)endpoints.get(i)).getBinding()))
203                     return (Endpoint)endpoints.get(i);
204             }
205             return null;
206         }
207         
208         protected void add(Endpoint e) {
209             endpoints.add(e);
210             if (hard == null && e.getElement() != null) {
211                 String v=XML.assign(e.getElement().getAttributeNS(null,"isDefault"));
212                 if (v != null && (v.equals("1") || v.equals("true")))  // explicit default
213                     hard=e;
214                 else if (v == null && soft == null)            // implicit default
215                     soft=e;
216             }
217             else if (hard == null && soft == null) {
218                 // No default yet, so this one qualifies as an implicit.
219                 soft=e;
220             }
221         }
222     }
223     
224     class XMLKeyDescriptor implements KeyDescriptor {
225
226         private int use = KeyDescriptor.UNSPECIFIED;
227         private KeyInfo keyInfo = null;
228         private ArrayList /* <XMLEncryptionMethod> */ methods = new ArrayList();
229
230         XMLKeyDescriptor(Element e) {
231             if (XML.safeCompare(e.getAttributeNS(null,"use"),"encryption"))
232                 use = KeyDescriptor.ENCRYPTION;
233             else if (XML.safeCompare(e.getAttributeNS(null,"use"),"signing"))
234                 use = KeyDescriptor.SIGNING;
235             
236             e = XML.getFirstChildElement(e);
237             try {
238                 keyInfo = new KeyInfo(e, null);
239             }
240             catch (XMLSecurityException e1) {
241                 log.error("unable to process ds:KeyInfo element: " + e1.getMessage());
242             }
243             
244             e = XML.getNextSiblingElement(e);
245             while (e != null && XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EncryptionMethod")) {
246                 methods.add(new XMLEncryptionMethod(e));
247             }
248         }
249
250         public int getUse() {
251             return use;
252         }
253
254         public Iterator getEncryptionMethods() {
255             return methods.iterator();
256         }
257
258         public KeyInfo getKeyInfo() {
259             return keyInfo;
260         }
261     }
262
263     class XMLEncryptionMethod implements EncryptionMethod {
264
265         String alg = null;
266         String params = null;
267         int size = 0;
268         
269         public XMLEncryptionMethod(Element e) {
270             alg = XML.assign(e.getAttributeNS(null, "Algorithm"));
271             e = XML.getFirstChildElement(e);
272             while (e != null) {
273                 if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.XMLENC_NS,"KeySize")) {
274                     if (e.hasChildNodes())
275                         size = Integer.parseInt(e.getFirstChild().getNodeValue());
276                 }
277                 else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.XMLENC_NS,"OAEParams")) {
278                     if (e.hasChildNodes())
279                         params = XML.assign(e.getFirstChild().getNodeValue());
280                 }
281                 e = XML.getNextSiblingElement(e);
282             }
283         }
284         
285         public String getAlgorithm() {
286             return alg;
287         }
288
289         public int getKeySize() {
290             return size;
291         }
292
293         public byte[] getOAEPparams() {
294             return params.getBytes();
295         }
296
297         public Iterator getEncryptionMethodInformation() {
298             return null;
299         }
300
301         public void setKeySize(int arg0) {
302             throw new UnsupportedOperationException("EncryptionMethod implementation is read-only.");
303         }
304
305         public void setOAEPparams(byte[] arg0) {
306             throw new UnsupportedOperationException("EncryptionMethod implementation is read-only.");
307         }
308
309         public void addEncryptionMethodInformation(Element arg0) {
310             throw new UnsupportedOperationException("EncryptionMethod implementation is read-only.");
311         }
312
313         public void removeEncryptionMethodInformation(Element arg0) {
314             throw new UnsupportedOperationException("EncryptionMethod implementation is read-only.");
315         }
316     }
317
318     class XMLKeyAuthority implements KeyAuthority {
319         private int depth = 1;
320         private ArrayList /* <KeyInfo> */ keys = new ArrayList();
321         
322         XMLKeyAuthority(Element e) {
323             if (e.hasAttributeNS(null,"VerifyDepth"))
324                 depth = Integer.parseInt(e.getAttributeNS(null,"VerifyDepth"));
325             e = XML.getFirstChildElement(e, XML.XMLSIG_NS, "KeyInfo");
326             while (e != null) {
327                 try {
328                     keys.add(new KeyInfo(e, null));
329                 }
330                 catch (XMLSecurityException e1) {
331                     log.error("unable to process ds:KeyInfo element: " + e1.getMessage());
332                 }
333                 e = XML.getNextSiblingElement(e, XML.XMLSIG_NS, "KeyInfo");
334             }
335         }
336         
337         public int getVerifyDepth() {
338             return depth;
339         }
340
341         public Iterator getKeyInfos() {
342             return keys.iterator();
343         }
344         
345     }
346         
347     class XMLOrganization implements Organization {
348         private Element root = null;
349         private HashMap /* <String,String> */ names = new HashMap();
350         private HashMap /* <String,String> */ displays = new HashMap();
351         private HashMap /* <String,URL> */ urls = new HashMap();
352
353         public XMLOrganization(Element e) throws MetadataException {
354             root = e;
355             e=XML.getFirstChildElement(e);
356             while (e != null) {
357                 if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"OrganizationName")) {
358                     if (e.hasChildNodes()) {
359                         names.put(e.getAttributeNS(XML.XML_NS,"lang"),XML.assign(e.getFirstChild().getNodeValue()));
360                     }
361                 }
362                 else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"OrganizationDisplayName")) {
363                     if (e.hasChildNodes()) {
364                         displays.put(e.getAttributeNS(XML.XML_NS,"lang"),XML.assign(e.getFirstChild().getNodeValue()));
365                     }
366                 }
367                 else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"OrganizationURL")) {
368                     if (e.hasChildNodes()) {
369                         URL u;
370                         try {
371                             u = new URL(e.getFirstChild().getNodeValue());
372                         }
373                         catch (MalformedURLException e1) {
374                             throw new MetadataException("OrganizationURL was invalid: " + e1);
375                         }
376                         urls.put(e.getAttributeNS(XML.XML_NS,"lang"),u);
377                     }
378                 }
379                 e=XML.getNextSiblingElement(e);
380             }
381         }
382         
383         public String getName() {
384             return getName("en");
385         }
386
387         public String getName(String lang) {
388             return (String)names.get(lang);
389         }
390
391         public String getDisplayName() {
392             return getDisplayName("en");
393         }
394
395         public String getDisplayName(String lang) {
396             return (String)displays.get(lang);
397         }
398
399         public URL getURL() {
400             return getURL("en");
401         }
402
403         public URL getURL(String lang) {
404             return (URL)urls.get(lang);
405         }
406         
407     }
408     
409         class XMLContactPerson implements ContactPerson {
410             private Element root = null;
411                 private int             type;
412         private String  company = null;
413                 private String  givenName = null;
414         private String  surName = null;
415                 private ArrayList /* <String> */ emails = new ArrayList();
416         private ArrayList /* <String> */ telephones = new ArrayList();
417
418                 public XMLContactPerson(Element e) throws MetadataException {
419             root = e;
420             String rawType = null;
421             
422             // Old metadata or new?
423             if (XML.isElementNamed(root, edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"Contact")) {
424                 rawType = root.getAttributeNS(null,"Type");
425                 surName = XML.assign(root.getAttributeNS(null,"Name"));
426                 if (XML.isEmpty(surName)) {
427                     throw new MetadataException("Contact is missing Name attribute.");
428                 }
429                 if (root.hasAttributeNS(null,"Email"))
430                     emails.add(e.getAttributeNS(null,"Email"));
431             }
432             else {
433                 rawType = root.getAttributeNS(null,"contactType");
434                 Node n=null;
435                 e=XML.getFirstChildElement(root);
436                 while (e != null) {
437                     if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Company")) {
438                         if (e.hasChildNodes())
439                             company=XML.assign(e.getFirstChild().getNodeValue());
440                     }
441                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"GivenName")) {
442                         if (e.hasChildNodes())
443                             givenName=XML.assign(e.getFirstChild().getNodeValue());
444                     }
445                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"SurName")) {
446                         if (e.hasChildNodes())
447                             surName=XML.assign(e.getFirstChild().getNodeValue());
448                     }
449                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EmailAddress")) {
450                         if (e.hasChildNodes())
451                             emails.add(XML.assign(e.getFirstChild().getNodeValue()));
452                     }
453                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"TelephoneNumber")) {
454                         if (e.hasChildNodes())
455                             telephones.add(XML.assign(e.getFirstChild().getNodeValue()));
456                     }
457                     e=XML.getNextSiblingElement(e);
458                 }
459             }
460                         
461                         if (rawType.equalsIgnoreCase("TECHNICAL")) {
462                                 type = ContactPerson.TECHNICAL;
463                         } else if (rawType.equalsIgnoreCase("SUPPORT")) {
464                                 type = ContactPerson.SUPPORT;
465                         } else if (rawType.equalsIgnoreCase("ADMINISTRATIVE")) {
466                                 type = ContactPerson.ADMINISTRATIVE;
467                         } else if (rawType.equalsIgnoreCase("BILLING")) {
468                                 type = ContactPerson.BILLING;
469                         } else if (rawType.equalsIgnoreCase("OTHER")) {
470                                 type = ContactPerson.OTHER;
471                         } else {
472                                 throw new MetadataException("Contact has unknown contact type.");
473                         }
474                 }
475
476                 public int getType() {
477                         return type;
478                 }
479
480                 public String getGivenName() {
481                         return givenName;
482                 }
483
484         public String getSurName() {
485             return surName;
486         }
487         
488         public String getCompany() {
489             return company;
490         }
491
492         public Iterator getEmailAddresses() {
493                         return emails.iterator();
494                 }
495
496                 public Iterator getTelephoneNumbers() {
497                         return telephones.iterator();
498                 }
499         
500         public Element getElement() {
501             return root;
502         }
503         }
504     
505     class Role implements RoleDescriptor {
506         private Element root = null;
507         private XMLEntityDescriptor provider = null;
508         private URL errorURL = null;
509         private Organization org = null;
510         private ArrayList /* <ContactPerson> */ contacts = new ArrayList();
511         private long validUntil = Long.MAX_VALUE;
512         protected ArrayList /* <String> */ protocolEnum = new ArrayList();
513         protected ArrayList /* <KeyDescriptor> */ keys = new ArrayList();
514
515         public Role(XMLEntityDescriptor provider, long validUntil, Element e) throws MetadataException {
516             root = e;
517             this.validUntil = validUntil;
518             this.provider = provider;
519             
520             // Check the root element namespace. If SAML2, assume it's the std schema.
521             if (e != null && edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
522                
523                 if (e.hasAttributeNS(null,"validUntil")) {
524                     SimpleDateFormat formatter = null;
525                     String dateTime = XML.assign(e.getAttributeNS(null,"validUntil"));
526                     int dot = dateTime.indexOf('.');
527                     if (dot > 0)
528                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
529                     else
530                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
531                     formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
532                     try {
533                         validUntil=Math.min(validUntil,formatter.parse(dateTime).getTime());
534                     }
535                     catch (ParseException e1) {
536                         log.warn("Role descriptor contains invalid expiration time");
537                     }
538                 }
539                 
540                 if (e.hasAttributeNS(null,"errorURL")) {
541                     try {
542                         errorURL=new URL(e.getAttributeNS(null,"errorURL"));
543                     }
544                     catch (MalformedURLException e1) {
545                         log.error("Role descriptor contains malformed errorURL");
546                     }
547                 }
548                 
549                 // Chop the protocol list into pieces...assume any whitespace can appear in between.   
550                 protocolEnum.addAll(Arrays.asList(e.getAttributeNS(null,"protocolSupportEnumeration").split("\\s")));
551                 
552                 e = XML.getFirstChildElement(root,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"KeyDescriptor");
553                 while (e != null) {
554                     keys.add(new XMLKeyDescriptor(e));
555                     e = XML.getNextSiblingElement(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"KeyDescriptor");
556                 }
557
558                 e = XML.getFirstChildElement(root,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Organization");
559                 if (e != null)
560                     org=new XMLOrganization(e);
561
562                 e = XML.getFirstChildElement(root,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"ContactPerson");
563                 while (e != null) {
564                     contacts.add(new XMLContactPerson(e));
565                     e = XML.getNextSiblingElement(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"ContactPerson");
566                 }
567             }
568         }
569         
570         public EntityDescriptor getEntityDescriptor() {
571             return provider;
572         }
573
574         public Iterator getProtocolSupportEnumeration() {
575             return protocolEnum.iterator();
576         }
577
578         public boolean hasSupport(String version) {
579             return protocolEnum.contains(version);
580         }
581
582         public boolean isValid() {
583             return System.currentTimeMillis() < validUntil;
584         }
585
586         public URL getErrorURL() {
587             return (errorURL != null) ? errorURL : provider.getErrorURL();
588         }
589
590         public Iterator getKeyDescriptors() {
591             return keys.iterator();
592         }
593
594         public Organization getOrganization() {
595             return (org != null) ? org : provider.getOrganization();
596         }
597
598         public Iterator getContactPersons() {
599             return (contacts.isEmpty()) ? provider.getContactPersons() : contacts.iterator();
600         }
601
602         public Element getElement() {
603             return root;
604         }
605     }
606     
607     class SSORole extends Role implements SSODescriptor {
608         private XMLEndpointManager artifact = new XMLEndpointManager();
609         private XMLEndpointManager logout = new XMLEndpointManager();
610         private XMLEndpointManager nameid = new XMLEndpointManager();
611         private ArrayList /* <String> */ formats = new ArrayList();
612
613         public SSORole(XMLEntityDescriptor provider, long validUntil, Element e) throws MetadataException {
614             super(provider, validUntil, e);
615             
616             // Check the root element namespace. If SAML2, assume it's the std schema.
617             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
618                 int i;
619                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"ArtifactResolutionService");
620                 for (i=0; i<nlist.getLength(); i++)
621                     artifact.add(new XMLIndexedEndpoint((Element)nlist.item(i)));
622
623                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"SingleLogoutService");
624                 for (i=0; i<nlist.getLength(); i++)
625                     logout.add(new XMLEndpoint((Element)nlist.item(i)));
626
627                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"ManageNameIDService");
628                 for (i=0; i<nlist.getLength(); i++)
629                     nameid.add(new XMLEndpoint((Element)nlist.item(i)));
630
631                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"NameIDFormat");
632                 for (i = 0; i < nlist.getLength(); i++) {
633                                         if (nlist.item(i).hasChildNodes()) {
634                                                 Node tnode = nlist.item(i).getFirstChild();
635                                                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
636                                                         formats.add(tnode.getNodeValue());
637                                                 }
638                                         }
639                                 }
640             }
641             else {
642                 // For old style, we just do SAML 1.1 compatibility with Shib handles.
643                 protocolEnum.add(XML.SAML11_PROTOCOL_ENUM);
644                 formats.add(Constants.SHIB_NAMEID_FORMAT_URI);
645             }
646         }
647
648         public EndpointManager getArtifactResolutionServiceManager() {
649             return artifact;
650         }
651
652         public EndpointManager getSingleLogoutServiceManager() {
653             return logout;
654         }
655
656         public EndpointManager getManageNameIDServiceManager() {
657             return nameid;
658         }
659
660         public Iterator getNameIDFormats() {
661             return formats.iterator();
662         }
663     }
664     
665     class IDPRole extends SSORole implements IDPSSODescriptor, ScopedRoleDescriptor {
666         private ArrayList /* <Scope> */ scopes = new ArrayList();
667         private XMLEndpointManager sso = new XMLEndpointManager();
668         private XMLEndpointManager mapping = new XMLEndpointManager();
669         private XMLEndpointManager idreq = new XMLEndpointManager();
670         private ArrayList /* <String> */ attrprofs = new ArrayList();
671         private ArrayList /* <SAMLAttribute> */ attrs = new ArrayList();
672         private boolean wantAuthnRequestsSigned = false;
673         private String sourceId = null;
674
675         public IDPRole(XMLEntityDescriptor provider, long validUntil, Element e) throws SAMLException {
676             super(provider, validUntil, e);
677             NodeList domains=null;
678             
679             // Check the root element namespace. If SAML2, assume it's the std schema.
680             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
681                 String flag=XML.assign(e.getAttributeNS(null,"WantAuthnRequestsSigned"));
682                 wantAuthnRequestsSigned=(XML.safeCompare(flag,"1") || XML.safeCompare(flag,"true"));
683                 
684                 // Check for extensions.
685                 Element ext=XML.getFirstChildElement(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Extensions");
686                 if (ext != null) {
687                     Element ext1=XML.getFirstChildElement(ext,XML.SAML_ARTIFACT_SOURCEID,"SourceID");
688                     if (ext1 != null && ext1.hasChildNodes())
689                         sourceId=ext1.getFirstChild().getNodeValue();
690                     // Save off any domain elements for later.
691                     domains = ext.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"Scope");
692                 }
693                 
694                 int i;
695                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"SingleSignOnService");
696                 for (i=0; i<nlist.getLength(); i++)
697                     sso.add(new XMLEndpoint((Element)(nlist.item(i))));
698
699                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"NameIDMappingService");
700                 for (i=0; i<nlist.getLength(); i++)
701                     mapping.add(new XMLEndpoint((Element)(nlist.item(i))));
702
703                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AssertionIDRequestService");
704                 for (i=0; i<nlist.getLength(); i++)
705                     idreq.add(new XMLEndpoint((Element)(nlist.item(i))));
706
707                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AttributeProfile");
708                 for (i=0; i<nlist.getLength(); i++) {
709                     if (nlist.item(i).hasChildNodes())
710                         attrprofs.add(nlist.item(i).getFirstChild().getNodeValue());
711                 }
712
713                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"Attribute");
714                 for (i=0; i<nlist.getLength(); i++) {
715                     // For now, we need to convert these to plain SAML 1.1 attributes.
716                     Element src=(Element)(nlist.item(i));
717                     Element copy=e.getOwnerDocument().createElementNS(XML.SAML_NS,"Attribute");
718                     copy.setAttributeNS(null,"AttributeName",src.getAttributeNS(null,"Name"));
719                     copy.setAttributeNS(null,"AttributeNamespace",src.getAttributeNS(null,"NameFormat"));
720                     src=XML.getFirstChildElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
721                     while (src != null) {
722                         src=XML.getNextSiblingElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
723                         Element val=e.getOwnerDocument().createElementNS(XML.SAML_NS,"AttributeValue");
724                         NamedNodeMap attrs = src.getAttributes();
725                         for (int j=0; j<attrs.getLength(); j++)
726                             val.setAttributeNodeNS((Attr)(e.getOwnerDocument().importNode(attrs.item(j),true)));
727                         while (src.hasChildNodes())
728                             val.appendChild(src.getFirstChild());
729                         copy.appendChild(val);
730                     }
731                     attrs.add(SAMLAttribute.getInstance(copy));
732                 }
733             }
734             else {
735                 attrprofs.add(Constants.SHIB_ATTRIBUTE_NAMESPACE_URI);
736                 int i;
737                 domains = e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"Domain");
738                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"HandleService");
739                 for (i=0; i<nlist.getLength(); i++) {
740                     // Manufacture an endpoint for the "Shib" binding.
741                     sso.add(
742                         new XMLEndpoint(Constants.SHIB_AUTHNREQUEST_PROFILE_URI,((Element)nlist.item(i)).getAttributeNS(null,"Location"))
743                         );
744
745                     // We're going to "mock up" a KeyDescriptor that contains the specified Name as a ds:KeyName.
746                     Element kd=e.getOwnerDocument().createElementNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"KeyDescriptor");
747                     Element ki=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyInfo");
748                     Element kn=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyName");
749                     kn.appendChild(
750                         e.getOwnerDocument().createTextNode(((Element)nlist.item(i)).getAttributeNS(null,"Name"))
751                         );
752                     ki.appendChild(kn);
753                     kd.appendChild(ki);
754                     kd.setAttributeNS(null,"use","signing");
755                     keys.add(new XMLKeyDescriptor(kd));
756                 }
757             }
758             
759             if (domains != null) {
760                 for (int i=0; i < domains.getLength(); i++) {
761                     String dom=(domains.item(i).hasChildNodes()) ? domains.item(i).getFirstChild().getNodeValue() : null;
762                     if (dom != null) {
763                         String regexp=XML.assign(((Element)domains.item(i)).getAttributeNS(null,"regexp"));
764                         scopes.add(
765                             new Scope(dom,(XML.safeCompare(regexp,"true") || XML.safeCompare(regexp,"1")))
766                             );
767                     }
768                 }
769             }
770         }
771
772         public Iterator getScopes() {
773             return scopes.iterator();
774         }
775
776         public boolean getWantAuthnRequestsSigned() {
777             return wantAuthnRequestsSigned;
778         }
779
780         public EndpointManager getSingleSignOnServiceManager() {
781             return sso;
782         }
783
784         public EndpointManager getNameIDMappingServiceManager() {
785             return mapping;
786         }
787
788         public EndpointManager getAssertionIDRequestServiceManager() {
789             return idreq;
790         }
791
792         public Iterator getAttributeProfiles() {
793             return attrprofs.iterator();
794         }
795
796         public Iterator getAttributes() {
797             return attrs.iterator();
798         }
799     }
800
801     class AARole extends Role implements AttributeAuthorityDescriptor, ScopedRoleDescriptor {
802         private ArrayList /* <Scope> */ scopes = new ArrayList();
803         private XMLEndpointManager query = new XMLEndpointManager();
804         private XMLEndpointManager idreq = new XMLEndpointManager();
805         private ArrayList /* <String> */ attrprofs = new ArrayList();
806         private ArrayList /* <String> */ formats = new ArrayList();
807         private ArrayList /* <SAMLAttribute> */ attrs = new ArrayList();
808
809         public AARole(XMLEntityDescriptor provider, long validUntil, Element e) throws SAMLException {
810             super(provider, validUntil, e);
811             NodeList domains=null;
812             
813             // Check the root element namespace. If SAML2, assume it's the std schema.
814             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
815                 
816                 // Check for extensions.
817                 Element ext=XML.getFirstChildElement(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Extensions");
818                 if (ext != null) {
819                     // Save off any domain elements for later.
820                     domains = ext.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"Scope");
821                 }
822                 
823                 int i;
824                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AttributeService");
825                 for (i=0; i<nlist.getLength(); i++)
826                     query.add(new XMLEndpoint((Element)(nlist.item(i))));
827
828                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AssertionIDRequestService");
829                 for (i=0; i<nlist.getLength(); i++)
830                     idreq.add(new XMLEndpoint((Element)(nlist.item(i))));
831
832                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AttributeProfile");
833                 for (i=0; i<nlist.getLength(); i++) {
834                     if (nlist.item(i).hasChildNodes())
835                         attrprofs.add(nlist.item(i).getFirstChild().getNodeValue());
836                 }
837
838                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"Attribute");
839                 for (i=0; i<nlist.getLength(); i++) {
840                     // For now, we need to convert these to plain SAML 1.1 attributes.
841                     Element src=(Element)(nlist.item(i));
842                     Element copy=e.getOwnerDocument().createElementNS(XML.SAML_NS,"Attribute");
843                     copy.setAttributeNS(null,"AttributeName",src.getAttributeNS(null,"Name"));
844                     copy.setAttributeNS(null,"AttributeNamespace",src.getAttributeNS(null,"NameFormat"));
845                     src=XML.getFirstChildElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
846                     while (src != null) {
847                         src=XML.getNextSiblingElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
848                         Element val=e.getOwnerDocument().createElementNS(XML.SAML_NS,"AttributeValue");
849                         NamedNodeMap attrs = src.getAttributes();
850                         for (int j=0; j<attrs.getLength(); j++)
851                             val.setAttributeNodeNS((Attr)(e.getOwnerDocument().importNode(attrs.item(j),true)));
852                         while (src.hasChildNodes())
853                             val.appendChild(src.getFirstChild());
854                         copy.appendChild(val);
855                     }
856                     attrs.add(SAMLAttribute.getInstance(copy));
857                 }
858             }
859             else {
860                 // For old style, we just do SAML 1.1 compatibility with Shib handles.
861                 protocolEnum.add(XML.SAML11_PROTOCOL_ENUM);
862                 formats.add(Constants.SHIB_NAMEID_FORMAT_URI);
863                 attrprofs.add(Constants.SHIB_ATTRIBUTE_NAMESPACE_URI);
864                 domains = e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"Domain");
865                 int i;
866                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AttributeAuthority");
867                 for (i=0; i<nlist.getLength(); i++) {
868                     // Manufacture an endpoint for the SOAP binding.
869                     query.add(
870                         new XMLEndpoint(
871                             SAMLBinding.SOAP,
872                             ((Element)nlist.item(i)).getAttributeNS(null,"Location")
873                             )
874                         );
875
876                     // We're going to "mock up" a KeyDescriptor that contains the specified Name as a ds:KeyName.
877                     Element kd=e.getOwnerDocument().createElementNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"KeyDescriptor");
878                     Element ki=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyInfo");
879                     Element kn=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyName");
880                     kn.appendChild(
881                         e.getOwnerDocument().createTextNode(((Element)nlist.item(i)).getAttributeNS(null,"Name"))
882                         );
883                     ki.appendChild(kn);
884                     kd.appendChild(ki);
885                     kd.setAttributeNS(null,"use","signing");
886                     keys.add(new XMLKeyDescriptor(kd));
887                 }
888             }
889
890             if (domains != null) {
891                 for (int i=0; i < domains.getLength(); i++) {
892                     String dom=(domains.item(i).hasChildNodes()) ? domains.item(i).getFirstChild().getNodeValue() : null;
893                     if (dom != null) {
894                         String regexp=XML.assign(((Element)domains.item(i)).getAttributeNS(null,"regexp"));
895                         scopes.add(
896                             new Scope(dom,(XML.safeCompare(regexp,"true") || XML.safeCompare(regexp,"1")))
897                             );
898                     }
899                 }
900             }
901         }
902
903         public Iterator getScopes() {
904             return scopes.iterator();
905         }
906
907         public EndpointManager getAttributeServiceManager() {
908             return query;
909         }
910
911         public EndpointManager getAssertionIDRequestServiceManager() {
912             return idreq;
913         }
914
915         public Iterator getAttributeProfiles() {
916             return attrprofs.iterator();
917         }
918
919         public Iterator getAttributes() {
920             return attrs.iterator();
921         }
922
923         public Iterator getNameIDFormats() {
924             return formats.iterator();
925         }
926     }
927     
928     class SPRole extends SSORole implements SPSSODescriptor {
929         private boolean authnRequestsSigned = false;
930         private boolean wantAssertionsSigned = false;
931         private XMLEndpointManager asc = new XMLEndpointManager();
932         
933         public SPRole(XMLEntityDescriptor provider, long validUntil, Element e) throws MetadataException {
934             super(provider, validUntil, e);
935
936             // Check the root element namespace. If SAML2, assume it's the std schema.
937             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
938                 String flag=XML.assign(e.getAttributeNS(null,"AuthnRequestsSigned"));
939                 authnRequestsSigned=(XML.safeCompare(flag,"1") || XML.safeCompare(flag,"true"));
940                 flag=XML.assign(e.getAttributeNS(null,"WantAssertionsSigned"));
941                 wantAssertionsSigned=(XML.safeCompare(flag,"1") || XML.safeCompare(flag,"true"));
942                 
943                 int i;
944                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AssertionConsumerService");
945                 for (i=0; i<nlist.getLength(); i++)
946                     asc.add(new XMLIndexedEndpoint((Element)(nlist.item(i))));
947
948                 /*
949                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"Attribute");
950                 for (i=0; i<nlist.getLength(); i++) {
951                     // For now, we need to convert these to plain SAML 1.1 attributes.
952                     Element src=(Element)(nlist.item(i));
953                     Element copy=e.getOwnerDocument().createElementNS(XML.SAML_NS,"Attribute");
954                     copy.setAttributeNS(null,"AttributeName",src.getAttributeNS(null,"Name"));
955                     copy.setAttributeNS(null,"AttributeNamespace",src.getAttributeNS(null,"NameFormat"));
956                     src=XML.getFirstChildElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
957                     while (src != null) {
958                         src=XML.getNextSiblingElement(src,edu.internet2.middleware.shibboleth.common.XML.SAML2ASSERT_NS,"AttributeValue");
959                         Element val=e.getOwnerDocument().createElementNS(XML.SAML_NS,"AttributeValue");
960                         NamedNodeMap attrs = src.getAttributes();
961                         for (int j=0; j<attrs.getLength(); j++)
962                             val.setAttributeNodeNS((Attr)(e.getOwnerDocument().importNode(attrs.item(j),true)));
963                         while (src.hasChildNodes())
964                             val.appendChild(src.getFirstChild());
965                         copy.appendChild(val);
966                     }
967                     attrs.add(SAMLAttribute.getInstance(copy));
968                 }
969                 */
970             }
971             else {
972                 int i;
973                 NodeList nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AssertionConsumerServiceURL");
974                 for (i=0; i<nlist.getLength(); i++) {
975                     // Manufacture an endpoint for the POST profile.
976                     asc.add(
977                         new XMLEndpoint(SAMLBrowserProfile.PROFILE_POST_URI,((Element)nlist.item(i)).getAttributeNS(null,"Location"))
978                         );
979                 }
980                 
981                 nlist=e.getElementsByTagNameNS(edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AttributeRequester");
982                 for (i=0; i<nlist.getLength(); i++) {
983                     // We're going to "mock up" a KeyDescriptor that contains the specified Name as a ds:KeyName.
984                     Element kd=e.getOwnerDocument().createElementNS(edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"KeyDescriptor");
985                     Element ki=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyInfo");
986                     Element kn=e.getOwnerDocument().createElementNS(XML.XMLSIG_NS,"KeyName");
987                     kn.appendChild(
988                         e.getOwnerDocument().createTextNode(((Element)nlist.item(i)).getAttributeNS(null,"Name"))
989                         );
990                     ki.appendChild(kn);
991                     kd.appendChild(ki);
992                     kd.setAttributeNS(null,"use","signing");
993                     keys.add(new XMLKeyDescriptor(kd));
994                 }
995             }
996         }
997
998         public boolean getAuthnRequestsSigned() {
999             return authnRequestsSigned;
1000         }
1001
1002         public boolean getWantAssertionsSigned() {
1003             return wantAssertionsSigned;
1004         }
1005
1006         public EndpointManager getAssertionConsumerServiceManager() {
1007             return asc;
1008         }
1009
1010         public Iterator getAttributeConsumingServices() {
1011             // TODO Auto-generated method stub
1012             return null;
1013         }
1014
1015         public AttributeConsumingService getDefaultAttributeConsumingService() {
1016             // TODO Auto-generated method stub
1017             return null;
1018         }
1019
1020         public AttributeConsumingService getAttributeConsumingServiceByID(String id) {
1021             // TODO Auto-generated method stub
1022             return null;
1023         }
1024     }
1025     
1026     class XMLEntityDescriptor implements ExtendedEntityDescriptor {
1027         private Element root = null;
1028         private EntitiesDescriptor parent = null;
1029         private String id = null;
1030         private URL errorURL = null;
1031         private Organization org = null;
1032         private ArrayList /* <ContactPerson> */ contacts = new ArrayList();
1033         private ArrayList /* <RoleDescriptor> */ roles = new ArrayList();
1034         private AffiliationDescriptor affiliation = null;
1035         private HashMap /* <String,String> */ locs = new HashMap();
1036         private long validUntil = Long.MAX_VALUE;
1037         private ArrayList /* <KeyAuthority> */ keyauths = new ArrayList();
1038         
1039         public XMLEntityDescriptor(Element e, XMLMetadataProvider wrapper, long validUntil, EntitiesDescriptor parent) throws SAMLException {
1040             root = e;
1041             this.parent = parent;
1042             this.validUntil = validUntil;
1043
1044             // Check the root element namespace. If SAML2, assume it's the std schema.
1045             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
1046                 id=e.getAttributeNS(null,"entityID");
1047
1048                 if (e.hasAttributeNS(null,"validUntil")) {
1049                     SimpleDateFormat formatter = null;
1050                     String dateTime = XML.assign(e.getAttributeNS(null,"validUntil"));
1051                     int dot = dateTime.indexOf('.');
1052                     if (dot > 0)
1053                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
1054                     else
1055                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
1056                     formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
1057                     try {
1058                         validUntil=Math.min(validUntil,formatter.parse(dateTime).getTime());
1059                     }
1060                     catch (ParseException e1) {
1061                         log.warn("Entity descriptor contains invalid expiration time");
1062                     }
1063                 }
1064
1065                 Element child=XML.getFirstChildElement(e);
1066                 while (child != null) {
1067                     // Process the various kinds of children that we care about...
1068                     if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Extensions")) {
1069                         Element ext = XML.getFirstChildElement(child,edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"KeyAuthority");
1070                         while (ext != null) {
1071                             keyauths.add(new XMLKeyAuthority(ext));
1072                             ext = XML.getNextSiblingElement(ext,edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"KeyAuthority");
1073                         }
1074                     }
1075                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"ContactPerson")) {
1076                         contacts.add(new XMLContactPerson(child));
1077                     }
1078                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Organization")) {
1079                         org=new XMLOrganization(child);
1080                     }
1081                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AdditionalMetadataLocation")) {
1082                         Node loc=child.getFirstChild();
1083                         if (loc != null)
1084                             locs.put(child.getAttributeNS(null,"namespace"),loc.getNodeValue());
1085                     }
1086                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"IDPSSODescriptor")) {
1087                         roles.add(new IDPRole(this,validUntil,child));
1088                     }
1089                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"AttributeAuthorityDescriptor")) {
1090                         roles.add(new AARole(this,validUntil,child));
1091                     }
1092                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"SPSSODescriptor")) {
1093                         roles.add(new SPRole(this,validUntil,child));
1094                     }
1095                     child = XML.getNextSiblingElement(child);
1096                 }
1097             }
1098             else {
1099                 id=e.getAttributeNS(null,"Name");
1100                 if (e.hasAttributeNS(null,"ErrorURL")) {
1101                     try {
1102                         errorURL=new URL(e.getAttributeNS(null,"ErrorURL"));
1103                     }
1104                     catch (MalformedURLException e1) {
1105                         log.error("Site descriptor contains invalid ErrorURL");
1106                     }
1107                 }
1108                 
1109                 boolean idp=false,aa=false,sp=false;    // only want to build a role once
1110                 Element child=XML.getFirstChildElement(e);
1111                 while (child != null) {
1112                     // Process the various kinds of OriginSite children that we care about...
1113                     if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"Contact")) {
1114                         contacts.add(new XMLContactPerson(child));
1115                     }
1116                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"HandleService") && !idp) {
1117                         // Create the IDP role if needed.
1118                         roles.add(new IDPRole(this, validUntil, e));
1119                         idp=true;
1120                     }
1121                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AttributeAuthority") && !aa) {
1122                         // Create the AA role if needed.
1123                         roles.add(new AARole(this, validUntil, e));
1124                         aa=true;
1125                     }
1126                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AssertionConsumerServiceURL") && !sp) {
1127                         // Create the SP role if needed.
1128                         roles.add(new SPRole(this, validUntil, e));
1129                         sp=true;
1130                     }
1131                     else if (XML.isElementNamed(child,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"AttributeRequester") && !sp) {
1132                         // Create the SP role if needed.
1133                         roles.add(new SPRole(this, validUntil, e));
1134                         sp=true;
1135                     }
1136                     child = XML.getNextSiblingElement(child);
1137                 }
1138             }
1139
1140             // Each map entry is a list of the descriptors with this ID.
1141             ArrayList list;
1142             if (wrapper.sites.containsKey(id)) {
1143                 list = (ArrayList)wrapper.sites.get(id);
1144             }
1145             else {
1146                 list = new ArrayList();
1147                 wrapper.sites.put(id,list);
1148             }
1149             list.add(this);
1150             
1151             // Look for an IdP role, and register the artifact source ID and endpoints.
1152             IDPRole idp=null;
1153             for (int i=0; i<roles.size(); i++) {
1154                 if (roles.get(i) instanceof IDPRole) {
1155                     idp = (IDPRole)roles.get(i);
1156                     if (idp.sourceId != null) {
1157                         if (wrapper.sources.containsKey(idp.sourceId)) {
1158                             list = (ArrayList)wrapper.sources.get(idp.sourceId);
1159                         }
1160                         else {
1161                             list = new ArrayList();
1162                             wrapper.sources.put(idp.sourceId,list);
1163                         }
1164                         list.add(this);
1165                     }
1166                     else {
1167                         String sourceId;
1168                         try {
1169                             sourceId = new String(Hex.encode(Util.generateSourceId(id)));
1170                         }
1171                         catch (NoSuchAlgorithmException e1) {
1172                             log.error("caught exception while encoding sourceId: " + e1.getMessage());
1173                             continue;
1174                         }
1175                         if (wrapper.sources.containsKey(sourceId)) {
1176                             list = (ArrayList)wrapper.sources.get(sourceId);
1177                         }
1178                         else {
1179                             list = new ArrayList();
1180                             wrapper.sources.put(sourceId,list);
1181                         }
1182                         list.add(this);
1183                     }
1184                     Iterator locs=idp.getArtifactResolutionServiceManager().getEndpoints();
1185                     while (locs.hasNext()) {
1186                         String loc=((Endpoint)locs.next()).getLocation();
1187                         if (wrapper.sources.containsKey(loc)) {
1188                             list = (ArrayList)wrapper.sources.get(loc);
1189                         }
1190                         else {
1191                             list = new ArrayList();
1192                             wrapper.sources.put(loc,list);
1193                         }
1194                         list.add(this);
1195                     }
1196                 }
1197             }
1198         }
1199         
1200         public String getId() {
1201             return id;
1202         }
1203
1204         public boolean isValid() {
1205             return System.currentTimeMillis() < validUntil;
1206         }
1207
1208         public Iterator getRoleDescriptors() {
1209             return roles.iterator();
1210         }
1211
1212         public RoleDescriptor getRoleByType(Class type, String protocol) {
1213             for (int i=0; i<roles.size(); i++) {
1214                 RoleDescriptor role = (RoleDescriptor)roles.get(i);
1215                 if (type.isInstance(role) && role.hasSupport(protocol))
1216                     return role;
1217             }
1218             return null;
1219         }
1220
1221         public IDPSSODescriptor getIDPSSODescriptor(String protocol) {
1222             return (IDPSSODescriptor)getRoleByType(IDPSSODescriptor.class, protocol);
1223         }
1224
1225         public SPSSODescriptor getSPSSODescriptor(String protocol) {
1226             return (SPSSODescriptor)getRoleByType(SPSSODescriptor.class, protocol);
1227         }
1228
1229         public AuthnAuthorityDescriptor getAuthnAuthorityDescriptor(String protocol) {
1230             return (AuthnAuthorityDescriptor)getRoleByType(AuthnAuthorityDescriptor.class, protocol);
1231         }
1232
1233         public AttributeAuthorityDescriptor getAttributeAuthorityDescriptor(String protocol) {
1234             return (AttributeAuthorityDescriptor)getRoleByType(AttributeAuthorityDescriptor.class, protocol);
1235         }
1236
1237         public PDPDescriptor getPDPDescriptor(String protocol) {
1238             return (PDPDescriptor)getRoleByType(PDPDescriptor.class, protocol);
1239         }
1240
1241         public AffiliationDescriptor getAffiliationDescriptor() {
1242             return affiliation;
1243         }
1244
1245         public Organization getOrganization() {
1246             return org;
1247         }
1248
1249         public Iterator getContactPersons() {
1250             return contacts.iterator();
1251         }
1252
1253         public Map getAdditionalMetadataLocations() {
1254             return Collections.unmodifiableMap(locs);
1255         }
1256
1257         public EntitiesDescriptor getEntitiesDescriptor() {
1258             return parent;
1259         }
1260
1261         public Element getElement() {
1262             return root;
1263         }
1264         
1265         public long getValidUntil() {
1266             return validUntil;
1267         }
1268         
1269         public URL getErrorURL() {
1270             return errorURL;
1271         }
1272
1273         public Iterator getKeyAuthorities() {
1274             return keyauths.iterator();
1275         }
1276     }
1277     
1278     class XMLEntitiesDescriptor implements ExtendedEntitiesDescriptor {
1279         private Element root = null;
1280         private EntitiesDescriptor parent = null;
1281         private String name = null;
1282         private ArrayList /* <EntitiesDescriptor> */ groups = new ArrayList();
1283         private ArrayList /* <EntityDescriptor> */ providers = new ArrayList();
1284         private long validUntil = Long.MAX_VALUE;
1285         private ArrayList /* <KeyAuthority> */ keyauths = new ArrayList();
1286         
1287         public XMLEntitiesDescriptor(Element e, XMLMetadataProvider wrapper, long validUntil, EntitiesDescriptor parent) throws SAMLException {
1288             root = e;
1289             this.parent = parent;
1290             this.validUntil = validUntil;
1291             name = XML.assign(e.getAttributeNS(null, "Name"));
1292
1293             // Check the root element namespace. If SAML2, assume it's the std schema.
1294             if (edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS.equals(e.getNamespaceURI())) {
1295
1296                 if (e.hasAttributeNS(null,"validUntil")) {
1297                     SimpleDateFormat formatter = null;
1298                     String dateTime = XML.assign(e.getAttributeNS(null,"validUntil"));
1299                     int dot = dateTime.indexOf('.');
1300                     if (dot > 0)
1301                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
1302                     else
1303                         formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
1304                     formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
1305                     try {
1306                         validUntil=Math.min(validUntil,formatter.parse(dateTime).getTime());
1307                     }
1308                     catch (ParseException e1) {
1309                         log.warn("Entities descriptor contains invalid expiration time");
1310                     }
1311                 }
1312
1313                 e = XML.getFirstChildElement(e);
1314                 while (e != null) {
1315                     if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"Extensions")) {
1316                         Element ext = XML.getFirstChildElement(e,edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"KeyAuthority");
1317                         while (ext != null) {
1318                             keyauths.add(new XMLKeyAuthority(ext));
1319                             ext = XML.getNextSiblingElement(ext,edu.internet2.middleware.shibboleth.common.XML.SHIBMETA_NS,"KeyAuthority");
1320                         }
1321                     }
1322                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EntitiesDescriptor"))
1323                         groups.add(new XMLEntitiesDescriptor(e, wrapper, this.validUntil, this));
1324                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SAML2META_NS,"EntityDescriptor"))
1325                         providers.add(new XMLEntityDescriptor(e, wrapper, this.validUntil, this));
1326                     e = XML.getNextSiblingElement(e);
1327                 }
1328             }
1329             else {
1330                 e = XML.getFirstChildElement(e);
1331                 while (e != null) {
1332                     if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"SiteGroup"))
1333                         groups.add(new XMLEntitiesDescriptor(e, wrapper, this.validUntil, this));
1334                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"OriginSite"))
1335                         providers.add(new XMLEntityDescriptor(e, wrapper, this.validUntil, this));
1336                     else if (XML.isElementNamed(e,edu.internet2.middleware.shibboleth.common.XML.SHIB_NS,"DestinationSite"))
1337                         providers.add(new XMLEntityDescriptor(e, wrapper, this.validUntil, this));
1338                     e = XML.getNextSiblingElement(e);
1339                 }
1340             }
1341         }
1342
1343         public String getName() {
1344             return name;
1345         }
1346
1347         public boolean isValid() {
1348             return System.currentTimeMillis() < validUntil;
1349         }
1350
1351         public EntitiesDescriptor getEntitiesDescriptor() {
1352             return parent;
1353         }
1354
1355         public Iterator getEntitiesDescriptors() {
1356             return groups.iterator();
1357         }
1358
1359         public Iterator getEntityDescriptors() {
1360             return providers.iterator();
1361         }
1362
1363         public Element getElement() {
1364             return root;
1365         }
1366
1367         public Iterator getKeyAuthorities() {
1368             return keyauths.iterator();
1369         }
1370     }
1371 }