This is an automated email from the ASF dual-hosted git repository. kusal pushed a commit to branch WW-5352-struts-param-doc in repository https://gitbox.apache.org/repos/asf/struts-site.git
commit 6f940a9f815b7bfadc43ff632f53bcb784244894 Author: Kusal Kithul-Godage <g...@kusal.io> AuthorDate: Tue Jan 30 04:14:00 2024 +1100 WW-5352 Update documentation for @StrutsParameter --- source/core-developers/basic-validation.md | 3 + source/core-developers/client-validation.md | 3 + source/core-developers/conversion-validator.md | 3 +- source/core-developers/file-upload-interceptor.md | 3 + source/core-developers/file-upload.md | 9 +++ .../core-developers/type-conversion-annotation.md | 4 ++ source/core-developers/type-conversion.md | 2 + .../core-developers/using-non-field-validators.md | 3 + .../using-visitor-field-validator.md | 1 + source/core-developers/validation-annotation.md | 1 + source/core-developers/validation.md | 1 + source/core-developers/wildcard-mappings.md | 2 + source/getting-started/coding-actions.md | 4 +- source/getting-started/processing-forms.md | 19 ++++-- source/plugins/junit/index.md | 1 + .../plugins/portlet/struts-2-portlet-tutorial.md | 10 ++- source/security/index.md | 74 +++++++++++++++++++++- 17 files changed, 134 insertions(+), 9 deletions(-) diff --git a/source/core-developers/basic-validation.md b/source/core-developers/basic-validation.md index 32a7c4e1f..38a59c94b 100644 --- a/source/core-developers/basic-validation.md +++ b/source/core-developers/basic-validation.md @@ -70,6 +70,7 @@ public class QuizAction extends ActionSupport { return name; } + @StrutsParameter public void setName(String name) { this.name = name; } @@ -78,6 +79,7 @@ public class QuizAction extends ActionSupport { return age; } + @StrutsParameter public void setAge(int age) { this.age = age; } @@ -86,6 +88,7 @@ public class QuizAction extends ActionSupport { return answer; } + @StrutsParameter public void setAnswer(String answer) { this.answer = answer; } diff --git a/source/core-developers/client-validation.md b/source/core-developers/client-validation.md index 106eb755b..987be8d67 100644 --- a/source/core-developers/client-validation.md +++ b/source/core-developers/client-validation.md @@ -74,6 +74,7 @@ public class QuizAction extends ActionSupport { return name; } + @StrutsParameter public void setName(String name) { this.name = name; } @@ -82,6 +83,7 @@ public class QuizAction extends ActionSupport { return age; } + @StrutsParameter public void setAge(int age) { this.age = age; } @@ -90,6 +92,7 @@ public class QuizAction extends ActionSupport { return answer; } + @StrutsParameter public void setAnswer(String answer) { this.answer = answer; } diff --git a/source/core-developers/conversion-validator.md b/source/core-developers/conversion-validator.md index 5f42510fb..f0fdf7520 100644 --- a/source/core-developers/conversion-validator.md +++ b/source/core-developers/conversion-validator.md @@ -81,7 +81,8 @@ public class MyActionSupport extends ActionSupport { public Integer getMyIntegerField() { return this.myIntegerField; } - + + @StrutsParameter public void setMyIntegerField(Integer myIntegerField) { this.myIntegerField = myIntegerField; } diff --git a/source/core-developers/file-upload-interceptor.md b/source/core-developers/file-upload-interceptor.md index 5dd64b68b..28427e25f 100644 --- a/source/core-developers/file-upload-interceptor.md +++ b/source/core-developers/file-upload-interceptor.md @@ -90,14 +90,17 @@ You must set the encoding to <code>multipart/form-data</code> in the form where private String contentType; private String filename; + @StrutsParameter public void setUpload(File file) { this.file = file; } + @StrutsParameter public void setUploadContentType(String contentType) { this.contentType = contentType; } + @StrutsParameter public void setUploadFileName(String filename) { this.filename = filename; } diff --git a/source/core-developers/file-upload.md b/source/core-developers/file-upload.md index d36031185..dee678efc 100644 --- a/source/core-developers/file-upload.md +++ b/source/core-developers/file-upload.md @@ -101,14 +101,17 @@ public class UploadAction extends ActionSupport { private String contentType; private String filename; + @StrutsParameter public void setUpload(File file) { this.file = file; } + @StrutsParameter public void setUploadContentType(String contentType) { this.contentType = contentType; } + @StrutsParameter public void setUploadFileName(String filename) { this.filename = filename; } @@ -185,6 +188,7 @@ public class MultipleFileUploadUsingArrayAction extends ActionSupport { return this.uploads; } + @StrutsParameter public void setUpload(File[] upload) { this.uploads = upload; } @@ -193,6 +197,7 @@ public class MultipleFileUploadUsingArrayAction extends ActionSupport { return this.uploadFileNames; } + @StrutsParameter public void setUploadFileName(String[] uploadFileName) { this.uploadFileNames = uploadFileName; } @@ -201,6 +206,7 @@ public class MultipleFileUploadUsingArrayAction extends ActionSupport { return this.uploadContentTypes; } + @StrutsParameter public void setUploadContentType(String[] uploadContentType) { this.uploadContentTypes = uploadContentType; } @@ -232,6 +238,7 @@ public class MultipleFileUploadUsingListAction extends ActionSupport { return this.uploads; } + @StrutsParameter public void setUpload(List<File> uploads) { this.uploads = uploads; } @@ -240,6 +247,7 @@ public class MultipleFileUploadUsingListAction extends ActionSupport { return this.uploadFileNames; } + @StrutsParameter public void setUploadFileName(List<String> uploadFileNames) { this.uploadFileNames = uploadFileNames; } @@ -248,6 +256,7 @@ public class MultipleFileUploadUsingListAction extends ActionSupport { return this.uploadContentTypes; } + @StrutsParameter public void setUploadContentType(List<String> contentTypes) { this.uploadContentTypes = contentTypes; } diff --git a/source/core-developers/type-conversion-annotation.md b/source/core-developers/type-conversion-annotation.md index f163f9385..26aacdd1e 100644 --- a/source/core-developers/type-conversion-annotation.md +++ b/source/core-developers/type-conversion-annotation.md @@ -92,21 +92,25 @@ The `TypeConversion` annotation can be applied at property and method level. private HashMap keyValues = null; @TypeConversion(type = ConversionType.APPLICATION) + @StrutsParameter public void setConvertInt( String convertInt ) { this.convertInt = convertInt; } @TypeConversion(converterClass = XWorkBasicConverter.class) + @StrutsParameter public void setConvertDouble( String convertDouble ) { this.convertDouble = convertDouble; } @TypeConversion(rule = ConversionRule.COLLECTION, converterClass = String.class) + @StrutsParameter public void setUsers( List users ) { this.users = users; } @TypeConversion(rule = ConversionRule.MAP, converterClass = BigInteger.class) + @StrutsParameter public void setKeyValues( HashMap keyValues ) { this.keyValues = keyValues; } diff --git a/source/core-developers/type-conversion.md b/source/core-developers/type-conversion.md index dd2918f70..2bb12530d 100644 --- a/source/core-developers/type-conversion.md +++ b/source/core-developers/type-conversion.md @@ -322,6 +322,7 @@ public class MyBeanAction implements Action { private List beanList = new ArrayList(); private Map beanMap = new HashMap(); + @StrutsParameter(depth = 2) public List getBeanList() { return beanList; } @@ -330,6 +331,7 @@ public class MyBeanAction implements Action { this.beanList = beanList; } + @StrutsParameter(depth = 2) public Map getBeanMap() { return beanMap; } diff --git a/source/core-developers/using-non-field-validators.md b/source/core-developers/using-non-field-validators.md index da37d0d43..b5e3a2e2c 100644 --- a/source/core-developers/using-non-field-validators.md +++ b/source/core-developers/using-non-field-validators.md @@ -42,6 +42,7 @@ public class NonFieldValidatorsExampleAction extends AbstractValidationActionSup return someText; } + @StrutsParameter public void setSomeText(String someText) { this.someText = someText; } @@ -50,6 +51,7 @@ public class NonFieldValidatorsExampleAction extends AbstractValidationActionSup return someTextRetype; } + @StrutsParameter public void setSomeTextRetype(String someTextRetype) { this.someTextRetype = someTextRetype; } @@ -58,6 +60,7 @@ public class NonFieldValidatorsExampleAction extends AbstractValidationActionSup return someTextRetypeAgain; } + @StrutsParameter public void setSomeTextRetypeAgain(String someTextRetypeAgain) { this.someTextRetypeAgain = someTextRetypeAgain; } diff --git a/source/core-developers/using-visitor-field-validator.md b/source/core-developers/using-visitor-field-validator.md index 77403e1c0..34d70299f 100644 --- a/source/core-developers/using-visitor-field-validator.md +++ b/source/core-developers/using-visitor-field-validator.md @@ -40,6 +40,7 @@ public class VisitorValidatorsExampleAction extends AbstractValidationActionSupp private User user; + @StrutsParameter(depth = 1) public User getUser() { return user; } diff --git a/source/core-developers/validation-annotation.md b/source/core-developers/validation-annotation.md index 79aea0a2e..f101b10f5 100644 --- a/source/core-developers/validation-annotation.md +++ b/source/core-developers/validation-annotation.md @@ -48,6 +48,7 @@ Validation annotation must be applied at Type level. @RequiredFieldValidator(type = ValidatorType.FIELD, message = "You must enter a value for bar.") @IntRangeFieldValidator(type = ValidatorType.FIELD, min = "6", max = "10", message = "bar must be between ${min} and ${max}, current value is ${bar}.") + @StrutsParameter public void setBar(int bar) { this.bar = bar; } diff --git a/source/core-developers/validation.md b/source/core-developers/validation.md index 7e18143c3..0653f0ae5 100644 --- a/source/core-developers/validation.md +++ b/source/core-developers/validation.md @@ -292,6 +292,7 @@ The same mechanism can be used with annotations as follow: @RequiredStringValidator(key = "errors.required", messageParams = { "getText('username.field.name')" }) +@StrutsParameter public void setUsername(String username) { this.username = username; } diff --git a/source/core-developers/wildcard-mappings.md b/source/core-developers/wildcard-mappings.md index c697fcb57..edeef03e2 100644 --- a/source/core-developers/wildcard-mappings.md +++ b/source/core-developers/wildcard-mappings.md @@ -96,6 +96,8 @@ URL and extracted as parameters, for example: @Namespace{"/users/{userID}"); public class DetailsAction exends ActionSupport { private Long userID; + + @StrutsParameter public void setUserID(Long userID) {...} } ``` diff --git a/source/getting-started/coding-actions.md b/source/getting-started/coding-actions.md index d6355d010..e7ee6a5cb 100644 --- a/source/getting-started/coding-actions.md +++ b/source/getting-started/coding-actions.md @@ -105,6 +105,7 @@ public String getUserName() { return userName; } +@StrutsParameter public void setUserName(String userName) { this.userName = userName; } @@ -128,7 +129,8 @@ You should see the following page.  When the form is submitted, Struts will call any set methods of the HelloWorldAction class that match the form field -names. So in this example method `setUserName` was called and passed the value the user entered in the `userName` form field. +names and are annotated with `@StrutsParameter`. So in this example method `setUserName` was called and passed the value +the user entered in the `userName` form field. On the `index.jsp` we also have a Struts 2 action link (see tutorial [Using Struts 2 Tags](using-tags)) that includes a query string parameter: `userName=Bruce+Phillips`. If you click on that link you should see the following result: diff --git a/source/getting-started/processing-forms.md b/source/getting-started/processing-forms.md index 1579d6946..4e0e311d1 100644 --- a/source/getting-started/processing-forms.md +++ b/source/getting-started/processing-forms.md @@ -128,7 +128,7 @@ The Struts 2 form will submit to an action named register. We'll need to define Note the four Struts 2 textfield tags. Each tag has a name value that includes an attribute of the `Person` class (e.g. `firstName`). The name attribute's value also has a reference to an object called `personBean`. This object is of type `Person`. When we create the Action class that handles this form submission, we'll have to specify that object -in that Action class (see below). +in that Action class and annotate it (see below). The complete name value, `personBean.firstName`, instructs Struts 2 to use the input value for that textfield as the argument to the personBean object's `setFirstName` method. So if the user types "Bruce" in the textfield that has @@ -166,7 +166,8 @@ public class Register extends ActionSupport { return SUCCESS; } - + + @StrutsParameter(depth = 1) public Person getPersonBean() { return personBean; } @@ -178,8 +179,18 @@ public class Register extends ActionSupport { } ``` -In the `Register` class note that we've declared an attribute named `personBean` of type `Person` and there is a public -get and set method for this object. +In the `Register` class, note that we've declared an attribute named `personBean` of type `Person`, there are public +getter and setter methods for this object, and the getter is annotated with `@StrutsParameter(depth = 1)`. + +In the previous [Coding Struts 2 Actions](coding-actions) tutorial, we annotated the username <strong>setter</strong>, +which took a simple String as its parameter type, with `@StrutsParameter`. In this example, we are using a "Bean" +object (sometimes referred to as a DTO or model object) to encapsulate the form data. When we choose to use a DTO +instead of a primitive, String, or other TypeConverter supported object, we must annotate the <strong>getter</strong> +method instead, and also assign a depth corresponding to how deep the DTO graph is. In this case, the `Person` object +does not have any further DTOs or collections within it, so a depth of 1 will suffice. + +For more information on these annotations and their security implications, please refer +to [Security](../security/index#defining-and-annotating-your-action-parameters). The `Register` class also overrides the `execute` method. The `execute` method is the one we will specify in the `struts.xml` to be called in response to the register action. In this example, the `execute` method just returns diff --git a/source/plugins/junit/index.md b/source/plugins/junit/index.md index 94d512719..225905e38 100644 --- a/source/plugins/junit/index.md +++ b/source/plugins/junit/index.md @@ -44,6 +44,7 @@ public class TestAction extends ActionSupport { return name; } + @StrutsParameter public void setName(String name) { this.name = name; } diff --git a/source/plugins/portlet/struts-2-portlet-tutorial.md b/source/plugins/portlet/struts-2-portlet-tutorial.md index ff1cf2fdb..2dd99a31e 100644 --- a/source/plugins/portlet/struts-2-portlet-tutorial.md +++ b/source/plugins/portlet/struts-2-portlet-tutorial.md @@ -187,10 +187,12 @@ public class AddBookmarkAction extends DefaultActionSupport { private String name; private String url; + @StrutsParameter public void setName(String name) { this.name = name; } + @StrutsParameter public void setUrl(String url) { this.url = url; } @@ -259,10 +261,12 @@ public class AddBookmarkAction extends DefaultActionSupport implements PortletPr private PortletPreferences portletPreferences; + @StrutsParameter public void setName(String name) { this.name = name; } + @StrutsParameter public void setUrl(String url) { this.url = url; } @@ -484,6 +488,7 @@ public class DeleteBookmarkAction extends DefaultActionSupport implements Portle private PortletPreferences portletPreferences; + @StrutsParameter public void setBookmarkName(String bookmarkName) { this.bookmarkName = bookmarkName; } @@ -563,7 +568,8 @@ public class EditBookmarkAction extends DefaultActionSupport implements PortletP public String getOldName() { return oldName; } - + + @StrutsParameter public void setOldName(String oldName) { this.oldName = oldName; } @@ -572,10 +578,12 @@ public class EditBookmarkAction extends DefaultActionSupport implements PortletP return url; } + @StrutsParameter public void setUrl(String url) { this.url = url; } + @StrutsParameter public void setName(String name) { this.name = name; } diff --git a/source/security/index.md b/source/security/index.md index 5735febb1..a30497adb 100644 --- a/source/security/index.md +++ b/source/security/index.md @@ -113,8 +113,75 @@ header to each JSP file <%@ page contentType="text/html; charset=UTF-8" %> ``` +### Defining and annotating your Action parameters + +> Note: Since 6.4 using `struts.parameters.requireAnnotations=true`. Or by default from 7.0. + +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. + +```java +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. + +```java +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 + +If you are finding updating your application with this new annotation time-consuming, you can temporarily combine the +above option with `struts.parameters.requireAnnotations.transitionMode=true`. When this mode is enabled, only 'nested' +parameters, i.e. DTOs or Collections represented by public getters on Action classes, will require annotations. This +means public setters will still be exposed for parameter injection. Notably, +the [auto-allowlisting capability](#allowlist-capability), which is also supported by these annotations, is not degraded +in any way, so it proves a useful transitioning option for applications that wish to enable the OGNL allowlist as soon +as possible. + ### Do not define setters when not needed +> Note: Only relevant if you are not using `struts.parameters.requireAnnotations=true` as per the previous section. + You should carefully design your actions without exposing anything via setters and getters, thus can leads to potential security vulnerabilities. Any action's setter can be used to set incoming untrusted user's value which can contain suspicious expression. Some Struts `Result`s automatically populate params based on values in @@ -380,14 +447,17 @@ Now, in addition to enforcing the exclusion list, classes involved in OGNL expre allowlisted classes and packages. By default, all required Struts classes are allowlisted as well as any classes that are defined in your `struts.xml` package configurations. +We highly recommend enabling the [parameter annotation](#defining-and-annotating-your-action-parameters) capability to +ensure any necessary parameter injection types are allowlisted, in addition to its other benefits. + You can add additional classes and packages to the allowlist with: - `struts.allowlist.classes`: comma-separated list of allowlisted classes. - `struts.allowlist.packages`: comma-separated list of allowlisted packages, matched using string comparison via `startWith`. Note that classes in subpackages are also allowlisted. -Generally, the only additional classes or packages you will need to configure are those model classes that you wish to -be constructed/manipulated by Struts form submissions (i.e. parameter injected). +Depending on the functionality of your application, you may not need to manually allowlist any classes. Please monitor +your application logs for any warnings about blocked classes and add them to the allowlist as necessary. #### Extensibility