Fusioncube

The online journey of a technophile, by Steve Brownlee

Warden


Quick Links

Introduction
Installing Warden
Using Privileges Without Warden
Managing Privileges With Warden
View States
Inclusive/Exclusive Privilege Challenge


Introduction

By using Warden, you will avoid hard-coding user privileges in your application code. Instead, you will challenge specific actions, and if the security model changes for a piece of functionality, you simply update an XML configuration file and your code remains unchanged.

It even supports multiple view states because actions can change their authorization requirements based upon the state of the application, not just user privileges.

^ Back to top


Installing Warden

Warden is simple, the zip file contains the Warden class, a logging utility (which you can replace, or even remove) and a sample definition file. Create a warden directory in the root of your application server and extract the files there.

Download Warden

^ Back to top


Using Privileges Without Warden

Whenever certain aspects of your application need to be restricted to certain users, the basic way of controlling access is to put conditional logic around the feature. Here’s an example.

<h2>Application Settings</h2>
<ul>
   <li>
      Theme : #theme#
      <cfif listFind(user.privileges, "APP_ADMINISTRATOR")>
         < a href="/Modify/?id=#setting_id#">Modify Theme< /a>
      </cfif>
   </li>
</cfif>
</ul>

Pretty simple, but now imagine that you have to lock down 200 features in your app with that condition. Ok, fine once it’s done it’s done and you never have to worry about it again… just a lot of copying and pasting.

But wait, that was fine 6 months ago, but now your company has hired a couple of field trainers to go out and teach your customers how to use your product. In order for them to be able to effectively show off all of the features, they have to have access to much of the code your locked down to be for administrators only.

Now you have to go through the process of copying and pasting a line of code 150 times again, while the remaining 50 features are still locked down to just administrators.

<h2>Application Settings</h2>
<ul>
   <li>
      Theme : #theme#
      <cfif listFind(user.privileges, "APP_ADMINISTRATOR") or
            listFind(user.privileges, "TRAINER")>
         < a href="/Modify/?id=#setting_id#">Modify Theme< /a>
      </cfif>
   </li>
</cfif>
</ul>

Um, no. Next week, your company is hiring a product manager that needs access to 25 of those remaining 50 features that were admin only….

<h2>Application Settings</h2>
<ul>
   <li>
      Theme : #theme#
      <cfif listFind(user.privileges, "APP_ADMINISTRATOR") or
            listFind(user.privileges, "TRAINER") or
            listFind(user.privileges, "APP_MANAGER")>
         < a href="/Modify/?id=#setting_id#">Modify Theme< /a>
      </cfif>
   </li>
</cfif>
</ul>

And you know what? the privilege APP_ADMINISTRATOR is too generic now because of the new roles. Can you go ahead and replace that string with APP_OWNER?

See how this can quickly become a nightmare?

^ Back to top


Managing Privileges With Warden

Since you’ll want to use Warden throughout your application, a simplistic way to start would to be to create an instance of it in the Application scope. If you are using ColdSpring, you could easily create a Warden bean and inject it directly into other components, but there are risks to consider which I will cover later.

<cffunction name="onApplicationStart" output="false">
   <cfscript>
   // Create an instance of Warden in the application scope
   application.Warden = createObject("component", "warden.Warden").init();
   application.Warden.loadChallengesFromXmlFile("/Warden/Warden.xml");
   </cfscript>
</cffunction>

<cffunction name="onSessionStart" output="false">
   // For this simplistic example, we will create a basic
   // structure to hold the privileges
   <cfset session.user = {} />
   <cfset session.user.privileges = "TRAINER,APP_OWNER" />
</cffunction>

Now, when you have a feature in your application that needs to be restricted to certain users, you simply need to challenge the action and pass the current user’s list of privileges. For now, Warden only accepts a comma-delimited list. I will support other types in the future, but you can also modify the code yourself quite easily.

<h2>Application Settings</h2>
<ul>
   <li>
      Theme : #theme#
      <cfif application.Warden.challenge("ModifyTheme", session.user.privileges)>
         < a href="/Modify/?id=#setting_id#">Modify Theme< /a>
      </cfif>
   </li>
</cfif>
</ul>

Then you create a Warden definition file with the following guidelines.

The root element is challenges and must contain an application attribute. By default Warden will log debug messages to a file with this name.

The only child element of challenges is challenge. Each action, or feature, that needs to be restricted in your application will have its own challenge, and will be identified by the id attribute.

The only child element of challenge is allowedPrivileges. In most cases, there will only be one allowedPrivileges child in each challenge – the default set – which is identified by specifying the viewstate=”default” attribute.

Lastly, the only child element of allowedPrivileges is privilege. Contained in each privilege node is a string containing each privilege that is allowed to access the challenged feature.

<challenges application="CompanyApp">

  <challenge id="ModifyTheme">

    <allowedPrivileges viewstate="default">
      <privilege>APP_OWNER</privilege>
      <privilege>TRAINER</privilege>
      <privilege>APP_MANAGER</privilege>
    </allowedPrivileges>

  </challenge>

</challenges>

Now, any time the access needs to change for a challenged feature, you simply update the privileges in your Warden definition file.

^ Back to top


View States

I implemented the concept of view states because, very often, views in your application may have more than one state, or mode. For example, you may have a page that lists products with filtering options on the page so that, depending on what the user has chosen, the same view could show different products, or show different options.

Let’s look at a basic example.


<cfparam mode="#url.mode#" default="basic" />

<h2>Jazz Albums</h2>
<ul>
   <cfloop query="#albums#"><cfoutput>
   <li>Album name : #name#</li>
   <li>Artist: #artists#</li>
   <li>Release date: #release_date#</li>
   <li>
      <cfif application.Warden.challenge("DeleteAlbum", session.user.privileges, "advanced")>
         < a href="/DeleteAlbum/?id=#album_id#">Delete Album< /a>
      </cfif>
      <cfif application.Warden.challenge("ModifyAlbum", session.user.privileges, "advanced")>
         < a href="/ModifyAlbum/?id=#album_id#">Modify Album Properties< /a>
      </cfif>
      <cfif application.Warden.challenge("EnableAdvanced", session.user.privileges, "basic")>
         < a href="/Browse/?mode=advanced">Switch to advanced view< /a>
      </cfif>
   </li>
   </cfoutput></cfloop>
</cfif>
</ul>

Here’s the requirements for who can access the three links.

Only administrators and trainers can switch to advanced mode. Also, when in advanced mode, the link should not appear for anyone (obviously).

<challenges application="CompanyApp">

  <challenge id="EnableAdvanced">

    <allowedPrivileges viewstate="basic">
      <privilege>ADMINISTRATOR</privilege>
      <privilege>TRAINER</privilege>
    </allowedPrivileges>

  </challenge>

</challenges>

When in advanced mode, administrators can delete an album, and modify an album, so add a challenge for each action in the advanced view state with only administrator privileges.

  <challenge id="ModifyAlbum">

    <allowedPrivileges viewstate="advanced">
      <privilege>ADMINISTRATOR</privilege>
    </allowedPrivileges>

  </challenge>

  <challenge id="DeleteAlbum">

    <allowedPrivileges viewstate="advanced">
      <privilege>ADMINISTRATOR</privilege>
    </allowedPrivileges>

  </challenge>

Also, when in advanced mode, the trainer should be able to modify the album, but not delete it. All you need to do is update the ModifyAlbum challenge to include the trainer privilege.

  <challenge id="ModifyAlbum">

    <allowedPrivileges viewstate="advanced">
      <privilege>ADMINISTRATOR</privilege>
      <privilege>TRAINER</privilege>
    </allowedPrivileges>

  </challenge>

^ Back to top


Inclusive and Exclusive Challenges

This concept is simple. A user either must have one of the required privileges, or they must have all of the required privileges. In the former case (which is default) you would pass a parameter value of EXCLUSIVE, and in the latter, the value would be INCLUSIVE.

So using the example from above, just assume that there is a feature on your site that requires a user to be both an administrator and a trainer in order to access it. Let’s say modifying an album requires both. All you do is make it an inclusive challenge.

<cfif application.Warden.challenge("ModifyAlbum",
                                   session.user.privileges,
                                   "advanced",
                                   "INCLUSIVE")>
   < a href="/ModifyAlbum/?id=#album_id#">Modify Album Properties< /a>
</cfif>

^ Back to top





About Steve

I am a technologist, and have been ever since 1980 when I got my very first TRS-80 and programmed it to do my math homework. I love to share the gift of technology with others and show them the wonderful things it can do for them, and how they should not fear it, but embrace it.

Latest Tweets

  • Ok... stayed up way too late trying out website designs for my wife's new nonprofit. The kids will be getting me u... — http://t.co/QrKh5iBI
  • Am I the only one who likes Google’s new privacy policy? http://t.co/qwcym5wH
  • All that time wasted learning the .NET framework - Fusioncube - http://t.co/krANoWmg
  • @marcesher libraries like Less only do what you tell them. You can make a mixin to do that, but it doesn't assume anything (which is good)
  • Circus about to start. Girls are so excited they can't stand it!!!! (with Sabrina and Tessa at @BrdgstoneArena) [pic] — http://t.co/PXwi5emj

Subscribe

Entries (RSS)
Comments (RSS)