View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.xml.secure;
19  
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertSame;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.StringReader;
26  import java.io.StringWriter;
27  import java.util.Properties;
28  
29  import javax.xml.parsers.DocumentBuilderFactory;
30  import javax.xml.transform.ErrorListener;
31  import javax.xml.transform.OutputKeys;
32  import javax.xml.transform.Transformer;
33  import javax.xml.transform.TransformerException;
34  import javax.xml.transform.TransformerFactory;
35  import javax.xml.transform.URIResolver;
36  import javax.xml.transform.dom.DOMSource;
37  import javax.xml.transform.stream.StreamResult;
38  import javax.xml.transform.stream.StreamSource;
39  
40  import org.junit.jupiter.api.Tag;
41  import org.junit.jupiter.api.Test;
42  
43  @Tag("trax")
44  class SecureTransformerTest {
45  
46      /**
47       * A transformer a caller configured before this library saw it, the shape a caller's own Templates hands out.
48       */
49      private static SecureTransformer wrap(final URIResolver carried) throws Exception {
50          final Transformer delegate = TransformerFactory.newInstance().newTransformer(AttackTestSupport.resourceSource("with-document.xsl"));
51          delegate.setURIResolver(carried);
52          return new SecureTransformer(delegate, null, null, false);
53      }
54  
55      @Test
56      void adoptsAResolverTheDelegateAlreadyCarries() throws Exception {
57          final URIResolver carried = (href, base) -> new StreamSource(new StringReader("<opted-in/>"));
58          final SecureTransformer transformer = wrap(carried);
59          assertSame(carried, transformer.getURIResolver(), "the resolver the delegate carried must survive the wrapping");
60          final StringWriter output = new StringWriter();
61          transformer.transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(output));
62          assertTrue(output.toString().contains("opted-in"), "the carried resolver must answer document()");
63          assertFalse(output.toString().contains(AttackTestSupport.LEAKED_MARKER), "the real resource must not be fetched");
64      }
65  
66      @Test
67      void carriesTheAdoptedResolverThroughReset() throws Exception {
68          final URIResolver carried = (href, base) -> new StreamSource(new StringReader("<opted-in/>"));
69          final SecureTransformer transformer = wrap(carried);
70          // reset() re-seeds the floor, and the seed is the resolver the delegate carried, not the factory's.
71          transformer.reset();
72          assertSame(carried, transformer.getURIResolver(), "reset must restore the resolver the delegate carried");
73          final StringWriter output = new StringWriter();
74          transformer.transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(output));
75          assertTrue(output.toString().contains("opted-in"), "the carried resolver must still answer document() after a reset");
76          assertFalse(output.toString().contains(AttackTestSupport.LEAKED_MARKER), "the real resource must not be fetched");
77      }
78  
79      @Test
80      void forwardsEveryTransformerMethod() throws Exception {
81          final TransformerFactory factory = TransformerFactory.newInstance();
82          final SecureTransformer transformer = new SecureTransformer(factory
83                  .newTemplates(new StreamSource(new StringReader(
84                          "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'><xsl:template match='/'/></xsl:stylesheet>")))
85                  .newTransformer(), null, null, false);
86          transformer.clearParameters();
87          transformer.setParameter("p", "v");
88          assertNotNull(transformer.getParameter("p"));
89          transformer.setOutputProperty(OutputKeys.METHOD, "xml");
90          assertNotNull(transformer.getOutputProperty(OutputKeys.METHOD));
91          transformer.setOutputProperties(new Properties());
92          assertNotNull(transformer.getOutputProperties());
93          transformer.setErrorListener(new ErrorListener() {
94  
95              @Override
96              public void error(final TransformerException e) {
97              }
98  
99              @Override
100             public void fatalError(final TransformerException e) {
101             }
102 
103             @Override
104             public void warning(final TransformerException e) {
105             }
106         });
107         assertNotNull(transformer.getErrorListener());
108         transformer.setURIResolver((href, base) -> null);
109         assertNotNull(transformer.getURIResolver());
110         transformer.transform(new DOMSource(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()), new StreamResult(new StringWriter()));
111         transformer.reset();
112     }
113 
114     @Test
115     void keepsTheFloorUnderACarriedResolverThatDeclines() throws Exception {
116         // Adopting the caller's resolver must not make the floor reachable around: what the resolver declines stays unfetched.
117         final SecureTransformer transformer = wrap((href, base) -> null);
118         final StringWriter output = new StringWriter();
119         transformer.transform(AttackTestSupport.streamSource("<root/>"), new StreamResult(output));
120         assertFalse(output.toString().contains(AttackTestSupport.LEAKED_MARKER), "document() the resolver declined must not be fetched");
121     }
122 }