dk.itst.oiosaml.sp.metadata.IdpMetadata.java Source code

Java tutorial

Introduction

Here is the source code for dk.itst.oiosaml.sp.metadata.IdpMetadata.java

Source

/*
 * The contents of this file are subject to the Mozilla Public 
 * License Version 1.1 (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.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an 
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express 
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 *
 * The Original Code is OIOSAML Java Service Provider.
 * 
 * The Initial Developer of the Original Code is Trifork A/S. Portions 
 * created by Trifork A/S are Copyright (C) 2008 Danish National IT 
 * and Telecom Agency (http://www.itst.dk). All Rights Reserved.
 * 
 * Contributor(s):
 *   Joakim Recht <jre@trifork.com>
 *   Rolf Njor Jensen <rolf@trifork.com>
 *
 */
package dk.itst.oiosaml.sp.metadata;

import java.io.File;
import java.io.FilenameFilter;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.metadata.ArtifactResolutionService;
import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
import org.opensaml.saml2.metadata.AttributeService;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.KeyDescriptor;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.SingleSignOnService;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.signature.X509Data;

import dk.itst.oiosaml.common.SAMLUtil;
import dk.itst.oiosaml.configuration.SAMLConfiguration;
import dk.itst.oiosaml.error.Layer;
import dk.itst.oiosaml.error.WrappedException;
import dk.itst.oiosaml.security.SecurityHelper;
import dk.itst.oiosaml.sp.service.util.Constants;

/**
 * Utility class to extract relevant values of the meta data related to the Login Site.
 * 
 * @author Joakim Recht <jre@trifork.com>
 * @author Rolf Njor Jensen <rolf@trifork.com>
 *
 */
public class IdpMetadata {
    public static final String VERSION = "$Id: IdpMetadata.java 2964 2008-06-02 11:34:06Z jre $";
    public static final String METADATA_DIRECTORY = "common.saml2.metadata.idp.directory";
    private static IdpMetadata instance;

    private static final Logger log = Logger.getLogger(IdpMetadata.class);

    private final Map<String, Metadata> metadata = new HashMap<String, Metadata>();

    public IdpMetadata(String protocol, EntityDescriptor... entityDescriptor) {
        for (EntityDescriptor descriptor : entityDescriptor) {
            if (metadata.containsKey(descriptor.getEntityID())) {
                metadata.get(descriptor.getEntityID())
                        .addCertificates(new Metadata(descriptor, protocol).getCertificates());
            } else {
                metadata.put(descriptor.getEntityID(), new Metadata(descriptor, protocol));
            }
        }
    }

    public static IdpMetadata getInstance() {
        if (instance == null) {
            Configuration conf = SAMLConfiguration.getSystemConfiguration();
            String directory = SAMLConfiguration.getStringPrefixedWithBRSHome(conf, METADATA_DIRECTORY);
            File idpDir = new File(directory);
            File[] files = idpDir.listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".xml");
                }
            });
            String protocol = conf.getString(Constants.PROP_PROTOCOL);

            List<EntityDescriptor> descriptors = new ArrayList<EntityDescriptor>();
            for (File md : files) {
                log.info("Loading " + protocol + " metadata from " + md);
                try {
                    XMLObject descriptor = SAMLUtil.unmarshallElementFromFile(md.getAbsolutePath());
                    if (descriptor instanceof EntityDescriptor) {
                        descriptors.add((EntityDescriptor) descriptor);
                    } else if (descriptor instanceof EntitiesDescriptor) {
                        EntitiesDescriptor desc = (EntitiesDescriptor) descriptor;
                        descriptors.addAll(desc.getEntityDescriptors());
                    } else {
                        throw new RuntimeException("Metadata file " + md
                                + " does not contain an EntityDescriptor. Found " + descriptor.getElementQName()
                                + ", expected " + EntityDescriptor.ELEMENT_QNAME);
                    }
                } catch (RuntimeException e) {
                    log.error("Unable to load metadata from " + md
                            + ". File must contain valid XML and have EntityDescriptor as top tag", e);
                    throw e;
                }
            }
            if (descriptors.isEmpty()) {
                throw new IllegalStateException(
                        "No IdP descriptors found in " + directory + "! At least one file is required.");
            }
            instance = new IdpMetadata(protocol, descriptors.toArray(new EntityDescriptor[descriptors.size()]));
        }
        return instance;
    }

    public static void setMetadata(IdpMetadata metadata) {
        instance = metadata;
    }

    public Metadata getMetadata(String entityID) {
        Metadata md = metadata.get(entityID);
        if (md == null) {
            throw new IllegalArgumentException("No metadata found for " + entityID);
        }
        return md;
    }

    /**
     * Check if SAML Discovery Profile should be enabled.
     * 
     * If more than one metadata file exists, discovery should be enabled, and this method will return true.
     */
    public boolean enableDiscovery() {
        return metadata.size() > 1;
    }

    /**
     * Get the first registered metadata.
     * 
     * This method should only be used when {@link #enableDiscovery()} returns <code>true</code>, as the 
     * metadata list is not ordered.
     */
    public Metadata getFirstMetadata() {
        return getMetadata(getEntityIDs().iterator().next());
    }

    public Collection<String> getEntityIDs() {
        return metadata.keySet();
    }

    public Metadata findSupportedEntity(String... entityIds) {
        for (String entityId : entityIds) {
            Metadata md = metadata.get(entityId);
            if (md != null) {
                return md;
            }
        }
        throw new IllegalArgumentException("No supported idp found in " + Arrays.toString(entityIds)
                + ". Supported ids: " + metadata.keySet());
    }

    public static class Metadata {
        private EntityDescriptor entityDescriptor;
        private IDPSSODescriptor idpSSODescriptor;
        private Collection<X509Certificate> certificates = new ArrayList<X509Certificate>();
        private Collection<X509Certificate> validCertificates = new HashSet<X509Certificate>();

        private Metadata(EntityDescriptor entityDescriptor, String protocol) {
            this.entityDescriptor = entityDescriptor;
            idpSSODescriptor = entityDescriptor.getIDPSSODescriptor(protocol);
            try {
                X509Certificate cert = SecurityHelper.buildJavaX509Cert(getCertificateNode().getValue());
                certificates.add(cert);
                validCertificates.add(cert);
            } catch (CertificateException e) {
                throw new WrappedException(Layer.BUSINESS, e);
            }
        }

        public void addCertificates(Collection<X509Certificate> certificates) {
            this.certificates.addAll(certificates);
            this.validCertificates.addAll(certificates);
        }

        /**
         * 
         * @return The entityID of the Login Site
         */
        public String getEntityID() {
            return entityDescriptor.getEntityID();
        }

        /**
         * 
         * @return The location (URL) of {@link ArtifactResolutionService}.
         */
        public String getArtifactResolutionServiceLocation(String binding) throws IllegalArgumentException {
            for (ArtifactResolutionService artifactResolutionService : idpSSODescriptor
                    .getArtifactResolutionServices()) {
                if (SAMLConstants.SAML2_SOAP11_BINDING_URI.equals(artifactResolutionService.getBinding())) {
                    return artifactResolutionService.getLocation();
                }
            }
            throw new IllegalArgumentException("No artifact resolution service for binding " + binding);
        }

        /**
         * Get a signon service location for a specific binding.
         * @param binding SAML binding name,
         * @return The url for the location.
         * @throws IllegalArgumentException if the binding is not present in metadata.
         */
        public String getSingleSignonServiceLocation(String binding) throws IllegalArgumentException {
            for (SingleSignOnService service : idpSSODescriptor.getSingleSignOnServices()) {
                if (service.getBinding().equals(binding)) {
                    return service.getLocation();
                }
            }
            throw new IllegalArgumentException("Binding " + binding + " not found");
        }

        public String getAttributeQueryServiceLocation(String binding) throws IllegalArgumentException {
            AttributeAuthorityDescriptor descriptor = entityDescriptor
                    .getAttributeAuthorityDescriptor(SAMLConstants.SAML20P_NS);
            if (descriptor == null)
                throw new IllegalArgumentException("Metadata does not contain a AttributeAuthorityDescriptor");
            for (AttributeService service : descriptor.getAttributeServices()) {
                if (binding.equals(service.getBinding())) {
                    return service.getLocation();
                }
            }
            throw new IllegalArgumentException("Binding " + binding + " not found in AttributeServices");
        }

        public List<SingleSignOnService> getSingleSignonServices() {
            return idpSSODescriptor.getSingleSignOnServices();
        }

        /**
         * 
         * @return The location (URL) of {@link SingleSignOnService} at the Login Site
         */
        public String getSingleLogoutServiceLocation() {
            String url = null;
            if (idpSSODescriptor.getSingleLogoutServices().size() > 0) {
                SingleLogoutService singleLogoutService = idpSSODescriptor.getSingleLogoutServices().get(0);
                url = singleLogoutService.getLocation();
            }
            return url;
        }

        /**
         * 
         * @return The response location (URL) of {@link SingleSignOnService} at the Login Site
         */
        public String getSingleLogoutServiceResponseLocation() {
            if (idpSSODescriptor.getSingleLogoutServices().size() > 0) {
                List<SingleLogoutService> singleLogoutServices = idpSSODescriptor.getSingleLogoutServices();

                // Prefer POST binding - due to browser redirect limitations.
                SingleLogoutService singleLogoutService = idpSSODescriptor.getSingleLogoutServices().get(0);
                for (SingleLogoutService sls : singleLogoutServices) {
                    if (sls.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) {
                        singleLogoutService = sls;
                        break;
                    }
                }

                String location = singleLogoutService.getResponseLocation();
                if (location == null) {
                    location = singleLogoutService.getLocation();
                }
                return location;
            }
            return null;
        }

        /**
         * 
         * @return The certificate node from the metadata associated with the Login
         *         Site
         */
        private org.opensaml.xml.signature.X509Certificate getCertificateNode() {
            if (idpSSODescriptor != null && idpSSODescriptor.getKeyDescriptors().size() > 0) {
                KeyDescriptor keyDescriptor = idpSSODescriptor.getKeyDescriptors().get(0);
                if (keyDescriptor.getKeyInfo().getX509Datas().size() > 0) {
                    X509Data x509Data = keyDescriptor.getKeyInfo().getX509Datas().get(0);
                    if (x509Data.getX509Certificates().size() > 0) {
                        return x509Data.getX509Certificates().get(0);
                    }
                }
            }
            throw new IllegalStateException("IdP Metadata does not contain a certificate: " + getEntityID());
        }

        Collection<X509Certificate> getAllCertificates() {
            return certificates;
        }

        /**
         * Get a list of all valid certificates for this IdP.
         * 
         * Any expired or revoked certificates will not be included in the list. 
         */
        public Collection<X509Certificate> getCertificates() {
            Collection<X509Certificate> res = new ArrayList<X509Certificate>();
            for (X509Certificate certificate : validCertificates) {
                if (certificate.getNotAfter().after(new Date())) {
                    res.add(certificate);
                } else {
                    log.debug("Local Metadata certificate for " + getEntityID() + " expired at "
                            + certificate.getNotAfter() + ", current: " + new Date());
                }
            }
            return res;
        }

        void setCertificateValid(X509Certificate cert, boolean valid) {
            if (valid) {
                validCertificates.add(cert);
            } else {
                validCertificates.remove(cert);
            }
        }

        /**
         * Find a supported login endpoint.
         * @throws IllegalArgumentException If no services match the selected bindings. 
         */
        public Endpoint findLoginEndpoint(String[] bindings) {
            if (bindings == null)
                throw new IllegalArgumentException("bindings cannot be null");

            for (String binding : bindings) {
                for (SingleSignOnService service : idpSSODescriptor.getSingleSignOnServices()) {
                    if (service.getBinding().equalsIgnoreCase(binding)) {
                        return service;
                    }
                }
            }
            throw new IllegalArgumentException("No SingleSignonService found for " + Arrays.toString(bindings));
        }

        /**
         * Get the name format for an attribute.
         * 
         * @param attribute The attribute to look for.
         * @param defaultFormat The format to return if the attribute is not present in idp metadata.
         */
        public String getAttributeNameFormat(String attribute, String defaultFormat) {
            for (Attribute attr : idpSSODescriptor.getAttributes()) {
                if (attribute.equals(attr.getName())) {
                    return attr.getNameFormat();
                }
            }
            return defaultFormat;
        }

        public Collection<PublicKey> getPublicKeys() {
            Collection<PublicKey> res = new ArrayList<PublicKey>();
            for (X509Certificate cert : getCertificates()) {
                res.add(cert.getPublicKey());
            }
            return res;
        }
    }

}