eidassaml.starterkit.EidasRequest.java Source code

Java tutorial

Introduction

Here is the source code for eidassaml.starterkit.EidasRequest.java

Source

/* 
 * 
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 * http://ec.europa.eu/idabc/eupl
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 * 
 * Date: 09 Feb 2016
 * Authors: Governikus GmbH & Co. KG
 * 
*/
package eidassaml.starterkit;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opensaml.Configuration;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.impl.AuthnRequestMarshaller;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import eidassaml.starterkit.person_attributes.EidasPersonAttributes;
import eidassaml.starterkit.template.TemplateLoader;

/**
 * 
 * @author hohnholt
 *
 */
public class EidasRequest {

    private static final Log LOG = LogFactory.getLog(EidasRequest.class);

    private final static String attributeTemplate = "<eidas:RequestedAttribute Name=\"$NAME\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" isRequired=\"$ISREQ\"/>";
    public final static SimpleDateFormat SimpleDf = Constants.SimpleSamlDf;

    private final static List<EidasNaturalPersonAttributes> MINIMUM_DATASET = Arrays
            .asList(new EidasNaturalPersonAttributes[] { EidasNaturalPersonAttributes.PersonIdentifier,
                    EidasNaturalPersonAttributes.FamilyName, EidasNaturalPersonAttributes.FirstName,
                    EidasNaturalPersonAttributes.DateOfBirth });

    private String id;
    private String destination;
    private String issuer;
    private String issueInstant;
    private String providerName;
    private boolean forceAuthn;
    private boolean isPassive;
    private EidasRequestSectorType selectorType = EidasRequestSectorType.Public;
    private EidasNameIdType nameIdPolicy = EidasNameIdType.Transient;
    private EidasLoA authClassRef = EidasLoA.High;

    private EidasSigner signer = null;
    private AuthnRequest request = null;
    private Map<EidasPersonAttributes, Boolean> requestedAttributes = new HashMap<>();

    private EidasRequest() {

    }

    public EidasRequest(String _destination, String _issuer, String _providerName, EidasSigner _signer) {
        id = "_" + Utils.GenerateUniqueID();
        destination = _destination;
        issuer = _issuer;
        signer = _signer;
        providerName = _providerName;
        issueInstant = SimpleDf.format(new Date());
        this.forceAuthn = true;
        this.isPassive = false;
    }

    public EidasRequest(String _destination, String _issuer, String _providerName, EidasSigner _signer,
            String _id) {
        id = _id;
        destination = _destination;
        issuer = _issuer;
        signer = _signer;
        providerName = _providerName;
        issueInstant = SimpleDf.format(new Date());
        this.forceAuthn = true;
    }

    public EidasRequest(String _destination, EidasRequestSectorType _selectorType, EidasNameIdType _nameIdPolicy,
            EidasLoA _loa, String _issuer, String _providerName, EidasSigner _signer) {
        id = "_" + Utils.GenerateUniqueID();
        destination = _destination;
        issuer = _issuer;
        providerName = _providerName;
        signer = _signer;
        selectorType = _selectorType;
        nameIdPolicy = _nameIdPolicy;
        authClassRef = _loa;
        issueInstant = SimpleDf.format(new Date());
        this.forceAuthn = true;
        this.isPassive = false;
    }

    public EidasRequest(String _id, String _destination, EidasRequestSectorType _selectorType,
            EidasNameIdType _nameIdPolicy, EidasLoA _loa, String _issuer, String _providerName,
            EidasSigner _signer) {
        id = _id;
        destination = _destination;
        issuer = _issuer;
        providerName = _providerName;
        signer = _signer;
        selectorType = _selectorType;
        nameIdPolicy = _nameIdPolicy;
        authClassRef = _loa;
        issueInstant = SimpleDf.format(new Date());
        this.forceAuthn = true;
        this.isPassive = false;
    }

    public byte[] generate(Map<EidasPersonAttributes, Boolean> _requestedAttributes)
            throws IOException, XMLParserException, UnmarshallingException, CertificateEncodingException,
            MarshallingException, SignatureException, TransformerFactoryConfigurationError, TransformerException {
        byte[] returnvalue = null;
        StringBuilder attributesBuilder = new StringBuilder();
        for (Map.Entry<EidasPersonAttributes, Boolean> entry : _requestedAttributes.entrySet()) {
            attributesBuilder.append(attributeTemplate.replace("$NAME", entry.getKey().getName()).replace("$ISREQ",
                    entry.getValue().toString()));
        }

        String template = TemplateLoader.GetTemplateByName("auth");
        template = template.replace("$ForceAuthn", Boolean.toString(this.forceAuthn));
        template = template.replace("$IsPassive", Boolean.toString(this.isPassive));
        template = template.replace("$Destination", destination);
        template = template.replace("$Id", id);
        template = template.replace("$IssuerInstand", issueInstant);
        template = template.replace("$ProviderName", providerName);
        template = template.replace("$Issuer", issuer);
        template = template.replace("$requestAttributes", attributesBuilder.toString());
        template = template.replace("$NameIDPolicy", nameIdPolicy.NAME);
        template = template.replace("$AuthClassRef", authClassRef.NAME);

        if (null != selectorType) {
            template = template.replace("$SPType", "<eidas:SPType>" + selectorType.NAME + "</eidas:SPType>");
        } else {
            template = template.replace("$SPType", "");
        }

        BasicParserPool ppMgr = new BasicParserPool();
        ppMgr.setNamespaceAware(true);
        List<Signature> sigs = new ArrayList<Signature>();

        try (InputStream is = new ByteArrayInputStream(template.getBytes(Constants.UTF8_CHARSET))) {

            Document inCommonMDDoc = ppMgr.parse(is);
            Element metadataRoot = inCommonMDDoc.getDocumentElement();
            UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
            Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(metadataRoot);
            request = (AuthnRequest) unmarshaller.unmarshall(metadataRoot);

            XMLSignatureHandler.addSignature(request, signer.getSigKey(), signer.getSigCert(), signer.getSigType(),
                    signer.getSigDigestAlg());
            sigs.add(request.getSignature());

            AuthnRequestMarshaller arm = new AuthnRequestMarshaller();
            Element all = arm.marshall(request);
            if (sigs.size() > 0)
                Signer.signObjects(sigs);

            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
                trans.transform(new DOMSource(all), new StreamResult(bout));
                returnvalue = bout.toByteArray();
            }
        }

        return returnvalue;
    }

    public boolean isPassive() {
        return isPassive;
    }

    public void setPassive(boolean isPassive) {
        this.isPassive = isPassive;
    }

    public void setIsForceAuthn(Boolean forceAuthn) {
        this.forceAuthn = forceAuthn;
    }

    public boolean isForceAuthn() {
        return this.forceAuthn;
    }

    public String getId() {
        return id;
    }

    public String getDestination() {
        return destination;
    }

    public String getIssuer() {
        return issuer;
    }

    public String getIssueInstant() {
        return issueInstant;
    }

    public Set<Entry<EidasPersonAttributes, Boolean>> getRequestedAttributes() {
        return requestedAttributes.entrySet();
    }

    /**
     * running EidasRequest.generate or EidasRequest.Parse creates is object
     * 
     * @return the opensaml authnrespuest object or null. if not null, this object provides all information u can get via opensaml
     */
    public AuthnRequest getAuthnRequest() {
        return request;
    }

    public EidasRequestSectorType getSelectorType() {
        return selectorType;
    }

    public void setSelectorType(EidasRequestSectorType selectorType) {
        this.selectorType = selectorType;
    }

    public EidasNameIdType getNameIdPolicy() {
        return nameIdPolicy;
    }

    public void setNameIdPolicy(EidasNameIdType nameIdPolicy) {
        this.nameIdPolicy = nameIdPolicy;
    }

    public String getProviderName() {
        return providerName;
    }

    public void setProviderName(String providerName) {
        this.providerName = providerName;
    }

    public EidasLoA getLevelOfAssurance() {
        return authClassRef;
    }

    public void setLevelOfAssurance(EidasLoA levelOfAssurance) {
        this.authClassRef = levelOfAssurance;
    }

    public static EidasRequest Parse(InputStream is)
            throws XMLParserException, UnmarshallingException, ErrorCodeException, IOException {
        return Parse(is, null);
    }

    public static EidasRequest Parse(InputStream is, List<X509Certificate> authors)
            throws XMLParserException, UnmarshallingException, ErrorCodeException, IOException {
        EidasRequest eidasReq = new EidasRequest();
        BasicParserPool ppMgr = new BasicParserPool();
        ppMgr.setNamespaceAware(true);

        Document inCommonMDDoc = ppMgr.parse(is);

        Element metadataRoot = inCommonMDDoc.getDocumentElement();
        UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(metadataRoot);
        eidasReq.request = (AuthnRequest) unmarshaller.unmarshall(metadataRoot);

        if (authors != null) {
            CheckSignature(eidasReq.request.getSignature(), authors);
        }

        //isPassive SHOULD be false
        if (!eidasReq.request.isPassive()) {
            eidasReq.setPassive(eidasReq.request.isPassive());
        } else {
            throw new ErrorCodeException(ErrorCode.ILLEGAL_REQUEST_SYNTAX,
                    "Unsupported IsPassive value:" + eidasReq.request.isPassive());
        }

        //forceAuthn MUST be true
        if (eidasReq.request.isForceAuthn()) {
            eidasReq.setIsForceAuthn(eidasReq.request.isForceAuthn());
        } else {
            throw new ErrorCodeException(ErrorCode.ILLEGAL_REQUEST_SYNTAX,
                    "Unsupported ForceAuthn value:" + eidasReq.request.isForceAuthn());
        }

        eidasReq.id = eidasReq.request.getID();
        //there should be one AuthnContextClassRef
        AuthnContextClassRef ref = eidasReq.request.getRequestedAuthnContext().getAuthnContextClassRefs().get(0);
        if (null != ref) {
            eidasReq.authClassRef = EidasLoA.GetValueOf(ref.getDOM().getTextContent());
        } else {
            throw new ErrorCodeException(ErrorCode.ILLEGAL_REQUEST_SYNTAX, "No AuthnContextClassRef element.");
        }
        String namiIdformat = eidasReq.request.getNameIDPolicy().getFormat();
        eidasReq.nameIdPolicy = EidasNameIdType.GetValueOf(namiIdformat);

        eidasReq.issueInstant = SimpleDf.format(eidasReq.request.getIssueInstant().toDate());
        eidasReq.issuer = eidasReq.request.getIssuer().getDOM().getTextContent();
        eidasReq.destination = eidasReq.request.getDestination();

        if (null != eidasReq.request.getProviderName() && !eidasReq.request.getProviderName().isEmpty()) {
            eidasReq.providerName = eidasReq.request.getProviderName();
        } else {
            throw new ErrorCodeException(ErrorCode.ILLEGAL_REQUEST_SYNTAX, "No providerName attribute.");
        }

        eidasReq.selectorType = null;
        for (XMLObject extension : eidasReq.request.getExtensions().getOrderedChildren()) {
            if ("RequestedAttributes".equals(extension.getElementQName().getLocalPart())) {
                for (XMLObject attribute : extension.getOrderedChildren()) {
                    Element el = attribute.getDOM();
                    EidasPersonAttributes eidasPersonAttributes = getEidasPersonAttributes(el);
                    if (null != eidasPersonAttributes) {
                        eidasReq.requestedAttributes.put(eidasPersonAttributes,
                                Boolean.parseBoolean(el.getAttribute("isRequired")));
                    }
                }
            } else if ("SPType".equals(extension.getElementQName().getLocalPart())) {
                eidasReq.selectorType = EidasRequestSectorType.GetValueOf(extension.getDOM().getTextContent());
            }
        }
        if (!containsMinimumDataSet(eidasReq.requestedAttributes)) {
            throw new ErrorCodeException(ErrorCode.ILLEGAL_REQUEST_SYNTAX,
                    "Request does not contain minimum dataset.");
        }
        return eidasReq;
    }

    /**
     * Returns {@link EidasPersonAttributes} enum from given {@link Element}. 
     * In case enum can not be found null is returned; unknown attributes should be ignored.
     * 
     * @param el
     * @return
     */
    private static EidasPersonAttributes getEidasPersonAttributes(Element el) {
        EidasPersonAttributes eidasPersonAttributes = null;
        try {
            eidasPersonAttributes = EidasNaturalPersonAttributes.GetValueOf(el.getAttribute("Name"));
        } catch (ErrorCodeException e) {
            try {
                eidasPersonAttributes = EidasLegalPersonAttributes.GetValueOf(el.getAttribute("Name"));
            } catch (ErrorCodeException e1) {
                LOG.warn("Attribute " + el.getAttribute("Name") + " not an eIDAS attribute. Ignoring.");
            }
        }
        return eidasPersonAttributes;
    }

    private static void CheckSignature(Signature sig, List<X509Certificate> trustedAnchorList)
            throws ErrorCodeException {
        if (sig == null)
            throw new ErrorCodeException(ErrorCode.SIGNATURE_CHECK_FAILED);

        XMLSignatureHandler.checkSignature(sig,
                trustedAnchorList.toArray(new X509Certificate[trustedAnchorList.size()]));

    }

    private static boolean containsMinimumDataSet(Map<EidasPersonAttributes, Boolean> requestedAttributes) {
        if (null != requestedAttributes) {
            return MINIMUM_DATASET.stream().allMatch(p -> requestedAttributes.containsKey(p));
        } else {
            return false;
        }
    }

}