Thoughts On User / Page Request Security Model
I recently finished a rather large, complex, ColdFusion-based (of course) application. The security within the application is more robust than anything that I've ever coded before; it works well enough, but it certainly has left me wanting to find a better solution for applications going forward. I've spent the meditating on security ideas and I just can't seem to find the sweet spot.
The application is event-based just like most ColdFusion applications that we see out there. As such, each request maps to a single event. For example, several news-related page requests might use one of the following events:
- news
- news.view
- news.edit
- api.news.get-detail
The security surrounding events in the application is both "event-based" and "target-based"; by that I mean that access to a given event is checked at both the event level and, if necessary, at the target level. Taking the news-related events as an example, all users in the system might have access the "news" event. But, only administrators can access the "news.edit" event in general; but, even within the "news.edit" event, administrators may only edit target items (ex. news record, ID=2403) that they themselves created.
Now, to complicate things even further, each user within the system can have several different roles associated to the same login. So for example, Molly might have a single set of credentials but belong to both the Administrators and Staff groups. Even though she belongs to multiple groups, each page request that Molly makes can only be evaluated in the context of a single mode (due to display constraints). This mode is based on the directory being accessed:
/admins/?event=news
/staff/?event=news
So what does this all mean? Permissions are assigned at the time of login and a user might have permissions for multiple groups; but, event access is challenged based both on user-specific permissions AND page-specific settings.
At first, I started playing around with the API that I would want to use and thought it would be elegant to see this all come off some sort of User object:
User.GetPermissions( "news" ).CanAccess()
User.GetPermissions( "news.edit" ).CanAccess()
User.GetPermissions( "news.edit" ).CanAccess( Target = 2403 )
Implementation aside, this seems nice - until you take into account the fact that access is challenged in the context of page-specific settings. Since a user might be making simultaneous requests in different modes (it's very common for a single user to have multiple browser tabs open in different "modes"), I can't store page-request-mode information inside the user.
Given this situation, I would either have to pass in the page-specific settings to the permissions method:
User.GetPermissions( RequestSettings, "news.edit" ).CanAccess()
... or, I can totally shift my mental model regarding security and start thinking about access as a request-based challenge rather than a user-based challenge. Meaning, security is no longer about what the User has access to, but rather about what the Request has access to:
Request.GetPermissions( "news.edit" ).CanAccess()
Using this methodology, we could easily instantiate a Request object using both page-settings and a User instance.
The request-based methodology leads to a cleaner API, but I think we can all agree that there's something about it that just doesn't feel right; security should be user-based because it's the user that requires security in the first place. So, how can we keep it user-based but still use a clean API? One way might be to simply pass in an entire Event object to the permissions method that aggregates both the page-specific settings and the event:
User.GetPermissions( EventObject ).CanAccess( Target = 2403 )
This feels OK, but falls apart when we realize that when it comes to security, we don't just check the current page request access - we check the current page request and conditions regarding future page requests. Meaning, of course we have to challenge the current request permissions, but with things like page layout, we also have to check out future page requests:
<cfif NOT XXXXX.GetPermissions( "news" ).CanAccess()>
<!--- Throw Authorization error. --->
</cfif>
<!--- Check Add rights. --->
<cfif XXXXX.GetPermissions( "news.edit" ).CanAccess()>
<a href="##?event=news.edit">Add News Item</a>
</cfif>
<!--- Check edit rights. --->
<cfif XXXXX.GetPermissions( "news.edit" ).CanAccess( Target = 2403 )>
<a href="##?event=news.edit&id=2403">Edit Item</a>
</cfif>
Because of this, we need a way to not only challenge access for the current page request but also to check access for subsequent page request events. This either puts the kibosh on passing in an Event object that contains the page-specific settings and the event (as the event must be dynamic), or it means that we simply need two different methods for checking security. I think clearly, if we have two ways to perform the same action, that's a huge red flag; so, I think the only logical conclusion is that we have to dump the unified Event concept when checking security.
At this point, I only see what doesn't work, not what works. To be continued....
Want to use code from this post? Check out the license.
Reader Comments
Just thinking out loud here, but ...
What if you gave each object a key and the user a keychain? You could then do:
SecurityManager.UserHasAccess( UserID, ObjectKey, AccessLevel )
The ObjectKey can be given to any object, whether an Event or a Target or some future kind of object that you don't know about yet. This unties the security from the implementation a bit.
If you have a common object base, then you just add a GetKey method in the ancestor class that creates a UUID at object creation, or gets it from the DB.
I tend to bake AccessLevel into my security managers, so that I can specify if they have Read, Write, or Full control.
I second Rick's comment. I like using a key/keyring method.
It's also nice cause you can deny access if they have a key, not just allow. And they allow for lots of flexibility as the site evolves.
@Rick,
Can you clarify what the ObjectKey is a bit? I am not sure I am following along. One of things that I was contemplating was having each event (ie. "news.edit") map to an EventPermissions object, which is where I was going with the:
GetPermissions( "news.edit" ) ---> returns eventPermissions
... then the EventPermissions woudl have the access method:
EventPermissions.CanAccess( ID )
... of course, you would need a way to store the user with the event permissions which I didn't like.
I think moving all of this into a "higher power", the SecurityService / SecurityManager, would solve a lot of the dependency issues.
I guess, I was just looking for a way to make the calls smaller; it seems like a lot of information to pass around for each access check. But maybe, that's just how it needs to be done.
The ObjectKey is a token, a GUID/UUID, nothing more. For things that map to database rows, like Targets, you can use a GUID in your database. For things that don't really map, such as Events, you can hard-code them in your constructor. As long as they are unique, you won't have any problems. (And you can make your SecurityManager class smart enough to cache.)
If you want to be really sneaky, you could bake this into your ancestor class so that anything you build automagically gets it:
<cfcomponent displayName="MyBaseClass">
function init( SecurityManager ) {
this.SecurityManager = arguments.SecurityManager;
this.ObjectKey = '{00000000-0000-0000-0000-000000000001}';
}
function userAccess( UserID ) {
return this.SecurityManager.getUserAccessToObject( UserID, this.ObjectKey );
}
function userCanRead( UserID ) {
return userAccess(arguments.UserID) gte this.SecurityManager.ACCESS_READ;
}
function userCanWrite( UserID ) { ... }
function userCanManage( UserID ) { ... }
</cfcomponent>
<cfcomponent displayName="MyEventClass" extends="MyBaseClass">
function init() {
super.init();
this.ObjectKey = '{00000000-0000-0000-0000-000000000002}';
}
</cfcomponent>
Then, in code:
<cfif thisEvent.userCanRead( request.UserID )>
blah blah blah
<cfif thisEvent.userCanWrite( request.userID )>
Edit this text
</cfif>
<cfif thisEvent.userCanManage( request.userID )>
Delete this text
</cfif>
</cfif>
Targets would work the same way.
You're probably going to want to have a few "filter"-type methods where you can pass in a UserID, AccessLevel, and an array of ObjectKeys, and get back the ones you have access to. It makes doing lists of things faster.
I know that it isn't exactly on topic, but have you ever checked out any python books? This one (http://www.amazon.com/exec/obidos/ISBN=1887902996) is especially good because it doesn't just hit on Python, but more on event-driven programming, target-driven programing, and tiered programming. Since going through it my coding has really improved, as I have started thinking about problems in a whole different light.
I've usually modeled this by using two different objects:
One, the Access Control Service knows how to read the Permissions collection and determine whether a user has a given type of permission based on their permissions. This CFC is composed of a bunch of different functions which return boolean based on whether or not the permissions object passed in is allowed to perform an action.
The other, a Permissions object, just serves as a collection of the various classes of permissions that can be assigned to a user. The interface is generally two functions: addPermission( scope, role ) and hasPermission( scope, role )
Thus, most of my calls look like this:
<cfif accessControlService.canEditContent( request.user.getPermissions() )>
<!--- Some protected Code --->
</cfif>
or
<cfif accessControlService.canEditContent( request.user.getPermissions(), scope )>
<!--- Some protected Code --->
</cfif>
This allows the flexibility to change the logic behind the canEditContent to be very customizable for the various actions, and also allows you to change the way the Permissions object works internally without changing the accessControlService.
Security isn't really the responsibility of the User, nor of the Request, nor of the Target.
Security is a separate area of responsibility.
You may want something like:
myAuthorizationServiceObject.IsAuthorized(user, action, target)
I guess here is really where the finer points of OO get complicated. On one hand, when you think about "idealized" objects, I think it could be fair to say that a User *should* know what they can and can't do - they are idealized, smart objects.
But, on the other hand, I think its much easier to both conceptualize and implement if the security concerns are handed off to the security service / manager.
If we pass this off to the security service, then I think what we can do is create a new instance of some sort of security object per page request.
Thanks for all the feedback guys.
Security isn't "one thing" which can easily managed by a single object. Security is comprised of user rights (which are attributes of a user), and Constraints (which prevent users from reading, writing, or executing some things)
Think of the real world. We don't rely on people to say which houses they have access to. We give the people a collection of Keys, and then we have a set of Locks on the houses. When a Person with the right Key attempts to enter a house with the right Lock, then they are granted access. A user doesn't know for certain whether they have access to a house until they use their key to try it. The locks may have been changed since the last time they were there, so they may be locked out.
@Adam A User object should know the rights/roles/groups/capabilities that it has, because a list of rights/roles/groups/capabilities is a property of each User. But if the User object starts becoming aware of actions and targets, that's far too much information and behavior which is far outside the scope of what a User object actually is. But generic application code which simply needs a yes/no answer to the question, can a given user perform a given action on a given target?, should not deal with rights/roles/groups/capabilities directly - it should ask the AuthorizationService instance whether a given user is allowed to perform a given action on a given target, and then the AuthorizationService goes and does multiple things, including checking the given user's roles.
@Ben The "idealized" super-objects you imagine are the opposite of what object-oriented programming styles encourage! User objects are *not* centralized and smart - they have a specific, delimited, specified responsibility, and they perform that exact responsibility remarkably well, but they don't do anything else. The User object should simply be a representation of what a User is, and the AuthorizationService object should simply be a object that knows whether users are allowed to perform actions on targets or not. Note that AuthorizationService aggregates three very distinct concepts: user, action, target: and none of these concepts is a subsidiary of any of the other two. They should be kept separate and distinct, and only aggregated in another kind of thing whose purpose is to aggregate them and deal with them in combination.
Sorry, this may be a bit off topic, but I just have to know. Why do you like Coldfusion so much? Like... rather than PHP or Python or Ruby? I just don't get it. Maybe it's because I come from a PHP background, but since I got a job doing Coldfusion I've started to hate programming. I used to love programming when I was writing PHP applications. Now I hate it. I hate it because I hate Coldfusion. What makes you like it so much? I just don't get it. Please don't take this as a flame, I don't mean it to be insulting, ya know I feel like to each their own, but I just don't understand what makes Coldfusion good. The syntax alone is enough to make me hate it.
@Luke
Not sure how you can talk about syntax and then say that Python has great syntax. Some people like it, but Python is a very different syntax than most other languages as well (more of a scripting style than a tag-base, but still).
I think that it also depends on what you are programing, how you program it and who you program it with. There have been some CF apps that I have worked on that drove me crazy. There have also been some PHP apps that I could not stand. If you work with some programmers that just get it, the experience is usually a good one.
@Brandon
Python does have great syntax. In fact I like its syntax better than any other language. Hands down. What I hate about Coldfusion's syntax is that it is so verbose that it's unreadable. Python code is just the opposite. Because of the fact that it uses indentation (whitespace) rather than curly braces, all python code looks the same and it is clear, concise and completely easy to read. It's beautiful. Coldfusion has two different syntaxes, neither of which can I get comfortable in.
Once again I don't mean to flame or troll or anything like that, and I apologize for de-railing the discussion. Like I said, to each their own. If you guys like Coldfusion, that's great, but I just can't get used to it. I just don't lke it at all.
Ben, rather than me de-railing the discussion any further, perhaps you can write a post about what makes you like Coldfusion over other langauges? I'm just really curious to know. Thanks :)
Before this goes any further, let's not make this a conversation about why any given language is good or bad. Most programming languages are really good for different reasons. And, most have different syntax.
Plus, the concept of security is not language specific, so let's concentrate on security, not on languages.
@Luke, If CF made you hate your job, I would suggest finding another job :)
@Adam, @Justice,
I think the idea of modeling the "real world" only gets us so far. It's a starting point, that's all. When we get into the programming world, from what I have learned, we are supposed to take the good things about the real world and then make them even better. As such, I was taught that objects should be idealized.
For example, a dollar bill, in the real world, has no concept of where it has been; but, in the digital world, a DollarBill object might certainly have a collection of previous owners because "ideally" it would know where it has been, even though this doesn't model the real world capabilities.
That said, I am not going to argue that a User *should* know about all of its security because, I don't really think it does. At least not in a system where the security is robust.
I am going to continue exploring.
Good point Ben. Once again... sorry to de-rail the conversation. My bad. Maybe if I just understood Coldfusion as well as you I'd like it more. I guess I'll just keep at it.
@All,
Some more, albeit brief, thoughts on security based on what we've discussed here:
www.bennadel.com/index.cfm?dax=blog:1577.view
@Luke,
No worries my man; first off, switching to any language is hard and can be uncomfortable. But this applies to almost anything. It's like when your girlfriend gets a new hair cut and you're all like, Uggg, your hair looks messed, yo! And then a few weeks go by and you really like it. Then, she switches back to her old hair style and you're all like, Uggg, your hair looks messed, yo :) Change simply takes some getting used to.
ColdFusion definitely has some down sides; but, it also has some huge upsides like its ease of execution in things like XML, HTTP requests, PDF generation, SQL statement creation, FTP, command line, language extension.
@ben actually, I disagree with your assertion about "Idealized" objects. I wouldn't give a Bill object knowledge of where it had been, because that knowledge has nothing to do with being a Bill. I would instead create a BillTravelLog object which records a Bill and a collection of owners.
@Adam,
You might certainly store that in a travel log; but, wouldn't the Bill be the one that composes it? After all, shouldn't things know where they've been? If I asked you where you were last night, do you consult and external service?
@Ben,
In general, it comes back to my statement about "that knowledge has nothing to do with being a Bill". I suppose it depends on your application. In general, your objects should be pretty limited in scope. They should only have the information and methods that pertains to what they are (from the perspective of the system) and what they can do. Thus, a bill might have a method named "changeOwners(Log ownerLog,User newOwner)", but the bill itself doesn't need to know who has owned it in order to be a Bill, so there probably isn't any need to save that information in the Bill. This becomes especially obvious when you consider that there are other objects (such as Coins, Houses, and Cars) which have one or more owners over their life span. You could do some sort of Inherritance to manage that (say, an "Assets" class), but remember the old OO maxim: Favor Composition over Inherritance. In the end, it's better to make each class as pure to it's purpose as possible. Thus, a Bill might have a "OwnershipLog" attribute, if that's important to what a bill is in your system, but the bill itself shouldn't know where it's been. That's knowledge that should be abstracted away from the bill.
@Adam,
I am certainly new to the OO thought process, so I can't argue too strongly one way or the other. But, "idealization" or not, what about things like a Library Book. In the "real" world, a library book certainly does know who owned it - each owner must sign and date the back flap of a book (at least that's how it worked when I was in school). In that case, would you say that the library book knew who owned it? Or would you delegate that information to the Library Accounting system?
@Ben You would probably ask the librarian, who deals with this particular set of concepts (definitions, intricacies, interrelationships) day in and day out.
From my experience, a library book is just another book, which happens to belong to the library. So the library has many books, and it also has many loans of books to customers. The librarian keeps a record of such loans, and incidentally happens to keep a copy of that record in the back flap of the book for the convenience of the borrower.
Unfotunately, Ultra-Objects is the style of object definition which ColdFusion makes easiest, because writing small classes and small functions in ColdFusion is a chore, and because ColdFusion is missing certain language features which would make such classes and functions much easier to write and much more expressive.
It is not that easy to learn good practices of object-oriented programming in ColdFusion: you must branch out to languages like Smalltalk, Ruby, Python, Java, or C# to begin to understand how to construct enterprise-scale systems using minuscule objects which do only one thing but which do that thing well, and which accurately capture the real-world concept they are intended to model. Once you learn the techniques well in another, more object-oriented, language, you can begin to apply them much more powerfully in ColdFusion.
@Justice,
I have been trying to play around with some other languages in OO, but it's a slow process for me; and, one that I wish I could devote more time to :)
@Ben,
For other OO languages, I would suggest starting to do some Flex. ActionScript is a very powerful OO language, fully typed and everything like Java, and without the object instantiation penalty of CF. Besides, things you build in Flex can act as front-ends to your CF apps very easily.
@Adam,
I am a fan of action script, although I have not really tried FLEX yet. I used to do a bit of scripting in Flash 8 and earlier days (AS2) and enjoyed it. My only hesitation about using any Flash-based OO to help me learn is that so much of that stuff is event-based which I don't think translates that well to ColdFusion OO. Because it is an interface technology, there's a lot of loading and clicking and hovering and callbacks, which I don't think are necessarily helpful to ColdFusion (although I could be way off - just a gut feeling).
I think to learn more about OO, I have to use a different backend language like Ruby or Python or something.
Well something I do is have a field in the database called permissions and authors where you can store the set permissions
for an example
permissions = author, all, admin etc...
I would also create the author(s) in a CF list then just loop through it to see if the user has permission to modify the entry or not. I know it might not be the best solutions but it works wonders.
I have been trying to play around with some other languages in OO, but it's a slow process for me; and, one that I wish I could devote more time to :)
If your not sanguine to redesigning the entire site... Consider using a servlet to create and serve up web page content dynamically on request. The request would contain the required access levels parameters needed and the servlet component would control which factory to call into play. When a request comes in for a new object, needed permissions and roles are sorted out. Users are denied or allowed access to the resource which would be freshly baked for the user. Objects are created using the factory pattern and the need for page level security is minimized as resources are created when requested..??