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

se.idsec.signservice.security.sign.xml.XMLSignatureLocation Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019-2024 IDsec Solutions 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.idsec.signservice.security.sign.xml;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.util.ArrayList;
import java.util.List;

/**
 * Tells where in an XML document the signature should be inserted or found.
 *
 * @author Martin Lindström ([email protected])
 * @author Stefan Santesson ([email protected])
 */
@Slf4j
public class XMLSignatureLocation {

  /**
   * Enum for indicating the point within a selected parent node.
   */
  public enum ChildPosition {
    /** First in the document. */
    FIRST,

    /** Last in the document. */
    LAST
  }

  /** Indicator for first or last child of a selected parent node. */
  private final ChildPosition childPosition;

  /**
   * The XPath expression for selecting the parent node (or {@code null} which means the parent node is the document
   * root element).
   */
  private XPathExpression xPathExpression;

  /** The textual representation of the XPath expression (for logging). */
  private String xPath;

  /**
   * Constructor setting up the signature location to "the last child of the document root element".
   */
  public XMLSignatureLocation() {
    this(ChildPosition.LAST);
  }

  /**
   * Constructor setting of the signature location to "the first child of the document root element"
   * ({@code childPosition} == {@link ChildPosition#FIRST} or "the last child of the document root element"
   * ({@code childPosition} == {@link ChildPosition#LAST}.
   *
   * @param childPosition first of last child of the document root element
   */
  public XMLSignatureLocation(final ChildPosition childPosition) {
    this.childPosition = childPosition;
  }

  /**
   * Constructor accepting an XPath expression for finding the parent element of where we should insert/find the
   * signature element. Note that the result of evaluating the XPath expression MUST be one single node.
   * 

* Note: Beware of that the document supplied to {@link #insertSignature(Element, Document)} or * {@link #getSignature(Document)} may be created using a namespace aware parser and you may want to use the * {@code local-name()} XPath construct. *

* * @param parentXPath the XPath expression for locating the parent node of the signature element * @param childPosition whether to insert/find the signature as the first or last child of the given parent node * @throws XPathExpressionException for illegal XPath expressions */ public XMLSignatureLocation(final String parentXPath, final ChildPosition childPosition) throws XPathExpressionException { this.childPosition = childPosition; this.xPath = parentXPath; this.xPathExpression = XPathFactory.newInstance().newXPath().compile(parentXPath); } /** * Inserts the given {@code Signature} element into the document according to this object's configuration. *

* Note: If the owner document of the given {@code Signature} element is not the same as the {@code document} * paramater, the element is imported into this document. *

* * @param signature the element to insert * @param document the document to which the signature element should be inserted * @throws XPathExpressionException for XPath selection errors */ public void insertSignature(final Element signature, final Document document) throws XPathExpressionException { // If the signature element comes from a different document, import it. final boolean sameOwner = XMLUtils.getOwnerDocument(signature) == document; final Node signatureNode = sameOwner ? signature : document.importNode(signature, true); Node parentNode = this.xPathExpression != null ? (Node) this.xPathExpression.evaluate(document, XPathConstants.NODE) : document.getDocumentElement(); if (parentNode == null) { // Node was not found final String msg = String.format("Could not find XML node for insertion of Signature - XPath: %s", this.xPath); log.error(msg); throw new XPathExpressionException(msg); } // If the XPath "/" was given we help a bit ... if (parentNode.getNodeType() == Node.DOCUMENT_NODE) { parentNode = ((Document) parentNode).getDocumentElement(); } if (ChildPosition.LAST == this.childPosition) { parentNode.appendChild(signatureNode); } else { parentNode.insertBefore(signatureNode, parentNode.getFirstChild()); } } /** * Finds a signature element based on this object's settings. * * @param document the document to locate the signature element * @return the signature element or null if no Signature element is found * @throws XPathExpressionException for XPath selection errors */ public Element getSignature(final Document document) throws XPathExpressionException { final List nodes = new ArrayList<>(); if (this.xPathExpression != null) { final NodeList _nodes = (NodeList) this.xPathExpression.evaluate(document, XPathConstants.NODESET); if (_nodes.getLength() == 0) { return null; } for (int i = 0; i < _nodes.getLength(); i++) { nodes.add(_nodes.item(i)); } } else { nodes.add(document.getDocumentElement()); } // If we get more than one hit for the parent node, we fail if more than one holds a signature element. // Element signatureElement = null; for (Node parentNode : nodes) { if (parentNode.getNodeType() == Node.DOCUMENT_NODE) { parentNode = ((Document) parentNode).getDocumentElement(); } final NodeList childs = parentNode.getChildNodes(); if (childs.getLength() == 0) { continue; } // Skip comments (and empty text nodes) int pos = ChildPosition.LAST == this.childPosition ? childs.getLength() - 1 : 0; Node signatureNode = null; while (signatureNode == null && pos >= 0 && pos < childs.getLength()) { final Node n = childs.item(pos); if (n.getNodeType() == Node.COMMENT_NODE || (n.getNodeType() == Node.TEXT_NODE && StringUtils.isBlank(n.getTextContent()))) { if (ChildPosition.LAST == this.childPosition) { pos--; } else { pos++; } } else { signatureNode = n; } } if (signatureNode != null && signatureNode.getNodeType() == Node.ELEMENT_NODE) { if (javax.xml.crypto.dsig.XMLSignature.XMLNS.equals(signatureNode.getNamespaceURI()) && "Signature".equals(signatureNode.getLocalName())) { if (signatureElement != null) { throw new XPathExpressionException("XPath expression found more than one Signature element"); } signatureElement = (Element) signatureNode; } } } return signatureElement; } /** * Method that can be used to verify that the supplied XPath expression can be used for the supplied document. * * @param document the document to evaluate the XPath expression against * @throws XPathExpressionException if the XPath expression is incorrect (does not find a node) */ public void testInsert(final Document document) throws XPathExpressionException { if (this.xPathExpression != null) { final Node parentNode = (Node) this.xPathExpression.evaluate(document, XPathConstants.NODE); if (parentNode == null) { throw new XPathExpressionException( String.format("Could not find XML node for insertion of Signature - XPath: %s", this.xPath)); } log.debug("XPath expression '{}' evaluated to node '{}'", this.xPath, parentNode.getLocalName()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy