Real world authorization implementation considerations
Nitpicker corner: this post discusses authorization, which assumes that you already know who the user is. Discussion of authentication methods, how we decide who the user is, would be outside the scope of this post.
I had a lot of experience with building security systems. After all, sooner or later, whatever your project is, you are going to need one. At some point, I got tired enough of doing that that I wrote Rhino Security, which codify a lot of the lessons that I learned from all of those times. And I learned a lot from using Rhino Security in real world projects as well.
When coming to design the authorization bundle for RavenDB, I had decided to make a conscious effort to detail the underlying premise that I have when I am approaching the design of a security system.
You can’t manage authorization at the infrastructure level
That seems to be an instinctual response by most developers when faced with the problem, “we will push it to the infrastructure and handle this automatically”. The usual arguments is that we want to avoid the possibility of the developer forgetting to include the security checks and that it makes it easier to develop.
The problem is that when you put security decisions in the infrastructure, you are losing the context in which a certain operation is performed. And context matters. It matters even more when we consider the fact that there are actually two separate levels of security that we need to consider:
- Infrastructure related – can I read / write to this document?
- Business related – can I perform [business operation] on this entity?
Very often, we try to use the first to apply the second. This is often the can when we have a business rule that specify that a user shouldn’t be able to access certain documents which we try to apply at the infrastructure level.
For a change, we will use the example of a debt collection agency.
As a debt collector, I can negotiate a settlement plan with a debtor, so the agency can resolve the debt.
- Debt collectors can only negotiate settlement plans for debts under 50,000$
- Only managers can negotiate settlement plans for debts over 50,000$
Seems simple, right? We will assume that we have a solution in store and say that the role of DebtCollectors can’t read/write to documents about settlement plans of over 50K$. I am not sure how you would actually implement this, but let us say that we did just that. We solved the problem at the infrastructure level and everyone is happy.
Then we run into a problem, a Debt Collector may not be allow to do the actual negotiation with a heavy debtor, but there is a whole lot of additional work that goes on that the Debt Collector should do (check for collateral, future prospects, background check, etc).
The way that the agency works, the Debt Collector does a lot of the preliminary work, then the manager does the actual negotiation. That means that for the same entity, under different contexts, we have very different security rules. And these sort of requirements are the ones that are going to give you fits when you try to apply them at the infrastructure level.
You can argue that those sort of rules are business logic, not security rules, but the way the business think of them, that is exactly what they are.
The logged on user isn’t the actual user
There is another aspect for this. Usually when we need to implement security system like this, people throw into the ring the notion of Row Level Security and allowing access to specific rows by specific logins. That is a non starter from the get go, for several reasons. The previous point about infrastructure level security applies here as well, but the major problem is that it just doesn’t work when you have more than a pittance of users.
All Row Level Security solutions that I am aware of (I am thinking specifically of some solutions provided by database vendors) requires you to login into the database using a specific user, from which your credentials can be checked against specific rows permissions.
Consider the case where you have a large number of users, and you have to login to the database for each user using their credentials. What is going to be the affect on the system?
Well, there are going to be two major problems. The first is that you can wave goodbye to small & unimportant things like connection pooling, since each user have their own login, they can’t share connections, which is going to substantially increase the cost of talking to the database.
The second is a bit more complex to explain. When the system perform an operation as a result of a user action, there are distinct differences between work that the system performs on behalf of the user and work that the system performs on behalf of the system.
Let us go back to our Debt Collection Agency and look at an example:
As a Debt Collector, I can finalize a settlement plan with a debtor, so the agency can make a lot of money.
- A Debt Collector may only view settlement plans for the vendors that they handle debt collection for.
- Settlement plan cannot be finalized if (along with other plans that may exists) the settlement plan would result in over 70% of the debtor salary going into paying debts.
This is pretty simple scenario. If I am collecting debts for ACME, I can’t take a peek and see how debts handle be EMCA, ACME’s competitor, are handled. And naturally, if the debtor’s income isn’t sufficient to pay the debt, it is pretty obvious that the settlement plan isn’t valid, and we need to consider something else.
Now, let us look at how we would actually implement this, the first rule specifies that we can’t see other settlement plans, but for us to enforce the second rule, we must see them, even if they belong to other creditors. In other words, we have a rule where the system need to execute in the context of the system and not in the context of the user.
You will be surprised how often such scenarios come up when building complex systems. When your security system is relying on the logged on user for handling security filtering, you are going to run into a pretty hard problem when it comes the time to handle those scenarios.
Considerations
So, where does this leave us? It leave us with the following considerations when the time comes to build an authorization implementation:
- You can’t handle authorization in the infrastructure, there isn’t enough context to make decisions there.
- Relying on the logged on user for row/document level security is a good way to have a wall hit your head in a considerable speed.
- Authorization must be optional, because we need to execute some operations to ensure valid state outside the security context of a single user.
- Authorization isn’t limited to the small set of operations that you can perform from infrastructure perspective (Read / Write) but have business meaning that you need to consider.
Comments
Hi,
One correction: Oracle (and possibly other databases as well) has the concept of a _proxy user_, which allows application servers to login with one set of credentials on behalf of other users. The combination of proxy users, row-level security and connection pools works quite well in Oracle.
This does not invalidate the general point of the article, with which I fully agree.
LL
LL,
That is a good point, which I assume resolve the problem of connection pooling.
I too agree with the point of the article, but you seem to be ignoring a major software use-case - not all software runs on a webserver. There could be client (winforms) apps that connect to the database.
Ayende,
You mentioned Rhino security. I love it, but I want to use Linq (not ICriteria). Do you think you will ever modify it for Linq or do you know if anyone else already has? Thanks.
Configurator,
A single user app uses no security.
A multi user app shouldn't talk directly to a DB, that arch died in the mid 90s
David,
The place to ask is in the rhino tools dev mailing list
Why should a Winforms LAN app never talk to the DB directly?
And who says a single machine is the same as a single user?
Hi configurator.
it is not secure because every user of your app has direct access to the DB server. But....You gotta do what you gotta do.
Besides that, Ayende's post has more concern about enterprise applications. At least I think it is. lol.
Configurator,
LAN app talking to a DB directly have only two modes:
There is not security (since the users can connect to the DB directly and issue whatever query they want)
There is a need for security, and the only way to apply it (and business rules, for that matter) is in the infrastructure. That means either using some Row Level Security feature or stored procedures.
I outlined the problems with infrastructure level security in this post, and I think that the problems with SP are well documented by now.
Have you considered moving the authorization to the infrastructure partially? For instance viewing rights can be easily implemented as filters for quering. I took part in implementing a system, where, for the specific cases, even some properties could have been configured, to be shown/hidden.
Speaking about the context, have you ever tried move the authorization to the infrastructure and use some kind of a scope, as the current operation context provider?
Szymon,
How you implement filtering is a separate topic.
Yes, you certainly want that to be an infrastructure piece.
But the problem is how you provide enough context to make things useful.
As for the idea of a scope.
Consider how much more messy it is to write the code for the second scenario that I have in the post with explicit scopes.
Then consider the fact that this is a highly simplified scenario, and real world scenarios may requires a lot of different contexts for different parts of the operation
Ayende,
by saying 'a _lot of different context' I understand a logic operation (for instance: concatenation) on the mentioned conditions. Would you express them as a composite condition, like (using specifications)
if (new IsCustomerGoldSpec (customer) && new HitOrderLimit(customer))
in your code, or maybe you would try to hide it in some way?
Szymon,
Not that way, no. That invert the association, because that states what the CONDITIONS for the operation are.
I don't care about this, I want to state if the op is permitted, and the condition executed by the infrastructure.
Something like:
Security.IsAllowed("/Order/Approve", entity, user)
That truely clarifies your point of view. I'll think about it :)
Take care
Ayende, I don't know if you remember but I discussed with you and Fabio about NH filtering subclasses - I had to do it by myself.
I had exactly these kind of problems of authorization (filtering) and I wanted to use NH filters.
The advantage to use NH filters is because they're simple to disable in case of a super admin user, for example. But they need more power in my opinion.
This reminds me that I complained about one more thing. I have to enable or disable them in my GetSession() method, where I don't have too much information about the context. So I asked you to create a new event where I could enable or disable filters just before NH build the query.
Do you remember? What do you think?
Cassio,
NH Filters aren't suitable for that.
You probably want to look at Rhino Security, which does all of that for you.
I didn't look at Rhino Security until now but I have a restriction to use it. I work at a company and we use few third party frameworks and libraries as possible. You know.
Today we use only NH, but I will take a look at RS.
Thanks
I've looked at Rhino Security a little bit when you posted previously about it. The operation view of things looks similar to how AzMan works. The one thing that RS adds that is lacking with AzMan is that you pass the entity along which provides powerful context for authorization. In our current project we've had two instances (could be more yet) where the entity involved in the operation mattered and we had to implement a solution over top of AzMan.
Overall though I much prefer the operation-based authorization instead of role-based for systems of any complexity.
Good things to keep in mind here though. Thanks.
Second blog comment in one day (massively unusual for me)
Trying to clarify your distinction between infrastucture and framework.
I take it you mean "Infrastructure" as in o/s, db, .... both of which are ultimately frameworks anyway?
All are exposing a security model, it's just Rhino's is better than the SQL Server / AD, or am I missing a point. (Quite possibly)
I would like to see a security model that maps not only entities, but inferred rights imbued due to relationships (both explicit and inferred).
Ultimately the "user / security principle" is an entity, the relationship it has with other entities should drive what actions it can perform. E.g you become a manager on a project, you should now be able to see the names of the other people that are team members on the project.
Still anything is better than SharePoint's ISecurityTrimmer, and the ugliness of returning 1,000,000 needless items to just discard 999.999 due to permision constraints :)
Si
Simon,
Infrastructure to me mean code that isn't tied to a particular project.
In other words, if you have code that you can use in two projects, it is infrastructure
So Rhino Security IS infrastructure too?
TBH I think the debt collection example you use could be better defined with a fairly a simple Workflow process, this would limit the "negotiators" by virtue of the actors you set on the "negotiate" activity.
You map the Workflow to a "case" entity, this also allows you to delegate to someone not normally permitted by the value rules (for example someone may have experience with that sector, and you feel they may be able to better extract the money)
It also gives you a baked in mechanism for things like escalation and periodic chase ups.... (as well as maintaining the same security model for the case's lifetime)
In this case your assertion is correct the Workflow design IS specialised to that task, and therefore not Infrastructure. :)
Now we just need a Workflow engine that can handle this... WF4 is not it!
Si
Simon,
Yes, RS is infrastructure. How you use it isn't.
Comment preview