Ayende @ Rahien

Refunds available at head office

Rhino Security: External API

When I thought about Rhino Security, I imagine it with a single public interface that had exactly three methods:

  • IsAllowed
  • AddPermissionsToQuery
  • Why

When I sat down and actually wrote it, it turned out to be quite different. Turn out that you usually want to handle editing permissions, not just check permissions. The main interface that you'll deal with is usually IAuthorizationService:

image

It has the three methods that I thought about (plus overloads), and with the exception of renaming Why() to GetAuthorizationInformation(), it is pretty much how I conceived it. That change was motivated by the desire to get standard API concepts. Why() isn't a really good method name, after all.

For building the security model, we have IAuthorizationRepository:

image

This is a much bigger interface, and it composes almost everything that you need to do in order to create the security model that you want. I am at the point where this is getting just a tad too big, another few methods and I'll need to break it in two, I think. I am not quite sure how to do this and keep it cohesive.

Wait, why do we need things like CreateOperation() in the first place? Can't we just create the operation and call Repository<Operation>.Save() ?

No, you can't, this interface is about more than just simple persistence. It is also handling logic related to keeping the security model. What do I mean by that? For example, CreateOperation("/Account/Edit") will actually generate two operations, "/Account" and "/Account/Edit", where the first is the parent of the second.

This interface also ensures that we are playing nicely with the second level cache, which is also important.

I did say that this interface is almost everything, what did I leave out?

The actual building of the permissions, of course:

image

This is utilizing a fluent interface in order to define the permission. A typical definition would be:

permissionsBuilderService
	.Allow("/Account/Edit")
	.For(CurrentUser)
	.OnEverything()
	.DefaultLevel()
	.Save();

permissionsBuilderService
	.Deny("/Account/Edit")
	.For("Administrators")
	.OnEverything()
	.DefaultLevel()
	.Save();

This allow the current user to edit all accounts, and deny all members of the administrators group account editing permission.

And that sums all the interfaces that you have to deal with in order to work with Rhino Security.

Next, the required extension points.

Comments

El Guapo
01/25/2008 01:36 PM by
El Guapo

I am very interested to see how you flow security context from a client application to a service. So far you have dealt only with the domain model. You aren't looking at existing alternatives. In an Enterprise environment you are going to need to deal with security context flow across service boundaries. There is such a security system, it is called issued token security and Microsoft has already provided a whole infrastructure around WCF and IssuedToken authentication and authorization using a Security Token Server (i.e. STS). This is all code that has already been written, debugged, and problems solved.

In short I fear you are re-inventing the wheel and not considering all the deep issues that others who have come before you have dealt with.

Ayende Rahien
01/25/2008 02:21 PM by
Ayende Rahien

El,

I think that you are over extending the scope of Rhino Security. It is not supposed to deal with authentication or tokens by any means.

In fact, it is agnostic to it.

What it is supposed to do is to provide a very flexible way to answer: "Are you allowed to do Xyz".

That is all.

To put it in another way, I would use Rhino Security on the token issuer machine to generate the list of permissions that the token has.

The token generation, the authentication, the token validation, etc are all things that are external to Rhino Security.

Only the part where you ask which permissions do I have is in Rhino Security scope.

Peter F.
01/25/2008 03:00 PM by
Peter F.

This is more a conceptual comment than about your implementation, but do you see the Rhino.Security User/UserGroup construct as solely part of the security model? In other words, aside from groups as an organizing entity for assigning permissions, groups might serve other purposes in a system, such as message broadcast, batch operations, or common profile properties. Do you see Rhino.Security as sharing a user/group model or replicating it? In other words, you put User as its own domain entity, but what about UserGroup?

Second, I'd be very interested to hear your thoughts on how one might adapt Rhino.Security to use an external user store, such as LDAP, where users and groups are centrally defined. In many cases, one already has to deal with it for SSO or authentication in general, but cross-service joining of these security services for queries just won't work as is. So where in Rhino.Security could be the place to insert aggregating queries across service boundaries?

Ayende Rahien
01/25/2008 03:19 PM by
Ayende Rahien

Peter,

I'll put up an example of integrating with Active Directory.

As for Rhino Group's users groups. This is a bit more complicated.

I don't view them in the same way that I view groups in the user store.

Let me give you an example, at work, I am a member of the following groups:

AgileTeam

DevTeam

BusinessSolutionsTeam

Let us say that I have a bug tracking system that is using Rhino Security. In this system, I'll have the following groups:

"Administrators of Project Y"

"Members of Projcet X"

"Members of Projcet Y"

"Tier 4 for Project Z"

Those are logical groups in the system, that have no reflection on the outside groups that I am a part of.

Those are data dependent groups, basically. And those (and entities groups) are what make Rhino Security flexible.

You can copy the user's LDAP groups, certainly, but that would be very limiting from the security perspective.

Peter F.
01/25/2008 06:13 PM by
Peter F.

So the argument is that groups are contextually bound to the application in which they are meaningful. If you want to use groups that exist in another or abstracted context (such as LDAP), the burden is to contextualize those groups somehow.

To take your example, the simple thing vis a vis user management is the hard thing programmatically, namely to say "Any members of #AgileTeam are 'Administrators of Project Y' ". As members are added to #AgileTeam (in the context of LDAP), they are conferred privileges on Project Y (in the context of Rhino.Bugger), as they are removed, they lose privileges.

Now, I'm not sure that you'd even want to do that (have administration of an application take place outside of it) for all kinds of reasons, but the ease of management and synchronization of users is the issue I'm thinking about.

Peter F.
01/25/2008 06:21 PM by
Peter F.

Btw, the approach to this mismatch that I've tried in the past is to provide a mapping of external groups to internal groups. The mapping exists in the context of the application.

LDAP:AgileTeam maps to LocalGroups:ProjectYAdmins.

Then, if you want to query for all the members, the mappings are used to translate the query from your security context to the user group context. Conversely, if you are using windows authentication, the principal's group affiliations are mapped to create the local context.

ischen
01/25/2008 09:50 PM by
ischen

Great stuff! I really like your work. Few notes though-

  1. If I were to design an authorization service based on permission,

    I would not restrict myself to user/group context, as there are cases

    where the consumer of my application is allowed / denied a feature

    based on a context different than his/her identity or group

    membership. An example would be a license context (license

    granting fewer features, etc).

  2. Did you intend in providing the AddPermissionsToQuery method

    to 'filter out' results for the user? Is so, then there are situations

    where this could return misleading results. (The fact that I cannot

    access an entity, does not mean that it doesn't exist).

  3. I, as a practice, refrain from returning arrays as a method result.

    arrays are not read only (well, their size IS, but the element value

    isn't). This is especially important when returning results from a

    cache.

    I would instead use ReadOnlyCollection(s).

I am a faithful reader of your blog . Great job man!

Ayende Rahien
01/26/2008 02:29 AM by
Ayende Rahien

Peter,

Rhino Bugger, argh! This has too many bad interpretations.

Yes, such a mapping would be possible, and would make it easy to deal with it.

I'll gladly accept patches to that end.

Ayende Rahien
01/26/2008 02:46 AM by
Ayende Rahien

ischen,

1/ At that point, you would associate this user with the group of people that has a specific license, I fail to see the problem.

2/ Yes, there is a difference, and if there is a need to distinguish between the two, you are free to do so. However, this is usually not the case, most often you want to make the data that the user cannot see invisible.

3/ Um, no. I am returning the query results there, I am not keeping the array around in a cache somewhere. The user is free to do whatever they want with it.

Stuart C
01/26/2008 10:59 AM by
Stuart C

I am really excited by this topic as I have had similar ideas in the past but have non have been realised. I have had a look at the the rhino security code and although I your implementation seems sound I am concerned about the dependencies rhino.security has on externals such as castle active record.

I do not use active record nor do I intend to (no criticism here just choice), would I be forced to use active record or could I use rhino.security with my own nhibernate persistence model?

Ayende Rahien
01/26/2008 11:14 AM by
Ayende Rahien

You can use Rhino Security with NHibernate only, yes.

Anders
01/26/2008 05:40 PM by
Anders

Rhino Commons is getting extremely interesting and rather large.

Could it be time to create a dedicated forum for it?

Ayende Rahien
01/26/2008 09:04 PM by
Ayende Rahien

Andres,

There is one.

The mailing list rhino-tools-dev at google groups.

Comments have been closed on this topic.