Working with the CrafterCMS Groovy Sandbox
Applicable to CrafterCMS 4.0Phil Nguyen
CrafterCMS supports a powerful Groovy-based scripting layer for server side programming. CrafterCMS's configurable sandbox policies determine what the scripting layer can and cannot do. Some syntax is more likely to trigger sandbox flags than others. In this tutorial, we will offer some convenient tips to work with the Groovy Sandbox. You can also refer to this documentation on how to configure the sandbox.
Tips for coding
In the following examples, we will use a sample site that is created with the blueprint Editorial. Check this documentation on how to create a site.
Looking up a Bean by ID
We will demonstrate how to look up a bean by ID, access a request parameter, and import an external library to your script. Following beans are enabled by default without any additional configuration: https://docs.craftercms.org/current/reference/api/groovy-java-api.html
Here is the steps in case you want to include a bean that is not in the list.
Step 1
Add the bean to Engine Site Application Context : In this example, we include the bean crafter.textEncryptor
Step 2
Add a new script bean.get.groovy with the following content:
def result = [:]
def textEncryptor = applicationContext.getBean("crafter.textEncryptor")
def rawText = "This is a test"
def encryptedText = textEncryptor.encrypt(rawText)
def decryptedText = textEncryptor.decrypt(encryptedText)
result.rawText = rawText
result.encryptedText = encryptedText
result.decryptedText = decryptedText
return result
Note that we use applicationContext.getBean(“crafter.textEncryptor”) instead of applicationContext[“crafter.textEncryptor”] to get the new bean which we have included in the previous step.
Using GetBean avoids having to update the sandbox policies to allow the ["..."] syntax which can be unsafe. You can learn more about the groovy sandbox black list polices by referring to this documentation
For reference here is the policy we did not need to modify on the Groovy Sandbox Black list.
# staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object java.lang.String
Step 3
Verify the script is running correctly
curl 'http://localhost:8080/api/bean.json' -H 'Cookie: crafterSite=<YOUR_SITE>; JSESSIONID=<YOUR_SESSION>'
{
"rawText": "This is a test" ,
"encryptedText": "CCE-V1#sT17oILeZqfHQvLSRvo8Bo2Kys6oYSTVz7K8RL0+npc=" ,
"decryptedText": "This is a test"
}
Accessing a Request Parameter
Let’s take a look on how to access a parameter from HTTP GET and POST requests.
HTTP GET Request
In order to get parameters from a GET request, use `params` variable which is available by default.
Let take a look at the script `search.get.groovy`:
import org.craftercms.sites.editorial.SearchHelper
def userTerm = params.userTerm
def categories = params[ "categories[]" ]
def start = params.start ? params.start as Integer : 0
def rows = params.rows ? params.rows as Integer : 10
def searchHelper = new SearchHelper(elasticsearchClient, urlTransformationService)
def results = searchHelper.search(userTerm, categories, start, rows)
return results
curl 'http://localhost:8080/api/search.json?useTerm=winter&start=0&rows=2&categories%5B%5D=style' \
-H 'Cookie: crafterSite=<YOUR_SITE>; JSESSIONID=<YOUR_SESSION>'
In this example, each variable has following value:
- userTerm : winter
- categories : style (Note that [] has been encoded to %5B%5D as a part of the URI)
- start : 0
- rows : 2
HTTP POST request
In order to get parameters from a POST body, we must read POST body first. The search example above can be rewrite to use POST as of following:
import org.craftercms.sites.editorial.SearchHelper
import groovy.json.JsonSlurper
def reader = request.getReader()
def body = ""
def content = reader.readLine()
while (content != null ) {
body += content
content = reader.readLine()
}
def searchParams = new JsonSlurper().parseText(body)
def userTerm = searchParams.userTerm
def categories = searchParams[ "categories[]" ]
def start = searchParams.start ? searchParams.start as Integer : 0
def rows = searchParams.rows ? searchParams.rows as Integer : 10
def searchHelper = new SearchHelper(elasticsearchClient, urlTransformationService)
def results = searchHelper.search(userTerm, categories, start, rows)
return results
A sample request:
curl --location --request POST 'http://localhost:8080/api/search.json' \
--header 'authorization: Bearer <Your_Auth_Key>' \
--header 'Cookie: crafterSite=<YOUR_SITE>; XSRF-TOKEN=<YOUR_TOKEN>; JSESSIONID=<YOUR_SESSION>' \
--header 'Content-Type: application/json' \
--data-raw '{
"userTerm": "winter",
"categories[]": "style",
"start": 0,
"rows": 2
}'
Loading Grapes
There are cases that you may want to include an external library to your Groovy script. In order to achieve this, you can use grapes annotation @Grab .
Here is an example script that load the module commons-math3 from org.apache.commons .
First, create a new script under scripts > rest with name grape.get.groovy :
@Grab (group= 'org.apache.commons' , module= 'commons-math3' , version= '3.6.1' , initClass= false )
import org.apache.commons.math3.fraction.Fraction
import org.apache.commons.math3.fraction.FractionFormat
def lhs = new Fraction( 1 , 4 );
def rhs = new Fraction( 2 , 7 );
def sum = lhs.add(rhs);
def str = new FractionFormat().format(sum);
return str
Send an HTTP GET request:
curl 'http://localhost:8080/api/grape.json' \
-H 'Cookie: crafterSite=<YOUR_SITE>; JSESSIONID=<YOUR_SESSION>'
You should get the result:
"15 / 28"
Note : If your Groovy code need to use external dependencies you can use Grapes, however, when the Groovy sandbox is enabled dependencies can only be downloaded during the initial compilation and not during runtime. For this reason it is required to add an extra parameter initClass=false in the annotations to prevent them to be copied to the classes.
Related Posts
What Is HTMX?
Amanda Jones
CrafterCMS: A Modern Open Source Alternative to WordPress
Amanda Lee
Building Future-Ready B2B Commerce Experiences with Headless CMS
Sara Williams
Basic Digital Asset Management with a Headless CMS
Amanda Jones