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

com.adobe.granite.auth.saml.util.SamlReader Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.granite.auth.saml.util;

import com.adobe.granite.auth.saml.model.Assertion;
import com.adobe.granite.auth.saml.model.Attribute;
import com.adobe.granite.auth.saml.model.AuthnStatement;
import com.adobe.granite.auth.saml.model.Issuer;
import com.adobe.granite.auth.saml.model.LogoutRequest;
import com.adobe.granite.auth.saml.model.Message;
import com.adobe.granite.auth.saml.model.NameId;
import com.adobe.granite.auth.saml.model.Response;
import com.adobe.granite.auth.saml.model.Status;
import com.adobe.granite.auth.saml.model.Subject;
import com.adobe.granite.auth.saml.model.xml.SamlXmlConstants;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.utils.EncryptionConstants;
import org.joda.time.DateTimeZone;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.Key;
import java.security.Provider;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

/**
 * SamlReader is responsible for reading saml objects from an input stream.
 *
 */
public class SamlReader {
    private XMLSignatureFactory xmlSignatureFactory;

    static {
        org.apache.xml.security.Init.init();
    }

    /**
     * default log
     */
    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Creates a new instance of XmlUnmarshaler.
     */
    public SamlReader() {
        super();
        try {
            xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM", (Provider) Class.forName(System.getProperty("jsr105Provider", "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI")).newInstance());
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            log.error("Could not instantiate XML Signature Factory.", e);
        }
    }

    /**
     * Reads a response from the xml document contained in the given input stream.
     *
     * @param inputStream     InputStream containing the xml document to unmarshal.
     * @param decryptionKey   Private key used to decrypt encrypted assertions (dont decrypt if key is null)
     * @param verificationKey The public key of the IdP to verify the XML signatures
     * @param isSamlRequest   Whether we are handling a SamlRequest or a SamlResponse message
     * @return Response created from the xml document in the input stream.
     * @throws SamlReaderException An error occurred while unmarshaling the Response.
     * @throws IOException         An error occurred while processing the {@link InputStream}
     */
    public Message read(final InputStream inputStream, final Key decryptionKey, final Key verificationKey, boolean isSamlRequest) throws SamlReaderException, IOException {
        final DocumentBuilderFactory builderFactory;
        final DocumentBuilder builder;
        try {
            builderFactory = createBuilderFactory();
            builder = builderFactory.newDocumentBuilder();
            builder.setErrorHandler(new LoggingErrorHandler());
            Document doc = builder.parse(inputStream);
            Message msg = null;
            if (!isSamlRequest) {
                // this is a SAML Response message
                msg = parse(doc, verificationKey, decryptionKey);
                if (msg == null) {
                    throw new SamlReaderException("Unable to parse document from stream.", new Exception());
                }
                // store decoded/decrypted message body
                Transformer transformer = TransformerFactory.newInstance().newTransformer();
                StringWriter sw = new StringWriter();
                transformer.transform(new DOMSource(doc), new StreamResult(sw));
                msg.setRawMessage(sw.toString());
            } else {
                // this is a SAML Request message
                msg = parseRequest(doc, verificationKey);
                if (msg == null) {
                    throw new SamlReaderException("Unable to parse document from stream.", new Exception());
                }
            }
            return msg;
        } catch (ParserConfigurationException e) {
            throw new SamlReaderException("Unable to create document builder factory", e);
        } catch (SAXException e) {
            throw new SamlReaderException("Unable to parse document from stream", e);
        } catch (TransformerConfigurationException e) {
            throw new SamlReaderException("Unable to parse document from stream", e);
        } catch (TransformerException e) {
            throw new SamlReaderException("Unable to parse document from stream", e);
        }
    }

    public Message read(final InputStream inputStream, final Key decryptionKey, final Key verificationKey) throws SamlReaderException, IOException {
        return read(inputStream, decryptionKey, verificationKey, false);
    }

    /**
     * Create and configure a new document builder factory
     */
    private DocumentBuilderFactory createBuilderFactory() throws ParserConfigurationException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        // do not expand entity reference nodes
        dbf.setExpandEntityReferences(false);

        // do not include external general entities
        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);

        // do not include external parameter entities or the external DTD subset
        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        // build the grammar but do not use the default attributes and attribute types information it contains
        dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);

        // ignore the external DTD completely
        dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        return dbf;
    }

    // -- implementation methods --

    /**
     * Creates a Response instance by parsing the given document.
     *
     * @param document        Document instance containing an xml response instance.
     * @param verificationKey The public key of the IdP to verify the XML signatures
     * @param decryptionKey   The (optional) private key of the SP to decrypt encrypted assertion elements
     * @return Response instance created from the given document.
     */
    protected Response parse(final Document document, final Key verificationKey, final Key decryptionKey) {
        final Element responseElement = getChildElement(document, SamlXmlConstants.RESPONSE_ELEMENT);
        if (responseElement == null) {
            log.error("Unable to read Response element from document.");
            return null;
        }
        responseElement.setIdAttribute("ID", true);

        final Response response = new Response();
        response.setId(responseElement.getAttribute(SamlXmlConstants.ID_ATTR));
        response.setVersion(responseElement.getAttribute(SamlXmlConstants.VERSION_ATTR));
        response.setIssueInstant(getDateAttr(responseElement, SamlXmlConstants.ISSUE_INSTANT_ATTR));

        final Element statusElement = getChildElement(responseElement, SamlXmlConstants.STATUS_ELEMENT);
        if (null != statusElement) {
            response.setStatus(parseStatus(statusElement));
        }

        final Element issuerElement = getChildElement(responseElement, SamlXmlConstants.ISSUER_ELEMENT);
        if (null != issuerElement) {
            response.setIssuer(parseIssuer(issuerElement));
        }

        response.setInResponseTo(responseElement.getAttribute(SamlXmlConstants.IN_RESPONSE_TO_ATTR));
        response.setDestination(responseElement.getAttribute(SamlXmlConstants.DESTINATION_ATTR));

        response.setSignatureValid(verifyElementSignature(responseElement, verificationKey));

        // potentially decrypt assertions
        if (decryptionKey != null) {
            this.decryptResponse(document, decryptionKey);
        }

        final NodeList nodeList = responseElement.getElementsByTagNameNS(SamlXmlConstants.SAML_ASSERTION_NAMESPACE, SamlXmlConstants.ASSERTION_ELEMENT);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Element assertionElement = (Element) nodeList.item(i);
            Assertion assertion = parseAssertion(assertionElement);

            final Element subjectElement = getChildElement(assertionElement, SamlXmlConstants.SUBJECT_ELEMENT);
            if (null != subjectElement) {
                final Element nameIdElement = getChildElement(subjectElement, SamlXmlConstants.NAME_ID_ELEMENT);
                if (null != nameIdElement) {
                    response.setNameId(nameIdElement.getTextContent());
                    if (nameIdElement.hasAttribute(SamlXmlConstants.FORMAT_ATTR)) {
                        response.setNameIdFormat(nameIdElement.getAttribute(SamlXmlConstants.FORMAT_ATTR));
                    }
                    if (nameIdElement.hasAttribute(SamlXmlConstants.NAME_QUALIFIER_ATTR)) {
                        response.setNameQualifier(nameIdElement.getAttribute(SamlXmlConstants.NAME_QUALIFIER_ATTR));
                    }
                    if (nameIdElement.hasAttribute(SamlXmlConstants.SP_NAME_QUALIFIER_ATTR)) {
                        response.setSpNameQualifier(nameIdElement.getAttribute(SamlXmlConstants.SP_NAME_QUALIFIER_ATTR));
                    }
                }
            }
            assertion.setSignatureValid(verifyElementSignature((Element)nodeList.item(i), verificationKey));
            if (!assertion.isSignatureValid()) {
                // potentially inherit response signature
                assertion.setSignatureValid(response.isSignatureValid());
            }
            response.addAssertion(assertion);
        }

        return response;
    }

    protected Response parse(Document document, Key verificationKey) {
        return parse(document, verificationKey, null);
    }

    /**
     * Parse a SAML LogoutRequest message
     *
     * @param document        Document instance containing the SAML LogoutRequest message
     * @param verificationKey The IdP's public key to verify the signature of the message
     * @return LogoutRequest message created from the given message
     */
    private LogoutRequest parseRequest(Document document, Key verificationKey) {
        final Element requestElement = getChildElement(document, SamlXmlConstants.LOGOUT_REQUEST);
        if (requestElement == null) {
            log.error("Could not find LogoutRequest element in document");
            return null;
        }

        // read attributes of LogoutRequest
        final LogoutRequest logoutRequest = new LogoutRequest();
        if (requestElement.hasAttribute(SamlXmlConstants.ID_ATTR)) {
            logoutRequest.setId(requestElement.getAttribute(SamlXmlConstants.ID_ATTR));
        }
        if (requestElement.hasAttribute(SamlXmlConstants.ISSUE_INSTANT_ATTR)) {
            logoutRequest.setIssueInstant(getDateAttr(requestElement, SamlXmlConstants.ISSUE_INSTANT_ATTR));
        }
        if (requestElement.hasAttribute(SamlXmlConstants.DESTINATION_ATTR)) {
            logoutRequest.setDestination(requestElement.getAttribute(SamlXmlConstants.DESTINATION_ATTR));
        }

        // parse child elements
        final Element issuerElement = getChildElement(requestElement, SamlXmlConstants.ISSUER_ELEMENT);
        if (issuerElement != null) {
            logoutRequest.setIssuer(parseIssuer(issuerElement));
        }
        final Element nameIdElement = getChildElement(requestElement, SamlXmlConstants.NAME_ID_ELEMENT);
        if (nameIdElement != null) {
            if (nameIdElement.hasAttribute(SamlXmlConstants.NAME_FORMAT_ATTR)) {
                logoutRequest.setNameIdFormat(nameIdElement.getAttribute(SamlXmlConstants.NAME_FORMAT_ATTR));
            }
            logoutRequest.setNameId(nameIdElement.getTextContent());
        }
        final LinkedList sessionIndexElements = getChildElements(requestElement, SamlXmlConstants.SESSION_INDEX_ELEMENT);
        for (Element sessionIndexElement : sessionIndexElements) {
            logoutRequest.addSessionIndex(sessionIndexElement.getTextContent());
        }
        logoutRequest.setSignatureValid(verifyElementSignature(requestElement, verificationKey));
        return logoutRequest;
    }

    /**
     * Verify the cryptographic signature of a (decrypted) element.
     *
     * @param signedElement The element to be verified.
     * @param verificationKey  The public key of the IDP
     * @return True if the element is signed (potentially via signature inheritance) and all signatures are valid,
     *         False otherwise
     */
    private boolean verifyElementSignature(Element signedElement, Key verificationKey) {
        // Check verification key
        if (verificationKey == null) {
            log.debug("Cannot verify signature. Public key of IDP missing.");
            return false;
        }

        // Find enveloped signature nodes
        NodeList children = signedElement.getChildNodes();
        LinkedList signatureNodes = new LinkedList ();
        for (int i = 0; i < children.getLength(); i++) {
            if (XMLSignature.XMLNS.equals(children.item(i).getNamespaceURI()) && "Signature".equals(children.item(i).getLocalName())) {
                signatureNodes.add(children.item(i));
            }
        }

        if (signatureNodes.size() == 0) {
            // no signature node found
            // check if this is an assertion element contained within another assertion
            if (SamlXmlConstants.ASSERTION_ELEMENT.equals(signedElement.getTagName()) &&
                    signedElement.getParentNode() != null &&
                    signedElement.getParentNode().getNodeType() == Element.ELEMENT_NODE) {
                Element parentElement = (Element) signedElement.getParentNode();
                if (SamlXmlConstants.ASSERTION_ELEMENT.equals(parentElement.getTagName())) {
                    // recursively verify parent assertion
                    return verifyElementSignature(parentElement, verificationKey);
                }
            } else {
                log.debug("Signature verification failed. No signature.", signedElement);
                return false;
            }
        }

        // Validate all signature nodes
        for (Node signatureNode : signatureNodes) {
            DOMValidateContext validateContext = new DOMValidateContext(verificationKey, signatureNode);
            validateContext.setIdAttributeNS(signedElement, null, SamlXmlConstants.ID_ATTR);
            try {
                XMLSignature signature = xmlSignatureFactory.unmarshalXMLSignature(validateContext);

                // Validate Reference
                // MUST contain _single_ ds:reference containing same-document reference to the ID of the root element
                List references = signature.getSignedInfo().getReferences();
                if (references.size() != 1) {
                    log.debug("Signature node MUST contain a single ds:reference field.");
                    return false;
                }
                String refURI = references.get(0).getURI();
                if (refURI == null || refURI.isEmpty() || !refURI.startsWith("#")) {
                    log.debug("Signature node with empty or invalid reference field.");
                    return false;
                }
                if (!refURI.substring(1).equals(signedElement.getAttribute(SamlXmlConstants.ID_ATTR))) {
                    log.debug("Signature reference MUST point to the enveloping element.");
                    return false;
                }

                // Validate Signature
                if (!signature.validate(validateContext)) {
                    return false;
                }
            } catch (MarshalException e) {
                log.error("Unable to unmarshal XML signature.", e);
                return false;
            } catch (XMLSignatureException e) {
                log.error("Unable to validate signature.", e);
                return false;
            }
        }
        return true;
    }

    protected Document decryptResponse(final Document document, final Key decryptionKey) {
        final NodeList encryptedAssertions = document.getElementsByTagNameNS(
                SamlXmlConstants.SAML_ASSERTION_NAMESPACE,
                SamlXmlConstants.ENCRYPTED_ASSERTION_ELEMENT);
        if (0 >= encryptedAssertions.getLength()) {
            throw new RuntimeException("No EncryptedAssertion element was found");
        }

        for (int i = 0; i < encryptedAssertions.getLength(); i++) {
            this.decryptAssertion((Element) encryptedAssertions.item(i), decryptionKey);
        }

        return document;
    }

    /**
     * Decrypts data contained in the given encrypted assertion. The EncryptedAssertion element is then replaced
     * by the decrypted Assertion element.
     *
     * @param encryptedAssertion EncrtypedAssertion element to decrypt.
     * @param decryptionKey      Private key used to decrypt encrypted assertions.
     */
    protected void decryptAssertion(final Element encryptedAssertion, final Key decryptionKey) {
        XMLCipher xmlCipher;
        final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setNamespaceAware(true);
        final DocumentBuilder builder;

        try {
            // get encrypted data node (must be the only xenc:EncryptedData element in the saml:EncryptedAssertion node)
            Element encryptedDataNode = (Element) encryptedAssertion.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_ENCRYPTEDDATA).item(0);

            // get xenc:EncryptionMethod node and read algorithm attribute
            Element algorithmNode = (Element) encryptedDataNode.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_ENCRYPTIONMETHOD).item(0);
            String algorithm = algorithmNode.getAttributeNS(null, EncryptionConstants._ATT_ALGORITHM);
            builder = builderFactory.newDocumentBuilder();

            // initialize xmlcipher with private key from repository as KEK
            xmlCipher = XMLCipher.getInstance();
            xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
            xmlCipher.setKEK(decryptionKey);

            // register the retrieval-method encrypted key resolver as an internal key resolver
            xmlCipher.registerInternalKeyResolver(new RetrievalMethodEncryptedKeyResolver(algorithm, decryptionKey));

            // decrypt xenc:EncryptedData node to byte array
            byte[] decryptedAssertion = xmlCipher.decryptToByteArray(encryptedDataNode);
            Document doc = builder.parse(new ByteArrayInputStream(decryptedAssertion));
            Node decryptedNode = encryptedAssertion.getOwnerDocument().importNode(doc.getDocumentElement(), true);

            // replace encrypted assertion element
            final Element parentElement = (Element) encryptedAssertion.getParentNode();
            parentElement.removeChild(encryptedAssertion);
            parentElement.appendChild(decryptedNode);
        } catch (final XMLEncryptionException e) {
            throw new RuntimeException("Error decrypting response", e);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException("Error decrypting response", e);
        } catch (SAXException e) {
            throw new RuntimeException("Error decrypting response", e);
        } catch (IOException e) {
            throw new RuntimeException("Error decrypting response", e);
        }
    }

    /**
     * Creates an Issuer instance from the given issuer element.
     *
     * @param issuerElement Element containing an xml issuer.
     * @return Issuer instance created from the given issuer element.
     */
    protected Issuer parseIssuer(final Element issuerElement) {
        final String value = issuerElement.getTextContent();
        if (null == value) {
            return null;
        } else {
            return new Issuer(value.trim());
        }
    }

    /**
     * Creates an Assertion instance from the given assertion element.
     *
     * @param assertionElement Element containing an xml assertion.
     * @return Assertion instance created from the given assertion element.
     */
    protected Assertion parseAssertion(final Element assertionElement) {
        final Assertion assertion = new Assertion();

        // parse Subject element
        final Element subjectElement = getChildElement(assertionElement, SamlXmlConstants.SUBJECT_ELEMENT);
        if (null != subjectElement) {
            Subject subject = new Subject();
            final Element nameIdElement = getChildElement(subjectElement, SamlXmlConstants.NAME_ID_ELEMENT);
            if (nameIdElement != null) {
                NameId nameId = new NameId();
                nameId.setValue(nameIdElement.getTextContent());
                nameId.setFormat(nameIdElement.getAttribute(SamlXmlConstants.FORMAT_ATTR));
                subject.setNameId(nameId);
            }
            assertion.setSubject(subject);
            // TODO parse base/encrypted-ids
            // TODO parse subject conditions
        }

        final Element attributeStatement = getChildElement(assertionElement, SamlXmlConstants.ATTRIBUTE_STATEMENT_ELEMENT);
        if (null != attributeStatement) {
            final NodeList nodeList = attributeStatement.getElementsByTagNameNS(SamlXmlConstants.SAML_ASSERTION_NAMESPACE, SamlXmlConstants.ATTRIBUTE_ELEMENT);
            for (int i = 0; i < nodeList.getLength(); i++) {
                assertion.addAttribute(parseAttribute((Element) nodeList.item(i)));
            }
        }

        // parse conditions
        final Element conditionsElement = getChildElement(assertionElement, SamlXmlConstants.CONDITIONS_ELEMENT);
        if (conditionsElement.hasAttribute(SamlXmlConstants.NOT_BEFORE_ATTR)) {
            assertion.setNotBefore(getDateAttr(conditionsElement, SamlXmlConstants.NOT_BEFORE_ATTR));
        }
        if (conditionsElement.hasAttribute(SamlXmlConstants.NOT_ON_OR_AFTER_ATTR)) {
            assertion.setNotOnOrAfter(getDateAttr(conditionsElement, SamlXmlConstants.NOT_ON_OR_AFTER_ATTR));
        }

        // audience restriction attribute is mandatory
        final Element audienceRestrictionElement = getChildElement(conditionsElement, SamlXmlConstants.AUDIENCE_RESTRICTION_ELEMENT);
        if (null != audienceRestrictionElement) {
            final NodeList nodeList = audienceRestrictionElement.getElementsByTagNameNS(SamlXmlConstants.SAML_ASSERTION_NAMESPACE, SamlXmlConstants.AUDIENCE_ELEMENT);
            for (int i = 0; i < nodeList.getLength(); i++) {
                assertion.addAudienceRestriction(nodeList.item(i).getTextContent());
            }
        }

        // parse authn statement element(s)
        final LinkedList authnStatementElements = getChildElements(assertionElement, SamlXmlConstants.AUTHN_STATEMENT_ELEMENT);
        for (Element authnStatementElement : authnStatementElements) {
            AuthnStatement authnStatement = new AuthnStatement();
            if (authnStatementElement.hasAttribute(SamlXmlConstants.AUTHN_INSTANT_ATTR)) {
                authnStatement.setAuthnInstant(getDateAttr(authnStatementElement, SamlXmlConstants.AUTHN_INSTANT_ATTR));
            }
            if (authnStatementElement.hasAttribute(SamlXmlConstants.SESSION_NOT_ON_OR_AFTER_ATTR)) {
                authnStatement.setSessionNotOnOrAfter(getDateAttr(authnStatementElement, SamlXmlConstants.SESSION_NOT_ON_OR_AFTER_ATTR));
            }
            if (authnStatementElement.hasAttribute(SamlXmlConstants.SESSION_INDEX_ATTR)) {
                authnStatement.setSessionIndex(authnStatementElement.getAttribute(SamlXmlConstants.SESSION_INDEX_ATTR));
            }
            assertion.addAuthnStatement(authnStatement);
            // TODO parse SubjectLocality & AuthnContext elements
        }

        return assertion;
    }

    /**
     * Creates an Attribute instance from the given attribute element.
     *
     * @param attributeElement Xml Element containing attribute data.
     * @return Attribute instance created from the given element.
     */
    protected Attribute parseAttribute(final Element attributeElement) {
        final Attribute attribute = new Attribute();
        attribute.setName(attributeElement.getAttribute(SamlXmlConstants.NAME_ATTR));
        attribute.setNameFormat(attributeElement.getAttribute(SamlXmlConstants.NAME_FORMAT_ATTR));

        final NodeList valueElements = attributeElement.getElementsByTagNameNS(SamlXmlConstants.SAML_ASSERTION_NAMESPACE, SamlXmlConstants.ATTRIBUTE_VALUE_ELEMENT);
        for (int i = 0; i < valueElements.getLength(); i++) {
            final Element valueElement = (Element) valueElements.item(i);
            attribute.addAttributeValue(valueElement.getTextContent().trim());
        }

        return attribute;
    }

    /**
     * Creates a Status instance from the given status element.
     *
     * @param statusElement Status instance created from the given element.
     * @return Status instance created from the given element.
     */
    protected Status parseStatus(final Element statusElement) {
        final Status status = new Status();
        final Element statusCodeElement = getChildElement(statusElement, SamlXmlConstants.STATUS_CODE_ELEMENT);
        if (null != statusCodeElement) {
            status.setStatusCode(statusCodeElement.getAttribute(SamlXmlConstants.VALUE_ATTR));
            return status;
        }
        return null;
        // TODO JH need to recursively check for status codes...
    }

    /**
     * Retrieve a date value from a specific attribute.
     *
     * @param element  Element containing the attribute.
     * @param attrName Name of the attribute containing the date.
     * @return Date created from the given attribute, or null if not found.
     */
    protected Calendar getDateAttr(final Element element, final String attrName) {
        final String value = element.getAttribute(attrName);
        if (null != value && !"".equals(value)) {
            Calendar calendar = ISODateTimeFormat.dateTimeParser().withZone(DateTimeZone.forOffsetHours(0)).parseDateTime(value).toCalendar(Locale.getDefault());
            return calendar;
        }
        return null;
    }

    /**
     * Retrieves the first child element of the provided node which matches the given name.
     *
     * @param node Parent node to retrieve a child element from.
     * @param name String containing the name of the child element to retrieve.
     * @return Child Element matching the given name or null if not found.
     */
    protected Element getChildElement(final Node node, final String name) {
        final NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            final Node child = nodeList.item(i);
            if (Node.ELEMENT_NODE == child.getNodeType()) {
                if (child.getLocalName().equals(name)) {
                    return (Element) child;
                }
            }
        }
        return null;
    }

    /**
     * Retrieves a list of child elements of the provided node which match the given name
     *
     * @param node Parent node to retrieve child elements from
     * @param name String containing the name of the child elements to retrieve
     * @return A linked list containing all child elements matching the give name
     */
    protected LinkedList getChildElements(final Node node, final String name) {
        final NodeList nodeList = node.getChildNodes();
        final LinkedList childElements = new LinkedList();
        for (int i = 0; i < nodeList.getLength(); i++) {
            final Node child = nodeList.item(i);
            if (Node.ELEMENT_NODE == child.getNodeType()) {
                if (child.getLocalName().equals(name)) {
                    childElements.add((Element) child);
                }
            }
        }
        return childElements;
    }

    private class LoggingErrorHandler implements ErrorHandler {
        @Override
        public void warning(SAXParseException exception) throws SAXException {
            log.warn(exception.getMessage());
        }

        @Override
        public void error(SAXParseException exception) throws SAXException {
            log.error(exception.getMessage());
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
            log.error(exception.getMessage());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy