Sandboxing
Flowable offers configuration options to sandbox a running installation to avoid and / or limit certain functionality.
Here we are going to explain the different mechanism and what each sandboxing mechanism does and how it can be configured.
Repetitions / Loops
v3.14.0+Using Flowable it is possible to create model repetitions / loops. If you create such a model you are going to have a thread / DB connection that is never going to be closed or if you are using async you will keep creating jobs and the process / case will never end.
This mechanism can be enabled by setting the flowable.sandbox.max-allowed-repetitions
property.
The value in this property will be used to determine how many times a process / case instance is allowed to go through the same element.
The following scenarios are covered:
- Process loop (in memory or with wait states (e.g. async service task) in between)
- Multi instance
- Non-interruptible boundary events
- Case loop (in memory or with wait states (e.g. async service task) in between) (e.g. modelling repeatable unlimited task)
- Case plan item transitions (e.g. available -> unavailable -> available)
Strict Expression Mode
v3.15.0+By default, Flowable exposes all available beans and all methods on those beans during expression evaluation.
If you want to enable strict mode (only where specific Flowable beans are exposed and values cannot be set through expressions) then the property flowable.sandbox.expression.strict-mode
needs to be set to true
.
When this is the case the following beans / functions are going to be available:
flw.time
- exposing the Flowable Time Utilsflw.string
- exposing the Flowable String Utilsflw.math
- exposing the Flowable Math Utilsflw.locale
- exposing the Flowable Locale Utilsflw.format
- exposing the Flowable Format Utilsflw.base64
- exposing the Flowable Base64 Utilsflw.io
- exposing the Flowable IO Utilsflw.setOutput(varName, value)
- for setting a variable value in the current scopeflw.setTransientOutput(varName, value)
- for setting a transient variable value in the current scopeflw.setLocalOutput(varName, value)
- for setting a transient variable value in the current local scopeseq:next(sequenceKey)
- for getting the next formatted sequence valueseq:nextNumber(sequenceKey)
- for getting the next number sequence valuejson:array()
- for creating a JSON arrayjson:arrayWithSize(size)
- for creating a JSON array with a specific sizejson:object()
- for creating a JSON objectvars:get(varName)
- for safe getting a variablevars:getOrDefault(varName, defaultValue)
- for safe getting a variable or using the default value if the variable does not existvars:containsAny(varName, values)
- for safe getting checking if a variable contains any of the provided valuesvars:containsAny(varName, values)
- for safe getting checking if a variable contains all the provided valuesvars:equals(varName, value)
- for safe comparing a variable value to a given valuevars:notEquals(varName, value)
- for safe comparing a variable value to a given valuevars:exists(varName)
- for checking if a variable with the given name existsvars:isEmpty(varName)
- for safe checking if a variable with the given name is emptyvars:isNotEmpty(varName)
- for safe checking if a variable with the given name is not emptyvars:lowerThan(varName, value)
- for safe comparing if a variable value is lower than a given valuevars:lowerThanOrEquals(varName, value)
- for safe comparing if a variable value is lower than or equal to a given valuevars:greaterThan(varName, value)
- for safe comparing if a variable value is greater than a given valuevars:greaterThanOrEquals(varName, value)
- for safe comparing if a variable value is greater than or equal to a given valuevars:base64(varName)
- for safe base64 encoding a variable value with the given nameisUserInAnyGroup(userId, groupKeys)
- for checking if a user is a member of the given groupsisUserInNoGroups(userId, groupKeys)
- for checking if a user is not a member of the given groupsfindUser(userId)
- for getting the user informationfindGroupMemberEmails(groupKeys)
- for getting the emails of all the members of the given groupsfindGroupMemberUserIds(groupKeys)
- for getting the user ids of all the members of the given groups
If you want to make your custom beans available in strict expression mode then you need to annotate them with the @AllowedBeanInStrictMode
annotation.
In order to expose public methods of your beans or types in strict mode you can use the @AllowedInStrictMode
annotation.
This annotation can be used on a class or a method.
When it is on a class then all the public methods of that class will be available in strict mode.
If you want to disable a specific method you can use the @NotAllowedInStrictMode
annotation.
Max transaction duration
v3.15.0+In order to limit the max transaction duration, Flowable provides a way to configure the max amount of time a flowable command can be executed.
This can be configured using the flowable.sandbox.max-command-duration
property.
e.g. for a max value of 30 seconds it looks like:
flowable.sandbox.max-command-duration=30s
Rate Limiting
v3.15.0+Flowable provides a way to rate limit the following:
- Started Case / Process instances
- Plan Item instances / Activities started
- Jobs scheduled / run
Out of the box Flowable provides integration with Bucket4J.
To enable this the property flowable.sandbox.rate-limit.type
should be set to bucket4j
.
If you are building your own Flowable application you'll need to add the following dependency
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>${bucket4j.version}</version>
</dependency>
Doing this will enable the in memory single node rate limiting.
If you want to have a distributed rate limiting then you'll have to decide which of the available mechanisms from Bucket4J you want to use and expose a bean of type ProxyManager<String>
.
The following properties can be used to configure the rate limiting
# This configuration allows starting 10 instances per minute and 100 per hour
# It allows us to protect from spending all the available tokens for an hour within one minute
flowable.sandbox.rate-limit.instance-start[0].capacity=20
flowable.sandbox.rate-limit.instance-start[0].duration=PT1M
flowable.sandbox.rate-limit.instance-start[1].capacity=200
flowable.sandbox.rate-limit.instance-start[1].duration=PT1H
# This configuration allows starting 50 activities / plan item instance per minute and 500 per hour
# It allows us to protect from spending all the available tokens for an hour within one minute
flowable.sandbox.rate-limit.activity-start[0].capacity=100
flowable.sandbox.rate-limit.activity-start[0].duration=PT1M
flowable.sandbox.rate-limit.activity-start[1].capacity=1000
flowable.sandbox.rate-limit.activity-start[1].duration=PT1H
# This configuration allows scheduling 20 jobs per minute and 200 per hour
# It allows us to protect from spending all the available tokens for an hour within one minute
flowable.sandbox.rate-limit.job-schedule[0].capacity=20
flowable.sandbox.rate-limit.job-schedule[0].duration=PT1M
flowable.sandbox.rate-limit.job-schedule[1].capacity=200
flowable.sandbox.rate-limit.job-schedule[1].duration=PT1H
# This configuration allows executing 10 jobs instance per minute and 100 per hour
# It allows us to protect from spending all the available tokens for an hour within one minute
flowable.sandbox.rate-limit.job-execute[0].capacity=10
flowable.sandbox.rate-limit.job-execute[0].duration=PT1M
flowable.sandbox.rate-limit.job-execute[1].capacity=100
flowable.sandbox.rate-limit.job-execute[1].duration=PT1H
Disable Scripting
If you want to disable scripting completely you can set the flowable.sandbox.script.disabled
property to true
.