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

se.litsec.opensaml.saml2.metadata.provider.AbstractMetadataProvider Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/*
 * Copyright 2016-2018 Litsec AB
 *
 * Licensed 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 se.litsec.opensaml.saml2.metadata.provider;

import java.security.cert.X509Certificate;
import java.util.*;
import java.util.function.Predicate;

import javax.annotation.Nonnull;
import javax.xml.namespace.QName;

import org.joda.time.DateTime;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.common.xml.SAMLSchemaBuilder;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain;
import org.opensaml.saml.metadata.resolver.filter.impl.PredicateFilter;
import org.opensaml.saml.metadata.resolver.filter.impl.PredicateFilter.Direction;
import org.opensaml.saml.metadata.resolver.filter.impl.SchemaValidationFilter;
import org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter;
import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver;
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
import org.opensaml.security.credential.CredentialResolver;
import org.opensaml.security.credential.impl.StaticCredentialResolver;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider;
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
import se.litsec.opensaml.utils.ObjectUtils;
import se.litsec.opensaml.utils.PredicateWrapper;

/**
 * Abstract base class for the {@link MetadataProvider} interface.
 * 
 * @author Martin Lindström ([email protected])
 */
public abstract class AbstractMetadataProvider extends AbstractInitializableComponent implements MetadataProvider {

  /** Logging instance. */
  private Logger log = LoggerFactory.getLogger(AbstractMetadataProvider.class);

  /** Whether the metadata returned by queries must be valid. Default: true. */
  private boolean requireValidMetadata = true;

  /**
   * 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: false.
   */
  private boolean failFastInitialization = false;

  /**
   * The certificate that was used to sign metadata that is downloaded. If this attribute is assigned the provider is
   * configured to expect a valid signature on downloaded metadata.
   */
  private X509Certificate signatureVerificationCertificate = null;

  /** Tells whether XML schema validation should be performed on downloaded metadata. Default: false. */
  private boolean performSchemaValidation = false;

  /** A list of inclusion predicates that will be applied to downloaded metadata. */
  private List> inclusionPredicates = null;

  /** A list of exclusion predicates that will be applied to downloaded metadata. */
  private List> exclusionPredicates = null;

  /** The downloaded metadata. */
  private XMLObject metadata;

  /** The time when the metadata was downloaded. */
  private DateTime downloadTime;

  /** {@inheritDoc} */
  @Override
  public synchronized Optional getMetadata() {
    return Optional.ofNullable(this.metadata);
  }

  /** {@inheritDoc} */
  @Override
  public Optional getMetadataDOM() throws MarshallingException {
    Optional md = this.getMetadata();
    if (!md.isPresent()) {
      return Optional.empty();
    }
    if (md.get().getDOM() != null) {
      return Optional.of(md.get().getDOM());
    }
    return Optional.of(ObjectUtils.marshall(md.get()));
  }

  /** {@inheritDoc} */
  @Override
  public Optional getLastUpdate() {
    if (RefreshableMetadataResolver.class.isInstance(this.getMetadataResolver())) {
      return Optional.ofNullable(((RefreshableMetadataResolver) this.getMetadataResolver()).getLastUpdate());
    }
    return Optional.ofNullable(this.downloadTime);
  }

  /** {@inheritDoc} */
  @Override
  public void refresh() throws ResolverException {
    if (RefreshableMetadataResolver.class.isInstance(this.getMetadataResolver())) {
      ((RefreshableMetadataResolver) this.getMetadataResolver()).refresh();
    }
    else {
      log.debug("Refresh of metadata is not supported by {}", this.getClass().getName());
    }
  }

  /** {@inheritDoc} */
  @Override
  public Iterable iterator() {
    return new EntityDescriptorIterator(this.getMetadata());
  }

  /** {@inheritDoc} */
  @Override
  public Iterable iterator(QName role) {
    return new EntityDescriptorIterator(this.getMetadata(), role);
  }

  /** {@inheritDoc} */
  @Override
  public Optional getEntityDescriptor(String entityID) throws ResolverException {
    CriteriaSet criteria = new CriteriaSet();
    criteria.add(new EntityIdCriterion(entityID));
    return Optional.ofNullable(this.getMetadataResolver().resolveSingle(criteria));
  }

  /** {@inheritDoc} */
  @Override
  public Optional getIDPSSODescriptor(String entityID) throws ResolverException {
    CriteriaSet criteria = new CriteriaSet();
    criteria.add(new EntityIdCriterion(entityID));
    criteria.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
    EntityDescriptor ed = this.getMetadataResolver().resolveSingle(criteria);
    return ed != null ? Optional.ofNullable(ed.getIDPSSODescriptor(SAMLConstants.SAML20P_NS)) : Optional.empty();
  }

  /** {@inheritDoc} */
  @Override
  public Optional getSPSSODescriptor(String entityID) throws ResolverException {
    CriteriaSet criteria = new CriteriaSet();
    criteria.add(new EntityIdCriterion(entityID));
    criteria.add(new EntityRoleCriterion(SPSSODescriptor.DEFAULT_ELEMENT_NAME));
    EntityDescriptor ed = this.getMetadataResolver().resolveSingle(criteria);
    return ed != null ? Optional.ofNullable(ed.getSPSSODescriptor(SAMLConstants.SAML20P_NS)) : Optional.empty();
  }

  /** {@inheritDoc} */
  @Override
  public List getIdentityProviders() throws ResolverException {
    List list = new ArrayList<>();
    Iterable it = this.iterator(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
    it.forEach(list::add);
    return list;
  }

  /** {@inheritDoc} */
  @Override
  public List getServiceProviders() throws ResolverException {
    List list = new ArrayList<>();
    Iterable it = this.iterator(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    it.forEach(list::add);
    return list;
  }

  /**
   * Assigns the metadata that was downloaded.
   * 
   * @param metadata
   *          metadata object
   */
  private synchronized void setMetadata(XMLObject metadata) {
    this.metadata = metadata;
    this.downloadTime = new DateTime();
  }

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

    try {
      this.createMetadataResolver(this.requireValidMetadata, this.failFastInitialization, this.createFilter());
    }
    catch (ResolverException e) {
      throw new ComponentInitializationException(e);
    }
    this.initializeMetadataResolver();
  }

  /**
   * Creates the filter(s) that this instance should be configured with.
   * 
   * @return a metadata filter
   */
  protected MetadataFilter createFilter() {

    List filters = new ArrayList<>();

    // Verify signature?
    if (this.signatureVerificationCertificate != null) {
      CredentialResolver credentialResolver = new StaticCredentialResolver(new BasicX509Credential(this.signatureVerificationCertificate));
      KeyInfoCredentialResolver keyInfoResolver = new BasicProviderKeyInfoCredentialResolver(Arrays.asList(new RSAKeyValueProvider(),
        new InlineX509DataProvider(), new DSAKeyValueProvider(), new DEREncodedKeyValueProvider()));
      ExplicitKeySignatureTrustEngine trustEngine = new ExplicitKeySignatureTrustEngine(credentialResolver, keyInfoResolver);
      filters.add(new SignatureValidationFilter(trustEngine));
    }

    // Schema validation?
    if (this.performSchemaValidation) {
      filters.add(new SchemaValidationFilter(new SAMLSchemaBuilder(SAMLSchemaBuilder.SAML1Version.SAML_11)));
    }

    // Inclusion predicates?
    if (this.inclusionPredicates != null) {
      for (Predicate p : this.inclusionPredicates) {
        filters.add(new PredicateFilter(Direction.INCLUDE, PredicateWrapper.wrap(p)));
      }
    }

    // Exclusion predicates?
    if (this.exclusionPredicates != null) {
      for (Predicate p : this.exclusionPredicates) {
        filters.add(new PredicateFilter(Direction.EXCLUDE, PredicateWrapper.wrap(p)));
      }
    }

    // Install the mandatory filter that saves downloaded metadata.
    filters.add(metadata -> {
      setMetadata(metadata);
      return metadata;
    });

    if (filters.size() == 1) {
      return filters.get(0);
    }
    else {
      MetadataFilterChain chain = new MetadataFilterChain();
      chain.setFilters(filters);
      return chain;
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void doDestroy() {
    super.doDestroy();
    this.destroyMetadataResolver();
  }

  /**
   * Creates the specific {@link MetadataResolver} instance for the provider implementation.
   * 

* The {@code filter} parameter is a {@link MetadataFilter} that must be installed for the resolver. Any other * filters that should be installed by the specific instance should be placed last in a filter chain. *

* * @param requireValidMetadata * should be passed into {@link MetadataResolver#setRequireValidMetadata(boolean)} * @param failFastInitialization * should be passed into {@link AbstractMetadataResolver#setFailFastInitialization(boolean)} (if applicable) * @param filter * filter that must be installed for the resolver * @throws ResolverException * for errors creating the resolver */ protected abstract void createMetadataResolver(boolean requireValidMetadata, boolean failFastInitialization, MetadataFilter filter) throws ResolverException; /** * Initializes the metadata resolver. * * @throws ComponentInitializationException * for initialization errors */ protected abstract void initializeMetadataResolver() throws ComponentInitializationException; /** * Destroys the metadata resolver. */ protected abstract void destroyMetadataResolver(); /** * Sets whether the metadata returned by queries must be valid. * * @param requireValidMetadata * whether the metadata returned by queries must be valid */ public void setRequireValidMetadata(boolean requireValidMetadata) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); this.requireValidMetadata = requireValidMetadata; } /** * 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); failFastInitialization = failFast; } /** * Assigns the certificate that was used to sign metadata that is downloaded. If this attribute is assigned the * provider is configured to expect a valid signature on downloaded metadata. * * @param signatureVerificationCertificate * the certificate to assign */ public void setSignatureVerificationCertificate(X509Certificate signatureVerificationCertificate) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); this.signatureVerificationCertificate = signatureVerificationCertificate; } /** * Assigns whether XML schema validation should be performed on downloaded metadata. * * @param performSchemaValidation * whether schema validation should be performed */ public void setPerformSchemaValidation(boolean performSchemaValidation) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); this.performSchemaValidation = performSchemaValidation; } /** * Assigns a list of inclusion predicates that will be applied to downloaded metadata. * * @param inclusionPredicates * predicates * @see MetadataProviderPredicates */ public void setInclusionPredicates(List> inclusionPredicates) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); this.inclusionPredicates = inclusionPredicates; } /** * Assigns a list of exclusion predicates that will be applied to downloaded metadata. * * @param exclusionPredicates * predicates * @see MetadataProviderPredicates */ public void setExclusionPredicates(List> exclusionPredicates) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); this.exclusionPredicates = exclusionPredicates; } /** * Iterates over EntitiesDescriptor or EntityDescriptor. */ protected static class EntityDescriptorIterator implements Iterator, Iterable { private Iterator iterator = null; public EntityDescriptorIterator(Optional metadata) { this(metadata, null); } public EntityDescriptorIterator(Optional metadata, QName role) { if (!metadata.isPresent()) { return; } if (metadata.get() instanceof EntityDescriptor) { this.iterator = Collections.singletonList((EntityDescriptor) metadata.get()).iterator(); } else if (metadata.get() instanceof EntitiesDescriptor) { List edList = setup((EntitiesDescriptor) metadata.get(), role); this.iterator = edList.iterator(); } else { throw new IllegalArgumentException("Expected EntityDescriptor or EntitiesDescriptor"); } } private static List setup(EntitiesDescriptor entitiesDescriptor, QName role) { List edList = new ArrayList<>(); entitiesDescriptor.getEntityDescriptors().stream().filter(filterRole(role)).forEach(edList::add); for (EntitiesDescriptor ed : entitiesDescriptor.getEntitiesDescriptors()) { edList.addAll(setup(ed, role)); } return edList; } public static Predicate filterRole(QName role) { return e -> role == null || !e.getRoleDescriptors(role).isEmpty(); } @Override public boolean hasNext() { return this.iterator != null && this.iterator.hasNext(); } @Override public EntityDescriptor next() { if (this.iterator != null) { return this.iterator.next(); } throw new NoSuchElementException(); } @Nonnull @Override public Iterator iterator() { return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy