Struts module

Available:

Confluence 8.5.3 and later
(replaced XWork introduced in 1.4)

This module was previously defined as xwork in Confluence versions prior to 8.5.3. The xwork module will continue to be supported until Confluence 10.0 for backwards compatibility.

Purpose of this module

Struts plugin modules enable you to deploy Struts actions and views as a part of your plugins.

OGNL Class Allowlist

The Struts web framework is underpinned by an expression language technology known as OGNL. Historically, the OGNL technology has been the source of numerous critical vulnerabilities. Atlassian has developed a number of security measures to protect against the reoccurrence of such vulnerabilities.

One such measure is the OGNL class allowlist. The allowlist is a list of classes that are permitted to be used in OGNL expressions. The allowlist comprises classes from the following sources:

  • Pre-defined list of classes, designated as safe by Atlassian and Struts
  • Classes defined as an Action or other Struts package component in a Struts module (see Configuration elements)
  • Classes returned by an Action class getter, annotated with @StrutsParameter (see Defining Request Parameters)
  • Classes listed in struts.allowlist.classes in a Struts module (see Configuration elements)
  • Classes belonging to a package listed in struts.allowlist.packages in a Struts module (see Configuration elements)

Generally, defining your Struts package components in a Struts module descriptor and annotating all your Struts parameters with @StrutsParameter will be sufficient to allow your plugin to function as expected. However, some Struts functionality may require additional classes to be added to the allowlist. Please observe the log output for warnings and add classes to the allowlist as appropriate. Plugins usually only need to allowlist model or DTO classes.

Avoid allowlisting classes which contain privileged or dangerous members. Examples of a privileged member may be a method which writes to disk or performs some administrative task. Such members should be defined in a class that is not allowlisted and outside your Action class which is allowlisted by default.

Multipart Requests

Additionally, the Struts module should also be used to define multipart request parsing rules. Ordinarily, multipart requests will only be parsed if the request is authorized according to the enforceSiteAccess method in ConfluencePermissionEnforcer:

  • Unauthenticated multipart requests will only be parsed if the site has enabled global anonymous access
  • Authenticated multipart requests from license-unassigned users will only be parsed if the site has enabled unlicensed access
  • Authenticated multipart requests from licensed-assigned users will always be parsed

For endpoints that are intended to parse multipart requests outside the above access criteria, they can be allowlisted using the multipart-upload-allowlist element within a Struts module.

Configuration

The root element for the Struts plugin module is struts. It does not accept a class attribute. It accepts the following child elements for configuration:

Elements

  • package - standard Struts package definition, multiple permitted per module
    • namespace (attribute) - subject to the following RegEx validation: [a-zA-Z0-9/\-]*
    • action - standard Struts action definition
      • name (attribute) - subject to the following RegEx validation: [a-zA-Z0-9\-]*
      • method (attribute) - subject to the following RegEx validation: [a-zA-Z_]*[0-9]*
    • All other sub-elements supported by Struts are permitted
  • constant - used to provide additional Struts related configuration, only 1 of each of the following is permitted per module
    • name="struts.allowlist.classes" value="" (attributes) - the value should be a comma-separated list of plugin classes which are allowed to be used in OGNL expressions
    • name="struts.allowlist.packages" value="" (attributes) - the value should be a comma-separated list of plugin class packages which are allowed to be used in OGNL expressions, note that subpackages are also allowed
  • multipart-upload-allowlist - only 1 multipart-upload-allowlist element is permitted per module
    • regex - a RegEx pattern to match against the request URI (excluding the context path), it will parse multipart requests for any matching requests irrespective of the request's authorisation state.

Example

1
2
<struts name="livesearchaction" key="livesearchaction">
    <package name="livesearch" extends="default" namespace="/plugins/livesearch">
        <default-interceptor-ref name="defaultStack" />
        <action name="livesearch" class="com.atlassian.confluence.extra.livesearch.LiveSearchAction" method="doDefault">
            <result name="success" type="velocity">/templates/extra/livesearch/livesearchaction.vm</result>
        </action>
    </package>

    <constant name="struts.allowlist.classes" value="
              com.plugin.MyDtoClass,
              com.plugin.MyModelClass
    "/>
    <constant name="struts.allowlist.packages" value="
              com.plugin.dto,
              com.plugin.model
    "/>  

    <multipart-upload-allowlist>
        <regex>/admin/test\.action.*</regex>
        <regex>/plugins/livesearch/livesearch\.action.*</regex>
    </multipart-upload-allowlist>
</struts>

Writing an Action

Struts actions should extend ConfluenceActionSupport, which provides a number of helper methods and components that are useful when writing an Action that works within Confluence.

Other action base-classes can be found within Confluence, but we recommend you don't use them - the hierarchy of action classes in Confluence is over-complicated, and likely to be simplified in the future in a way that will break your plugins.

Defining Request Parameters

Request parameters, such as those submitted by a form, can be stored on your Struts Action class by defining getters and setters for them. For example, if you have a form with a field called name, you can store the value of that field by defining a public void setName(String name) method on your Action class, and then importantly, annotating this method with @StrutsParameter. The presence of this annotation indicates that the method is intended for parameter injection and is safe to be invoked by any user who can view the Action.

1
2
private String name;

@StrutsParameter
public void setName(String name) {
    this.name = name;
}

If you wish to populate a DTO (Data Transfer Object) instead of setting the parameters directly on the Action class, you can define a getter for the DTO on your Action class instead. For example, define a method public MyDto getFormData() which is also annotated by @StrutsParameter(depth = 1). Then, a parameter with name formData.fullName will be mapped to the setter setFullName on that DTO. Note that the @StrutsParameter annotation has a depth field which dictates the depth to which parameter injection is permitted. The default value is 0, which only allows setting parameters directly on the Action class as in the first example. A depth of 1 indicates that the immediate public properties of an object returned by the getter are permitted to be set. If you have further nested objects, you can increase the depth accordingly. Do not set this depth field to a value greater than the minimum required for your use case.

1
2
private MyDto formData = new MyDto();

@StrutsParameter(depth = 1)
public MyDto getFormData() {
    return formData;
}

public static class MyDto {
    private String fullName;

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }
}

It is critical that any method you annotate with @StrutsParameter is safe for any user who can view that corresponding action to invoke (including any public methods on objects returned by that method and so forth). Any getters you annotate should only ever return a DTO or a collection/hierarchy of DTOs. Do NOT mix business logic or service references with your parameter injection methods and DTOs. Additionally, any database DTOs should be entirely separate from request parameter/form DTOs.

Do NOT under any circumstance, annotate a method that returns one of the following unsafe objects:

  • live Hibernate persistent objects
  • container or Spring-managed beans, or any other live components/services
  • objects (or objects that contain references to objects) that contain setter methods that are used for anything other than setting form parameter values

Compatibility Note: The @StrutsParameter annotation was introduced in Confluence 8.8.0 and 8.5.6. The deprecated @ParameterSafe annotation will continue to be supported until Confluence 10.0, but will have a fixed depth field value of 2.

Enforcing XSRF protection

Please refer to the following guide to ensure your Struts actions are protected by XSRF tokens where necessary, and how to ensure these tokens are included in your forms and links:

Enable XSRF protection for your app

Accessing your Actions

Actions are added to the Struts core configuration within Confluence, which means they are accessed like any other action.

For example, given the above atlassian-plugin.xml, the livesearch action would be accessed at <host>/<context-path>/plugins/livesearch/livesearch.action.

Creating a Velocity Template for Output

Your Velocity template must be specified with a leading slash (/) in the plugin XML configuration, and the path must correspond to the path inside the plugin JAR file.

In the above example, the Velocity template must be found in /templates/extra/livesearch/livesearchaction.vm inside the plugin JAR file. In the example plugin's source code, this would mean the Velocity template should be created in src/main/resources/template/extra/livesearch/.

Inside your Velocity template, you can use $action to refer to your action, and many other variables to access Confluence functionality. See Confluence Objects Accessible From Velocity for more information.

Notes

Some issues to be aware of when developing or configuring a Struts plugin:

  • Your packages should always extend the default Confluence package. It is useful to be aware of what this provides to you in the way of interceptors and result types. Extending any other package will modify that package's configuration across the entire application, which is not supported or desirable.
  • You can give your packages any namespace you like, but we recommend using /plugins/unique/value - that is prefixing plugin packages with /plugins and then adding a string globally unique to your plugin. The only name you can't use is servlet as the /plugins/servlet URL pattern is reserved for the Servlet Module.
  • Views must be bundled in the JAR file in order to be used by your actions. This almost always means using Velocity views.
  • It is useful to be aware of the actions and features already bundled with Confluence, for example your actions will all be auto-wired by Spring (see Accessing Confluence Components from Plugin Modules) and your actions can use useful interfaces like PageAware and SpaceAware to reduce the amount of work they need to do.
  • Currently only Struts actions are protected by the temporary secure administrator sessions. Other plugin types, such as REST services or servlets are not checked for an administrator session.
    • All Struts actions mounted under /admin will automatically be protected by secure administrator sessions. To opt out of this protection you can mark your class or Struts action method with the WebSudoNotRequired annotation.
    • Conversely, all Struts actions mounted outside the /admin namespace are not protected and can be opted in by adding the WebSudoRequired annotation.
    • Both of these annotations work on the class or the action method. If you mark a method with the annotation, only action invocations invoking that method will be affected by the annotations. If you annotate the class, any invocation to that class will be affected. Subclasses inherit these annotations.

See also

Rate this page: