1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.xml.secure;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertSame;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29
30 import javax.xml.xpath.XPath;
31 import javax.xml.xpath.XPathFactory;
32 import javax.xml.xpath.XPathFactoryConfigurationException;
33 import javax.xml.xpath.XPathFunctionResolver;
34 import javax.xml.xpath.XPathVariableResolver;
35
36 import org.junit.jupiter.api.Assumptions;
37 import org.junit.jupiter.api.Tag;
38 import org.junit.jupiter.api.Test;
39
40 @Tag("xpath")
41 class SecureXPathFactoryTest {
42
43
44
45
46 private static final String XPATH_GROUP_LIMIT = "jdk.xml.xpathExprGrpLimit";
47
48
49
50
51
52
53
54
55
56 private static Method propertyMethod(final String name, final Class<?>... parameterTypes) {
57 try {
58 return XPathFactory.class.getMethod(name, parameterTypes);
59 } catch (final NoSuchMethodException e) {
60 Assumptions.abort("XPathFactory." + name + " requires Java 18 or later");
61 throw new AssertionError("unreachable");
62 }
63 }
64
65 @Test
66 void createsAndConfiguresAFactoryForTheDefaultObjectModel() throws Exception {
67 final XPathFactory factory = SecureXPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
68 final XPathFunctionResolver resolver = (name, arity) -> null;
69 factory.setXPathFunctionResolver(resolver);
70 final XPathVariableResolver variableResolver = name -> null;
71 factory.setXPathVariableResolver(variableResolver);
72 assertTrue(factory.isObjectModelSupported(XPathFactory.DEFAULT_OBJECT_MODEL_URI));
73 final SecureXPath xpath = (SecureXPath) factory.newXPath();
74 assertSame(resolver, xpath.getXPathFunctionResolver());
75 assertSame(variableResolver, xpath.getXPathVariableResolver());
76 }
77
78 @Test
79 void createsSecureXPathFromStaticEntryPoints() {
80 assertInstanceOf(SecureXPath.class, SecureXPathFactory.newInstance().newXPath());
81 assertInstanceOf(SecureXPath.class, SecureXPathFactory.newDefaultInstance().newXPath());
82 }
83
84 @Test
85 void delegatesTheJava18PropertyApi() throws Exception {
86
87
88 final Method setProperty = propertyMethod("setProperty", String.class, String.class);
89 final Method getProperty = propertyMethod("getProperty", String.class);
90 final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
91 setProperty.invoke(factory, XPATH_GROUP_LIMIT, "5");
92 assertEquals("5", getProperty.invoke(factory, XPATH_GROUP_LIMIT), "a property set on the secured factory must be read back from the delegate");
93 }
94
95 @Test
96 void preservesANullXPathFromTheDelegate() {
97 final XPathFactory delegate = new XPathFactory() {
98
99 @Override
100 public boolean getFeature(final String name) {
101 return false;
102 }
103
104 @Override
105 public boolean isObjectModelSupported(final String objectModel) {
106 return true;
107 }
108
109 @Override
110 public XPath newXPath() {
111 return null;
112 }
113
114 @Override
115 public void setFeature(final String name, final boolean value) {
116 }
117
118 @Override
119 public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
120 }
121
122 @Override
123 public void setXPathVariableResolver(final XPathVariableResolver resolver) {
124 }
125 };
126 assertNull(SecureXPathFactory.secure(delegate).newXPath());
127 }
128
129 @Test
130 void reportsAnUnknownPropertyLikeTheDelegate() {
131 final Method getProperty = propertyMethod("getProperty", String.class);
132 final XPathFactory factory = SecureXPathFactory.newDefaultInstance();
133 final InvocationTargetException thrown = assertThrows(InvocationTargetException.class,
134 () -> getProperty.invoke(factory, "jdk.xml.noSuchProperty"));
135 assertInstanceOf(IllegalArgumentException.class, thrown.getCause(), "an unrecognized property must surface the delegate's own rejection");
136 }
137
138 @Test
139 void wrapsARejectedRequiredFeatureInSecureException() {
140 final XPathFactory rejectingFactory = new XPathFactory() {
141
142 @Override
143 public boolean getFeature(final String name) {
144 return false;
145 }
146
147 @Override
148 public boolean isObjectModelSupported(final String objectModel) {
149 return true;
150 }
151
152 @Override
153 public XPath newXPath() {
154 return null;
155 }
156
157
158
159
160
161
162 @Override
163 public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
164 throw new XPathFactoryConfigurationException(name);
165 }
166
167 @Override
168 public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
169 }
170
171 @Override
172 public void setXPathVariableResolver(final XPathVariableResolver resolver) {
173 }
174 };
175 assertThrows(SecureException.class, () -> SecureXPathFactory.secure(rejectingFactory));
176 }
177 }