cas.cfc
cas.cfc
<!--- ColdFusion CAS Client Component
By: John Watson
jwatson2@ucmerced.edu
Univeristy of California, Merced
This client is compliant with the CAS 2.0 Protocol specification written/tested with ColdFusion 8
Additional features:
Single Sign Out
Attributes (per JA-SIG CAS Client for Java 3.1)
Does not support:
Proxying
v1.0 [2009-05-01] - Initial Release
--->
<cfcomponent displayname="CF CAS Component" output="no" author="John Watson">
<cffunction name="init" access="public" output="no" returntype="cas" hint="Initialize the CAS object">
<cfargument name="cas_server" type="string" required="yes" hint="URL for the CAS server (Ex. https://cas.school.edu/cas)" />
<cfargument name="service" type="string" required="yes" hint="URL for the service (Ex. http://someapp.school.edu/app)" />
<cfargument name="final_page" type="string" required="no" default="" hint="If 'direct_forwarding' is disabled, redirect user to this page" />
<cfargument name="default_page" type="string" required="no" default="/" hint="After successful authentication, redirect user to their originally intended page" />
<cfargument name="direct_forwarding" type="boolean" required="no" default="no" hint="After successful authentication, redirect user to their originally intended page" />
<cfargument name="gateway" type="boolean" required="no" default="false" hint="Enable gatewaying" />
<cfargument name="gateway_page" type="string" required="no" default="/index.cfm" hint="Page to send user to after being gatewayed and not being authenticated" />
<cfargument name="renew" type="boolean" required="no" default="false" hint="Require primary authentication on CAS (disable single sign on)" />
<cfargument name="isCAS3" type="boolean" required="no" default="true" hint="Is this a CAS3 server" />
<cfset Variables.cas_server = Arguments.cas_server & IIF(Right(Arguments.cas_server,1) is not "/",DE('/'),DE('')) />
<cfset Variables.service = Arguments.service />
<cfset Variables.final_page = Arguments.final_page />
<cfset Variables.default_page = Arguments.default_page />
<cfset Variables.direct_forwarding = Arguments.direct_forwarding />
<cfset Variables.gateway = Arguments.gateway />
<cfset Variables.gateway_page = Arguments.gateway_page />
<cfset Variables.renew = Arguments.renew />
<cfset Variables.isCAS3 = Arguments.isCAS3 />
<cfset Variables.username = "" />
<cfset Variables.requestedPage = "" />
<cfset Variables.attributes = {} />
<cfset Variables.gatewayed = false />
<cfreturn this />
</cffunction>
<!--- Some of the logic in this function is derived from the CFML script by Christian Stuck --->
<cffunction name="validate" access="public" output="no" returntype="void" hint="Validate user with CAS, if fail, send them to login page">
<cfargument name="requestedPage" type="string" required="no" default="" hint="Requested page that is asking for CAS validation" />
<cfset var service_ticket = "" />
<cfif StructKeyExists(URL,"ticket")>
<cfset service_ticket = URL.ticket />
</cfif>
<!--- User is not logged in --->
<cfif Variables.username is "">
<!--- Don't have a ST to validate --->
<cfif service_ticket is "">
<cfif Variables.gateway>
<cfif Variables.gatewayed>
<!--- If the user is on the gateway page and has been gatewayed, let them through --->
<cfif Arguments.requestedPage is Variables.gateway_page>
<cfset Variables.gatewayed = false />
<cfreturn />
<!--- If not on the right page but have been to CAS send them to the gateway page --->
<cfelse>
<cflocation url="#Variables.gateway_page#" addtoken="no" />
</cfif>
<cfelse>
<cfset Variables.gatewayed = true />
</cfif>
</cfif>
<!--- Store the page the user requested for when they come back --->
<cfset Variables.requestedPage = IIf(Variables.direct_forwarding,'Arguments.requestedPage','Variables.default_page') />
<cfinvoke method="login" />
<cfelse>
<cfinvoke method="serviceTicketValidate">
<cfinvokeargument name="service_ticket" value="#service_ticket#" />
</cfinvoke>
<!--- ST Validation failed, get a new one --->
<cfif Variables.username is "">
<cfinvoke method="login" />
</cfif>
<!--- Map the ST to SessionID for Single-Sign-Out --->
<cflock timeout="0" scope="Application" throwOnTimeout="no" type="exclusive">
<cfset Application.CASSessions["#service_ticket#"] = Session.sessionId />
</cflock>
<!--- Clean up if user was gatewayed but were already authenticated --->
<cfif Variables.gateway>
<cfset Variables.gatewayed = false />
</cfif>
<cflocation url="#Variables.requestedPage#" addtoken="no" />
</cfif>
</cfif>
</cffunction>
<cffunction name="serviceTicketValidate" access="public" output="no" returntype="void" hint="Validate the service ticket">
<cfargument name="service_ticket" type="string" required="yes" hint="The ST to validate" />
<!--- Contact the CAS server to validate the ticket --->
<cfhttp url="#Variables.cas_server#serviceValidate" method="get">
<cfhttpparam name="ticket" value="#Arguments.service_ticket#" type="url" />
<cfhttpparam name="service" value="#Variables.service#" type="url" />
</cfhttp>
<!--- Received a valid XML response --->
<cfif IsXML(cfhttp.FileContent)>
<cfset XMLobj = XmlParse(cfhttp.fileContent)>
<!--- Check for the cas:user tag --->
<cfset CASuser = XmlSearch(XMLobj, "cas:serviceResponse/cas:authenticationSuccess/cas:user")>
<!--- Set the username to the value --->
<cfif ArrayLen(CASuser)>
<cfset Variables.username = CASuser[1].XmlText />
</cfif>
<!--- Search for cas:attributes --->
<cfset CASattributes = XmlSearch(XMLobj, "cas:serviceResponse/cas:authenticationSuccess/cas:attributes")>
<!--- Go through all the attributes and add them to the attributes struct --->
<cfif ArrayLen(CASattributes)>
<cfloop array=#CASattributes[1].XmlChildren# index="attribute">
<cfset StructInsert(Variables.attributes,RemoveChars(attribute.XmlName,1,Find(":",attribute.XmlName)),attribute.XmlText)/>
</cfloop>
</cfif>
</cfif>
</cffunction>
<cffunction name="login" access="public" output="no" returntype="void" hint="Call CAS login page">
<cfargument name="forceRenew" required="no" type="boolean" default="false" hint="Force them to provide primary authentication" />
<cflocation url="#Variables.cas_server#login?service=#Variables.service##Iif(Variables.renew OR Arguments.forceRenew,DE('&renew=true'),DE(''))##Iif(Variables.gateway,DE('&gateway=true'),DE(''))#" addtoken="no" />
</cffunction>
<cffunction name="logout" access="public" output="no" returntype="void" hint="Call CAS logout page">
<cfif Variables.isCAS3>
<cflocation url="#Variables.cas_server#logout#IIf(Len(Variables.final_page),DE('?url=#Variables.final_page#'),DE(''))#" addtoken="no" />
<cfelse>
<cflocation url="#Variables.cas_server#logout#IIf(Len(Variables.final_page),DE('?destination=#Variables.final_page#'),DE(''))#" addtoken="no" />
</cfif>
</cffunction>
<cffunction name="isLogoutRequest" access="public" output="no" returntype="boolean" hint="Check if it's a single sign out request">
<cfset var endRequest = StructKeyExists(Form,"logoutrequest") AND IsXML(Form.logoutrequest) />
<cfset var sessionTracker = CreateObject("java","coldfusion.runtime.SessionTracker") />
<cfif endRequest>
<cftry>
<cfset xmlDoc = XmlParse(Form.logoutrequest) />
<cfset xmlRes = XmlSearch(xmlDoc,"samlp:LogoutRequest/samlp:SessionIndex") />
<cfcatch type="any">
<cflog text="#cfcatch#" file="saml" />
<cfreturn false />
</cfcatch>
</cftry>
<cftry>
<cflock timeout="0" scope="Application" throwOnTimeout="no" type="exclusive">
<!--- Check to see if we know this ST --->
<cfif StructKeyExists(Application.CASSessions,xmlRes[1].xmlText)>
<!--- Attempt to retrieve the Session --->
<cfset sessionToKill = sessionTracker.getSession("#Application.applicationName#_#Application.CASSessions['#xmlRes[1].xmlText#']#") />
<!--- If it does, clear it out --->
<cfif IsDefined("sessionToKill")>
<cfset StructClear(sessionToKill) />
</cfif>
<!--- Forget the ST --->
<cfset StructDelete(Application.CASSessions,xmlRes[1].xmlText) />
</cfif>
</cflock>
<!--- Problem with destroying the session, doesn't exist? --->
<cfcatch type="any">
<cfreturn true />
</cfcatch>
</cftry>
</cfif>
<cfreturn endRequest />
</cffunction>
<cffunction name="invalidate" access="public" output="no" returntype="void" hint="Invalidate the CAS Session">
<cfset Variables.username = "" />
</cffunction>
<cffunction name="getUsername" access="public" output="no" returntype="string" hint="Get the authenticated CAS username">
<cfreturn Variables.username />
</cffunction>
<cffunction name="getAttribute" access="public" output="no" returntype="any" hint="Get an attribute that was returned by CAS (if it doesn't exist returns "")">
<cfargument name="key" type="string" required="yes" hint="The attribute to retrieve" />
<cfreturn IIf(StructKeyExists(Variables.attributes,Arguments.key),'Variables.attributes.#Arguments.key#',DE('')) />
</cffunction>
<cffunction name="getAllAttributes" access="public" output="no" returntype="struct" hint="Get the whole attributes struct">
<cfreturn Variables.attributes />
</cffunction>
</cfcomponent>