Apereo CAS - Webflow Decorations

Posted by Misagh Moayyed on April 25, 2019 · 8 mins read ·
Content Unavailable
Your browser is blocking content on this website. Please check your browser settings and try again.
This blog post was originally posted on Apereo GitHub Blog.

Overview

There are times where you may need to modify the CAS login webflow to include additional pieces of data, typically fetched from outside resources and endpoints. Examples include displaying announcements on the CAS login screen or calling a REST API to fetch today’s Cafeteria menu, etc. While the webflow itself can certainly be extended in many fancy ways, one easy option is to let CAS decorate the login webflow automatically by reaching out outside sources to fetch data while taking care of the internal webflow configuration and injections on its own. Of course, once data is fetched and made available to CAS you still have the responsibility of using that data to properly display it in the appropriate view and style it correctly…and that’s what we are going to do here!

Our use case is such:

  • Examine the incoming application URL that has submitted a login request to CAS.
  • If it’s an https URL, display a message on the screen to reassure the user of their security and safety.
  • If it’s NOT an https URL, display a message on the screen anyway to frighten and notify the user!

Our starting position is based on:

Configuration

Imagine we have the following service definition registered with CAS:

{
  "@class": "org.apereo.cas.services.RegexRegisteredService",
  "serviceId": "^https://.*",
  "name": "HTTPS and IMAPS",
  "id": 10000001,
  "description": "Authorize that support HTTPS and IMAPS protocols.",
}

The serviceId field above indicates that all URLs starting with https:// are recognized by our CAS server. Later on, we may relax this pattern to allow http-based URLs as well so as to allow our logic for URL detection and follow-up messages on the screen.

Next, we are going to teach CAS about the location of a script that would handle the execution of our use case and conditions:

cas.webflow.login-decorator.groovy.location=file:/path/to/GroovWebflowDecorator.groovy

…of course, our Groovy script would be:

import java.util.*
import java.io.*
import org.apereo.cas.web.support.*

def run(Object[] args) {
    def requestContext = args[0]
    def applicationContext = args[1]
    def logger = args[2]

    def service = WebUtils.getService(requestContext)
    // def request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext)
    logger.info("Decorating the login view for ${service}")
    if (service != null) {
        if (service.id.startsWith("https://")) {
            requestContext.flowScope.put("decoration", 
                new Decoration(title: "decoration.title.secure",
                            description: "decoration.description.secure"))
        } else {
            requestContext.flowScope.put("decoration", 
                new Decoration(title: "decoration.title.insecure",
                            description: "decoration.description.insecure"))
        }
    }
}

class Decoration implements Serializable {
    private static final long serialVersionUID = 8517547235465666978L
    String title
    String description
}

The above script simply attempts to stuff a Decoration object into the webflow using the lookup key decoration. Our object carries two fields for title and description that point to keys in our language bundles. The webflow will be decorated based on the incoming service and our condition therein, exposing access to our data object under the key decoration, which can then be used in CAS views to display data, etc.

Mainly, etc.

Groovy Script
The script is cached and watched for changes. As you adjust the logic and update the script, CAS may detect changes to the file and auto-refresh its cached version of it after a small delay.

Of course, the CAS message/language bundle (typically custom_messages.properties file) should also contain the text for our message keys/codes as well:

decoration.title.secure=Secured!
decoration.title.insecure=Insecure!

decoration.description.secure=This application runs behind https.
decoration.description.insecure=This application runs behind http!

At this point, the webflow is properly decorated with the data we need to display. All we have to do is find a relevant CAS view and display that data, perhaps in serviceui.html somewhere:

<div th:if="${decoration}">
    <h3 th:utext="#{${decoration.title}}" />
    <p th:utext="#{${decoration.description}}" />
</div>

That should do it.

Test

If you attempt to access CAS using an application that does in fact run behind https, the following picture is what you should expect:

image

Next, if you relax the serviceId requirement to allow for http applications as well, you might see the following outcome:

image

Bonus

If you need access to the raw HTTP request, you can always use:

def request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext)
println(request.getRemoteAddr())

A better strategy would be to use ClientInfoHolder instead:

import org.apereo.inspektr.common.web.*;

def clientInfo = ClientInfoHolder.getClientInfo();
def clientIp = clientInfo.getClientIpAddress();

So…

I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please know that all other use cases, scenarios, features, and theories certainly are possible as well. Feel free to engage and contribute as best as you can.

Happy Coding,

Misagh Moayyed