All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * Licensed to the University Corporation for Advanced Internet Development, 
 * Inc. (UCAID) under one or more contributor license agreements.  See the 
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID licenses this file to You under the Apache 
 * License, Version 2.0 (the "License"); you may not use this file except in 
 * compliance with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opensaml.saml.metadata.resolver.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import net.shibboleth.utilities.java.support.xml.QNameSupport;

import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Unmarshaller;
import org.opensaml.core.xml.io.UnmarshallerFactory;
import org.opensaml.core.xml.io.UnmarshallingException;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.metadata.resolver.filter.FilterException;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
import org.opensaml.saml.saml2.common.SAML2Support;
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import com.google.common.base.Strings;

/** An abstract, base, implementation of a metadata provider. */
public abstract class AbstractMetadataResolver extends AbstractIdentifiableInitializableComponent implements
        MetadataResolver {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(AbstractMetadataResolver.class);

    /** Unmarshaller factory used to get an unmarshaller for the metadata DOM. */
    private UnmarshallerFactory unmarshallerFactory;

    /** Whether metadata is required to be valid. */
    private boolean requireValidMetadata;

    /** Filter applied to all metadata. */
    private MetadataFilter mdFilter;

    /**
     * Whether problems during initialization should cause the provider to fail or go on without metadata. The
     * assumption being that in most cases a provider will recover at some point in the future. Default: true.
     */
    private boolean failFastInitialization;

    /** Backing store for runtime EntityDescriptor data. */
    private EntityBackingStore entityBackingStore;

    /** Pool of parsers used to process XML. */
    private ParserPool parser;

    /** Constructor. */
    public AbstractMetadataResolver() {
        failFastInitialization = true;
        requireValidMetadata = true;
        unmarshallerFactory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory();
    }

    /** {@inheritDoc} */
    @Override public boolean isRequireValidMetadata() {
        return requireValidMetadata;
    }

    /** {@inheritDoc} */
    @Override public void setRequireValidMetadata(boolean require) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        requireValidMetadata = require;
    }

    /** {@inheritDoc} */
    @Override @Nullable public MetadataFilter getMetadataFilter() {
        return mdFilter;
    }

    /** {@inheritDoc} */
    @Override public void setMetadataFilter(@Nullable MetadataFilter newFilter) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        mdFilter = newFilter;
    }

    /**
     * Gets whether problems during initialization should cause the provider to fail or go on without metadata. The
     * assumption being that in most cases a provider will recover at some point in the future.
     * 
     * @return whether problems during initialization should cause the provider to fail
     */
    public boolean isFailFastInitialization() {
        return failFastInitialization;
    }

    /**
     * Sets whether problems during initialization should cause the provider to fail or go on without metadata. The
     * assumption being that in most cases a provider will recover at some point in the future.
     * 
     * @param failFast whether problems during initialization should cause the provider to fail
     */
    public void setFailFastInitialization(boolean failFast) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        failFastInitialization = failFast;
    }

    /**
     * Gets the pool of parsers to use to parse XML.
     * 
     * @return pool of parsers to use to parse XML
     */
    @Nonnull public ParserPool getParserPool() {
        return parser;
    }

    /**
     * Sets the pool of parsers to use to parse XML.
     * 
     * @param pool pool of parsers to use to parse XML
     */
    public void setParserPool(@Nonnull final ParserPool pool) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
        parser = Constraint.isNotNull(pool, "ParserPool may not be null");
    }

    /** {@inheritDoc} */
    @Override @Nullable public EntityDescriptor resolveSingle(CriteriaSet criteria) throws ResolverException {
        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        Iterable iterable = resolve(criteria);
        if (iterable != null) {
            Iterator iterator = iterable.iterator();
            if (iterator != null && iterator.hasNext()) {
                return iterator.next();
            }
        }
        return null;
    }

    /**
     * Get the XMLObject unmarshaller factory to use.
     * 
     * @return the unmarshaller factory instance to use
     */
    protected UnmarshallerFactory getUnmarshallerFactory() {
        return unmarshallerFactory;
    }

    /** {@inheritDoc} */
    @Override protected final void doInitialize() throws ComponentInitializationException {
        super.doInitialize();

        try {
            initMetadataResolver();
        } catch (ComponentInitializationException e) {
            if (failFastInitialization) {
                log.error("Metadata provider failed to properly initialize, fail-fast=true, halting", e);
                throw e;
            } else {
                log.error("Metadata provider failed to properly initialize, fail-fast=false, "
                        + "continuing on in a degraded state", e);
            }
        }
    }

    /** {@inheritDoc} */
    @Override protected void doDestroy() {
        unmarshallerFactory = null;
        mdFilter = null;
        entityBackingStore = null;
        parser = null;

        super.doDestroy();
    }

    /**
     * Subclasses should override this method to perform any initialization logic necessary. Default implementation is a
     * no-op.
     * 
     * @throws ComponentInitializationException thrown if there is a problem initializing the provider
     */
    protected void initMetadataResolver() throws ComponentInitializationException {

    }

    /**
     * Unmarshalls the metadata from the given stream. The stream is closed by this method and the returned metadata
     * released its DOM representation.
     * 
     * @param metadataInput the input reader to the metadata.
     * 
     * @return the unmarshalled metadata
     * 
     * @throws UnmarshallingException thrown if the metadata can no be unmarshalled
     */
    @Nonnull protected XMLObject unmarshallMetadata(@Nonnull final InputStream metadataInput)
            throws UnmarshallingException {

        try {
            if (parser == null) {
                throw new UnmarshallingException("ParserPool is null, can't parse input stream");
            }
            log.trace("Parsing retrieved metadata into a DOM object");
            Document mdDocument = parser.parse(metadataInput);

            log.trace("Unmarshalling and caching metadata DOM");
            Unmarshaller unmarshaller = getUnmarshallerFactory().getUnmarshaller(mdDocument.getDocumentElement());
            if (unmarshaller == null) {
                String msg =
                        "No unmarshaller registered for document element "
                                + QNameSupport.getNodeQName(mdDocument.getDocumentElement());
                log.error(msg);
                throw new UnmarshallingException(msg);
            }
            XMLObject metadata = unmarshaller.unmarshall(mdDocument.getDocumentElement());
            return metadata;
        } catch (Exception e) {
            throw new UnmarshallingException(e);
        } finally {
            try {
                metadataInput.close();
            } catch (IOException e2) {
                log.debug("Failed to close input: {}", e2);
            }
        }
    }

    /**
     * Filters the given metadata.
     * 
     * @param metadata the metadata to be filtered
     * 
     * @return the filtered metadata
     * 
     * @throws FilterException thrown if there is an error filtering the metadata
     */
    @Nullable protected XMLObject filterMetadata(@Nullable final XMLObject metadata) throws FilterException {
        if (getMetadataFilter() != null) {
            log.debug("Applying metadata filter");
            return getMetadataFilter().filter(metadata);
        } else {
            return metadata;
        }
    }

    /**
     * Releases the DOM representation from the metadata object.
     * 
     * @param metadata the metadata object
     */
    protected void releaseMetadataDOM(@Nullable final XMLObject metadata) {
        if (metadata != null) {
            metadata.releaseDOM();
            metadata.releaseChildrenDOM(true);
        }
    }

    /**
     * Returns whether the given descriptor is valid. If valid metadata is not required this method always returns true.
     * 
     * @param descriptor the descriptor to check
     * 
     * @return true if valid metadata is not required or the given descriptor is valid, false otherwise
     */
    protected boolean isValid(@Nullable final XMLObject descriptor) {
        if (descriptor == null) {
            return false;
        }

        if (!isRequireValidMetadata()) {
            return true;
        }

        return SAML2Support.isValid(descriptor);
    }

    /**
     * Get list of descriptors matching an entityID.
     * 
     * @param entityID  entityID to lookup
     * @return  a list of descriptors
     * @throws ResolverException if an error occurs
     */
    @Nonnull @NonnullElements protected List lookupEntityID(@Nonnull @NotEmpty final String entityID)
            throws ResolverException {
        if (!isInitialized()) {
            throw new ResolverException("Metadata resolver has not been initialized");
        }

        if (Strings.isNullOrEmpty(entityID)) {
            log.debug("EntityDescriptor entityID was null or empty, skipping search for it");
            return Collections.emptyList();
        }

        List descriptors = lookupIndexedEntityID(entityID);
        if (descriptors.isEmpty()) {
            log.debug("Metadata backing store does not contain any EntityDescriptors with the ID: {}", entityID);
            return descriptors;
        }

        Iterator entitiesIter = descriptors.iterator();
        while (entitiesIter.hasNext()) {
            EntityDescriptor descriptor = entitiesIter.next();
            if (!isValid(descriptor)) {
                log.debug("Metadata backing store contained an EntityDescriptor with the ID: {}, " 
                        + " but it was no longer valid", entityID);
                entitiesIter.remove();
            }
        }

        return descriptors;
    }

    /**
     * Lookup the specified entityID from the index. The returned list will be a copy of what is stored in the backing
     * index, and is safe to be manipulated by callers.
     * 
     * @param entityID the entityID to lookup
     * 
     * @return list copy of indexed entityID's, may be empty, will never be null
     */
    @Nonnull @NonnullElements protected List lookupIndexedEntityID(
            @Nonnull @NotEmpty final String entityID) {
        List descriptors = getBackingStore().getIndexedDescriptors().get(entityID);
        if (descriptors != null) {
            return new ArrayList<>(descriptors);
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Create a new backing store instance for EntityDescriptor data. Subclasses may override to return a more
     * specialized subclass type. Note this method does not make the returned backing store the effective one in use.
     * The caller is responsible for calling {@link #setBackingStore(EntityBackingStore)} to make it the effective
     * instance in use.
     * 
     * @return the new backing store instance
     */
    @Nonnull protected EntityBackingStore createNewBackingStore() {
        return new EntityBackingStore();
    }

    /**
     * Get the EntityDescriptor backing store currently in use by the metadata resolver.
     * 
     * @return the current effective entity backing store
     */
    @Nonnull protected EntityBackingStore getBackingStore() {
        return entityBackingStore;
    }

    /**
     * Set the EntityDescriptor backing store currently in use by the metadata resolver.
     * 
     * @param newBackingStore the new entity backing store
     */
    protected void setBackingStore(@Nonnull EntityBackingStore newBackingStore) {
        entityBackingStore = Constraint.isNotNull(newBackingStore, "EntityBackingStore may not be null");
    }

    /**
     * Pre-process the specified entity descriptor, updating the specified entity backing store instance as necessary.
     * 
     * @param entityDescriptor the target entity descriptor to process
     * @param backingStore the backing store instance to update
     */
    protected void preProcessEntityDescriptor(@Nonnull final EntityDescriptor entityDescriptor,
            @Nonnull final EntityBackingStore backingStore) {

        backingStore.getOrderedDescriptors().add(entityDescriptor);
        indexEntityDescriptor(entityDescriptor, backingStore);
    }
    
    /**
     * Remove from the backing store all metadata for the entity with the given entity ID.
     * 
     * @param entityID the entity ID of the metadata to remove
     * @param backingStore the backing store instance to update
     */
    protected void removeByEntityID(@Nonnull final String entityID, @Nonnull final EntityBackingStore backingStore) {
        Map> indexedDescriptors = backingStore.getIndexedDescriptors();
        List descriptors = indexedDescriptors.get(entityID);
        if (descriptors != null) {
            backingStore.getOrderedDescriptors().removeAll(descriptors);
        }
        indexedDescriptors.remove(entityID);
    }

    /**
     * Index the specified entity descriptor, updating the specified entity backing store instance as necessary.
     * 
     * @param entityDescriptor the target entity descriptor to process
     * @param backingStore the backing store instance to update
     */
    protected void indexEntityDescriptor(@Nonnull final EntityDescriptor entityDescriptor,
            @Nonnull final EntityBackingStore backingStore) {

        String entityID = StringSupport.trimOrNull(entityDescriptor.getEntityID());
        if (entityID != null) {
            List entities = backingStore.getIndexedDescriptors().get(entityID);
            if (entities == null) {
                entities = new ArrayList<>();
                backingStore.getIndexedDescriptors().put(entityID, entities);
            } else if (!entities.isEmpty()) {
                log.warn("Detected duplicate EntityDescriptor for entityID: {}", entityID);
            }
            entities.add(entityDescriptor);
        }
    }

    /**
     * Pre-process the specified entities descriptor, updating the specified entity backing store instance as necessary.
     * 
     * @param entitiesDescriptor the target entities descriptor to process
     * @param backingStore the backing store instance to update
     */
    protected void preProcessEntitiesDescriptor(@Nonnull final EntitiesDescriptor entitiesDescriptor,
            EntityBackingStore backingStore) {

        for (XMLObject child : entitiesDescriptor.getOrderedChildren()) {
            if (child instanceof EntityDescriptor) {
                preProcessEntityDescriptor((EntityDescriptor) child, backingStore);
            } else if (child instanceof EntitiesDescriptor) {
                preProcessEntitiesDescriptor((EntitiesDescriptor) child, backingStore);
            }
        }
    }

    /**
     * The collection of data which provides the backing store for the processed metadata.
     */
    protected class EntityBackingStore {

        /** Index of entity IDs to their descriptors. */
        private Map> indexedDescriptors;

        /** Ordered list of entity descriptors. */
        private List orderedDescriptors;

        /** Constructor. */
        protected EntityBackingStore() {
            indexedDescriptors = new ConcurrentHashMap<>();
            orderedDescriptors = new ArrayList<>();
        }

        /**
         * Get the entity descriptor index.
         * 
         * @return the entity descriptor index
         */
        @Nonnull public Map> getIndexedDescriptors() {
            return indexedDescriptors;
        }

        /**
         * Get the ordered entity descriptor list.
         * 
         * @return the entity descriptor list
         */
        @Nonnull public List getOrderedDescriptors() {
            return orderedDescriptors;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy