View Javadoc

1   /*
2    * Copyright 2006 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 org.opensaml.saml2.metadata.provider;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  
25  import javax.xml.namespace.QName;
26  
27  import org.opensaml.saml2.common.SAML2Helper;
28  import org.opensaml.saml2.metadata.EntitiesDescriptor;
29  import org.opensaml.saml2.metadata.EntityDescriptor;
30  import org.opensaml.saml2.metadata.RoleDescriptor;
31  import org.opensaml.xml.XMLObject;
32  import org.opensaml.xml.io.Unmarshaller;
33  import org.opensaml.xml.io.UnmarshallingException;
34  import org.opensaml.xml.parse.ParserPool;
35  import org.opensaml.xml.util.DatatypeHelper;
36  import org.opensaml.xml.util.XMLHelper;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import org.slf4j.helpers.MessageFormatter;
40  import org.w3c.dom.Document;
41  
42  /** An abstract, base, implementation of a metadata provider. */
43  public abstract class AbstractMetadataProvider extends BaseMetadataProvider {
44  
45      /** Class logger. */
46      private final Logger log = LoggerFactory.getLogger(AbstractMetadataProvider.class);
47  
48      /** Cache of entity IDs to their descriptors. */
49      private HashMap<String, EntityDescriptor> indexedDescriptors;
50  
51      /** Pool of parsers used to process XML. */
52      private ParserPool parser;
53  
54      /** Constructor. */
55      public AbstractMetadataProvider() {
56          super();
57          indexedDescriptors = new HashMap<String, EntityDescriptor>();
58      }
59  
60      /** {@inheritDoc} */
61      public EntitiesDescriptor getEntitiesDescriptor(String name) throws MetadataProviderException {
62          if (DatatypeHelper.isEmpty(name)) {
63              return null;
64          }
65  
66          XMLObject metadata = getMetadata();
67          if (metadata == null) {
68              log.debug("Metadata document was empty, unable to look for an EntitiesDescriptor with the name {}", name);
69              return null;
70          }
71  
72          if (metadata instanceof EntitiesDescriptor) {
73              EntitiesDescriptor descriptor = (EntitiesDescriptor) metadata;
74              return getEntitiesDescriptorByName(name, descriptor);
75          }
76  
77          log.debug("Metadata document does not contain an EntitiesDescriptor with the name {}", name);
78          return null;
79      }
80  
81      /** {@inheritDoc} */
82      public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException {
83          if (DatatypeHelper.isEmpty(entityID)) {
84              return null;
85          }
86  
87          XMLObject metadata = getMetadata();
88          if (metadata == null) {
89              log.debug("Metadata document was empty, unable to look for an EntityDescriptor with the ID {}", entityID);
90              return null;
91          }
92  
93          EntityDescriptor descriptor = getEntityDescriptorById(entityID, metadata);
94          if (descriptor == null) {
95              log.debug("Metadata document does not contain an EntityDescriptor with the ID {}", entityID);
96              return null;
97          }
98          return descriptor;
99      }
100 
101     /** {@inheritDoc} */
102     public List<RoleDescriptor> getRole(String entityID, QName roleName) throws MetadataProviderException {
103         if (DatatypeHelper.isEmpty(entityID) || roleName == null) {
104             return null;
105         }
106 
107         EntityDescriptor entity = getEntityDescriptor(entityID);
108         if (entity != null) {
109             return entity.getRoleDescriptors(roleName);
110         } else {
111             return null;
112         }
113     }
114 
115     /** {@inheritDoc} */
116     public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol)
117             throws MetadataProviderException {
118         if (DatatypeHelper.isEmpty(entityID) || roleName == null || DatatypeHelper.isEmpty(supportedProtocol)) {
119             return null;
120         }
121 
122         List<RoleDescriptor> roles = getRole(entityID, roleName);
123         if (roles == null || roles.isEmpty()) {
124             return null;
125         }
126 
127         Iterator<RoleDescriptor> rolesItr = roles.iterator();
128         RoleDescriptor role;
129         while (rolesItr.hasNext()) {
130             role = rolesItr.next();
131             if (role != null && role.isSupportedProtocol(supportedProtocol)) {
132                 return role;
133             }
134         }
135 
136         return null;
137     }
138 
139     /**
140      * Gets the pool of parsers to use to parse XML.
141      * 
142      * @return pool of parsers to use to parse XML
143      */
144     public ParserPool getParserPool() {
145         return parser;
146     }
147 
148     /**
149      * Sets the pool of parsers to use to parse XML.
150      * 
151      * @param pool pool of parsers to use to parse XML
152      */
153     public void setParserPool(ParserPool pool) {
154         parser = pool;
155     }
156 
157     /**
158      * Clears the entity ID to entity descriptor index.
159      */
160     protected void clearDescriptorIndex() {
161         indexedDescriptors.clear();
162     }
163 
164     /**
165      * Unmarshalls the metadata from the given stream. The stream is closed by this method and the returned metadata
166      * released its DOM representation.
167      * 
168      * @param metadataInput the input reader to the metadata.
169      * 
170      * @return the unmarshalled metadata
171      * 
172      * @throws UnmarshallingException thrown if the metadata can no be unmarshalled
173      */
174     protected XMLObject unmarshallMetadata(InputStream metadataInput) throws UnmarshallingException {
175         try {
176             log.trace("Parsing retrieved metadata into a DOM object");
177             Document mdDocument = parser.parse(metadataInput);
178 
179             log.trace("Unmarshalling and caching metdata DOM");
180             Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(mdDocument.getDocumentElement());
181             if (unmarshaller == null) {
182                 String msg = MessageFormatter.format("No unmarshaller registered for document element {}", XMLHelper
183                         .getNodeQName(mdDocument.getDocumentElement()));
184                 log.error(msg);
185                 throw new UnmarshallingException(msg);
186             }
187             XMLObject metadata = unmarshaller.unmarshall(mdDocument.getDocumentElement());
188             return metadata;
189         } catch (Exception e) {
190             throw new UnmarshallingException(e);
191         } finally {
192             try {
193                 metadataInput.close();
194             } catch (IOException e) {
195                 // ignore
196             }
197         }
198     }
199 
200     /**
201      * Filters the given metadata.
202      * 
203      * @param metadata the metadata to be filtered
204      * 
205      * @throws FilterException thrown if there is an error filtering the metadata
206      */
207     protected void filterMetadata(XMLObject metadata) throws FilterException {
208         if (getMetadataFilter() != null) {
209             log.debug("Applying metadata filter");
210             getMetadataFilter().doFilter(metadata);
211         }
212     }
213 
214     /**
215      * Releases the DOM representation from the metadata object.
216      * 
217      * @param metadata the metadata object
218      */
219     protected void releaseMetadataDOM(XMLObject metadata) {
220         if (metadata != null) {
221             metadata.releaseDOM();
222             metadata.releaseChildrenDOM(true);
223         }
224     }
225 
226     /**
227      * Gets the EntityDescriptor with the given ID from the cached metadata.
228      * 
229      * @param entityID the ID of the entity to get the descriptor for
230      * @param metadata metadata associated with the entity
231      * 
232      * @return the EntityDescriptor
233      */
234     protected EntityDescriptor getEntityDescriptorById(String entityID, XMLObject metadata) {
235         EntityDescriptor descriptor = null;
236 
237         log.debug("Searching for entity descriptor with an entity ID of {}", entityID);
238         if (entityID != null && indexedDescriptors.containsKey(entityID)) {
239             descriptor = indexedDescriptors.get(entityID);
240             if (isValid(descriptor)) {
241                 log.trace("Entity descriptor for the ID {} was found in index cache, returning", entityID);
242                 return descriptor;
243             } else {
244                 indexedDescriptors.remove(descriptor);
245             }
246         }
247 
248         if (metadata != null) {
249             if (metadata instanceof EntityDescriptor) {
250                 log.trace("Metadata root is an entity descriptor, checking if it's the one we're looking for.");
251                 descriptor = (EntityDescriptor) metadata;
252                 if (!DatatypeHelper.safeEquals(descriptor.getEntityID(), entityID)) {
253                     // skip this one, it isn't what we're looking for
254                     descriptor = null;
255                 }
256                 if (!isValid(descriptor)) {
257                     log.trace("Found entity descriptor for entity with ID {} but it is no longer valid, skipping it.",
258                             entityID);
259                     descriptor = null;
260                 }
261             } else {
262                 log
263                         .trace("Metadata was an EntitiesDescriptor, checking if any of its descendant EntityDescriptor elements is the one we're looking for.");
264                 if (metadata instanceof EntitiesDescriptor) {
265                     descriptor = getEntityDescriptorById(entityID, (EntitiesDescriptor) metadata);
266                 }
267             }
268         }
269 
270         if (descriptor != null) {
271             log.trace("Located entity descriptor, creating an index to it for faster lookups");
272             indexedDescriptors.put(entityID, descriptor);
273         }
274 
275         return descriptor;
276     }
277 
278     /**
279      * Gets the entity descriptor with the given ID that is a descendant of the given entities descriptor.
280      * 
281      * @param entityID the ID of the entity whose descriptor is to be fetched
282      * @param descriptor the entities descriptor
283      * 
284      * @return the entity descriptor
285      */
286     protected EntityDescriptor getEntityDescriptorById(String entityID, EntitiesDescriptor descriptor) {
287         log.trace("Checking to see if EntitiesDescriptor {} contains the requested descriptor", descriptor.getName());
288         List<EntityDescriptor> entityDescriptors = descriptor.getEntityDescriptors();
289         if (entityDescriptors != null && !entityDescriptors.isEmpty()) {
290             for (EntityDescriptor entityDescriptor : entityDescriptors) {
291                 log.trace("Checking entity descriptor with entity ID {}", entityDescriptor.getEntityID());
292                 if (DatatypeHelper.safeEquals(entityDescriptor.getEntityID(), entityID) && isValid(entityDescriptor)) {
293                     return entityDescriptor;
294                 }
295             }
296         }
297 
298         log.trace("Checking to see if any of the child entities descriptors contains the entity descriptor requested");
299         EntityDescriptor entityDescriptor;
300         List<EntitiesDescriptor> entitiesDescriptors = descriptor.getEntitiesDescriptors();
301         if (entitiesDescriptors != null && !entitiesDescriptors.isEmpty()) {
302             for (EntitiesDescriptor entitiesDescriptor : descriptor.getEntitiesDescriptors()) {
303                 entityDescriptor = getEntityDescriptorById(entityID, entitiesDescriptor);
304                 if (entityDescriptor != null) {
305                     // We don't need to check for validity because getEntityDescriptorById only returns a valid
306                     // descriptor
307                     return entityDescriptor;
308                 }
309             }
310         }
311 
312         return null;
313     }
314 
315     /**
316      * Gets the entities descriptor with the given name.
317      * 
318      * @param name name of the entities descriptor
319      * @param rootDescriptor the root descriptor to search in
320      * 
321      * @return the EntitiesDescriptor with the given name
322      */
323     protected EntitiesDescriptor getEntitiesDescriptorByName(String name, EntitiesDescriptor rootDescriptor) {
324         EntitiesDescriptor descriptor = null;
325 
326         if (DatatypeHelper.safeEquals(name, rootDescriptor.getName()) && isValid(rootDescriptor)) {
327             descriptor = rootDescriptor;
328         } else {
329             List<EntitiesDescriptor> childDescriptors = rootDescriptor.getEntitiesDescriptors();
330             if (childDescriptors == null || childDescriptors.isEmpty()) {
331                 return null;
332             }
333             for (EntitiesDescriptor childDescriptor : childDescriptors) {
334                 childDescriptor = getEntitiesDescriptorByName(name, childDescriptor);
335                 if (childDescriptor != null) {
336                     descriptor = childDescriptor;
337                 }
338             }
339         }
340 
341         return descriptor;
342     }
343 
344     /**
345      * Returns whether the given descriptor is valid. If valid metadata is not required this method always returns true.
346      * 
347      * @param descriptor the descriptor to check
348      * 
349      * @return true if valid metadata is not required or the given descriptor is valid, false otherwise
350      */
351     protected boolean isValid(XMLObject descriptor) {
352         if (descriptor == null) {
353             return false;
354         }
355 
356         if (!requireValidMetadata()) {
357             return true;
358         }
359 
360         return SAML2Helper.isValid(descriptor);
361     }
362 }