Form Server Validations: Generic Form Validator using Javascript

Hey!

Little magic :tada: today!

Generally we buld HTML forms that are “Embedded forms” with the Angular API. These are client side/tasklist forms, but do not provide any form of validation on the server side. This is a problem for two reasons:

  1. Does not provide good submission security checks
  2. System to System interactions can bypass the validations that are used in the Angular forms.

So how do we fix this problem?

Well in the past we have to write a java class that does a custom validation. This was generally done on a per field basis or you add extra logic.

This is a huge pain because the BPMN and the Form are built rapidly, but when we need to generate the sever validations, we are stuck with compile jars and redeployment… No good!

So I have implemented the Camunda Form Validator: JS Server.

This is a Generic Form Validation solution that accesses Camunda’s Nashorn Script Engine to execute the validation.

It lets you add a form-validation.js script to your deployment, and with a simple configuration in the modeler on the Start Event or User Task form(s) you get your submissions added to the JS Validator.

your form-validation.js could be as simple as:

load('classpath:validationResult.js')

// The submissionsValues variable comes from the engine binding
validateData(submissionValues)

function validateData(values){
  if (values.containsKey('firstName') == false){
    return validationResult(false, {
                                     "detail": "FIELD_REQUIRED", 
                                     "message": "firstName is required"
                                    })
  }

  if (values.containsKey('age') && values['age'] < 18 ){
    return validationResult(false, {
                                     "detail": "AGE_LIMIT", 
                                     "message": "age must be 18 or older"
                                    })
  }
  
  // If no errors were found:
  return validationResult(true)
}

Check out the Github repo for the Jar and further usage examples. I have also included a ready to build docker setup that will let you test it out. See the Readme of the Repo.

Would love feedback and usage examples that come to mind.

Also @camunda it would be great is the following issue could be resolved, as it is a QOL blocker from the actual thrown errors being returned in the REST API and thus Tasklist:
https://app.camunda.com/jira/browse/CAM-8276

2 Likes

I have updated this usage with Validate.js usage:

// Validate.js Constraints
function getConstraints() {
  var constraints = {
    age: {
      presence: true,
      numericality: {
        onlyInteger: true,
        greaterThan: 18,
        lessThanOrEqualTo: 125,
      }
    }
  };
  return constraints
}

var validation = validate(jsonSubmission, getConstraints())

if (validation === undefined) {
  validationResult(true)
} else {
  validationResult(false, {
                            "detail": "VALIDATE.JS", 
                            "message": JSON.stringify(validation)
                          }
                    )
}

see the docs for further info

I have added Dynamic script calls through property configuration on the validator field:

see the v0.3.0 release at:

and see docs: that example usage: GitHub - StephenOTT/camunda-form-validator-js-server: Camunda BPM Form Validator plugin, enabling support for Javascript validation of Form Submissions for Start Events and User Tasks

TLDR:

  1. Add a validator_file property to the field that contains the .js filename (myscript.js).
  2. If you do not provide a validator_file property then a default will be used as fieldIdValue.js (example: given the field id is server_validation then it will default to looking for server_validation.js in the deployment.
  3. You can layer validations by adding multiple fields that call the JsFormValidation validator and call different validation files. layering lets you abstract common/reusable validators. (Also note that you can do similar by using the load() function in nashorn and just call a single script. Layering is provide for further configuration options.

Added performance enhancements and debug logging for a 0.4.0 release.

ScriptEngine variables are now static at the class level along with the Camunda Script Resolver cache usage to ensure that scripts are cached into a common engine rather than creating new engines for every submission. This leads to sub 75-80ms response times even when using validate.js in a load() function. Previous response was 500-800ms.

Note that first submission will always be longer as the cache needs to be warmed up.

I have updated with a “banned field” example where a user task has a validation restriction of a list of banned fields that are not allowed to be modified.

load('classpath:validationResult.js')
load('classpath:validate.min.js')
var JSONObject = Java.type('org.camunda.bpm.engine.impl.util.json.JSONObject')

var jsonSubmission = JSON.parse(new JSONObject(submissionValues).toString())

// Validate.js Constraints
function getConstraints() {
  var constraints = {
    age: {
      presence: true,
      numericality: {
        onlyInteger: true,
        greaterThan: 18,
        lessThanOrEqualTo: 125,
      }
    }
  };
  return constraints
}

// List of fields that are not allowed to be changed
function bannedFields(){
  // could also be loaded from another location (like a yaml or json file)
  return [
    "risk",
    "owner",
    "master_field",
    "current_state",
    "flagged",
    "priority"
  ]
}

// Check if the submission has any of the banned fields
function checkForBannedFields(submission, bannedFields) {
  // Loop through list of banned fields (nashorn loop)
  for each (var field in bannedFields){
    var hasBannedField = validate.contains(submission, field)
    // if a banned field was found then return true:
    if (hasBannedField == true) {
      return true
    }
  }
  // If no banned fields were found:
  return false
}

// if there is a banned field:
if (checkForBannedFields(jsonSubmission, bannedFields())){
  validationResult(false, {
                          "detail": "VALIDATE.JS", 
                          "message": 'Submission contains a banned field: ' + JSON.stringify(bannedFields())
                        }
                  )

// If no banned fields were found:
} else {
  // If no banned fields then continue:
  // Run Validations against Validate.js
  var validation = validate(jsonSubmission, getConstraints())

  if (validation === undefined) {
    validationResult(true)
  } else {
    validationResult(false, {
                              "detail": "VALIDATE.JS", 
                              "message": JSON.stringify(validation)
                            }
                      )
  }
}

PR for patch Add FormFieldValidationException Catching in the API by StephenOTT · Pull Request #296 · camunda/camunda-bpm-platform · GitHub

Hi @StephenOTT
I am getting below error when I hit below API

localhost:8080/engine-rest/process-definition/key/js-form-validation-test-1/submit-form

{
“type”: “RestException”,
“message”: “Cannot instantiate process definition js-form-validation-test-1:1:9f09d020-495c-11e9-898f-02423270994f: ENGINE-09008 Exception while instantiating class ‘io.digitalstate.camunda.JsFormValidation’: ENGINE-09017 Cannot load class ‘io.digitalstate.camunda.JsFormValidation’: io.digitalstate.camunda.JsFormValidation”
}

You have to provide more details. How did you install the plugin? What env, what Camunda version, which Bpmn etc.

HI @StephenOTT ,
Thank you for replying
I am following your git tutorial

I am using the latest version of camunda

you mentioned in git Add the jar to the Camunda classpath. But I don’t’ know where I need to add.
can you tell me where I need to keep this JAR FIle

Hi @StephenOTT
can you tell me, if I want to use pure javascript in my form validation. I don’t want to use any JAR file or java file

If you are not using the jar plugin then you have to validate with a execution listener but will be subject to the limitations that the plugin was built to overcome.

Thanks
can you tell me where I need to add JAR file

The jar goes into the /camunda/lib folder of your Camunda installation

1 Like

Thanks @StephenOTT
Now it’s working fine

Please star the GitHub project repo so we can know what is popular and being used. :+1:t2:

1 Like

Hello Stephen,

Thanks for this tutorial. I have added the digitalstate.camunda.js.server.form.validation-0.4.0-SNAPSHOT.jar in by /camunnda/lib folder. But when I am trying to hit the API (following your tutorial in github) I am getting exception

Cannot instantiate process definition c89cf5e4-c4f6-11ea-b2bc-0242cf66ffca: ENGINE-09008 Exception while instantiating class 'io.digitalstate.camunda.JsFormValidation': ENGINE-09017 Cannot load class 'io.digitalstate.camunda.JsFormValidation': io.digitalstate.camunda.JsFormValidation

I think it is unable to find JsFormValidation in the classpath of Camunda Server. Can you please help what I am missing? Thanks