XStream 1.4 upgrade
Confluence is working on upgrading XStream from 1.1.1 to 1.4.x. XStream is a library used to serialize objects to XML and back again.
- 27 June update (8.0.0-m015)
- 9 May update (7.18.0-m68)
- 22 February update
- 15 December update
- 9 December update (7.10.0-rc1)
- 1 December update (7.10.0-beta1)
- 26 October update (milestone 7.10.0-xstream-14-m02)
- 5 November update (milestone 7.10.0-xstream-14-m03)
Update from
We've enabled the XStream allowlist as the default and only behaviour (this will come into effect from Confluence 8.0), and we've removed the blocklist.
Update from
We've upgraded XStream to 1.4.19 in Confluence 7.18, but have not enabled the XStream allowlist at this time (we plan to do this in Confluence 8.0). If your app uses the Confluence API to interact with XStream, there should be no impact.
Apps that initialize their own XStream, and don't use the Confluence XStream API / ConfluenceXStreamCompat may break. If your app uses its own XStream, you'll need to mark the allowlist in that XStream instance.
Update from
confluence-compat-lib 1.4.2 has been released. This resolves an issue where the xstream-security module doesn't work correctly with the confluence-compat-lib in dev mode. Plugins can now use xstream-security module in atlassian-plugin.xml to mark the classes needed in XStream allowlist .
See CONFSERVER-74692 - Getting issue details... STATUS for more details, including Confluence versions that bundle the upgraded version of this library.
Update from
confluence-compat-lib 1.4.1 has been released!
To cater plugins requiring access to converters/alias as requested by
CONFSERVER-60576
-
Getting issue details...
STATUS
, we have added three more methods in XStreamManagerCompat
namely getXStream()
, registerConverter(converter, priority)
and alias(alias, class)
. These methods allow a plugin vendor to hook in XStream converters and aliases, regardless of the Confluence version, automatically. Ideally plugins using Bandana wouldn't need to use any of this, and these methods are only meant for plugins which were using XStream themselves in custom ways. Please see the current structure of the class in the code example below:
/**
* This class lets plugins to be compatible with all supported versions of Confluence for XStream.
* Since 7.10 Confluence uses newer XStream and has added methods in XStreamManager.
* XStreamManagerCompat is responsible for choosing between XStreamManager or creating a new XStream(Confluence 7.9 and prior)
*/
@Component
public class XStreamManagerCompat {
/**
* Serialize an object to a pretty-printed XML String.
*/
public String toXML(Object obj) {
return delegate.toXML(obj);
}
/**
* Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case * of an exception.
*/
public void toXML(Object obj, Writer writer) {
delegate.toXML(obj, writer);
}
/**
* Deserialize an object from an XML String.
*/
public Object fromXML(String xml) {
return delegate.fromXML(xml);
}
/**
* Deserialize an object from an XML Reader.
*/
public Object fromXML(Reader reader){
return delegate.fromXML(reader);
}
/**
* Provides a way to access 1.1.1 XStream object
* Supposed to be removed when Confluence 7.9 is EOL.
* @return XStream 1.1.1 equivalent object.
*/
public XStream getXStream() {
return delegate.getXStream();
}
/**
* Register a converter with the appropriate priority.
* @param XStream converter to register
* @param priority Priority to be used in XStream
*/
public void registerConverter(Converter converter, Integer priority) {
delegate.registerConverter(converter, priority);
}
/**
* Alias a Class to a shorter name to be used in XML elements.
* @param name Short name
* @param type Type to be aliased
*/
public void alias(String name, Class<?> type) {
delegate.alias(name, type); }
}
Update from
Confluence 7.10.0-rc1 is now available (Release Candidate 1). The release candidate (and the beta release before it) contains the XStream upgrade, and we strongly recommend you test your plugin, as you may need to make changes, as described below.
Update from
We've made the following changes in the 7.10.0-xstream-14-m03 milestone.
We have added one more method in ConfluenceXStreamManager to allow a plugin to use the plugin bundle Classloader while serialisations and avoid ClassCastExceptions arising from mismatched class loaders. In simple words, Confluence Core uses an uberClassLoader in its XStream, but plugin classes are generally loaded by a BundleClassLoader, once an XML XStream String is de-serialised it can result in a Class object which doesn't belong to the plugin's classloader and hence a ClassCastException. A plugin can utilise the following method from
ConfluenceXStreamManager
, to do-so:/** * Allows to use plugin OSGi classloaders and avoid ClassCastException in case of plugin re-installations. * * @param classLoader classLoader to use for serialization/deserialization in XStream * @return ConfluenceXStream based on classLoader */ ConfluenceXStream getPluginXStream(ClassLoader classLoader);
To use above API in our old example, the usage would look like:
public class ExampleClass { private final ConfluenceXStream pluginXStream; @Autowired public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) { //Note that classLoader being used below is plugin's classloader this.pluginXStream = confluenceXStreamManager.getPluginXStream(getClass().getClassLoader()); //Also noe that pluginXStream is directly made a field and can just be created once in plugin lifecycle. } private SomeObject toSomeObject(String someString) { return (SomeObject) pluginXStream.toXML(someString); } }
confluence-compat-lib
1.4.0 has been released to provide Backward-compatible code for plugins which want to keep a single version across all supported Confluence versions.
Plugins can utilise componentcom.atlassian.confluence.compat.setup.xstream.XStreamManagerCompat
plugins. It can be initiated by way of Spring or manually with no-arg constructorXStreamManagerCompat()
. See following for its signature:/** * This class lets plugins to be compatible with all supported versions of Confluence for XStream. * Since 7.10 Confluence uses newer XStream and has added methods in XStreamManager. * XStreamManagerCompat is responsible for choosing between XStreamManager or creating a new XStream(Confluence 7.9 and prior) */ @Component public class XStreamManagerCompat { /** * Serialize an object to a pretty-printed XML String. */ public String toXML(Object obj) { return delegate.toXML(obj); } /** * Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case * of an exception. */ public void toXML(Object obj, Writer writer) { delegate.toXML(obj, writer); } /** * Deserialize an object from an XML String. */ public Object fromXML(String xml) { return delegate.fromXML(xml); } /** * Deserialize an object from an XML Reader. */ public Object fromXML(Reader reader){ return delegate.fromXML(reader); } }
Plugins can use above compat API by referencing following example:
pom.xml snippet<dependency> <groupId>com.atlassian.confluence.compat</groupId> <artifactId>confluence-compat-lib</artifactId> <version>1.4.0</version> </dependency>
spring-component.xml<bean id="xStreamManagerCompat" class="com.atlassian.confluence.compat.setup.xstream.XStreamManagerCompat"/>
Example compatibility codepublic class ExampleClass { private final XStreamManagerCompat xStreamManagerCompat; @Autowired public ExampleClass(@Qualifier("xStreamManagerCompat") final XStreamManagerCompat xStreamManagerCompat) { this.xStreamManagerCompat = xStreamManagerCompat; } private SomeObject toSomeObject(String someString) { return (SomeObject) xStreamManagerCompat.toXML(someString); } }
Update from
Confluence is working on upgrading XStream from 1.1.1 to 1.4.13. XStream is a library to serialize objects to XML and back again.
Unfortunately, XStream 1.4.x breaks compatibility with the old format of stored data and ends up throwing ConversionException errors while un-marshalling data. To support existing data conversion, we are adding a layer of backward compatibility to support existing data in Bandana.
In most cases plugins will not need to use XStream by themselves for Bandana storage, but we suspect due to historical reasons, people might be creating their own XStream instances (as in the example mentioned in this Jira comment). Classes with their own "new XStream()"
instance will not benefit from our 1.1.1 compatible XStream.
We would also like to highlight that Bandana already contains XStream marshalling/unmarshalling under the hood, so if plugins are also handling marshals, it will cause double marshalling/unmarshalling which is unnecessary. We strongly recommend you move away from this approach and rely only on Bandana Manager's unmarshallers. Here are two examples of double marshalling/unmarshalling:
private static final BandanaContext BANDANA_CONTEXT = new ConfluenceBandanaContext();
private final XStream xStream = new XStream();
public SomeObject getSomeObject() {
Object aStringObject = this.bandanaManager.getValue(BANDANA_CONTEXT, "some-key");
if (aStringObject instanceof String) {
try {
Object someObject = this.xStream.fromXML((String)aStringObject);
if (someObject instanceof SomeObject) {
return (SomeObject) someObject;
}
} catch (ConversionException var3) {
LOG.error("couldn't parse data");
throw new Exception("Some Exception");
}
}
}
public void setSomeObject(SomeObject someObject) {
this.bandanaManager.setValue(BANDANA_CONTEXT, "some-key", this.xStream.toXML(someObject));
}
private DesignConfiguration getConfig() {
DesignConfiguration config = null;
try {
Serializable serializable = (Serializable) bandanaManager.getValue(confluenceBandanaContext, BD_KEY);
if (serializable instanceof DesignConfiguration) {
config = (DesignConfiguration) serializable;
} else if (serializable instanceof String) {
config = (DesignConfiguration) xStream.fromXML((String) serializable);
}
} catch (Exception e) {
logger.warn("Error converting DesignConfiguration", e);
}
return config;
}
For cases where plugins do need to use XStream for some reason, we are exposing Confluence managed XStream instances through ConfluenceXStreamManager. Following classes will help you start:
com.atlassian.confluence.setup.xstream.ConfluenceXStream
com.atlassian.confluence.setup.xstream.ConfluenceXStreamManager
(plugin accessible spring bean with id "xStreamManager")com.atlassian.confluence.impl.xstream.DefaultConfluenceXStreamManager
com.atlassian.confluence.setup.xstream.XStreamManager
(existing class which is now marked as deprecated in favour ofDefaultConfluenceXStreamManager
)
Spring bean "xStreamManager"
provides you ConfluenceXStream
which is a wrapper around XStream but with the backward compatibility layer. confluenceXStream.toXML(obj)
achieves you same results as xstream.toXML(obj)
. See:
public interface ConfluenceXStream {
/**
* Serialize an object to a pretty-printed XML String.
*
* @throws XStreamException if the object cannot be serialized
*/
String toXML(Object obj);
/**
* Serialize an object to the given Writer as pretty-printed XML. The Writer will be flushed afterwards and in case
* of an exception.
*
* @throws XStreamException if the object cannot be serialized
*/
void toXML(Object obj, Writer writer);
/**
* Deserialize an object from an XML String.
*
* @throws XStreamException if the object cannot be deserialized
*/
Object fromXML(String xml);
/**
* Deserialize an object from an XML Reader.
*
* @throws XStreamException if the object cannot be deserialized
*/
Object fromXML(Reader reader);
}
What you need to do
- Check whether there is double marshalling in Bandana (as mentioned in above examples). If there is, you'll need to avoid this, and add migration tasks to move away from this situation.
If you can get rid of double-marshalling, you might need to introduce a migration layer which runs when your plugin starts up. This could be done by leveraging ConfluenceXStream's
toXML(obj)
to create the right bandanaValue from String and then do the following:private DesignConfiguration getConfig() { DesignConfiguration config = null; try { config = (DesignConfiguration) bandanaManager.getValue(confluenceBandanaContext, BD_KEY); } catch (Exception e) { logger.warn("Error converting DesignConfiguration", e); } return config; }
If you can't get rid of double-marshalling, please use
confluenceXStreamManager.getConfluenceXStream.toXML(obj)
instead of your own xStream object. For example:public class ExampleClass { private final ConfluenceXStreamManager confluenceXStreamManager; @Autowired public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) { this.confluenceXStreamManager = confluenceXStreamManager; } private DesignConfiguration getConfig() { DesignConfiguration config = null; try { Serializable serializable = (Serializable) bandanaManager.getValue(confluenceBandanaContext, BD_KEY); if (serializable instanceof DesignConfiguration) { config = (DesignConfiguration) serializable; } else if (serializable instanceof String) { config = (DesignConfiguration) confluenceXStreamManager.getConfluenceXStream().toXML((String) serializable); } } catch (Exception e) { logger.warn("Error converting DesignConfiguration", e); } return config; } }
Note:
confluenceXStreamManager.getConfluenceXStream()
has to be done every time asconfluenceXStream
gets reset on each PluginInstall / PluginUninstall event.
- If your plugin is just relying on the BandanaManager API without touching XStream itself, this plugin is safe, there's no need to worry. Backward compatibility will come for free.
If your plugin requires XStream to do some custom stuff, use
confluenceXStreamManager.getConfluenceXStream.toXML(obj)
instead ofxStream.toXML(obj)
. See:public class ExampleClass { private final ConfluenceXStreamManager confluenceXStreamManager; @Autowired public ExampleClass(@ComponentImport ConfluenceXStreamManager confluenceXStreamManager) { this.confluenceXStreamManager = confluenceXStreamManager; } private SomeObject toSomeObject(String someString) { return (SomeObject) confluenceXStreamManager.getConfluenceXStream().toXML(someString); } }
Security changes required to use XStream from Confluence XStream API
- XStream 1.4.x brings its own security module, as opposed to the past where we had our own
XStreamSecurityClassFilter. This security module required to work in allow-list mode is not enabled by default. We have introduced a
xstream.allowlist.enable
system property which allows customers to enable the XStream allow list and block everything by default.- Note: To help you to make your plugin work with the security allowlist, the
xstream.allowlist.enable
system property is on by default in the EAP milestone. Plugins can use a new
xstream-security
module inatlassian-plugin.xml
to configure Confluence's XStreams with types, regex or wildcards. We strongly recommend plugins implement this to avoid XStream restrictions if a customer goes into more strict mode. See:<xstream-security key = "xstream-set" name="Some XStream allowlist set"> <type>com.atlassian.test.ExampleClass</type> <type>com.atlassian.test.AnotherExampleClass</type> <regex>com.atlassian.example.*</regex> <wildcard>com.some.package.**</wildcard> </xstream-security>
- These types, regex or wildcards are in line with what XStream states in their documentation, See https://x-stream.github.io/security.html#example for more information.
- XStream 1.5 will default to allow-list security behaviour. This could come in the next XStream upgrade.
Upcoming changes
We're still working on this upgrade. A future EAP milestone will:
- Introduce Upgrade task to migrate Bandana data in 1.1.1 format to 1.4.x data format
- Ensure CCMA understands server 1.1.1 format and creates export for cloud using 1.4.x format