Configure Service Access Strategy

The access strategy of a registered service provides fine-grained control over the service authorization rules. It describes whether the service is allowed to use the CAS server, allowed to participate in single sign-on authentication, etc. Additionally, it may be configured to require a certain set of principal attributes that must exist before access can be granted to the service. This behavior allows one to configure various attributes in terms of access roles for the application and define rules that would be enacted and validated when an authentication request from the application arrives.

Default Strategy

The default strategy allows one to configure a service with the following properties:

Field Description
enabled Flag to toggle whether the entry is active; a disabled entry produces behavior equivalent to a non-existent entry.
ssoEnabled Set to false to force users to authenticate to the service regardless of protocol flags (e.g. renew=true).
requiredAttributes A Map of required principal attribute names along with the set of values for each attribute. These attributes MUST be available to the authenticated Principal and resolved before CAS can proceed, providing an option for role-based access control from the CAS perspective. If no required attributes are presented, the check will be entirely ignored.
requireAllAttributes Flag to toggle to control the behavior of required attributes. Default is true, which means all required attribute names must be present. Otherwise, at least one matching attribute name may suffice. Note that this flag only controls which and how many of the attribute names must be present. If attribute names satisfy the CAS configuration, at the next step at least one matching attribute value is required for the access strategy to proceed successfully.
unauthorizedRedirectUrl Optional url to redirect the flow in case service access is not allowed.
caseInsensitive Indicates whether matching on required attribute values should be done in a case-insensitive manner. Default is false
rejectedAttributes A Map of rejected principal attribute names along with the set of values for each attribute. These attributes MUST NOT be available to the authenticated Principal so that access may be granted. If none is defined, the check is entirely ignored.
Are we sensitive to case?

Note that comparison of principal/required attribute names is case-sensitive. Exact matches are required for any individual attribute name.

Released Attributes

Note that if the CAS server is configured to cache attributes upon release, all required attributes must also be released to the relying party. See this guide for more info on attribute release and filters.

Examples

The following examples demonstrate access policy enforcement features of CAS.

Disable Service Access

Service is not allowed to use CAS:

1
2
3
4
5
6
7
8
9
10
11
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : false,
    "ssoEnabled" : true
  }
}

Enforce Attributes

To access the service, the principal must have a cn attribute with the value of admin AND a givenName attribute with the value of Administrator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "ssoEnabled" : true,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "admin" ] ],
      "givenName" : [ "java.util.HashSet", [ "Administrator" ] ]
    }
  }
}

To access the service, the principal must have a cn attribute with the value of admin OR a givenName attribute with the value of Administrator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "ssoEnabled" : true,
    "requireAllAttributes": false,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "admin" ] ],
      "givenName" : [ "java.util.HashSet", [ "Administrator" ] ]
    }
  }
}

To access the service, the principal must have a cn attribute whose value is either of admin, Admin or TheAdmin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "ssoEnabled" : true,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ]
    }
  }
}
Supported Syntax

Required values for a given attribute support regular expression patterns. For example, a phone attribute could require a value pattern of \d\d\d-\d\d\d-\d\d\d\d.

Static Unauthorized Redirect URL

Service access is denied if the principal does not have a cn attribute containing the value super-user. If so, the user will be redirected to https://www.github.com instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "@class": "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id": 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "unauthorizedRedirectUrl" : "https://www.github.com",
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "super-user" ] ]
    }
  }
}

Dynamic Unauthorized Redirect URL

Service access is denied if the principal does not have a cn attribute containing the value super-user. If so, the redirect URL will be dynamically determined based on outcome of the specified Groovy script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "@class": "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id": 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "unauthorizedRedirectUrl" : "file:/etc/cas/config/unauthz-redirect-url.groovy",
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "super-user" ] ]
    }
  }
}

The script itself will take the following form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.apereo.cas.*
import org.apereo.cas.web.support.*
import java.util.*
import java.net.*
import org.apereo.cas.authentication.*

URI run(final Object... args) {
    def registeredService = args[0]
    def requestContext = args[1]
    def applicationContext = args[2]
    def logger = args[3]
    
    logger.info("Redirecting to somewhere, processing [{}]", registeredService.name)
    /**
     * Stuff Happens...
     */
    return new URI("https://www.github.com");
}

The following parameters are provided to the script:

Field Description
registeredService The object representing the matching registered service in the registry.
requestContext The object representing the Spring Webflow RequestContext.
applicationContext The object representing the Spring ApplicationContext.
logger The object responsible for issuing log messages such as logger.info(...).

Enforce Combined Attribute Conditions

To access the service, the principal must have a cn attribute whose value is either of admin, Admin or TheAdmin, OR the principal must have a member attribute whose value is either of admins, adminGroup or staff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "requireAllAttributes" : false,
    "ssoEnabled" : true,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ],
      "member" : [ "java.util.HashSet", [ "admins", "adminGroup", "staff" ] ]
    }
  }
}

Enforce Must-Not-Have Attributes

To access the service, the principal must have a cn attribute whose value is either of admin, Admin or TheAdmin, OR the principal must have a member attribute whose value is either of admins, adminGroup or staff. The principal also must not have an attribute “role” whose value matches the pattern deny.+.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "testId",
  "name" : "testId",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "requireAllAttributes" : false,
    "ssoEnabled" : true,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ],
      "member" : [ "java.util.HashSet", [ "admins", "adminGroup", "staff" ] ]
    },
    "rejectedAttributes" : {
      "@class" : "java.util.HashMap",
      "role" : [ "java.util.HashSet", [ "deny.+" ] ]
    }
  }
}
Supported Syntax

Rejected values for a given attribute support regular expression patterns. For example, a role attribute could be designed with a value value pattern of admin-.*.

Global Groovy Script

Access strategy and authorization decision can be carried using a Groovy script for all services and applications. This policy is not tied to a specific application and is invoked for all services and integrations.

The following settings and properties are available from the CAS configuration catalog:

The configuration settings listed below are tagged as Required in the CAS configuration metadata. This flag indicates that the presence of the setting may be needed to activate or affect the behavior of the CAS feature and generally should be reviewed, possibly owned and adjusted. If the setting is assigned a default value, you do not need to strictly put the setting in your copy of the configuration, but should review it nonetheless to make sure it matches your deployment expectations.

The configuration settings listed below are tagged as Optional in the CAS configuration metadata. This flag indicates that the presence of the setting is not immediately necessary in the end-user CAS configuration, because a default value is assigned or the activation of the feature is not conditionally controlled by the setting value. You should only include this field in your configuration if you need to modify the default value.

Configuration Metadata

The collection of configuration properties listed in this section are automatically generated from the CAS source and components that contain the actual field definitions, types, descriptions, modules, etc. This metadata may not always be 100% accurate, or could be lacking details and sufficient explanations.

Be Selective

This section is meant as a guide only. Do NOT copy/paste the entire collection of settings into your CAS configuration; rather pick only the properties that you need. Do NOT enable settings unless you are certain of their purpose and do NOT copy settings into your configuration only to keep them as reference. All these ideas lead to upgrade headaches, maintenance nightmares and premature aging.

YAGNI

Note that for nearly ALL use cases, declaring and configuring properties listed here is sufficient. You should NOT have to explicitly massage a CAS XML/Java/etc configuration file to design an authentication handler, create attribute release policies, etc. CAS at runtime will auto-configure all required changes for you. If you are unsure about the meaning of a given CAS setting, do NOT turn it on without hesitation. Review the codebase or better yet, ask questions to clarify the intended behavior.

Naming Convention

Property names can be specified in very relaxed terms. For instance cas.someProperty, cas.some-property, cas.some_property are all valid names. While all forms are accepted by CAS, there are certain components (in CAS and other frameworks used) whose activation at runtime is conditional on a property value, where this property is required to have been specified in CAS configuration using kebab case. This is both true for properties that are owned by CAS as well as those that might be presented to the system via an external library or framework such as Spring Boot, etc.

When possible, properties should be stored in lower-case kebab format, such as cas.property-name=value. The only possible exception to this rule is when naming actuator endpoints; The name of the actuator endpoints (i.e. ssoSessions) MUST remain in camelCase mode.

Settings and properties that are controlled by the CAS platform directly always begin with the prefix cas. All other settings are controlled and provided to CAS via other underlying frameworks and may have their own schemas and syntax. BE CAREFUL with the distinction. Unrecognized properties are rejected by CAS and/or frameworks upon which CAS depends. This means if you somehow misspell a property definition or fail to adhere to the dot-notation syntax and such, your setting is entirely refused by CAS and likely the feature it controls will never be activated in the way you intend.

Validation

Configuration properties are automatically validated on CAS startup to report issues with configuration binding, specially if defined CAS settings cannot be recognized or validated by the configuration schema. The validation process is on by default and can be skipped on startup using a special system property SKIP_CONFIG_VALIDATION that should be set to true. Additional validation processes are also handled via Configuration Metadata and property migrations applied automatically on startup by Spring Boot and family.

Indexed Settings

CAS settings able to accept multiple values are typically documented with an index, such as cas.some.setting[0]=value. The index [0] is meant to be incremented by the adopter to allow for distinct multiple configuration blocks.

The outline of the script is as follows:

1
2
3
4
5
6
7
8
9
10
11
import org.apereo.cas.audit.*
import org.apereo.cas.services.*

def run(Object[] args) {
    def context = args[0] as AuditableContext
    def logger = args[1]
    logger.debug("Checking access for ${context.registeredService}")
    def result = AuditableExecutionResult.builder().build()
    result.setException(new UnauthorizedServiceException("Service unauthorized"))
    return result
}

The following parameters are passed to the script:

Parameter Description
context An AuditableContext object that carries auditable data such as registered services, authentication, etc.
logger The object responsible for issuing log messages such as logger.info(...).

Time-Based

The time-based access strategy allows one to configure a service with the following properties:

Field Description
startingDateTime Indicates the starting date/time whence service access may be granted. (i.e. 2015-10-11T09:55:16.552-07:00)
endingDateTime Indicates the ending date/time whence service access may be granted. (i.e. 2015-10-20T09:55:16.552-07:00)

Service access is only allowed within startingDateTime and endingDateTime:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "name" : "test",
  "id" : 62,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.TimeBasedRegisteredServiceAccessStrategy",
    "startingDateTime" : "2015-11-01T13:19:54.132-07:00",
    "endingDateTime" : "2015-11-10T13:19:54.248-07:00",
    "zoneId" : "UTC"
  }
}

The configuration of the public key component qualifies to use the Spring Expression Language syntax.

HTTP Request

This strategy allows one to configure a service with the following properties:

Field Description
ipAddress (Optional) Regular expression pattern compared against the client IP address.
userAgent (Optional) Regular expression pattern compared against the browser user agent.

The objective of this policy is examine specific properties of the HTTP request and make service access decisions by comparing those properties with pre-defined rules and patterns, such as those that might be based on an IP address, user-agent, etc.

1
2
3
4
5
6
7
8
9
10
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.HttpRequestRegisteredServiceAccessStrategy",
    "ipAddress" : "192.\\d\\d\\d.\\d\\d\\d.101",
    "userAgent": "Chrome.+"
  }
}

Remote Endpoint

This strategy allows one to configure a service access strategy with the following properties:

Field Description
endpointUrl Endpoint that receives the authorization request from CAS for the authenticated principal.
acceptableResponseCodes Comma-separated response codes that are considered accepted for service access.

The objective of this policy is to ensure a remote endpoint can make service access decisions by receiving the CAS authenticated principal as url parameter of a GET request. The response code that the endpoint returns is then compared against the policy setting and if a match is found, access is granted.

Here is an example of the remote endpoint access strategy authorizing service access based on response code:

1
2
3
4
5
6
7
8
9
10
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.RemoteEndpointServiceAccessStrategy",
    "endpointUrl" : "https://somewhere.example.org",
    "acceptableResponseCodes" : "200,202"
  }
}

Groovy Per Service

This strategy delegates to a Groovy script to dynamically decide the access rules requested by CAS at runtime:

1
2
3
4
5
6
7
8
9
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "id" : 1,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.services.GroovyRegisteredServiceAccessStrategy",
    "groovyScript" : "file:///etc/cas/config/access-strategy.groovy"
  }
}

The script itself may be designed as such by overriding the needed operations where necessary:

1
2
3
4
5
import org.apereo.cas.services.*
import java.util.*

class GroovyRegisteredAccessStrategy implements RegisteredServiceAccessStrategy {
}

The configuration of this component qualifies to use the Spring Expression Language syntax. Refer to the CAS API documentation to learn more about operations and expected behaviors.

Grouper

The grouper access strategy is enabled by including the following dependency in the WAR overlay:

1
2
3
4
5
<dependency>
  <groupId>org.apereo.cas</groupId>
  <artifactId>cas-server-support-grouper-core</artifactId>
  <version>${cas.version}</version>
</dependency>
1
implementation "org.apereo.cas:cas-server-support-grouper-core:${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-support-grouper-core"
}

This access strategy attempts to locate Grouper groups for the CAS principal. The groups returned by Grouper are collected as CAS attributes and examined against the list of required attributes for service access.

The following properties are available:

Field Description Values
groupField Attribute of the Grouper group used when converting the group to a CAS attribute. NAME, EXTENSION, DISPLAY_NAME or DISPLAY_EXTENSION.

You will also need to ensure grouper.client.properties is available on the classpath (i.e. src/main/resources) with the following configured properties:

1
2
3
grouperClient.webService.url = http://grouper.example.com/grouper-ws/servicesRest
grouperClient.webService.login = banderson
grouperClient.webService.password = password

Grouper access strategy based on group’s display extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "name" : "test",
  "id" : 62,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.grouper.services.GrouperRegisteredServiceAccessStrategy",
    "requireAllAttributes" : true,
    "requiredAttributes" : {
      "@class" : "java.util.HashMap",
      "grouperAttributes" : [ "java.util.HashSet", [ "faculty" ] ]
    },
    "groupField" : "DISPLAY_EXTENSION"
  }
}

While the grouper.client.properties is a hard requirement and must be presented, configuration properties can always be assigned to the strategy to override the defaults:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "name" : "test",
  "id" : 62,
  "accessStrategy" : {
    "@class" : "org.apereo.cas.grouper.services.GrouperRegisteredServiceAccessStrategy",
    "configProperties" : {
      "@class" : "java.util.HashMap",
      "grouperClient.webService.url" : "http://grouper.example.com/grouper-ws/servicesRest"
    },
    "groupField" : "DISPLAY_EXTENSION"
  }
}

Chaining Strategies

Multiple access strategies can be combined together to form complex rules and conditions in a chain. Using chains, one can implement advanced Boolean logic to group results together. Note that chains can contain other chains as well.

The following access strategy chain allows service access if the authenticated principal,

  • has an attribute key1 with a value of value1 AND an attribute key2 with a value of value2.

OR

  • has an attribute key3 with a value of value3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://.+",
  "name" : "test",
  "id" : 1,
  "accessStrategy" : {
    "@class": "org.apereo.cas.services.ChainingRegisteredServiceAccessStrategy",
    "strategies": [ "java.util.ArrayList",
      [ {
        "@class": "org.apereo.cas.services.ChainingRegisteredServiceAccessStrategy",
        "strategies": [ "java.util.ArrayList",
          [
            {
              "@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
              "requiredAttributes": {
                "@class": "java.util.LinkedHashMap",
                "key1": [ "java.util.LinkedHashSet", [ "value1" ] ]
              }
            },
            {
              "@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
              "requiredAttributes": {
                "@class": "java.util.LinkedHashMap",
                "key2": [ "java.util.LinkedHashSet", [ "value2" ] ]
              }
            }
          ]
        ],
        "operator": "AND"
      },
        {
          "@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
          "requiredAttributes": {
            "@class": "java.util.LinkedHashMap",
            "key3": [ "java.util.LinkedHashSet", [ "value3" ] ]
          }
        }
      ]
    ],
    "operator": "OR"
  }
}