Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Integrating Mura with the Jasig Central Authentication Services (CAS)

DZone's Guide to

Integrating Mura with the Jasig Central Authentication Services (CAS)

·
Free Resource

I've already posted most of this information on the Mura forums, but I wanted to post it here as well for anyone looking for information on this topic.

With the blessings of my boss, I've been playing around with the ColdFusion-powered Mura content management system to see if it might be a good CMS option for us.ᅠ From what I've seen so far, Mura is a well-thought out CMS system that is both powerful and easy to use, which is a difficult balancing act.

One of the things my boss wanted me to investigate was whether or not we could tie Mura in with our single sign-on solution, which is CAS (Central Authentication Service), a Jasig project originally created at Yale.ᅠ When a user tries to access a page or a site that requires them to authenticate, they are redirected to the CAS server and enters their LDAP-based user id and password on the CAS login page.ᅠ If they successfully authenticate, the CAS server redirects them back to the original site and stores a token as a cookie in the user's browser.ᅠ If the user visits a different website secured by CAS, the cookie allows them access to that site without the need to log in again.

Mura provides a plugin architecture that allows developers to intercept certain Mura events and run their own code.ᅠ A number of Mura shops have created plugins that intercept the Mura login events in order to tie in with their in-house directory and authentication servers, but those login events are triggered by the admin and user login forms built into Mura.ᅠ I couldn't go that route:ᅠ I needed to circumvent and replace the Mura login form(s) with the CAS login form and have Mura log in the user based on the credential information returned by CAS.

After some digging into the code and some trial-and-error, I came up with a way to authenticate Mura administrators via our CAS system.ᅠ First, I created admin user accounts in Mura where the username matched the username/user identifier returned by a successful CAS login.ᅠ Then I created a Mura plugin that would fire with the OnGlobalRequestStart event with the following event handler:

<cfif cgi.script_name EQ "/admin/index.cfm" and cgi.query_string EQ "">
    <cfif IsDefined('session.mura') and session.mura.userId EQ "">
        <cfif StructKeyExists(session,"NetId")>
	    <!---Look for an admin (type 2) account with the user identifier returned by CAS--->
            <cfquery name="qry" datasource="{My Mura Datasource}">
                select userId
                from tUsers
                where username= <cfqueryparam value="#session.NetId#" cfsqltype="cf_sql_varchar" />
                and type= <cfqueryparam value="2" cfsqltype="cf_sql_numeric" />
            </cfquery>
			
            <cfscript>
                loginData= StructNew();
                loginData.userID= qry.userId;
                loginData.siteId= "default";
                loginData.isAdminLogin= true;
                loginData.returnUrl= "index.cfm?fuseaction=cDashboard.main&siteid=default";
            </cfscript>
            <cfset StructDelete(session,"NetId")>
            <cfset application.loginManager.loginByUserID(loginData,"")>
			
        <cfelse>
            <cflocation url="/adminCASLogin.cfm" />	
        </cfif>
	
    </cfif>
</cfif>

...The code in this plugin will only execute if the user navigates to /admin/index.cfm (without any URL query parameters) and the "mura" struct in the session scope contains an empty userId key.ᅠ Without these conditionals, the login code will run at times when you don't want it to, like when the user is trying to log out or when saving page edits (apparently saving content changes involves navigating to the admin/index.cfm page without URL query parameters).

The NetId is the user identifier returned by CAS.ᅠ If it's not found in the session scope, the action is redirected to adminCASLogin.cfm, a variation on the CAS authentication script posted on the CAS website (https://wiki.jasig.org/display/CASC/ColdFusion+client+script).ᅠ The user enters their username and password on our CAS login form page, and if the log in is successful the adminCASLogin.cfm page copies the NetId to the session scope and uses cflocation to go to admin/index.cfm.

Now that the NetID exists in session, the plugin queries the tUsers table (the Mura database table containing, well, user records) for an admin account with a username that matches the NetId and gets back the Mura userId for that person, and provides that userId along with other values to the loginManager's loginByUserID() function.ᅠ The admin user ends up in the administrative Dashboard, logged in and ready to go.

The one drawback to this method is that when a user logs out, they are presented with the normal Mura admin login form, which can be a little confusing.ᅠ But without knowing the password to their Mura user record, they would still have to log in via CAS.

I wrote a similar plugin for requiring CAS authentication to access a particular Mura site (an "internal" site only viable to authorized personnel).ᅠ In this case, the event handler executes in response to the OnSiteRequestStart event:

<cfif StructKeyExists(session,"userId") EQ false>
    <cfif StructKeyExists(session,"NetId")>
        <!---Look for a user account with the user identifier returned by CAS--->
        <cfquery name="qry" datasource="{My Mura Datasource}">
            select userId, type
            from tUsers
            where username= <cfqueryparam value="#session.NetId#" cfsqltype="cf_sql_varchar" />
        </cfquery>
		
        <cfif qry.recordcount EQ 1>
            <cfset session.userId= session.NetId>
            <cfset StructDelete(session,"NetId")>
			
            <!---If they are an admin (type 2), log them in)--->
            <cfif qry.type EQ 2>
                <cfscript>
                    loginData= StructNew();
                    loginData.userID= qry.userId;
                    loginData.siteId= "internal";
                    loginData.isAdminLogin= true;
                    loginData.returnUrl= "/internal/index.cfm";
                </cfscript>		
                <cfset application.loginManager.loginByUserID(loginData,"")>
            </cfif>
        </cfif>

    <cfelse>
          <cflocation url="siteCASLogin.cfm" />	
    </cfif>
	
</cfif>

...in this case, I had to create my own session variable (session.userId) for denoting if the user had already authenticated via CAS.ᅠ The siteCASlogin.cfm file is pretty much the same as the adminCASLogin.cfm file in the first example except that when it completes the CAS authentication it uses cflocation to return to the index.cfm page of the site (rather than admin/index.cfm).

If the user successfully authenticates via CAS and has any sort of Mura user account, they will be granted access to the site, and if they are a Mura admin they will be logged in so they can edit the site if they wish.


Topics:

Published at DZone with permission of Brian Swartzfager, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}