org.gluu.saml.Response.java Source code

Java tutorial

Introduction

Here is the source code for org.gluu.saml.Response.java

Source

/*
 * oxCore is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
 *
 * Copyright (c) 2014, Gluu
 */

package org.gluu.saml;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.crypto.dsig.XMLSignature;
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.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xdi.xml.SimpleNamespaceContext;
import org.xml.sax.SAXException;

/**
 * Loads and validates SAML response
 * 
 * @author Yuriy Movchan Date: 24/04/2014
 */
public class Response {
    private final static SimpleNamespaceContext NAMESPACES;

    static {
        HashMap<String, String> preferences = new HashMap<String, String>() {
            {
                put("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
                put("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
            }
        };
        NAMESPACES = new SimpleNamespaceContext(preferences);
    }

    private Document xmlDoc;
    private SamlConfiguration samlSettings;

    public Response(SamlConfiguration samlSettings) throws CertificateException {
        this.samlSettings = samlSettings;
    }

    public void loadXml(String xml) throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory fty = DocumentBuilderFactory.newInstance();

        fty.setNamespaceAware(true);

        // Fix XXE vulnerability
        fty.setXIncludeAware(false);
        fty.setExpandEntityReferences(false);
        fty.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        fty.setFeature("http://xml.org/sax/features/external-general-entities", false);
        fty.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        DocumentBuilder builder = fty.newDocumentBuilder();
        ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes());
        xmlDoc = builder.parse(bais);
    }

    public void loadXmlFromBase64(String response) throws ParserConfigurationException, SAXException, IOException {
        Base64 base64 = new Base64();
        byte[] decodedResponse = base64.decode(response);
        String decodedS = new String(decodedResponse);
        loadXml(decodedS);
    }

    public boolean isValid() throws Exception {
        NodeList nodes = xmlDoc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");

        if (nodes == null || nodes.getLength() == 0) {
            throw new Exception("Can't find signature in document.");
        }

        if (setIdAttributeExists()) {
            tagIdAttributes(xmlDoc);
        }

        X509Certificate cert = samlSettings.getCertificate();
        DOMValidateContext ctx = new DOMValidateContext(cert.getPublicKey(), nodes.item(0));
        XMLSignatureFactory sigF = XMLSignatureFactory.getInstance("DOM");
        XMLSignature xmlSignature = sigF.unmarshalXMLSignature(ctx);

        return xmlSignature.validate(ctx);
    }

    private void tagIdAttributes(Document xmlDoc) {
        NodeList nodeList = xmlDoc.getElementsByTagName("*");
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                if (node.getAttributes().getNamedItem("ID") != null) {
                    ((Element) node).setIdAttribute("ID", true);
                }
            }
        }
    }

    private boolean setIdAttributeExists() {
        for (Method method : Element.class.getDeclaredMethods()) {
            if (method.getName().equals("setIdAttribute")) {
                return true;
            }
        }
        return false;
    }

    public String getNameId() throws XPathExpressionException {
        XPath xPath = XPathFactory.newInstance().newXPath();

        xPath.setNamespaceContext(NAMESPACES);
        XPathExpression query = xPath.compile("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID");
        return query.evaluate(xmlDoc);
    }

    public Map<String, List<String>> getAttributes() throws XPathExpressionException {
        Map<String, List<String>> result = new HashMap<String, List<String>>();
        XPath xPath = XPathFactory.newInstance().newXPath();

        xPath.setNamespaceContext(NAMESPACES);
        XPathExpression query = xPath
                .compile("/samlp:Response/saml:Assertion/saml:AttributeStatement/saml:Attribute");
        NodeList nodes = (NodeList) query.evaluate(xmlDoc, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);

            Node nameNode = node.getAttributes().getNamedItem("Name");
            if (nameNode == null) {
                continue;
            }

            String attributeName = nameNode.getNodeValue();
            List<String> attributeValues = new ArrayList<String>();

            NodeList nameChildNodes = node.getChildNodes();
            for (int j = 0; j < nameChildNodes.getLength(); j++) {
                Node nameChildNode = nameChildNodes.item(j);

                if (nameChildNode.getNamespaceURI().equalsIgnoreCase("urn:oasis:names:tc:SAML:2.0:assertion")
                        && nameChildNode.getLocalName().equals("AttributeValue")) {
                    NodeList valueChildNodes = nameChildNode.getChildNodes();
                    for (int k = 0; k < valueChildNodes.getLength(); k++) {
                        Node valueChildNode = valueChildNodes.item(k);
                        attributeValues.add(valueChildNode.getNodeValue());
                    }
                }
            }

            result.put(attributeName, attributeValues);
        }

        return result;
    }

    public void printDocument(OutputStream out) throws IOException, TransformerException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

        transformer.transform(new DOMSource(xmlDoc), new StreamResult(new OutputStreamWriter(out, "UTF-8")));
    }
}