From bb7161029669b6d2055ac42ff17de315ef1272f0 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Mon, 17 Oct 2022 08:54:03 +0200 Subject: [PATCH] WW-2815 Refactors XStreamHandler to allow to provide a custom configuration --- .../struts2/rest/handler/XStreamHandler.java | 33 ++-- .../XStreamAllowedClassNames.java} | 4 +- .../XStreamAllowedClasses.java} | 4 +- .../XStreamPermissionProvider.java | 2 +- .../rest/handler/xstream/XStreamProvider.java | 29 ++++ .../rest/handler/XStreamHandlerTest.java | 160 ++++++++++++++++++ 6 files changed, 216 insertions(+), 16 deletions(-) rename plugins/rest/src/main/java/org/apache/struts2/rest/handler/{AllowedClassNames.java => xstream/XStreamAllowedClassNames.java} (90%) rename plugins/rest/src/main/java/org/apache/struts2/rest/handler/{AllowedClasses.java => xstream/XStreamAllowedClasses.java} (90%) rename plugins/rest/src/main/java/org/apache/struts2/rest/handler/{ => xstream}/XStreamPermissionProvider.java (95%) create mode 100644 plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java create mode 100644 plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java index 22e597561..d3534e32b 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java @@ -21,6 +21,7 @@ package org.apache.struts2.rest.handler; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ModelDriven; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.security.ArrayTypePermission; import com.thoughtworks.xstream.security.ExplicitTypePermission; import com.thoughtworks.xstream.security.NoTypePermission; @@ -29,12 +30,15 @@ import com.thoughtworks.xstream.security.PrimitiveTypePermission; import com.thoughtworks.xstream.security.TypePermission; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClassNames; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClasses; +import org.apache.struts2.rest.handler.xstream.XStreamPermissionProvider; +import org.apache.struts2.rest.handler.xstream.XStreamProvider; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Collection; -import java.util.Date; import java.util.Map; import java.util.Set; @@ -68,7 +72,15 @@ public class XStreamHandler extends AbstractContentTypeHandler { } protected XStream createXStream(ActionInvocation invocation) { - XStream stream = new XStream(); + XStream stream; + if (invocation.getAction() instanceof XStreamProvider) { + LOG.debug("Using provider {} to create instance of XStream", invocation.getAction().getClass().getSimpleName()); + stream = ((XStreamProvider) invocation.getAction()).createXStream(); + } else { + LOG.debug("Creating default XStream instance using Stax driver: {}", StaxDriver.class.getSimpleName()); + stream = new XStream(new StaxDriver()); + } + LOG.debug("Clears existing permissions"); stream.addPermission(NoTypePermission.NONE); @@ -82,13 +94,13 @@ public class XStreamHandler extends AbstractContentTypeHandler { private void addPerActionPermission(ActionInvocation invocation, XStream stream) { Object action = invocation.getAction(); - if (action instanceof AllowedClasses) { - Set> allowedClasses = ((AllowedClasses) action).allowedClasses(); - stream.addPermission(new ExplicitTypePermission(allowedClasses.toArray(new Class[allowedClasses.size()]))); + if (action instanceof XStreamAllowedClasses) { + Set> allowedClasses = ((XStreamAllowedClasses) action).allowedClasses(); + stream.addPermission(new ExplicitTypePermission(allowedClasses.toArray(new Class[0]))); } - if (action instanceof AllowedClassNames) { - Set allowedClassNames = ((AllowedClassNames) action).allowedClassNames(); - stream.addPermission(new ExplicitTypePermission(allowedClassNames.toArray(new String[allowedClassNames.size()]))); + if (action instanceof XStreamAllowedClassNames) { + Set allowedClassNames = ((XStreamAllowedClassNames) action).allowedClassNames(); + stream.addPermission(new ExplicitTypePermission(allowedClassNames.toArray(new String[0]))); } if (action instanceof XStreamPermissionProvider) { Collection permissions = ((XStreamPermissionProvider) action).getTypePermissions(); @@ -101,13 +113,12 @@ public class XStreamHandler extends AbstractContentTypeHandler { protected void addDefaultPermissions(ActionInvocation invocation, XStream stream) { stream.addPermission(new ExplicitTypePermission(new Class[]{invocation.getAction().getClass()})); if (invocation.getAction() instanceof ModelDriven) { - stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()})); + stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()})); } stream.addPermission(NullPermission.NULL); stream.addPermission(PrimitiveTypePermission.PRIMITIVES); stream.addPermission(ArrayTypePermission.ARRAYS); stream.addPermission(CollectionTypePermission.COLLECTIONS); - stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class})); } public String getContentType() { @@ -125,7 +136,7 @@ public class XStreamHandler extends AbstractContentTypeHandler { @Override public boolean allows(Class type) { return type != null && type.isInterface() && - (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)); + (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)); } } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java similarity index 90% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java index c7c23ba58..340ac9c9b 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClassNames.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClassNames.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import java.util.Set; -public interface AllowedClassNames { +public interface XStreamAllowedClassNames { Set allowedClassNames(); } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java similarity index 90% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java index 149a32c90..504c35f89 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/AllowedClasses.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamAllowedClasses.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import java.util.Set; -public interface AllowedClasses { +public interface XStreamAllowedClasses { Set> allowedClasses(); } diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java similarity index 95% rename from plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java rename to plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java index b58bf91db..f163912cf 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamPermissionProvider.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamPermissionProvider.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.rest.handler; +package org.apache.struts2.rest.handler.xstream; import com.thoughtworks.xstream.security.TypePermission; diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java new file mode 100644 index 000000000..517029d74 --- /dev/null +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/xstream/XStreamProvider.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.rest.handler.xstream; + +import com.thoughtworks.xstream.XStream; + +/** + * An interface to be implemented by an action to create/provide an instance + * of XStream - it allows customisation per action + */ +public interface XStreamProvider { + XStream createXStream(); +} diff --git a/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java b/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java new file mode 100644 index 000000000..3510634ed --- /dev/null +++ b/plugins/rest/src/test/java/org/apache/struts2/rest/handler/XStreamHandlerTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.rest.handler; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.XWorkTestCase; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import com.thoughtworks.xstream.security.ExplicitTypePermission; +import com.thoughtworks.xstream.security.TypePermission; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClassNames; +import org.apache.struts2.rest.handler.xstream.XStreamAllowedClasses; +import org.apache.struts2.rest.handler.xstream.XStreamPermissionProvider; +import org.apache.struts2.rest.handler.xstream.XStreamProvider; + +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class XStreamHandlerTest extends XWorkTestCase { + + private XStreamHandler handler; + private MockActionInvocation ai; + + public void setUp() throws Exception { + super.setUp(); + handler = new XStreamHandler(); + ai = new MockActionInvocation(); + ActionSupport action = new ActionSupport(); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(Locale.US); + ai.setInvocationContext(context); + ai.setAction(action); + } + + public void testObjectToXml() throws Exception { + // given + SimpleBean obj = new SimpleBean(); + obj.setName("Jan"); + obj.setAge(12L); + obj.setParents(Arrays.asList("Adam", "Ewa")); + + // when + Writer stream = new StringWriter(); + handler.fromObject(ai, obj, null, stream); + + // then + stream.flush(); + assertThat(stream.toString()) + .contains("") + .contains("Jan") + .contains("12") + .contains("") + .contains("Adam") + .contains("Ewa") + .contains(""); + } + + public void testXmlToObject() { + // given + String xml = "Jan12AdamEwa"; + + SimpleBean obj = new SimpleBean(); + ai.setAction(new SimpleAction()); + + // when + Reader in = new StringReader(xml); + handler.toObject(ai, in, obj); + + // then + assertNotNull(obj); + assertEquals("Jan", obj.getName()); + assertEquals(12L, obj.getAge().longValue()); + assertNotNull(obj.getParents()); + assertThat(obj.getParents()) + .hasSize(2) + .containsExactly("Adam", "Ewa"); + } + + public void testXmlToObjectWithAliases() { + // given + String xml = "Jan12AdamEwa"; + + SimpleBean obj = new SimpleBean(); + ai.setAction(new SimpleAliasAction()); + + // when + Reader in = new StringReader(xml); + handler.toObject(ai, in, obj); + + // then + assertNotNull(obj); + assertEquals("Jan", obj.getName()); + assertEquals(12L, obj.getAge().longValue()); + assertNotNull(obj.getParents()); + assertThat(obj.getParents()) + .hasSize(2) + .containsExactly("Adam", "Ewa"); + } + + private static class SimpleAction implements XStreamAllowedClasses, XStreamAllowedClassNames, XStreamPermissionProvider { + @Override + public Set> allowedClasses() { + Set> classes = new HashSet<>(); + classes.add(SimpleBean.class); + return classes; + } + + @Override + public Set allowedClassNames() { + HashSet strings = new HashSet<>(); + strings.add(ArrayList.class.getName()); + return strings; + } + + @Override + public Collection getTypePermissions() { + ArrayList permissions = new ArrayList<>(); + permissions.add(new ExplicitTypePermission(new Class[]{String.class})); + return permissions; + } + } + + private static class SimpleAliasAction extends SimpleAction implements XStreamProvider { + @Override + public XStream createXStream() { + XStream stream = new XStream(new StaxDriver()); + stream.alias("parents", ArrayList.class); + stream.alias("data", SimpleBean.class); + return stream; + } + } +}