Scripting with Apache Groovy

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax.

Support is enabled by including the following module in the WAR overlay:

1
2
3
4
5
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-core-scripting</artifactId>
    <version>${cas.version}</version>
</dependency>
1
implementation "org.apereo.cas:cas-server-core-scripting:${project.'cas.version'}"
1
2
3
4
5
6
7
8
9
dependencyManagement {
    imports {
        mavenBom "org.apereo.cas:cas-server-support-bom:${project.'cas.version'}"
    }
}

dependencies {
    implementation "org.apereo.cas:cas-server-core-scripting"
}
1
2
3
4
5
6
7
8
9
10
dependencies {
    /*
        The following platform references are included automatically and are listed for reference only.

        implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
        implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        
    */
    implementation "org.apereo.cas:cas-server-core-scripting"
}
:information_source: Usage

Remember to include the above module in your CAS build if you intend to implement any sort of behavior with Apache Groovy and scripting. This includes evaluating policies, release attributes, modifying or customizing components, etc. Without the listed above module, Apache Groovy functionality and libraries will not be pulled into your CAS project and CAS would most likely fail to deliver at runtime.

CAS takes advantage of Apache Groovy in forms of either embedded or external scripts that allow one to, by default, dynamically build constructs, attributes, access strategies and a lot more.

As an example, the following construct is what’s referred to in CAS as an embedded or inline Groovy script:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "@class": "org.apereo.cas.services.CasRegisteredService",
  "serviceId": "^https://example.app.org/login",
  "name": "Sample",
  "id": 1,
  "attributeReleasePolicy" : {
    "@class" : "org.apereo.cas.services.ReturnMappedAttributeReleasePolicy",
    "allowedAttributes" : {
      "@class" : "java.util.TreeMap",
      "name" : "groovy { return ['casuser'] }"
    }
  }
}

By default, all such scripts are evaluated and executed dynamically using the Groovy meta object protocol. There is also support for the alternative that allows CAS to tune the Groovy compiler for static compilation. In this mode, all methods, properties, files, inner classes, etc. found in scripts will be type checked. If you wish to always compile Groovy scripts using CompileStatic, you may specify the following system property when you run CAS:

1
-Dorg.apereo.cas.groovy.compile.static=true

When CAS runs in CompileStatic mode, Groovy scripts most likely will need to be rewritten to remove all dynamic constructs. For example, the following Groovy script is one that uses dynamic/meta aspects of the Groovy programming language:

1
2
3
4
5
if (attributes['entitlement'].contains('admin')) {
    return [attributes['uid'].get(0).toUpperCase()]
} else {
    return attributes['identifier']
}

The same script in CompileStatic mode would be rewritten as:

1
2
3
4
5
6
def attributes = (Map) binding.getVariable('attributes')
if ((attributes.get('entitlement') as List).contains('admin')) {
    return [(attributes['uid'] as List).get(0).toString().toUpperCase()]
} else {
    return attributes['identifier'] as List
}

Script Execution

A compiled script is cached and shared by every request that uses it, and CAS runs one execution of that script at a time. An execution that arrives while the script is busy waits for its turn rather than being abandoned, so a script that performs slow work such as an HTTP call or a directory lookup becomes a bottleneck for every request that depends on it. Keep scripts short, and give any remote call its own timeout.

Variables that CAS passes to an inline script belong to the request that supplied them. They are visible only to that execution and are discarded once it completes, which means a script cannot observe the variables of an earlier or concurrent request.

Actuator Endpoints

The following endpoints are provided by CAS:

groovyCache
CAS endpoint5 operationsNot exposed by default
base path /cas/actuator/
1

Turn the endpoint on and expose it over the web. One entry covers every operation. By default only info, health and status are exposed.

1
2
management.endpoint.groovyCache.access=UNRESTRICTED
management.endpoints.web.exposure.include=groovyCache

Endpoints may be mapped to other paths. For example, to serve health at healthcheck:

1
management.endpoints.web.path-mapping.health=healthcheck