Keep It Simple

Security frameworks for web applications (if not software in general) seem to be an area in which programmers frequently over-think the problem.

A security application checks to see if the current user authorized to perform a given task, whether it's viewing a page or updating a record. The application needs to know two things - 1) who the user is 2) what they're trying to do. From that it needs to return a simple boolean value, yes they are or no they're not allowed to do x.

At all the places I've worked, the application security framework has been far more complicated. I'm not talking about roles -- those are actually a good idea. Roles are a logical grouping of the company's business rules, so internally the framework ought to be able to determine what roles the user belongs to and if those roles are permitted to perform the task. No, it's after roles that the problem becomes inflated.

Usually, I see people inflate the complexity of security by inflating the notion of the task. The task x becomes a site section [s] and a page [p] or a context [c] and then an action [a]. Typically, the action becomes a canonical list like read / write / list / execute. This makes more sense with an operating system, where the security framework's role may only be to guard the file system and therefore these actions apply in all contexts.

In a web application it's kinda silly because those actions won't apply in all or necessarily even in most contexts. Take for example an application which stores sensitive information about users, such as their social security number. A given user may be able to list the other users and view their names -- they may even be able to view the user detail which contains their social security number, yet not be allowed to view the social. In this case "read" may be the only action relevant to the social. Or there may be a content management system which allows only certain users to approve content -- in which case "execute" may be the only relevant action.

So in a typical web application security suite, you might see Security.checkPermission(user,"adminSection","products","edit") which determines if the current user is allowed to edit products in the admin area of the application, where "list" and "read" are both part of the available permissions, but neither are used because you want everyone to be able to see your products.

Some years ago Peter Cathcart Wason developed a cognitive science experiment generally known as the "2-4-6 problem". Participants are given a set of three numbers called a triplet (2-4-6) and told that these numbers follow a sequencing rule. They're then asked to deduce the rule through a simple game. They create another triplet and the experimenter tells them if their triplet is valid under the sequencing rule. When they believe they know what the rule is, they simply state the rule and the experimenter tells them if they've answered correctly and won the game.

So the guy would come in and be given the initial set, 2-4-6. He'd then make his triplet, 6-8-10, be told it conformed to the rule and call out his answer "even numbers". Overwhelmingly, most of the participants in this experiment guessed that the rule was "even numbers" or in some cases even more complicated rules like "counting up by twos". Most people failed to find the correct answer, which was "ascending numbers". The reason most people failed to find the correct answer in this test is because most people only proposed triplets they believed would be valid. Finding the correct answer to the test requires checking sequences you think won't be valid, until you've ruled out all the alternatives.

Programmers over complicate security frameworks because they only envision scenarios in which their initial concept is relevant. And then later when they discover another scenario they end up having to revise the security framework and make it even more complex to accommodate the new scenario.

The Members onTap plugin provides a security framework in which there are never any non-relevant items. It's accessed via a function request.tap.PolicyManager.isPermitted(task,user) which returns a boolean, yes they are or no they're not allowed to perform this task. These tasks can be anything - absolutely anything you want. And they can also be nested (using a forward-slash / character) or not. They're "auto-wired" into the application by using the path to the current base template as the default task. So for example, if a user is viewing the page /admin/product/edit.cfm, the application will deny them access if they don't have permission to perform the task "admin/product/edit". But that task could just as easily be a custom permission that has nothing to do with the context of the application's file structure, like "$myCustomPermission", where the $ is used to ensure the permission doesn't conflict with an existing file-based permission.

This also accounts for the site-section or page-section as well in a manner that's much more flexible than typical application security. Where the typical system would have an explicitly declared "site section" and/or "page", it would only allow a nesting hierarchy of one-to-two levels deep. The onTap framework's permission system, by omitting these as considerations, allows permissions to be indefinitely nested (which in practice is only likely to be at most about 5 levels). Thus when you test for the permission "admin/product/edit" you know that it is automatically testing the permission for "admin/product" and "admin" first and denying access to the nested sections if the user isn't allowed access to its parent. In other words, to "administer products", you first have to be allowed to "administer".

This is again, like the invention of the stirrup. It works and it works well across many contexts, precisely because it's simple and makes minimal assumptions about the environment.

Comments
Dan O'Keefe's Gravatar Isaac,
Are you saying onTap uses a combination of roles as well as file/directory structure security? I did not understand the part about the $myCustomPermission. Are you saying any function that starts with a "$" is an override to the existing security? Interesting post - I am working on a system where I need to implement a typical roles based security system but give them the ability to override it for a particular user for a particular function and maybe even for only a particular period of time. I am afraid if I do not get this part right I will end up down the path as you described where it gets overly complicated as well as bending it to meet an out of the ordinary request from the client.
Thanks
# Posted By Dan O'Keefe | 10/9/08 7:40 AM
ike's Gravatar Thanks Dan,

Roles and files/directories are two halves of a roles-based system for permissioning files. Roles being the who and files/directories being the what.

So in this article what I'm saying is that although I am using the security system to permission the "what" based on files and directories, that the system doesn't assume that the "what" will be files and directories. The "what" -- the "permission" -- is an arbitrary string that's broken up with a forward slash / for convenience to allow for nested permissions. The system has no idea whether that's a directory or file or if it's something all-together different like a database record. If you happen to use that to map to files, that's great, but you don't have to. You do have to determine how to connect the security system to whatever it is you're securing. In my case it was convenient to feed it a part of a file path as the "what" to be secured.

The use of the $ in my example isn't anything special as far as the permission system is concerned - it doesn't do anything different with it. It's just an example of a way that I might create a custom permission and ensure that it won't conflict with an existing permission for a directory or file. That's only the case because I already know that I'm using the system for files. The system doesn't know I'm using it for files, it doesn't care. But I do and I don't want conflicts. So I add a character that probably won't be found in any file or directory names and then I can test that permission wherever I need it.

I hope this clears things up a bit. :)

Something I didn't mention in this article is that my security system has a couple of layers. It's got an object in the application scope to manage permissions for the entire application and then a 2nd object in the request scope to manage permissions for an individual request. This allows me to override the permissions for an individual request to either allow or deny a user based on additional criteria. That may be a useful idea for you to work with for adding those additional, more complex criteria like periods of time without overcomplicating your basic security system. :)
# Posted By ike | 10/9/08 2:11 PM
Dan O'Keefe's Gravatar Thanks Isaac for the detailed response.

So the $ indicates to use the alternate permission checking versus the file based? Say a user does not have permission to /admin/product/edit.cfm but a custom permission comes in as you suggest with "$myCustomPermission", then you would use the alternate method of checking for permission and ignore the file based? I think the 2 layers is a good idea - maybe the role based in the application scope and any customer overrides in the request scope.
# Posted By Dan O'Keefe | 10/9/08 2:46 PM
ike's Gravatar I think there's still a bit of confusion about the $.

There aren't any "alternate permission checking" systems at all. None. Nada. Never have been, never will be.

The fact that there aren't any alternate permission checking systems is the reason why I suggest the $.

All the permissions ultimately are just arbitrary strings stored in a permission table in the database. They're completely arbitrary, so you could have a permission called "l33t/!1@2#3$4%5" and that would be just fine. It's arbitrary, so it doesn't mean anything at all. You have to supply it with meaning from *outside* the permission system.

When I use the security system to protect files, what I'm doing is supplying meaning to that arbitrary string in the database. I don't actually use the extension of the file, so the permission would be "admin/product/edit" instead of "admin/product/edit.cfm". Now all the permissions are stored in that one database table, so that string "admin/product/edit" has to be unique. If it's not unique, then I have a problem. So if I have "admin/product/edit" in my permissions I can only ever have one record for that permission because it's a unique permission.

Now I happen to know that the external meaning I'm providing for my permissions by default is derived from my file system. That means that any new permissions I add need to be distinct from any of my file or directory names because they're all going to be stored in the same database table. So I can't have a "store/product" directory and then also have a separate "store/product" permission for something else like a database record. I can't because that would be two permissions with the same arbitrary string. So I would add an extra character like the $ *because* I'm not likely to use a $ in the names of any of my directories. So all it's doing is preventing a duplicate permission.

It's exactly the same reason why for example in a Fusebox application, model-view-controler circuits are named things like "store" for the controller, "vStore" for the view and "mStore" for the model. Because the Fusebox framework can't have duplicates of any individual circuit name.
# Posted By ike | 10/9/08 3:41 PM
Dan O'Keefe's Gravatar Hi Isaac,

I was revisiting our exchange again and wanted to zero in on this paragraph and ask a question.

"Something I didn't mention in this article is that my security system has a couple of layers. It's got an object in the application scope to manage permissions for the entire application and then a 2nd object in the request scope to manage permissions for an individual request. This allows me to override the permissions for an individual request to either allow or deny a user based on additional criteria. That may be a useful idea for you to work with for adding those additional, more complex criteria like periods of time without overcomplicating your basic security system. :)"

Is it the same object in the app scope as the request scope, except maybe the request scope one checks on override table for a record or something like that?

Dan
# Posted By Dan O'Keefe | 10/29/08 12:21 PM
ike's Gravatar @Dan - request scope object + application scope object. No in my case these are separate objects. The application scope object is where all the general role/permission information and business logic happens. So when you say application.PolicyManager.isPermitted(user,"permission") it does whatever it needs with the db, etc. to figure that out and it may or may not be cached, but it applies to the entire application. The request object is much much simpler. Instead of actually housing business logic, the only thing the request object does is cache permissions. So when you say request.PolicyManager.isPermitted(user,"permission"), the request object has no idea if that's true or not. If it's cached an answer, either true or false, then it will return that cached answer. If it hasn't cached an answer, then it passes the question off to the application object, caches that answer and then returns it.

What the request object has (that you won't find in the application object) is a setPermission("permission") method. Because the request object only applies to the current logged in user and not to any other users, you can tell it "cache this answer for x permission". It only applies to the current request and the logged in user. If the system asks for permissions for anyone else it will go through the application object (bypassing the request object). That's how the controller is allowed to add more conditions to the permission, because it can tell the request object to cache a value without involving the permission system in the application scope. One caveat however is that you have to tell it to cache the answer before it checks the permission for the current page/event - so you may find yourself using the application object first to check and see if the user has the default permission before applying the additional conditions.

I feel like I may be making this explanation kind of muddy... :-/ If I can think of a better way to explain it, I'll let you know... or maybe I'll create a UML diagram, maybe a visual would make it easier to explain. :)
# Posted By ike | 10/29/08 2:40 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress