Since Scout 3.8.0 (Juno) we have the possibility to have a central input validation point in the server (See Bug 349659). I was asked how the mechanism works and I wanted to share it here.
The idea of central input validation is following: when a method is called in the server, the parameters are checked (including a partial or full check of the content of the formData).
From a security point of view, it can be useful to check the input parameters. Think of:
* Malicious clients
* Unsafe communication channel used by the client to communicate with the server (man-in-the-middle attacks).
I am not a security expert and you should check by yourself is the mechanism I describe make sense for your scout application or not.
----------------------------
== Information in the formData ==
If you have a look at the formData classes, the properties (relevant for validation) defined with the getConfiguredXxxx() methods are added to the FormData class.
Example (field):
@Order(10.0)
public class MyStringField extends AbstractStringField {
@Override
protected boolean getConfiguredMandatory() {
return true;
}
@Override
protected int getConfiguredMaxLength() {
return 100;
}
//...
}
Example (field data):
public static class MyString extends AbstractValueFieldData<String> {
private static final long serialVersionUID = 1L;
public MyString() {
}
/**
* list of derived validation rules.
*/
@Override
protected void initValidationRules(Map<String, Object> ruleMap) {
super.initValidationRules(ruleMap);
ruleMap.put(ValidationRule.MANDATORY, true);
ruleMap.put(ValidationRule.MAX_LENGTH, 100);
}
}
----------------------------
Using this information in the server
If you want to use this information server side, you need to change the Transaction delegate used in your application:
1/ You need your own TransactionDelegate class:
The easiest way is to extend DefaultTransactionDelegate. By default the method validateInput(IValidationStrategy, Object, Method, Object[]) is empty. To activate the default input validation, just override the method and call defaultValidateInput(IValidationStrategy, Object, Method, Object[]).
import java.lang.reflect.Method;
import org.eclipse.scout.rt.server.DefaultTransactionDelegate;
import org.eclipse.scout.rt.shared.validate.IValidationStrategy;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
public class MyAppTransactionDelegate extends DefaultTransactionDelegate {
public MyAppTransactionDelegate(Bundle[] loaderBundles, Version requestMinVersion, boolean debug) {
super(loaderBundles, requestMinVersion, debug);
}
@Override
protected void validateInput(IValidationStrategy validationStrategy, Object service, Method op, Object[] args) throws Exception {
defaultValidateInput(validationStrategy, service, op, args);
}
}
2/ After that you need your own service tunnel (MyAppServiceTunnelServlet)
This ServiceTunnelServlet will work with the TransactionDelegate class created in step 1:
import org.eclipse.scout.rt.server.ServiceTunnelServlet;
import org.eclipse.scout.rt.shared.servicetunnel.ServiceTunnelRequest;
import org.eclipse.scout.rt.shared.servicetunnel.ServiceTunnelResponse;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
public class MyAppServiceTunnelServlet extends ServiceTunnelServlet {
private static final long serialVersionUID = 1L;
@Override
protected ServiceTunnelResponse runServerJobTransactionWithDelegate(ServiceTunnelRequest req, Bundle[] loaderBundles, Version requestMinVersion, boolean debug) throws Exception {
return new MyAppTransactionDelegate(loaderBundles, requestMinVersion, debug).invoke(req);
}
}
3/ Register your service tunnel servlet in the server XML. Search for the servlet declaration with the alias "/process" and replace the class attribute with your new class created in step 2:
<servlet
alias="/process"
class="myapp.server.servlets.MyAppServiceTunnelServlet">
<init-param
name="min-version"
value="0.0.0">
</init-param>
</servlet>
----------------------------
== What is checked? ==
Then on each of your services, you can define which validation strategy should be applied (on the service class or on each of the methods)
There are 3 validations strategies:
* IValidationStrategy.PROCESS: all rules defined in each formFieldData of the formData are checked.
* IValidationStrategy.QUERY: check reduced to some rules (MaxLength, CodeValue, LookupValue, RegexMatch)
* IValidationStrategy.NO_CHECK: no check at all.
If you have some mandatory fields in you form, in when you open the form, you some of this field might be empty. It is probably not a good idea to use IValidationStrategy.PROCESS on the load() or on the prepareCreate() methods.
Here is how a process service might looks like:
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.rt.shared.validate.IValidationStrategy;
import org.eclipse.scout.rt.shared.validate.InputValidation;
import org.eclipse.scout.service.AbstractService;
public class MyService extends AbstractService implements IMyService {
@InputValidation(IValidationStrategy.QUERY.class)
@Override
public MyFormData prepareCreate(MyFormData formData) throws ProcessingException {
//TODO business logic here.
return formData;
}
@InputValidation(IValidationStrategy.PROCESS.class)
@Override
public MyFormData create(MyFormData formData) throws ProcessingException {
//TODO business logic here.
return formData;
}
@InputValidation(IValidationStrategy.QUERY.class)
@Override
public MyFormData load(MyFormData formData) throws ProcessingException {
//TODO business logic here.
return formData;
}
@InputValidation(IValidationStrategy.PROCESS.class)
@Override
public MyFormData store(MyFormData formData) throws ProcessingException {
//TODO business logic here.
return formData;
}
}
----------------------------
Related posts:
* Legacy Hint WarnMissingInputValidation
* Validation rules explained by Ivan Motsch.