Acceptable Use interface for Login Webflow

New CAS documentation site

CAS documentation has moved over to jasig.github.io/cas, starting with CAS version 4.x. The wiki will no longer be maintained. For the most recent version of the documentation, please refer to the aforementioned link.

At our institution we've always required users to agree to an appropriate use policy on first login in our portal, later we added other "interrupts" that can fire at login as required these include acceptable use and secret question/answer pair. As we've casified more and more applications  the requirement came about to instead include these interrupts as a component of the CAS login.

The second requirement was to store the user's interrupt status within the LDAP. As a Active Directory/Exchange shop we decided on using extensionAttribute8 but you should be able to use any attribute with this code.

We formatted the LDAP attribute such that we have interrupt identifiers paired with the date of last completion and the pairs separated by semi-colons. ie. AUP=05/18/2010;QNA=;  This indicates the user completed appropriate use policy on May 18th and will have to provide a secret question/Answer pair at next login.

Attached source includes the java sources for the classes that scan the LDAP attribute.

First to use the class you'll need an LDAPTemplate, depending on how your deployerConfigContext.xml is set up you may already have one defined, if not you probably have a LDAP context defined like so:

<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
	<property name="pooled" value="true"/>
	<property name="urls">
		<list>
			<value>ldap://ldap.example.tld</value>
		</list>
	</property>
	<property name="userDn" value="userName"/>
	<property name="password" value="password"/>
	<property name="baseEnvironmentProperties">
		<map>
			<entry key="com.sun.jndi.ldap.connect.timeout" value="10" />
			<entry key="java.naming.security.authentication" value="simple" />
		</map>
	</property>
</bean>

If you have a context already defined the LDAPTemplate is easy to add like this:

 <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
	<constructor-arg ref="contextSource" />
	<property name="ignorePartialResultException" value="true" />
</bean>

 The ignorePartialResultException value in this example is required to work with Active Directory.
Once you have the LDAP template you need to configure the bean for the new java class in cas-servlet.xml:

<bean id="CheckFlags" class="org.jasig.cas.web.flow.CheckFlags"
	p:filter="sAMAccountName=%u"
	p:ldapTemplate-ref="ldapTemplate"
	p:ldapAttrib="extensionAttribute8"
	p:searchBase="dc=example,dc=tld" />

 The properties:

  • filter defines how the username maps to LDAP attributes
  • ldapTemplate-ref names the LDAP template defined in the deployerConfigContext.xml
  • ldapAttrib is the LDAP attribute that will contain the string indicating which interrupts the user should see on next login
  • searchBase is the LDAP search based used when finding the username/attribute match

With this resolver added to the view factory the web flow can automatically find jsp pages named after your interrupt identifier:

<bean id="interruptResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"
	p:viewClass="org.springframework.web.servlet.view.JstlView"
	p:prefix="/WEB-INF/view/jsp/cctheme/ui/interrupts/"
	p:suffix=".jsp"
	p:order="2"/>

 Then update viewFactoryCreator to add the new resolver to the view Factory:

<bean id="viewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
	<property name="viewResolvers">
		<list>
			<ref local="viewResolver" />
			<ref local="interruptResolver" />
		</list>
	</property>
</bean>

 Now we need to tie the new beans into the login webflow in the file login-webflow.xml you should have:

<action-state id="sendTicketGrantingTicket">
	<evaluate expression="sendTicketGrantingTicketAction" />
	<transition to="serviceCheck" />
</action-state>

 Change the transition to fire off the new bean and check for interrupt identifiers:

<action-state id="sendTicketGrantingTicket">
	<evaluate expression="sendTicketGrantingTicketAction" />
	<transition to="CheckFlags" />
</action-state>

 Add the new the flow action to for the check:

<action-state id="CheckFlags">
	<evaluate expression="CheckFlags.check(flowRequestContext, flowScope.credentials)" result="flowScope.Flag" result-type="java.lang.String" />
	<transition on="noFlag" to="serviceCheck" />
	<transition to="showInterrupt" />
</action-state>

 This action fires the bean and if there are pending interrupts the code is stored to the flowscope and the transition passes to the custom view for the interrupt if there are no pending interrupts it passes back to the normal flow.

 The custom view in the web flow looks like:

<!-- Show custom view that interupts the login flow -->
<view-state id="showInterrupt" view="${flowScope.Flag}">
	<on-entry>
		<set name="viewScope.commandName" value="'IntData'" />
	</on-entry>
	<transition on="submit" to="SetFlag" />
</view-state>

The jsp for the acceptable use view is named the same as the interrupt identifier (AUP.jsp in this example) looks a bit like this:

<jsp:directive.include file="../includes/Top.jsp" />
<div class="info">
You must accept the college use agreement to continue<br />
<form:form commandName="${commandName}" htmlEscape="true" method="post">
    <input type="hidden" name="lt" value="${flowExecutionKey}" />
    <input type="hidden" name="_eventId" value="submit" />
    <div align="center"><input type="submit" value="Continue" id="btnSubmit" /></div>
</form:form>
</div>
<jsp:directive.include file="../includes/Bottom.jsp" />

NOTE: After working several hours trying to get this same idea working, I finally realized that for CAS 3.5.1 the jsp needs different hidden fields:

<jsp:directive.include file="../includes/Top.jsp" />
<div class="info">
You must accept the college use agreement to continue<br />
<form:form commandName="${commandName}" htmlEscape="true" method="post">
   	<input type="hidden" name="lt" value="${loginTicket}" />
    <input type="hidden" name="execution" value="${flowExecutionKey}" />
    <input type="hidden" name="_eventId" value="submit" />
    <div align="center"><input type="submit" value="Continue" id="btnSubmit" /></div>
</form:form>
</div>
<jsp:directive.include file="../includes/Bottom.jsp" />

Finally we add the SetFlag action which updates the LDAP attribute to record the user has completed the interrupt and passes flow back into the normal login flow (sort of - we actually go back a step in the flow to get a new ticket).

<action-state id="SetFlag">
	<evaluate expression="CheckFlags.update(flowRequestContext, flowScope.credentials, flowScope.Flag)" />
	<transition to="realSubmit" />
</action-state>

That's all you need if your interrupts are simple accept to proceed type, if you need more complex interrupts like our secret question/answer pair you'll need a few more pieces.  First you'll need a model to store the user feedback. (see IntData.java in the attached code)

Then you'll need to define your data update bean in cas-servlet.xml, something like:

<bean id="jdbcExample" class="sample.jdbc.jdbcExample"
	p:dataSource-ref="dataSource" />

To collect the user feedback you'll need to update the showInterrupt to populate the model:

Note the readFlow method in this example can be used to pull information from your data source and add it to your viewscope. 

<!-- Show custom view that interupts the login flow -->
<view-state id="showInterrupt" view="${flowScope.Flag}" model="IntData">
	<var name="IntData" class="org.jasig.cas.web.flow.IntData" />
	<binder>
		<binding property="field01" />
		<binding property="field02" />
	</binder>
	<on-entry>
		<set name="viewScope.commandName" value="'IntData'" />
	</on-entry>
	<on-render>
		<evaluate expression="jdbcExample.readFlow(flowScope.Flag,flowRequestContext,flowScope.credentials)" />
	</on-render>
	<transition on="submit" to="SaveInterrupt" bind="true">
		<set name="flowScope.IntData" value="IntData" />
	</transition>
</view-state>

 In the attached java there are two example methods for populating the question choices and to save the user's selection to a database. You'll need to modify as fits your environment.

 You then add a new action to pass the data into your bean for processing.

 <action-state id="SaveInterrupt">
	<evaluate expression="jdbcExample.writeFlow(flowScope.Flag,flowRequestContext,flowScope.credentials,flowScope.IntData)" />
	<transition on="Saved" to="SetFlag" />
	<transition on="Failed" to="showInterrupt" />
</action-state>

 And here is our QNA.jsp to give you an example form

<jsp:directive.include file="../includes/Top.jsp" />
 <div class="info">
 	<h2>Please choose a security question and answer.</h2>
    The following security question and answer will be used for handling lost and forgotten passwords.<br />In order to reset your password, you will be required to answer the question you choose.<br />
 	<form:form commandName="${commandName}" htmlEscape="true" method="post">
        <strong>Pick a Question:</strong><br />
        <form:select path="field01">
        	<c:forEach items="${questionList}" var="choice">
            	<option>${choice.question}</option>
            </c:forEach>
        </form:select><br /><br />
        <strong>Security Answer</strong><br />
        <form:input size="25" path="field02" htmlEscape="true" />
        <input type="hidden" name="lt" value="${flowExecutionKey}" />
        <input type="hidden" name="_eventId" value="submit" />
        <div align="center"><input type="submit" value="Continue" id="btnSubmit" /></div>
    </form:form>
</div>
<jsp:directive.include file="../includes/Bottom.jsp" />

 That's it, hope this is helpful to someone and I'm sure your end users will appreciate the value of your data collection efforts.