Database-based authorization?

Database-based authorization?

Old forum URL: forums.lhotka.net/forums/t/1981.aspx


bgilbert posted on Friday, December 15, 2006

Does anyone have an example of a pattern to store role authorization rules information in tables? We'd like to replace the hard-coded authorization rules checks in the authorization region.

We're in a Novell environment so we're not using Active Directory. We have the option, maybe, to use Novell's Identity Manager, but will most likely end up creating our own infrastructure using SQL Server. We would, however, like to abstract this part of it in case the data source changes.

Are there any CSLA-specific solutions? Is there anything in the Security Application Block of the Enterprise Library that would solve this? Any insights would be very much appreciated.

Barry Gilbert

RockfordLhotka replied on Friday, December 15, 2006

The basic structure of the solution is to create a read-only object that represents the authorization rules/roles for a given business object type. This read-only object would consist, at least, of the business object type name, and then a list of the properties of the object. For each property, you'd have four lists of roles (allow read, deny read, allow write, deny write).

The data structure I'm describing is not overly complex - just lists of lists.

If you construct this object as a ReadOnlyBase-derived object, you can load it via the data portal, just like any other object. Though you might load it from your custom database or whatever.

If you are using only per-type rules (in version 2.1) then you don't need to worry about caching these authorization infomation objects - CSLA .NET will cache the results once you load them into the actual object in AddAuthorizationRules().

If you are using per-instance rules then you might want to cache the authorization information objects. Though that's debatable, since the only reason to use per-instance rules is if the rules differ on a per-instance basis, which could make any sort of caching entirely impossible.

SonOfPirate replied on Friday, December 15, 2006

I've actually taken this a step further to facilitate globalization and to prevent tampering.

All of the rights/roles/priviledges (whatever you want to call them) are defined in an embedded resource file as string resources.  This allows the text description of the right/role to be globalized in the event that you provide a UI displaying what rights/roles a user has/is in.

All code uses the resource key to identify the right/role that you are checking for.  In our case, these are typically "rights" like "Can add projects" that are assigned to the various roles/groups in the application but sometimes contain the actual role names instead.

What this does is allow the code to remain consistent regardless of the language being used and, because the rights/roles are embedded as resources, they can't be tampered with!  Granted one can argue that this is overkill because we could use DB security, etc. to limit access to the database tables but I have worked (and am working now) in corporate environments where there are many hands involved and with access to the databases.  In fact, developers at the company I am consulting with now only have access to the databases through SQL Server Management Studio.  The IT guys and DBA's have direct access and admin rights to the systems and databases.  So, it's best to be safe. (I should mention these are all remote servers - located in another state!)

In our database, when we "map" or assign the rights/roles to a user, we simply use the text that corresponds to the resource key.  This way when we read the information back from the database it can be passed just like the resource key.

As Rocky described, our user (Principal object) has a list of rights/roles assigned and it is a matter of doing a simple string comparison to determine if the user has the desired right / is in the desired role.  So we have code like:

public static System.Boolean CanAddProjects
{
    get
    {
        return SecurityManager.Authorize(Resources.CanAddProjects);
    }
}

Our SecurityManager class is a collaborative object used to offload the common task of checking the current user's credentials.

Because the data pulled from the DB will match the string identified by the resource key, we will successfully authorize (or deny) the user.  And, our code is not subject to the contents of the database.  So, if some smart-alek DBA comes through and wipes out the table, everyone gets denied access and our app keeps right on running.

The one thing I am looking to do is standardize the text that is written to the DB.  We have dynamic, run-time user admin in most of our apps, so the records in our database our written on-the-fly.  This means that the string that we pass to the DB will match that admin user's culture.  So far this hasn't caused a problem because the admin is always in the same region, BUT.....

Not to mention, reading the database table is a lesson in multi-linguistics!

So, my next step is to change the admin portion that writes the data to the database to use a culture-invariant form of the resource and our SecurityManager converts the resource string that is passed in the same way.  I am considering a custom ResourceProvider but am not sure yet.  Always more work to do...

HTH

 

P.S. Sorry, I forget to mention the "access" rules for CanReadProperty, etc. contained in the AuthorizationRules collection.  This works the same way as in the book, except that we use the resource key when adding the rule to the collection.  So instead of Add("PropertyName", "Project Manager"), we would have Add("PropertyName", Resources.CanModifyProjects) or something like that.

bgilbert replied on Friday, December 15, 2006

Rocky,
Thanks for the reply. I guess I wonder how to handle a user that is a
member of two roles that have conflicting permissions. Would I need to
handle it the way SQL Server does, with implicit as well as explicit
permissions? It seems that the logic starts to get complex.

Have you considered including this sort of functionality for a future
version?

Thanks,
Barry Gilbert

>>> "RockfordLhotka" 12/15/06 12:12 PM >>>
The basic structure of the solution is to create a read-only object that
represents the authorization rules/roles for a given business object
type. This read-only object would consist, at least, of the business
object type name, and then a list of the properties of the object. For
each property, you'd have four lists of roles (allow read, deny read,
allow write, deny write).

The data structure I'm describing is not overly complex - just lists of
lists.

If you construct this object as a ReadOnlyBase-derived object, you can
load it via the data portal, just like any other object. Though you
might load it from your custom database or whatever.

If you are using only per-type rules (in version 2.1) then you don't
need to worry about caching these authorization infomation objects -
CSLA .NET will cache the results once you load them into the actual
object in AddAuthorizationRules().

If you are using per-instance rules then you might want to cache the
authorization information objects. Though that's debatable, since the
only reason to use per-instance rules is if the rules differ on a
per-instance basis, which could make any sort of caching entirely
impossible.

guyroch replied on Saturday, December 16, 2006

bgilbert:
I guess I wonder how to handle a user that is a member of two roles that have conflicting permissions. Would I need to handle it the way SQL Server does, with implicit as well as explicit permissions?

Ummm... conflicting permissions... How is that possible.

Usually what you'll end up having is a user that is in more than one security group, with each group having its own list of roles which either grants or denies access to a specific functions or tasks in your application. Typically you should create a list of all the grants from every groups a user belongs to. Then carefully replace any grant roles by its respective deny roles if one is present.

Consider the following security groups and associated roles.

Group 'Administrator' ::: CanReadWine, CanAddWine, CanEditWine, CanDeleteWine

Group 'Buyer' ::: CanReadWine, CanBuyWine, CanAskQuestionsAboutWine

Group 'Alcoolic' ::: CanReadWine, DenyBuyWine

Group 'GrandMa' ::: DenyReadWine, CanReadBrandy, CanBuyBrandy

So...

1) If your user is the groups 'Administrator' and 'Buyer' they should end up with the following roles...

CanReadWine, CanAddWine, CanEditWine, CanDeleteWine, CanBuyWine

2) If your user is the groups 'Buyer' and 'Alcoolic' they should end up with the following roles...

CanReadWine, DenyBuyWine, CanAskQuestionsAboutWine

In this second example all grants were added and then the DenyBuyWine trumped the CanBuyWine role.

3) If your user is the groups 'GrandMa' and 'Alcoolic' they should end up with the following roles...

DenyReadWine, DenyBuyWine, CanReadBrandy, CanBuyBrandy

In this example the DenyReadWine role from the 'GrandMa' group trumps the CanReadWine role from the 'Alcoolic' group.

Poor grand ma, she's alcoolic, she can't buy wine but she's smilling all the time nonetheless... she can buy brandy :)

Hope this helps.

Can you tell I'm a wine enthusiast :)

 

 

bgilbert replied on Saturday, December 16, 2006

Guy,

Thanks for your reply. I'm very familiar with the concept of least
priviledge and how this works in SQL Server. What I'm trying to
determine is the best way to implement this logic in a custom class in
code, storing the roles/groups in a table. At first glance, it seems
like a bunch of if/then's.

Barry

>>> "guyroch" 12/15/06 11:53 PM >>>
bgilbert:I guess I wonder how to handle a user that is a member of two
roles that have conflicting permissions. Would I need to handle it the
way SQL Server does, with implicit as well as explicit permissions?

Ummm... conflicting permissions... How is that possible.

Usually what you'll end up having is a user that is in more than one
security group, with each group having its own list of roles which
either grants or denies access to a specific functions or tasks in your
application. Typically you should create a list of all the grants from
every groups a user belongs to. Then carefully replace any grant roles
by its respective deny roles if one is present.

Consider the following security groups and associated roles.

Group 'Administrator' ::: CanReadWine, CanAddWine, CanEditWine,
CanDeleteWine

Group 'Buyer' ::: CanReadWine, CanBuyWine, CanAskQuestionsAboutWine

Group 'Alcoolic' ::: CanReadWine, DenyBuyWine

Group 'GrandMa' ::: DenyReadWine, CanReadBrandy, CanBuyBrandy

So...

1) If your user is the groups 'Administrator' and 'Buyer' they should
end up with the following roles...

CanReadWine, CanAddWine, CanEditWine, CanDeleteWine, CanBuyWine

2) If your user is the groups 'Buyer' and 'Alcoolic' they should end up
with the following roles...

CanReadWine, DenyBuyWine, CanAskQuestionsAboutWine

In this second example all grants were added and then the DenyBuyWine
trumped the CanBuyWine role.

3) If your user is the groups 'GrandMa' and 'Alcoolic' they should end
up with the following roles...

DenyReadWine, DenyBuyWine, CanReadBrandy, CanBuyBrandy

In this example the DenyReadWine role from the 'GrandMa' group trumps
the CanReadWine role from the 'Alcoolic' group.

Poor grand ma, she's alcoolic, she can't buy wine but she's smilling all
the time nonetheless... she can buy brandy :)

Hope this helps.

Can you tell I'm a wine enthusiast :)





SonOfPirate replied on Saturday, December 16, 2006

No, it's actually quite simple - just check for "Deny" rights first.  Like so:

1. Check if current user is denied the right to read wine (i.e. assigned DenyReadWine).  If so, fail.

2. Otherwise, check if the user is granted the right to read wine (i.e. assigned CanReadWine).  If so, succeed.

3. Otherwise, fail.

I believe this is what is already being done in Rocky's code.

Using Guy's example, if the user is in both the "Grandma" and "Alcoholic" groups and tries to "ReadWine", then your authorization code fails because the DenyReadWine (via the Grandma group) right supercedes the CanReadWine right provided by the Alcoholic group.

No extra conflict resolution is required.

 

Copyright (c) Marimer LLC