I should also say that I am coming to Fusebox 5.5 after having exclusively used Model-Glue v2 for the past year and a half or so. Since one's natural approach is to try and equate what you already know from your current framework of choice to the one you are now learning, that is what I will be doing as well, so you may often see me reference things in Model-Glue terminology to help bridge the knowledge gap. In fact, one of the first things I did was to rename my fuseaction to "event", just to make me feel a little more at home. :)
Okay, so one of the first things I needed to create for this app was some basic security; if the user is not logged on, they don't get past the first page, period. Though there are always many solutions to the same challenge, following is my own approach to the matter.
Step 1 is find the best place within the Fusebox event lifecycle (which I STILL don't fully know, nor could I find any clear documentation on) in which to "intercept and potentially redirect" our call. Basically, before our user arrives at their destination page, we want to see if it is a secured page (which all of mine are except for the login) AND if the user has already successfully authenticated, then redirect them to the login page if needed.
In order to maintain the user's state (keep track of whether or not they logged in, store their personal info in a handy, globally available package), I created a User.cfc that I make sure has been instantiated with an initial 'loggedin' setting of false (this is built into the CFC's init method), and placed into the session scope. In Fusebox 5.5.1, the most appropriate place to do this that I found is in the Application.CFC's "onRequestStart" method. Mine looks like this:
<cfargument name="targetPage" />
<cfset super.onRequestStart(arguments.targetPage) />
<!--- formerly in fusebox.init.cfm --->
<cfset self = myFusebox.getSelf() />
<cfset myself = myFusebox.getMyself() />
<cfif not structkeyexists(session,"user")>
<cfset session.user = createObject( 'component','model.user').init()/>
</cfif>
<!--- making our user object available in the event bean. Event is created by Fusebox and is available to us at this point. --->
<cfset event.setValue("user",session.user) />
</cffunction>
Notice that we are doing a call to "super.onRequeststart". This is because our Application.cfc is extending (<cfcomponent extends="fusebox5.Application" output="false">) Fusebox's Application.cfc, so we need to make sure the framework gets to execute its onRequestStart as well. If we didn't make the 'super' call, our method would override FB's method and things just wouldn't go well at all.
Okay, so now with every request, I ensure that I have at LEAST an empty user object in session. The remainder of the onRequestStart method places my user object into a handly little bucket Fusebox provides us called (thankfully) 'Event'. This works very well for me, as in Model-Glue it's ALL ABOUT the event, my brutha from anotha mutha. By putting user into 'event', i can access it from anywhere else in my application WITHOUT having to talk directly to session ( a big OO No No, for the most part).
Now, the request still has not yet arrived at its destination page, and Fusebox has another file yet to process before it renders any content: Fusebox.init.cfm.
Though I tried madly to do all of my intercept and redirect within application.cfc, alas I could not make it work. Feedback from Mr. Corfield affirmed that indeed application.cfc was not the best place to do what I was trying to do, anyway, and directed me to fusebox.init.cfm as the most proper location to perform any needed manipulation of the target fuseaction. Here is the content of my fusebox.init.cfm file:
<!--- if we're not authenticated and our current fuseaction is NOT our default fuseaction (in order to avoid a dreaded infinite loop), make it so --->
<cfif (not structkeyexists(session,"user") OR not session.user.getLoggedin()) AND attributes.fuseaction IS NOT myFusebox.getApplication().defaultfuseaction>
<!--- if user is not logged in, replace the current fuseaction/event name with our default --->
<cfset myFusebox.relocate(url=myFusebox.getApplication().myself & myFusebox.getApplication().defaultfuseaction) />
</cfif>
As you can see, i'm doing a simple check of the user object and the target fuseaction. If the user isn't logged in (or user object doesn't exist for whatever reason) AND our target fuseaction is something OTHER than our default fuseaction (login), then we're leveraging the fusebox 'Relocate' method to force the user to our login page.
That's it, it's that simple. If I had to do fuseaction level security, or individual page security, I think I'd probably STILL perform those actions at these exact same locations, only I'd be adding in additional checks for the user's roles and the fuseaction's security level.
Feedback on my approach solicited. :)
Doug out
UPDATE TO PREVIOUS POST (7/9/08)
Based on the comments I received, I made a little time to pursue the route I had proposed for implementing role/fuseaction based security and found that I could do it easily without having to add any more controllers or fuseactions. Building on all of the code above, I simply added to my user object a 'Roles' variable that contains a list of the authenticated user's roles. I then added to my global configuration bean an item 'publicFuseActions' that contains a list of the fuseactions I am NOT securing (since those are far fewer than the ones I AM securing...I could've easily reversed the contents of this list to contain FAs that ARE secured). Lastly, I made a modification to the security check in my fusebox.init.cfm to evaluate the following:
(
the user is NOT logged in
OR
user IS logged in AND the requested fuseaction IS a secure one AND the authenticated user does NOT have the appropriate role
)
AND
our requested fuseaction is NOT our default fuseaction
If the entire statement above evaluates to true, then our user needs to be redirected to the login page.
Here is my fusebox.init.cfm that accomplishes the above logic:
<cfif ((not structkeyexists(session,"user") OR not session.user.getLoggedin()) OR (listfindnocase(application.globals.config.getConfigSetting("publicFAs"),attributes.fuseaction) eq 0 AND listfindnocase("Admin",session.user.getRoles()) eq 0)) AND attributes.fuseaction IS NOT myFusebox.getApplication().defaultfuseaction>
<cfset myFusebox.relocate(url=myFusebox.getApplication().myself & myFusebox.getApplication().defaultfuseaction) />
</cfif>
Again, I have to ask what would be the motivation/need to create additional controllers and fuseactions to perform security/role based fuseaction routing when the single IF statement above, strategically placed, accomplishes it just fine? Am I over-simplifying things? (or is it really just that simple?
) . If I wanted to get even more elaborate, such as routing based on role, or a diversity of messages to match the situation, I would probably add a message dictionary to my global config bean and replace my growing IF statement with a call to a global security object that would perform appropriate evaluations and return the needed responses for me to execute an appropriate redirect and message. One more object in my model that I create as a singleton and have available to my fusebox.init.cfm...why complicate it any more than that? Again, input solicited.
Doug out. again.
