Ayende @ Rahien

Refunds available at head office

The data access challenge: Implement Rhino Security

Rhino Security is an awesome little framework that provide security infrastructure for applications. I created that after having to rebuild a security infrastructure five times, due to changing requirements. It is implemented on top of NHibernate.

I would like to challenge you to implement Rhino Security in your data access strategy of choice. Here is the design, intro and implementation notes. And of course that the code itself is accessible here.

If you think that your data access strategy is awesome, show me the code. Rhino Security is a non trivial example, but it is still quite small, about 1,700 lines of code. So that is quite doable.

Oh, and I would love to see implementations on non RDBMS platforms.

I don't expect anyone to step up and do this, by the way. For the simple reason that I don't think that this is possible. And yes, that is said in the vain hope that someone will actually show me one.

Comments

Frans Bouma
12/23/2008 09:20 AM by
Frans Bouma

"For the simple reason that I don't think that this is possible."

heh. :)

LLBLGen Pro has authorization build into the framework which uses 'Authorizer' objects, which you plug into the entities using dependency injection (our own or the one you choose, it's up to you). Authorizers are called for various actions, like setting a field, reading a field's value, saving an entity, loading an entity, even updating entities directly in the database.

The authorizer simply gets a call to the method which should authorize the specific action, and it gets passed in the entity at hand plus other information needed. As authorizers are written by the developer by simply deriving from a base class and implementing the spots where authorization needs to take place, it's very easy and also very flexible, as HOW things are authorized is up to the developer: e.g. call to a service to authorize an action for the thread's active user etc..

This gives the developer the ability to write 1 code base, and 1 set of authorizers (e.g. 1 class per entity) which allows the application to transparently hide data for user X, show the data for user Y, allow X to update field A, refuse the field to be edited by Y, materialize entities which have whatever property value for X and refuse that for Y etc. etc.

As it uses dependency injection, you can use our dependency injection scopes as well, which are scopes in your code to define new dependency injection meta-data for the entities created in that scope. It also allows you to drop in a dll with updated authorizers in the application bin folder if you want to and have the application configured to auto-discover DI meta-data.

We have an example which shows the features here:

http://www.llblgen.com/pages/examples.aspx
(scroll down to Windows Forms authorization example).

Not possible? Come one, Oren... not everyone is THAT stupid...

Frans Bouma
12/23/2008 09:25 AM by
Frans Bouma

To add a bit why this is done on the client and not on the server (db) -> an entity can be passed from user X to user Y. If the security is done inside the DB (filtering data for example, excluding fields etc.) the data won't be visible for Y if it was denied for X.

Ayende Rahien
12/23/2008 09:36 AM by
Ayende Rahien

Frans,

I am not talking about how to do security in general.

I am talking specifically about the approach that was done in Rhino Security.

Adaptive domain model and query enhancements.

Frans Bouma
12/23/2008 09:53 AM by
Frans Bouma

I thought the challenge was to solve a problem. If it's about re-implementing your solution to a problem, then what's the challenge? It will never be good enough.

ANyway, I wasn't talking about security in general, I was talking about entity-level security which isn't something 'trivial' or 'generic'. What I read from the page you linked to, it looked like the purpose of it was to provide that: entity level security. For example your examples:

" 1. A helpdesk representative can view account data, cannot edit it. The helpdesk representative also cannot view the account's projected revenue.

  1. Only managers can handle accounts marked as "Special Care"

  2. A team leader can handle all the cases handled by members in the team, team members can handle only their own cases."

are exactly the scenario's I'm talking about in my previous post.

Most O/R mappers don't have built-in entity level security, so you have to 'add' it to it. If that's not doable in a transparent way, it will never be secure.

But if your point wasn't about entity-level security but just to give rhino security a bit of attention, I'm sorry I've spend time on writing a reply.

Ayende Rahien
12/23/2008 09:58 AM by
Ayende Rahien

No, I am not talking about re implementing a solution. I am talking about something quite different.

From what I saw of the authorizer concepts that you have, you are doing the filtering in memory.

I am talking about being able to take this all the way to the actual query that you perform, as one important concept.

The second is the ability to answer why you made this decision.

I actually think that the second is a far more important concept, at least for system maintainability.

The second part, about adaptive domain models, this is an important feature if you are working in multi tenant apps or build reusable entities that can be plugged into different domain models.

Frans Bouma
12/23/2008 10:04 AM by
Frans Bouma

Btw, the 'design' page you link to shows a design (and implementation) of how to authorize action A for person P. Actually, that's not really the challenge.

The challenge is: if you have a LoB app, and you bind customer rows to a grid, and user X opened that form, you want to hide the CC info in that grid if X has no rights to see that (example), or limit other actions X wants to take. How you check if P can do action A, is a small challenge compared to make it transparent and always working for entity E. I.e.: if I do:

string cc = someCustomer.CCNumber;

what is 'cc' ? The whole point is to make it transparent for the developer if CCNumber returns an empty string or the real value depending on the fact which user is running the code. That's the challenge. How to check whether a user is in a group with a given set of ACLs... that's been solved a 100 times before, as it's a classic example of role-based security. The point of having this all integrated inside your entities, that is something else. Does rhino security solve that for you? E.g. the example of binding a set of entities to a grid, or the line of code above to name a few.

Similar analogy: auditing isn't about storing audit data, it's about recording actions. So showing a set of audit-data tables and queries to save the data is irrelevant, it's about when what is recorded.

Frans Bouma
12/23/2008 10:15 AM by
Frans Bouma

"From what I saw of the authorizer concepts that you have, you are doing the filtering in memory.I am talking about being able to take this all the way to the actual query that you perform, as one important concept."

Filtering in memory is required because an entity can be used in different contexts: i.e. through uniquing, an entity can be used by user A and user B. A might have an access right on field F, B doesn't.

You can reject rows being read into memory based on the authorizer though, and deny save/delete actions as well.

Adding where clauses to queries is limiting the system because it can't make an entity re-appear to have the data for a user who has access.

"The second is the ability to answer why you made this decision."

That question is IMHO irrelevant too, and also not always possible to answer that question (e.g. in the grid example) nor does it help the user much: an action is denied, end of story. What do you want to display there "Contact your system admin" ? In 22 languages?

Btw, another great point of having in-memory authorizers is that you can pre-check if a user has access to a given action and simply disable controls in a UI. IMHO far better than displaying something after the user has spend time doing things.

"The second part, about adaptive domain models, this is an important feature if you are working in multi tenant apps or build reusable entities that can be plugged into different domain models."

Yes, that's why authorizers are injected through dependency injection, so I can have entities which are used in different projects and use different authorizers on them, or even swap out authorizers over time if I want to, no coding required.

Ayende Rahien
12/23/2008 10:20 AM by
Ayende Rahien

Frans,

The whole point is to make it transparent for the developer if CCNumber returns an empty string or the real value depending on the fact which user is running the code.

Major design difference, I believe in contextual security, not ambient security.

Ayende Rahien
12/23/2008 10:25 AM by
Ayende Rahien

Does rhino security solve that for you?

Rhino Security will load only the data that you are allowed to see, filtered at the DB level.

"The second is the ability to answer why you made this decision."

That question is IMHO irrelevant too,

No, that is a question that can cost a project days and weeks in trying to find out what is going on when the system hit a complexity threshold. If you don't build visibility from day one, that is a BIG problem.

Yes, that's why authorizers are injected through dependency injection, so I can have entities which are used in different projects and use different authorizers on them, or even swap out authorizers over time if I want to, no coding required.

I am sorry, I didn't explained enough about adaptive domain model.

Adaptive Domain Model means that I can take a domain model for accounting and add it to my ERP model.

On the fly.

Or I can take a Online Shop model and swap my Cusotmer entity with their customer entity.

and also not always possible to answer that question

Huh? If it is not possible to answer the question, how do you make the security decision?

you can pre-check if a user has access to a given action and simply disable controls in a UI

That assume that you have a UI.

Frans Bouma
12/23/2008 10:58 AM by
Frans Bouma

"Major design difference, I believe in contextual security, not ambient security. "

what's ambient security if contextual security is (if I understand you correctly) security related on the context the element to secure is used in? Isn't that exactly what an authorizer does, i.e authorize the entity for the action to perform using the security system at hand (e.g. asp.net role based security, thread-based security, windows ACL's whatever) and simply allow or deny it?

"Rhino Security will load only the data that you are allowed to see, filtered at the DB level."

But who's 'you' in this context if you pass along an entity through multiple semantical contexts, through uniquing for example?

"No, that is a question that can cost a project days and weeks in trying to find out what is going on when the system hit a complexity threshold. If you don't build visibility from day one, that is a BIG problem."

Sorry, but I then don't understand what the why question solves. Actions on entities are very low-level. When an action doesn't happen, it's denied by an authorizer. That code is the developer's code. If the developer wants to track what's denied, he can, just log the deny actions issued by the authorizer using a logger. I don't see why the 'why' question has any relevance outside the authorizing element.

"I am sorry, I didn't explained enough about adaptive domain model.

Adaptive Domain Model means that I can take a domain model for accounting and add it to my ERP model.

On the fly."

And why can't I do that with my entities? All authorization is separated in different classes, injected by DI... what more separation do you need?

"Huh? If it is not possible to answer the question, how do you make the security decision?"

I was under the assumption the 'why' was a reason to let the user know why the action was denied. If that's not the case, any authorizing element can issue that question from within the authorizing element. So I don't see why that's such a big deal. Come to think of it, if I fetch a set of customers, how is 'why' returned back if I get 3 access denies?

"That assume that you have a UI. "

It was an example. You can use the same logic for routine control flow through your application if you want to.

The core difference is apparently that I have the authorization in-memory, in classes which are separated using IoC and DI, and you have it inside the DB. What's better? I'm not sure, I think my approach is better suitable for more flexibility (and no db requirement), but you obviously disagree. The thing still is: you didn't expect anyone to have such a system. If you don't want to look at my system, there's at least one o/r mapper which has this build in as well, dataobjects.net

Ayende Rahien
12/23/2008 11:06 AM by
Ayende Rahien

what's ambient security if contextual security is (if I understand you correctly) security related on the context the element to secure is used in?

Ambient security is filtering at the entity / interceptor level. If you want the current context, is must be stored in some globally accessible place for it to affect security decisions.

Contextual security means that the actual operational context is involved.

Security decisions are explicit and visible. See the discussion on the design of Rhino Security for more details.

But who's 'you' in this context if you pass along an entity through multiple semantical contexts, through uniquing for example?

The question has no meaning in the design of Rhino Security.

The API always accept the user of which behalf the operation is being done.

Sorry, but I then don't understand what the why question solves

Days & weeks of debugging.

. That code is the developer's code. If the developer wants to track what's denied, he can, just log the deny actions issued by the authorizer using a logger

We are back to debugging, and involving a developer. And in any complex system, trying to find the exact reason for a security decision may be daunting. In one of my system security was affected by ~15 aspects. Including when was the last time you had made a support call.

Digging through the log is not something that I ever want to do.

And why can't I do that with my entities? All authorization is separated in different classes, injected by DI... what more separation do you need?

I am not talking about authorization here. I am talking about taking independently developed domain modals and join them together.

So I don't see why that's such a big deal. Come to think of it, if I fetch a set of customers, how is 'why' returned back if I get 3 access denies?

You get to ask why globally or on particular instance. If you care about the difference, you may execute the query twice, with & without security filtering and query on the difference.

The core difference is apparently that I have the authorization in-memory, in classes which are separated using IoC and DI, and you have it inside the DB.

No, it is not. Adapative domain models & query enhancements are the features that I am talking about here.

I am using Rhino Security as an example here to show off features that make developing complex software solution trivial. Not to discuss the actual security implementation.

Frans Bouma
12/23/2008 11:45 AM by
Frans Bouma

You definitely don't understand my authorizer system apparently: how an entity E is authorized for action A, is up to the authorizer instance used, which can use a total different domain model at runtime, that's not important. The entity 'customer' has no relationship with 'user' or 'role', the action on customer does but that's code, not something part of the model. So the action authorization is delegated to an object which does know the concept of 'user': the authorizer.

But I digress... I'm not surprised you didn't get any answers last time.

MisterTom
12/23/2008 01:13 PM by
MisterTom

Frans,

Your approach is interesting, but I wonder how pagination can work with in memory filtering.

Frans Bouma
12/23/2008 01:50 PM by
Frans Bouma

@MisterTom

the authorization is at the entity object for data retrieval on field level, e.g. you want to read the value of property 'CreditCardNumber' on a Customer instance, or you alter a value on an entity instance

The authorization on data retrieval level is in the object materialization. So for every entity object that's materialized (data from datareader placed inside new entity class instance), the authorizer has to authorize it, if it's ok in the current context. If not, the authorizer supplies a flag what to do: clear all data (so an empty entity) or simply get rid of the entity object.

The authorization on data persistence level is in the o/r mapper core where the entities are pre-processed for queue placement: if an entity to be persisted has an authorizer assigned to it, the authorizer is consulted if the action (e.g. delete, insert, update) is allowed. If not, the entity is not placed in the queue to process.

So pagination is at the query level: you fetch page n of m entities, and that data is returned to the object materialization routine. That routine is then checking whether the load of the data is even possible (and then thus two choices: ignore data, but create instance, or drop instance) or you can postpone the authorization for the data till the data is accessed through the properties of the entity object. In the case of dropping the entity object, you could get back less objects than initially requested, true. The upside is that this is an edge case (paging over set with dropping of unwanted objects) so you benefit in all other cases of in-memory authorization of actions instead of at the db level. Plus, you can always add-on a filter to the query to process by a routine which is called along the way the query is processed, if you really need this.

Stuart C
12/23/2008 02:49 PM by
Stuart C

Frans,

The upside is that this is an edge case (paging over set with dropping of unwanted objects)

Unless I've misunderstood you, I'm not sure how you can describe paging as an edge case?

We frequently have to return lists of data which invariably need to be paged due to the potential amount of data returned and each entity in this list requires a security check to see if the current user context has access/permission to see these entities.

This is not possible in memory, or certainly not efficiently anyway and as you mentioned you would have to add-on a filter to the query to achieve this... This is something Rhino.Security addresses.

After having a look at your example, I noticed your authorizers hardcode the roles/permissions which makes runtime manipulation of security impossible and it also makes it difficult to build UI's which describe the current security state in a natural way as the permissions are stored in data?

Ayende, please correct if my assertions of the Rhino.Security are wrong but this is the understanding I came to after inspecting the code and reading your blog entries on the matter...

Ayende Rahien
12/23/2008 03:44 PM by
Ayende Rahien

Frans,

Even I don't spent every waking moment near a connected machine. In this case, I visited my grandparents.

which can use a total different domain model at runtime

You are missing the point, it IS NOT about authorization. It is about gluing together different domain models.

Simple example:

class Order

 ICustomer Customer

I can plug a different implementation of Customer as an associated entity based on what domain model I glue to this.

This has nothing at all to do with authorization. Authorization is simple the example.

Ayende Rahien
12/23/2008 03:46 PM by
Ayende Rahien

MisterTom,

It doesn't, from my experience with this approach. It fails, sometimes horribly so. Simple example, if you don't have access to any of the items in the first page.

Frans,

The upside is that this is an edge case

I hardly think that paging is an edge case.

Ayende Rahien
12/23/2008 03:48 PM by
Ayende Rahien

Stuart,

Yes, paging was an important scenario for Rhino Security, and one of the reasons that it does filtering at the DB level. I had the empty page problem due to security in a previous project.

Regarding UI for manipulation security, that is certainly a scenario that Rhino Security is built to support. It is quite easy, as you can imagine.

Frans Bouma
12/23/2008 04:16 PM by
Frans Bouma

"This has nothing at all to do with authorization. Authorization is simple the example."

Then why is the 'challenge' so obscurely formulated as rhino security which is about solving the problem of authorization. For example, if someone wants to authorize against active directory, a database approach of solving security is not really helpful. Or when you are not allowed to alter the schema. However, as this is about gluing together domains, you shouldn't have picked the problem of security, as that has many different approaches, all which are apparently wrong as they don't follow your approach.

"I hardly think that paging is an edge case. "

Me neither, that's why I specified the context further, Oren.

I also said that if you don't want this behavior, you can also add your own scheme to do so. The thing is however that if you want authorization and you use thread currentprincipal, active directory or whatever element outside the db, you can't expect from a framework to page data for you normally. If you want to have this filtering in the DB, great, but then forget authentication against sources outside the db.

@Stuart: for you too: I did specify it further, see above.

"After having a look at your example, I noticed your authorizers hardcode the roles/permissions which makes runtime manipulation of security impossible and it also makes it difficult to build UI's which describe the current security state in a natural way as the permissions are stored in data?"

It's an example, it shows at a very basic level how things work, e.g. how authorizers are setup to be injected, how they're called etc.. HOW you authorize things is up to you: call AD, use thread current principal, use a service, query the DB, do it your way, you're not limited to what's in the DB.

Of course, flexible security isn't done by hardcoding things in data. So you can query the db if you want to, or consult a service which provides the ACL's for you, query an in-memory cache if you want to, it's up to you.

Anyway, every approach has its strengths and weaknesses. As I addressed earlier, if paging is a problem, in THIS context (e.g. you're denying rows), use a different approach. The thing is: often you don't as you want to limit action rights on objects or per field. Also, if you want to authorize against something else than a table in a db (e.g. active directory), you're going to need a code-level solution.

Ayende Rahien
12/23/2008 04:24 PM by
Ayende Rahien

Then why is the 'challenge' so obscurely formulated as rhino security

Because it is small example that shows those non trivial concepts.

if someone wants to authorize against active directory

Then they still have the problem of doing row & field level security.

all which are apparently wrong as they don't follow your approach.

We are having two parallel arguments here. One is about security, second is about query enhancement and adaptive models.

I don't care about the way you implement security, I used that as a sample. I do care about the features that this is showing.

you can't expect from a framework to page data for you normally

Yes, you can. Either you want to have row level security or you don't. If you do, then you support paging. It is very simple.

HOW you authorize things is up to you:

And in Rhino Security, it is not up to you. It is a service that the framework provides. Different concept, with different design decisions.

Frans Bouma
12/23/2008 04:42 PM by
Frans Bouma

"Then they still have the problem of doing row & field level security."

you can do role-based security in AD, and some people want that. That means that you store what can be done by whom in AD, and thus have to access AD to authorize an action for user X.

"Yes, you can. Either you want to have row level security or you don't. If you do, then you support paging. It is very simple."

you really do not WANT to get it, do you?

If I have to call a service to authorize an entity if User X may see it, how can I do that authorization in the DB outside the service? You can't. That's what I meant.

Anyway, enough about this.

Ayende Rahien
12/23/2008 05:26 PM by
Ayende Rahien

If I have to call a service to authorize an entity if User X may see it

Change the implementation of the service so it can work with the query semantics in the database.

A trivial example would be to pregenerate valid ids for each row that you can use within the DB.

You still haven't touched the actual issue of adaptive domain models & query enhancements, by the way.

RafalG
12/24/2008 01:50 PM by
RafalG

I don't know if it should be called an adaptive data model or what, but in my company we had implemented security layer on top of the O/R mapper. Every query used for displaying data to users goes through the security layer and is automatically augmented with security predicates, depending on user's access rights. It also implements access control for displaying and editing fields and executing actions (implemented as methods in data objects). I don't want to compete in quality or quantity of the code, but the idea is just like your in Rhino security. BTW, it was done in 2003 and is used until now. So have to disagree with you - it's doable, even without employing a genius. Of course it could be made better in every aspect, but it's just a useful component, not the product itself.

Ayende Rahien
12/24/2008 04:14 PM by
Ayende Rahien

RafalG,

What O/R mapper are you using?

RafalG
12/24/2008 09:28 PM by
RafalG

BTW, when I said 'it could be made better in every aspect' I really mean it - it's certainly not a world class security layer and has lot of local folklore inside - but I'm talking about the idea, not it's implementation.

Phil Hobo
12/29/2008 04:06 PM by
Phil Hobo

I have to agree with Frans that it wasn't at all clear that the point of Rhino Security was Adaptive Domain Modeling or the ability to merge domains. I understood the project to address authorization filtering in queries. That is, row-level filtering in the db using NHibernate in a generic fashion. But I disagree with Frans that role-based authorization with ACLs is a solved problem.

The debate raised an interesting distinction between two security concerns. Frans seems to be committed to a security model that can constrain entities once they have been retrieved because in that case you make no assumptions about which entities will be returned (which would be a difficult decision to delegate to the source repository anyway) but rather you constrain what information in an entity is available (hence the CC example). The requesting user may have a contextual reason not to see the user.CreditCard yet it still makes sense for the model to have that property. Plus, the context could change because of esoteric business rules (e.g., it's after 9pm). In the case Frans described (seeing or not seeing a property on the entity), I don't see how row-level filtering helps -- you still want the entity but not every property.

Partial entity-hydration (authorized loading) gets you somewhere but you don't get the contextual security Oren is trying to achieve. In my opinion, row-level filtering is the more common scenario and I like the Rhino Security implementation very much for a bounded system. If you want to cross system boundaries, I don't see how you delegate query filtering in a safe, consistent way. I think you need to do that in-memory with whatever result set the service returns, against an authorization scheme specific to your context. I'm somewhat skeptical about the desirability of reusable domain models.

What about using dynamic proxies for property authorization? Your model stays as it is, but the authorization context is managed by the proxy...

Comments have been closed on this topic.