> -----Original Message-----
> From: Mike Soultanian [mailto:[EMAIL PROTECTED]
> Sent: Sunday, September 04, 2005 3:50 AM
> To: CF-Talk
> Subject: Re: Question about my security system
> 
> Sorry,
> Now, the standard setup that I've seen goes as following: Most
> applications might have 3 user levels, admin, moderator, user.  So,
> there will be a check in the processing portion of the message.cfm
> template that checks to see if the user has access to perform that action.
> 
> So you have something like the following somewhere in the template:

Then this is no different than my system or any other security system.  You
may be abstracting the template-level check but the rest is the same.

In a generic system your pseudo code might look like this:

retrieve userlevel

if userlevel=admin
   Entitlements = delete, edit, post, read
if userlevel=moderator
   Entitlements = edit, post, read
if userlevel=user
   Entitlements = post, read
endif

if URL.action=delete & Listfind(Entitlements, "delete")
   delete message
end if

That first part could be on the page, in an application.cfm or anyplace -
the key is that the user's entitlements are just a collection of labels (a
list in this case, a series of Booleans in yours).  That list of labels,
once you get it (however you get it) can be turned into a list, a series or
Booleans or whatever.

It's still the template that's protecting itself, not the security system
enforcing rules over the template.

In a system like mine, where the entitlementments are abstracted into a
service this would change to simply:

if URL.action=delete & isEntitled(UserID, "delete")
   delete message
end if

This is saying "I've got a user here that wants to delete - can he?"

The complexity of the system is abstracted - the page needs to know nothing
about possible entitlements, just the one specific it needs.

You can then use the same system for group-level checks.  For example:

if URL.action=delete & isEntitled(UserID, "admin,member")
   delete message
end if 

This is saying that "if the person is a member of the admin or moderator
groups, let'em delete".

The concept is exactly the same: a task-based entitlement like the first
just tends to result in more entitlements overall.  Just like there's an
"admin" group with some members there's a "delete" group with some members.

You can combine the two into groups and subgroups - this is closest to what
you have presented.  So, the admin group has a sub group of "delete, edit,
post, read" and some of those items are also assigned to other groups (it's
a many-to-many relationship).

You still would have a single "entitlements" checker that returned just true
or false but it might be called like this:

isEntitled(UserID, "admin") OR isEntitled(UserID, "admin")

Since admin contains "delete" the first would work.  Since "delete" is what
you want to do the latter would work.

With this you gain the concept of inheritance.  In your example, for
example, there's really no need to have the same permissions defined
multiple times.  Instead have permissions inherited:

type "Visitor" can "read"
type "Member" inherits "Visitor" and, in addition can "post"
type "Moderatator" inherits "Member" and, in addition can "edit"
type "Admin" inherits "Moderatator" and, in addition can "delete"

So, if you add somebody to the "moderator" group you can do:

isEntitled(User, "delete") and get false (neither moderator or any of it's
inherited roles has that permission).

Or 

isEntitled(User, "member") and get true (moderators are, through
inheritance, members).

Or 

isEntitled(User, "post") and get true (moderators can, through inheritance
of "Member" post).

The key is that all of that complexity (groups of groups, etc) is abstracted
away in a service-level component.  You need to know the group or task you
want to perform ("delete" or "member") but that's it - you ask the system
"can he?" and it says "yes" or "no".

> Obviously this might not be the best example, but I think it should
> illustrate my point.  So, based on whatever group the user is a member
> of, they'll be able to perform certain actions in the template
> message.cfm.

Exactly - that's the standard model.

> What I don't like about the example above is that I had to hard-code
> those checks in the template - in other words, I am explicitly coding
> admin, moderator, and user into the file.  What happens if I decide I
> want to add a super-moderator level to the whole application?  Now I
> need to go into every file and update the processing section to include
> super-moderator; that could be very time-intensive.

No - not if you're security system allows inheritance.  "Super-Moderator"
could (let's say) do everything "moderator" could do plus new stuff in new
templates.  The current system is left untouched by the added complexity in
the security model.

> Now here's my solution.  I want to abstract any group checking
> processing from the templates.  The template shouldn't "care" about
> group names or users or what not, it just needs to know if it's allowed
> to do something.  So, take the same example as above but rewrite it:
> 
> <cf_securitycheck actions="delete,edit,post,read">
>
> if url.action=delete & caller.actiondelete
>    delete message
> end if

But your template still needs to "know" about groups here.  How is that
different than, say (let's say, to stick with your checking model, that
"entitlements returns a struct of Boolean keys):

<cfset Entitlements = Security.getEntitlement(User) >

if url.action=delete & Entitlements.actiondelete
   delete message
end if

But, taking it further, since template already needs to know that
"actionDelete" is the entitlement it needs to check just do this:

if url.action=delete & Security.isEntitled(User, actiondelete)
   delete message
end if

This, of course, assumes that the "Security" exists, but that's really a
given.  Also I'm passing "user" into isEntitled() all over the place because
I wanted the security system loosely coupled with the application - it's
doesn't know anything about the system it's working on, you just pass an ID
and it'll let you know what's up.

> Now all you need to do in message.cfm is do a check to see if any of
> those variables were set to true and perform processing accordingly.
> Obviously it's somewhat complicated, and probably really database
> intensive, but it nicely abstracts any user processing from the end
> template (message.cfm in my example).

I still really don't see how that's any less complex that what I've
presented as the "standard model".  You're adding a lot of complexity under
the covers to track file names when you really don't need to in my opinion:
the templates still need to know which entitlements they need (since they
have to check for them) and since they need to know there's no reason to
store them in the database (as least that I can see).
 
> This idea is purely theoretical right now as I haven't figured out all
> of the nitty gritty details.  I might end up scrapping the idea because
> it is so database intensive, but I will still give it a try.  For my
> application I may be adding and removing groups throughout the life of
> the application and I don't want to be adding group checks into the
> processing sections of the templates.

The system as described is only database intensive if you let it be.
There's no reason that every call has to hit the database.

In my system (and most others that I've seen) the standard practice is to
cache the user entitlements into a memory scope (either application or
session).  That way the checks, even complex ones, can be made very quickly.

Even if you don't drop the "page-level" permissions (which you really,
really don't need) you could still cache them as well - even for a largish
site the memory usage for this would be pretty small (say a meg or two) and
that resource usage would be nothing compared to constant database calls.

To mange updates in a scheme like this I use mediator components through
which all access to the database are made.  For example the
"entitlementMediator" component can be asked:

EntitlementMediator.getEntitlement(UserID)

It first checks to see if it has it cached, if so it returns it.  If not it
checks the database, creates it and caches it.  Only references to the
cached Entitlement are then handed out: if something else comes along and
updates the Entitlement (like an update routine or something) that change
occurs instantly everywhere in the system.

It really is rather elegant.

Jim Davis




~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
Logware (www.logware.us): a new and convenient web-based time tracking 
application. Start tracking and documenting hours spent on a project or with a 
client with Logware today. Try it for free with a 15 day trial account.
http://www.houseoffusion.com/banners/view.cfm?bannerid=67

Message: http://www.houseoffusion.com/lists.cfm/link=i:4:217327
Archives: http://www.houseoffusion.com/cf_lists/threads.cfm/4
Subscription: http://www.houseoffusion.com/lists.cfm/link=s:4
Unsubscribe: http://www.houseoffusion.com/cf_lists/unsubscribe.cfm?user=89.70.4
Donations & Support: http://www.houseoffusion.com/tiny.cfm/54

Reply via email to