In my last entry, I walked us through the basic set up and design for the photo gallery application. In this entry we will start talking about the security and registration system. To be honest, this is something that I had trouble with when I was creating my first Model-Glue application. I'm not convinced I have the best answer for this, but this is what has worked for me so far, so I'm going to share it with others.
Like most sites, our application will have both a logon screen and a registration screen. But how do we handle security? Typically I'd use onRequestStart in an Application.cfc file to see if a request needs to be secured. So for example, I'd typically do something like this pseudo-code:
if the request is NOT for logon.cfm, or register.cfm, the user needs to be logged on so let's push them there and abort the request
But how do we handle this in Model-Glue? Let's cover some basic Model-Glue facts. Model-Glue defines events. These events simply define what your application can do. The default application template that we used has two events, Home and Exception. When we hit the application without an event, it is going to use a default event defined in the settings. In our case, it's the Home template. So let's start by securing the Home template.
Events can (and normally will) broadcast a message. When I first read this in the quick start guide, it didn't make much sense. The way I look at it is - sometimes events need additional information ("Am I logged in?" for example), and the event requests that information when the event is fired. Your event can even do something with the result.
So how can we use this? We've already said that we need to require people to logon or register. So we can modify the Home event to check if the user is logged in. If they aren't, we can force them to the logon page. Here is a modified version of the Home event:
<event-handler name="Home"> <broadcasts> <message name="getAuthenticated" /> </broadcasts> <views> <include name="body" template="dspBody.cfm" /> <include name="main" template="dspTemplate.cfm" /> </views> <results> <result name="notAuthenticated" do="Logon" /> </results> </event-handler>
What has changed here? I added a new message, getAuthenticated. I added a new result. The name of the result, notAuthenticated, means, only run this result if the notAuthenticated result was returned from the controller. As you can probably guess, my controller will either return authenticated or notAuthenticated. In my case, I only really care if the user is not authenticated.
Now we need to make the controller listen for the event. You may ask - why do I need to do this? What else is going to listen to my events? Well, the Model-Glue sample application only has one controller, but a Model-Glue production application may have many. You will also see that we can tell the controller to listen to event "foo" but call method "goo". This is nice in case our controller changes as it lets us keep our events using the older event name. So go up to your controllers block, go to myController, and after the onRequestEnd message, add a new one for getAuthenticated. The entire block will look like this now:
<controllers> <controller name="myController" type="controller.Controller"> <message-listener message="OnRequestStart" function="OnRequestStart" /> <message-listener message="OnRequestEnd" function="OnRequestEnd" /> <message-listener message="getAuthenticated" function="getAuthenticated" /> <!-- Message-Listener Template <message-listener message="BroadcastMessageName" function="ControllerFunctionToFire"> <argument name="AnEventArgument" value="aValue" /> </message-listener> --> </controller> </controllers>
If you want, you can get rid of the comment in there. It is just there to help you learn the format. As you can see, we added the getAuthenticated listener and it calls the same method in the controller. Now let's add the method to our controller. Open up controller/Controller.cfc, and add this method, before OnRequestStart:
<cffunction name="getAuthenticated" access="public" returntype="void" output="false" hint="I return if the user is authenticated."> <cfargument name="event" type="ModelGlue.Core.Event" required="true">
<cfset arguments.event.addResult("notAuthenticated") /> </cffunction>
So let's examine this line by line. All controller methods that listen to events are passed in a Model-Glue event. This is what you modify to pass results back and forth. It is a bit weird at first. I always want to return a value from methods, so this took some getting used to. But you can imagine this Event object as a bus making stops at all the events during your request. At each bus stop, a new value may get on or get off, or the bus driver may be given special instructions. In this case, I'm not adding any values per se, but adding a result to the event. As you can guess, I'm hard coding this now since we don't really have a Model yet, a back end. Therefore the result will always be that a user is not logged in.
Hopefully you are still with me. Since we are returning a result of notAuthenticated, and our Home event says to fire the Logon event, we now need to add that:
<event-handler name="Logon"> <broadcasts /> <views> <include name="body" template="dspLogon.cfm" /> </views> <results /> </event-handler>
This is pretty simular to how the original Home event was. I'm not broadcasting any events. I'm simply including a logon template. Notice that the Home event uses two views. One is the content (named body) and one is the display. Normally a logon page has slightly different layout than the rest of the site. Typically I'll create a view called "base" or "bare" for that and other pages (like the register page). For now though I'm going to keep it ugly and simple. The dspLogon.cfm file is included in the zip (which I didn't forget this time) and is nothing more than a simple form. There is one line of interest that I'll share here:
<form action="#viewstate.getValue("myself")#logonattempt" method="post">
The view state, as desribed in the Quick Start, is a collection of data passed to the view. So for example, if you were displaying a press release, the controller could ask the model for the data, and then set that data in the view state so that your view files could use it. I'll talk more about this later because, to be honest, it confused me at first. But for now, the thing I want to point out is that "myself" is a default value in the view state. You can use it to point to the root file of your Model-Glue application. All you need to add to the end is the event name. Since I'm using "Logon" to display the logon form, I'm using "logonattempt" for the "try to logon" event.
So we talked about a lot here. Let's do a quick summary of what changed:
- The first thing I did was add a new setting, DSN, to my ModelGlue.xml file. This setting will be available to my code. You can think of it like Application variables.
- I modified the Home event to broadcast an event. This event checks to see if we are logged in.
- I modified the controller to check and see if the user is logged on. For this release, I simply made it return false. I did this by adding a result to my event that the Home event will notice.
- I added the Logon event to handle displaying the logon form. Any request for the site's Home event will now force you to logon if you haven't done so.
As before, you can see this in action here.