Struts 6.3 upgrade in Confluence 8.6

Update: We are backporting the mentioned changes to Confluence 8.5 LTS immediately


Hi all,

We’ve upgraded our MVC framework from Struts 2.5 to Struts 6.3 in Confluence 8.6.

If your plugin targets an earlier version of Confluence, we recommend using confluence-compat-lib to call ActionContext and ServletActionContext methods.

We recently released version 1.6.1 which added support for Struts 6.3 as well as the following fixes:

  • Fixed #getParameters not being consistent between WebWork and Struts2
  • Implemented auto-initialisation of ActionContext when using #setContextMap
  • Enforced type safety where appropriate

This library now provides transparent compatibility between all currently supported versions of Confluence, including:

  • Confluence 7.19 LTS (WebWork 2.1)
  • Confluence 8.5 LTS (Struts 2.5)
  • Confluence 8.6 (Struts 6.3)

The library now requires the following additional package import:

com.opensymphony.xwork2.conversion.impl;resolution:=optional,

More details on how to use this library can be found in this guide.

As part of this upgrade, we’ve additionally implemented the following stricter security measures:

  • OGNL expressions cannot access proxied objects (e.g. Spring AOP)
  • OGNL expressions cannot access static fields
  • OGNL expressions cannot access fields that are non-public or without a public getter
  • OGNL expressions cannot access classes from the default package
  • OGNL expressions are limited to 150 characters
  • OGNL expressions cannot contain certain nodes/operations
  • Stricter naming requirements for actions ([a-zA-Z0-9\-]*), action methods ([a-zA-Z_]*[0-9]*), and namespaces ([a-zA-Z0-9/\-]*)
  • OGNL ($ognl) and Struts ($struts) objects removed from Velocity context

The upcoming EAP will contain most of the aforementioned changes, with the remainder to be progressively implemented prior to final release.

Regards,
Kusal Kithul-Godage
Software Engineer, Confluence Data Center

3 Likes

Hey @Kusal ,

I’m having difficulty on 8.6.0-m79 with the Struts upgrade. I’ve upgraded the compat lib to 1.6.0 and everything is working fine on earlier versions, but not for 8.6.0.

Our simple action is as follows:

 <xwork name="SR Confluence Space Admin Action" key="space-admin-action">
        <description>Action for SR Confluence Space Admin</description>
        <constant name="struts.devMode" value="true" />
        <package name="sr-conf-space-admin" extends="json-default, velocity-default" namespace="/scriptrunner-confluence/space_admin">
            <default-interceptor-ref name="validatingStack"/>
            <action name="items"
                    class="com.onresolve.scriptrunner.confluence.action.ConfluenceSpaceAdminAction"
                    method="fetchItems">
                <result name="success" type="velocity">/com/onresolve/scriptrunner/templates/vm/space-admin-items.vm
                </result>
                <result name="error" type="velocity">/com/onresolve/scriptrunner/templates/vm/space-admin-error.vm
                </result>
            </action>
        </package>
</xwork>

Invoking the action through the url:

http://localhost:8080/confluence/scriptrunner-confluence/space_admin/items.action?section=space_admin_builtin_scripts&key=ds

Results in this error:

[apache.struts2.dispatcher.Dispatcher] Could not find action or result: /confluence/scriptrunner-confluence/space_admin/items.action?section=space_admin_builtin_scripts&key=ds
There is no Action mapped for namespace [/] and action name [items] associated with context path [/confluence]. - [unknown location]

I can’t seem to figure out what would need to be changed on our end. It looks like there would need to be some changes to Confluence’s web.xml/struts.xml. Can you let me know if I’m missing something?

Thanks!

Apologies @jyamdogo, there was a typo in my original post. Namespaces must now meet the RegEx validation [a-zA-Z0-9/\-]* - underscores are no longer permitted. Hope that helps.

1 Like

So the plugin key can’t be used anymore for namespaces? The dot is not permitted anymore?

I thought it was documented somewhere that plugins were required to be deployed under /admin/{pluginKey}.

Hi @aragot

I understand that some of our tutorials and code examples have used the artifactId for the Struts namespace; although, I don’t believe that has ever been a requirement. Please let me know if you have found documentation stating otherwise and I will look into this further.

It is however definitely recommended to use a unique namespace name to avoid conflicting with other plugins.

And yes, periods are also now prohibited from namespaces.

The requirement of having apps under the namespace /admin/plugins/{appKey} is not here anymore, that’s correct. There was a reference to it in 2014 when the Marketplace was launched. Maybe the requirement was removed years ago.

Maybe, if you have the edit permissions, you can remove it from this example on this page as well: https://developer.atlassian.com/server/confluence/writing-a-space-admin-screen/#2--declare-an-action

1 Like

@Kusal , In your Strust 2 upgrade guide, I see that $req is deprecated in favour of $request, with the mention “For velocity VM files only. For now, we have put $req for backward compatibility, we might clean this up by end of 8.0” As you know, overwhelming cases of using this variable are for the context path: <a href="${req.contextPath}/my-link.action?..."

Can we already request that the duration of this backward compatibility is for two years after the next LTS? Here’s why:

  • App developers need to support customers,
  • Atlassian tells customers that LTS versions are supported for two years,
  • So, if Confluence 8.8 is LTS and published in December 2023, our plugins need to be compatible with it until December 2025, or it would be bad experience for customers,
  • Until then, we can’t use $request because it hasn’t been introduced for some customers, and we need to keep using $req for older customers.

I understand that the full $request object may be too hard for you to inject; A dummy object with ${req.contextPath} only would be satisfying. We are able to replace all other occurrences of $req with conditionals, but the case of contextPath is hairy because it is used extremely often, and a compatibility conditional in this position would be verbose.

Instead of working through the differences related to $req/$request with conditional logic in a template you may want to consider injecting the context path into the template render context yourself. In your Java code used to render the template you could use your own custom parameter:

// Backwards-compatible way to provide context path in the velocity template.
// Confluence 7.x uses $req and Confluence 8.x uses $request in velocity templates so
// we step around that incompatibility by providing our own custom parameter:
context.put("reqContextPath", request.getContextPath());

Then consume your custom $reqContextPath in your velocity template.

Dear @Kusal

may I ask where you published version 1.6.0 of the confluence-compat-lib? It is not here.

Thank you.

Andreas

It looks like Atlassian’s closed-source artifacts are no longer indexed on mvnrepository.com. They are still accessible via the Atlassian Maven proxy.

1 Like

Thank you for your contributions @turehoefner_appfire and @scott.dudley , I’ve opted for this syntax (rather than #set ($req = $request)) to avoid overriding $req if another plugin defines $request and still override it when Confluence doesn’t define $req anymore:

#if (!$req) #set ($req = $request) #end
1 Like

Hi there @Kusal,

after updating confluence-compat-lib from version 1.5.4 to 1.6.0 we’ve seen that class XStreamManagerCompat is not available anymore, is there any fix or solution?

Thanks in advance.

Regards,
Avelino.

3 Likes

Hi @avelino

Please use the com.atlassian.confluence.setup.xstream.ConfluenceXStreamManager directly.

Hi @Kusal,

Err…but doesn’t that defeat the whole purpose of using confluence-compat-lib? What if vendors want to support both Confluence 7.9 and Confluence 8.6 with the same JAR? Can this class be reinstated? (Don’t forget that vendor version support matrices are not necessarily the same as Atlassian’s.)

6 Likes

Hi @scott.dudley

Thanks for expressing your concerns. These classes have been reinstated in version 1.6.1 released today.

1 Like

Hi again @Kusal and thanks for the quick release.

After upgrading the lib to version 1.6.1, using Confluence 8.6.0-rc1, after installing the app, when trying to use it from Space tools option, we get the following error/warning messages:

[opensymphony.xwork2.ognl.SecurityMemberAccess] isAccessible Access to non-public [private com.atlassian.plugin.webresource.WebResourceManager com.comalatech.confluence.workflowcontainer.action.SpaceWorkflowsAction.webResourceManager] is blocked!

Also please see attached stacktrace.pdf file that includes the following error:

stacktrace.pdf (47.6 KB)

Regards,
Avelino

4 Likes

Hi @avelino

The first error is as stated, you must add a public getter for any private fields you wish to access via expressions.

To resolve the second error you must add the following additional package import to your plugin:
com.opensymphony.xwork2.conversion.impl;resolution:=optional,

I will look into amending the relevant changelog and documentation as I can see both these points have been missed.

3 Likes

Hi there @Kusal,

after applying the fixes you suggested we are good to go.

Thanks a lot for your support and quick replies!

Regards,
Avelino.

Hi there!

We are using the confluence-compat-lib 1.6.1 and try to put a key/value pair into actionContextCompatManager.getSession().

Unfortunately, calling getSession does not work in Confluence 8.6:

com.atlassian.confluence.api.service.exceptions.ServiceException: Struts6 ActionContext couldn't be initialized. at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:49) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.lambda$new$0(ActionContextCompatManager.java:31) at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers.java:181) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.getSession(ActionContextCompatManager.java:234)

(…)

Caused by: java.lang.ClassNotFoundException: com.opensymphony.xwork2.conversion.impl.ConversionData not found by net.seibertmedia.xxxxxxxx [309] at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1591) at org.apache.felix.framework.BundleWiringImpl.access$300(BundleWiringImpl.java:79) at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1976) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:467) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts6.<init>(ActionContextStruts6.java:47) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:47)

Are we doing something wrong here or is it an issue with Confluence 8.6 or the confluence-compat-lib? @Kusal

1 Like

When using confluence-compat-lib 1.5.4, the error is different, but still exists (which is no surprise because of the Struts upgrade in Confluence 8.6):

com.atlassian.confluence.api.service.exceptions.ServiceException: ActionContext couldn't be initialized. at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:45) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.lambda$new$0(ActionContextCompatManager.java:29) at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers.java:181) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.getSession(ActionContextCompatManager.java:205)

(…)

Caused by: java.lang.NoSuchMethodException: com.opensymphony.xwork2.ActionContext.setApplication(java.util.Map) at java.base/java.lang.Class.getMethod(Class.java:2227) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.getACStruts2Method(ActionContextStruts2AndWWCompat.java:229) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextStruts2AndWWCompat.<init>(ActionContextStruts2AndWWCompat.java:38) at com.atlassian.confluence.compat.struts2.actioncontext.ActionContextCompatManager.initialiseActionContextCompat(ActionContextCompatManager.java:36)