View Javadoc

1   /*
2    * Copyright  2003-2004 The Apache Software Foundation.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  
18  package org.apache.ws.security.message;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.ws.security.SOAPConstants;
23  import org.apache.ws.security.WSSConfig;
24  import org.apache.ws.security.util.WSSecurityUtil;
25  import org.apache.xml.security.signature.XMLSignatureInput;
26  import org.apache.xml.security.utils.XMLUtils;
27  import org.apache.xml.security.utils.resolver.ResourceResolverException;
28  import org.apache.xml.security.utils.resolver.ResourceResolverSpi;
29  import org.apache.xml.utils.URI;
30  import org.w3c.dom.Attr;
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Element;
33  import org.w3c.dom.NamedNodeMap;
34  import org.w3c.dom.Node;
35  
36  import java.util.HashSet;
37  import java.util.Set;
38  
39  /***
40   * XML-Security resolver that is used for resolving same-document URI like URI="#id".
41   * It is desgined to only work with SOAPEnvelopes.
42   * <p/>
43   *
44   * @author Davanum Srinivas (dims@yahoo.com).
45   */
46  public class EnvelopeIdResolver extends ResourceResolverSpi {
47      private static Log log =
48              LogFactory.getLog(EnvelopeIdResolver.class.getName());
49      private static Log tlog = LogFactory.getLog("org.apache.ws.security.TIME");
50  
51      private static EnvelopeIdResolver resolver = null;
52      private WSSConfig wssConfig;
53  
54      private boolean doDebug = false;
55      
56      private Document document;
57  
58      /***
59       * Singleton instance of the resolver.
60       * <p/>
61       *
62       * @return
63       */
64      public synchronized static ResourceResolverSpi getInstance(WSSConfig wssConfig) {
65          // instance comparison, should be same instance most of the time
66          // so no need for quals() here?
67          if (resolver == null || resolver.wssConfig != wssConfig) {
68              resolver = new EnvelopeIdResolver(wssConfig);
69          }
70          return resolver;
71      }
72  
73      private EnvelopeIdResolver(WSSConfig wssConfig) {
74          this.wssConfig = wssConfig;
75      }
76      
77      public EnvelopeIdResolver(WSSConfig wssConfig, Document document) {
78          this.wssConfig = wssConfig;
79          this.document = document;
80      }
81  
82      /***
83       * This is the workhorse method used to resolve resources.
84       * <p/>
85       *
86       * @param uri
87       * @param BaseURI
88       * @return
89       * @throws ResourceResolverException
90       */
91      public XMLSignatureInput engineResolve(Attr uri, String BaseURI)
92              throws ResourceResolverException {
93  
94          doDebug = log.isDebugEnabled();
95  
96          long t0 = 0, t1 = 0;
97          if (tlog.isDebugEnabled()) {
98              t0 = System.currentTimeMillis();
99          }
100 
101         String uriNodeValue = uri.getNodeValue();
102 
103         if (doDebug) {
104             log.debug("enter engineResolve, look for: " + uriNodeValue);
105         }
106     
107         Document doc = (this.document == null)? uri.getOwnerDocument() : this.document;
108         if (doc == null) {
109           throw new RuntimeException("No DOM Document is associated with the signature.");
110         }
111 
112         // Xalan fix for catching all namespaces
113         XMLUtils.circumventBug2650(doc);
114 
115         /*
116          * URI="#chapter1"
117          * Identifies a node-set containing the element with ID attribute
118          * value 'chapter1' of the XML resource containing the signature.
119          * XML Signature (and its applications) modify this node-set to
120          * include the element plus all descendents including namespaces and
121          * attributes -- but not comments.
122          */
123          
124         /*
125          * First lookup the SOAP Body element (processed by default) and
126          * check if it contains an Id and if it matches
127          */
128         String id = uriNodeValue.substring(1);
129         SOAPConstants sc = WSSecurityUtil.getSOAPConstants(doc.getDocumentElement());
130         Element selectedElem = WSSecurityUtil.findBodyElement(doc, sc);
131         if (selectedElem == null) {
132             throw new ResourceResolverException("generic.EmptyMessage",
133                     new Object[]{"Body element not found"},
134                     uri,
135                     BaseURI);
136         }
137         String cId = selectedElem.getAttributeNS(wssConfig.getWsuNS(), "Id");
138 
139         /*
140          * If Body Id match fails, look for a generic Id (without a namespace)
141          * that matches the URI. If that lookup fails, try to get a namespace
142          * qualified Id that matches the URI.
143          */
144         if (!id.equals(cId)) {
145             cId = null;
146             if ((selectedElem = WSSecurityUtil.getElementByWsuId(wssConfig, doc, uriNodeValue)) != null) {
147                 cId = selectedElem.getAttribute("Id");
148             } else if ((selectedElem = WSSecurityUtil.getElementByGenId(doc, uriNodeValue)) != null) {
149                 cId = selectedElem.getAttribute("Id");
150             }
151             if (cId == null) {
152                 throw new ResourceResolverException("generic.EmptyMessage",
153                         new Object[]{"Id not found"},
154                         uri,
155                         BaseURI);
156             }
157         }
158 
159         Set resultSet = dereferenceSameDocumentURI(selectedElem);
160         XMLSignatureInput result = new XMLSignatureInput(resultSet);
161         result.setMIMEType("text/xml");
162         try {
163             URI uriNew = new URI(new URI(BaseURI), uri.getNodeValue());
164             result.setSourceURI(uriNew.toString());
165         } catch (URI.MalformedURIException ex) {
166             result.setSourceURI(BaseURI);
167         }
168         if (tlog.isDebugEnabled()) {
169             t1 = System.currentTimeMillis();
170             tlog.debug("engineResolve= " + (t1 - t0));
171         }
172         if (doDebug) {
173             log.debug("exit engineResolve, result: " + result);
174         }
175         return result;
176     }
177 
178     /***
179      * This method helps the ResourceResolver to decide whether a
180      * ResourceResolverSpi is able to perform the requested action.
181      * <p/>
182      *
183      * @param uri
184      * @param BaseURI
185      * @return
186      */
187     public boolean engineCanResolve(Attr uri, String BaseURI) {
188         if (uri == null) {
189             return false;
190         }
191         String uriNodeValue = uri.getNodeValue();
192         return uriNodeValue.startsWith("#");
193     }
194 
195     /***
196      * Dereferences a same-document URI fragment.
197      *
198      * @param node the node (document or element) referenced by the
199      *             URI fragment. If null, returns an empty set.
200      * @return a set of nodes (minus any comment nodes)
201      */
202     private Set dereferenceSameDocumentURI(Node node) {
203         Set nodeSet = new HashSet();
204         if (node != null) {
205             nodeSetMinusCommentNodes(node, nodeSet, null);
206         }
207         return nodeSet;
208     }
209 
210     /***
211      * Recursively traverses the subtree, and returns an XPath-equivalent
212      * node-set of all nodes traversed, excluding any comment nodes.
213      *
214      * @param node    the node to traverse
215      * @param nodeSet the set of nodes traversed so far
216      * @param the     previous sibling node
217      */
218     private void nodeSetMinusCommentNodes(Node node,
219                                           Set nodeSet,
220                                           Node prevSibling) {
221         if (doDebug) {
222             log.debug("Tag: "
223                     + node.getNodeName()
224                     + ", '"
225                     + node.getNodeValue()
226                     + "'");
227         }
228         switch (node.getNodeType()) {
229             case Node.ELEMENT_NODE:
230                 NamedNodeMap attrs = node.getAttributes();
231                 if (attrs != null) {
232                     for (int i = 0; i < attrs.getLength(); i++) {
233                         if (doDebug) {
234                             log.debug("Attr: "
235                                     + attrs.item(i).getNodeName()
236                                     + ", '"
237                                     + attrs.item(i).getNodeValue()
238                                     + "'");
239                         }
240                         nodeSet.add(attrs.item(i));
241                     }
242                 }
243                 nodeSet.add(node);
244                 Node pSibling = null;
245                 for (Node child = node.getFirstChild();
246                      child != null;
247                      child = child.getNextSibling()) {
248                     nodeSetMinusCommentNodes(child, nodeSet, pSibling);
249                     pSibling = child;
250                 }
251                 break;
252             case Node.TEXT_NODE:
253             case Node.CDATA_SECTION_NODE:
254                 // emulate XPath which only returns the first node in
255                 // contiguous text/cdata nodes
256                 if (prevSibling != null
257                         && (prevSibling.getNodeType() == Node.TEXT_NODE
258                         || prevSibling.getNodeType()
259                         == Node.CDATA_SECTION_NODE)) {
260                     return;
261                 }
262             case Node.PROCESSING_INSTRUCTION_NODE:
263                 nodeSet.add(node);
264         }
265     }
266 }