Security decisions: Separate Operations & Queries
The question came up several times in the mailing list with regards to how the RavenDB Authorization Bundle operates, and I think it serves a broader discussion.
Let us imagine a system where we have contracts, which may be in several states:
- Mine – Contracts that an employee signed.
- Done – Standard users can view, Lawyers assigned to the company can sign.
- Draft – Lawyers can view / edit, Partners can approve.
- Proposed – Lawyers can create / edit, but only the lawyer that created it can view it, Partners can accept.
So far, fairly simple, right? Except the pure hell that you are going to get into when you are trying to show the users all of the contracts that they can see, sorted by edit date and in the NDA category.
Why am I being so negative here? Well, let us look at what we are going to have to do in the most trivial of cases:
In this sort of system, we are going to have to show the user all of the contracts that they are allowed to see, and show them some indication what operations they can do on each.
The problem is that generating this sort of view is expensive. Especially when you have large amount of data to work through. More interesting, from a UX perspective, it also doesn’t really work that well. Most users would want a better separation of the things that they can do, probably something like this:
This allows us to do a first level filtering on the data itself, rather than try to apply security rules to it.
In the first case, we need to get all the contracts that we are allowed to see. The security rules above are really simple, mind. But trying to translate them into an efficient query is going to be pretty hard. Both in terms of the code requires and the cost to actually perform the query on the server. There are other things that are involved as well, such as paging and sorting in such an environment. I have created several such systems in the past, Rhino Security is probably the most well known of them, and it gets really hard to optimize things and make sure that everything works when you start getting more complex security rules (especially when you have a user editable security system, which is a common request).
The second case is cheaper because we can limit the choices that we see in the query itself. We may still need to apply security concerns, but those goes through the query directly, rather than a security sub system. This kind of change usually force people to be more explicit in what they want, and it result in a system that tends to be simpler. The security rules aren’t just something arbitrary that can be defined, they are actually visible on the screen (My Contracts, Drafts, etc). Changing them isn’t something that is done on an administrator’s whim.
Yes, this is a way to manage the client and their expectations, but that is important. But what about the complex security that they want?
That might still be there, certainly, but that would be active mostly for operations (stuff that happen on a single entity), not on things that happen over all entities. It is drastically easier to make a single entity security decisions work efficiently than make it work over the whole set inside the database.
Comments
I am not entirely sure I grasp your suggested approach, tell me this: would you still recommend your approach if the permissions for a given user/contract were in a constant state of flux (eg: a workflow)
Furthermore, the only way you can determine user permissions on a given contract is an expensive black-box function. The function runs fast enough for a single contract but too slow for calculating a set of contracts in real time.
Security is not hard. Query results trimming is hard, because it's complex predicates degrades performance.
Well known way to improve calculation perfomance - cache. In database terms: just materialize relation Users-to-Object and add join to query.
@Pete the contract does go through a workflow, so as it moves from one state to the next the contract would/could "move" from tab to tab.
And rather than calculate the permissions at the time of viewing, the permissions can be calculated when the entity is saved, explicitly stored with the entity. Query on the stored value and queries are very fast.
I strongly disagree in terms of UX.
If you have similar items in a list, then tabs aren't a valid UX element to distinguish them. You simply have to take on the additional burden to do the list properly, no matter to what degree you could simplify development by not doing it. Anything else results in that kind of bad user interface design we often see in .net applications.
The only exception from that would be an internal application, that we aren't directly paid for. But in that case, it's unlikely that we want to have fine-grained security anyway...
In terms of UX there should be search-like interface (search box, refiners, "unlimited" scroll, previews), not tabs.
Hi Ayende,
What's software that do you use to create these kind of interface?
Thanks a lot.
This problem is not limited to security - it's rather a part of application's business logic that must be considered when designing the database structure. You don't usually implement a business process by means of security/filtering rules.
@Daniel - could scarcely disagree more. What precisely is the workflow here? "Show me every contract ever completed by the firm, ever, along with the types of actions I can take against them"? The notion of a filtered list (by type of action/workflow stage) seems fundamental to the notion of a work-flow / saga-centric application.
It doesn't have to be tabbed, per se, but the notion that any kind of "I'll puke it all up and you look for the good stuff" approach is better from a UI perspective doesn't pass the laugh test.
Can you elaborate on what you're thinking here? What's the user context/story, in your mind?
@Mads: http://www.balsamiq.com/
The business domain (contracts documents management) tends towards document-centric/state-centric workflows. Tabs may work OK as a UX metaphor, due to 'mode-switching' behaviours by the user, who tends to view these document collections as equivalents to physical 'in-trays'/'file drawers'. The 'information-partitioning' aspect of tabs can play well in that scenario. I usually also want to implement some filtering capabilities (e.g. by time periods) because that helps to surface 'actionable' information. 'Task'/'State'-based UX views are often good as well if the default (filtered) view focuses on entities associated to tasks assigned to the user...usually some kind of 'tasklist/inbox' metaphor. The ability to search/view status of entities assigned to others is required as well, but generally represents 'exception processing' scenarios.
Pete, Any time that you have to do a relatively cheap operation once, which becomes expensive in the aggregate, that is a problem. Your question depends on a lot of factor. Is this an operation that I can do on the user, and then do security decisions from there, or is this a case of having to do operations on the user AND each of the entities? If it is the later, I have lost, and what I am calling out in this post is that we need to explicitly design our software to avoid doing this.
With regards to things being in a state of flux, because of a workflow. That is usually NOT the case. You usually have entities moving through workflows, but the security rules themselves tend to be fairly stable.
gandjustas, Your suggestion would be nice, except for a few things:
In practice, it is common for me to have permission to do certain operations on an entity, but not others. I may be able to approve an order, but not reject it. Note that this is distinctly different than "write access".
At that points, you have NumberOfUsers X NumberOfOperations X NumberOfEntities in the association table. That leads to a hot spot in the database, and drastically increase the cost of queries.
Not to mention the fun fact of having to deal with the Cartesian result.
@Ayende Rahien, let's go deeper.
One relationship user-to-entity is oversimplification, but a good starting point.
Full picture looks like: 1) User have set of claims, claim = (type, value) 2) Aceess Control Entry (ACE) = claim + permission 3) ACL = set of ACEs 4) Each entity has one ACL
Linq query: from e in ctx.EntitySet where (from ace in e.ACL.ACEs where ace.Permission == "View" from u in ace.Claim.User where u.Id == currentUserId select u).Any() //or check claims available un context select e;
Security predicate may be extracted to enxtension method on ISecurable interface.
It's straightforward, but complete implementation on query filtering. This code and schema may be heavily optimized by indexes, denormalization etc.
For one-entity operations ACLs may be chached on client.
gandjustas, Now try to do what you have just done, but write it in SQL, and you'll realize what you happened. For real world scenarios, also add the notion of groups, where ACE can be for a group, not just a specific user. Then see where you are at.
I used this approach to entity level security in real app. There is no need notion of group because there is notion of claims. It's more generic way to group users.
In real app ratio users to acls is about 100. Entities to acls is about 1000. With SQL indexes it works fast enough.
Hi Ayende, I've been reading your series of posts on Rhino-Security and just wondered 3-4 years later whether you think your design and your articles are still the valid - what changes would you make if you were starting again from scratch? thanks John
Jradxl , The design is still valid, at its core. But the usage scenario I envisioned for it are no longer really relevant. You need to be aware that there is a non trivial cost for making security decisions, and that you need to be careful about it.
Whoa, this is weird, working on a contracts application and you just described the security model and "home" page. My issue now is I want to switch to RavenDb because it's getting really hard to fit all the data into a relational db.
@Daniel
Kinda hard to take you seriously when your equating platform with having a direct effect on UX. Bad design is bad design. My choice of programming language, for the most part, is inconsequential.
Now that being said, I am curious as to what is your suggestion related to this. I am not a huge fan of tabs but I do agree that dumping everything in a run on list is not a good expirence either. One of the most important things, and there is NO single correct answer, every solution WILL be different, that a document centeric application needs to ask first is do users follow a document start to finish (Entity centeric) OR do they work in particular modes (State centric) for a set of documents that happen to be in that state at a time. Each business is different.
Real life example: We had a compliance tool for Injury and Illness Prevention Program documents required to be made for all business that exist in the US State of California related to Workmans Compentsation. This tool was not dissimilar in fact to above scenario and when will drilled intothe user story it came out that people in fact work in this world by first putting on their "editor" cap and editing a whole assload of documents. They then later, perhaps even a day later, spend their time reviewing available documents and marking them up. Great! That's a very segregated user story that worked the way workmans comp in california is done. When we took this to Delaware the UI application needed to work very different. People spent all their time taking a SINGLE document from start to finish (published for employees) with is far more entity base.
Either approrach is no more correct then the other.
Comment preview