Alas, security is a business concern...
A while ago I posted a question regarding pre-built security infrastructure that I could use. At the time, I envisioned something the like of Windows's ACLs, where you can define permissions on individual objects. At hindsight, I think that I was very naive.
Here are a few requirements that I had to deal with in recent projects:
- you are only allowed to handle cases for customers in your assigned regions.
- you may only order fixes for products that you have purchased - in fact, you may not even see fixes for products that you don't own, because they may contain a fix for something that will not be fixed in your version.
- One of the more complex can be express as this meta permission scheme: Employees in HR department can have privileges from the Personal Permission Tree, as well as a manager to their direct reports. HR department has no privileges in the Assignments Permission Tree, only a manager has it, and only for direct reports, at most two level down.
- Temporal permissions - you may see this employee salary between 2002-01-01 to 2003-04-13.
- Temporal permissions with a twist - you may see this employee salary between 2002-01-01 to 2003-04-13, but you are allowed to modified them only up to three months ago.
Are they security? are they business logic? Then you have the weird cascading rules, including anything from permission on this object goes up two levels and down one, but on this object it goes up one level and down four. What about limitation permissions? For this object type, you have access to everything, unless you have been assigned some objects, in which case you are limited to only those.
From the user stand point, they are both. In fact, some of those are so extremely annoying permission scheme, intended to cover the 0.2% edge cases, and I am pretty sure that they will never be fully utilized. Nevertheless, they are core to the way the client think about their business, and they are certainly first class business concerns which has quite an affect on the business.
Because security is such a complex topic, and because it touches so intimately with the business requirements, I have found that I often need to tie it into the business concerns. Specifically, it is often advisable to tie it into the query infrastructure, since loading everything and then filtering by security has quite a few disadvantages (paging falls flat on its face, so does aggregate queries, performance can be affected in many scenarios, etc).
So, I don't think that there is a real generic solution, at least not if your application doesn't fall into the roles & permissions style, but I have built a nice solution for the current requirements. I still get confused by the requirements, and I spent a while the other day debugging why it didn't work as expected, to realize that my expectation were incorrect and in fact it was behaving 100% correctly.
Disregarding my inability to consider six interwoven different permissions schemes at a single point, the solution is actually a very good one and I am please by it, although I still cringe a bit when I go over the code. A query really shouldn't take more than three pages. :-)
Comments
In the projects I have worked on, it seems that there is a bit of confusion about what security is and what business logic is, and the difference (if any) between the two. I found it helpful to think about security as being the mechanism that you use to keep data invisible to different sets of users. This pretty much means it is just "read" security. "Write" security doesn't even seem like security to me as much as it is a business function. Letting a user write data that you don't want them to write to can only result in bad data, but no "secrets" have been revealed by doing this (unless the "read" security is based off the state of the data, in which case these writes are part of the concern of security).
So, to summarize, my definitions are:
Business Logic: Logic used to maintain a correct state of data for the application.
Security Logic: Logic used to keep different sets of users from reading sensitive data, or being able to infer something they should not be allowed to know.
I'd also like to add that I think the security layer should be behind the business layer, and not part of it. This is because it is dangerous to let the security logic depend of business logic which is likely to change and evolve, and could open up security holes...the security logic should be as simple (and hopefully declarative) as possible, and should have as few dependencies as possible.
Not worried about security of writes, eh? How about deletes? You need it all the way around, especially on writes.
Ayende - would love to hear a little more about your solution. Where it lives, how you've made it flexible for future changes, etc... I had a similar issue on a project where security was full of business rules, and am not really happy with the solution we came up with.
@Paul
Preventing deletes is important, just like preventing writes over existing data. If a user figures out how to "game the system" and deletes records, the solution is to reinsert the records from backup. A problem, but not something that is irreversible. Allowing a read is irreversible :)
Mike,
allowing a write or a delete is a bad idea because it can then lead to further problems done the road.
Assume that I changed my salary to 1$/day, and then the payroll process run, restoring it from backup wouldn't really help me much, right?
Then you have dependant objects that may have been created, etc.
I seriously don't agree that read is where it at, write & delete are just as important.
Like Brian, I would also like to hear more about the solution you've come up with. I'm going to work on a similar project in the near future that involves a centralized component that needs to do just that. It would be awesome if you could share it (if possible).
I've just developed a set of .NET providers around AzMan and ADAM, which do feature a centralised security component, and with the ability to add rules based permissions, as you describe. We are just about to implement temporaral access rules to it too. We can also add query based (dynamic) groups, which is quite handy (say a group with all employees of a department, or at a particular office .. without having to actually assign them to the groups, their membership gets automatically determined from their attributes/properties). It also supports hierarchical resources (with inheritance), and has a deny capability. The code is very clean and straightforward:
bool canRead = Authorization.IsAuthorized("ObjectPolicyId", "Read Resource Operation")
So far so good! Must admit I'm quite a fan of AzMan now (having previously attempted to build a permissions system in SQL, but performance issues killed it).
BTW how can anybody say that security is just "read" permissions? Security is also about maintaining the integrity of your system (from mailicious agents), as well as preventing access to sensitive data. That's a very naive approach. Just because some of your security requires business logic to determine, does not mean it is not security. It's not some binary condition of being either one or the other.
The important thing is we've now managed to abstract out the whole "who, when and what"
of our security sub-system to AzMan, and this can be locked down to non-developers/senior management/administrators. Granted the developers could just remove the access check code, but that code should really be under either constant peer review OR locked down to priviledged/trusted/senior developers. We also have automated tests in place to ensure that this is not likely to happen without being flagged up.
@Ayende
I'm not saying that writes and deletes aren't very important, but I'm just saying that they fall more under the level importance of the business rules themselves. Accidentally giving somebody the opportunity to change their salary can have the same negative effects as letting an authorized user enter an invalid salary accidentally. Both situations can cause major problems, but they had the same basic effect...the data is in a state that you don't want it to be in, and that payroll program is going to screw up. The amount of care that goes into keeping a user from entering in his own salary is on the same playing field as the amount of care that goes into making sure that an HR administrator doesn't enter "-100 $/H" into the salary field.
My point isn't that writes and deletes are unimportant. I am just saying that, in the projects I have worked on, they are not nearly as important as reads, because allowing an unauthorized read leads to a completely irreversible situation. Saying, "whoops! I guess thats a bug" when a user went into his employee record and erroneously unenrolled himself from the company health plan is bad, and makes me look bad as a developer, but ultimately, this is a fixable situation. However, saying "Whoops! I guess thats a bug" when Joe User can see the CEO's salary (and proceeds to tell everyone at lunch) would likely end my employment.
Security is also about maintaining the integrity of your system (from ****mailicious agents) .. an employee trying to modify their own salary (unless they want to pay themselves less :-)). An HR user in this case is and shouldn't be considered malicious, they're a trusted user in this domain. But even they would normally have limits.
****Validation is about maintaining the correctness of data in your system. "- $100/h" is a validation issue, whereas "paying $10,000" an hour can also become a security and business rule issue. Certain users might be allowed to pay out [period] (security), and some users might be allowed to pay out upto $1000 an hour (security, but with a business rule attached .. conditional security - to ensure the integrity of the system is not compromised).
There seems to be a distinction not being stated between the importance of an operation being carried out correctly and whether it's executed under the correct level of trust. Either way an incorrect/unauthorised read/write/delete/"replace operation here" in software could just as likely burn down a building, bankrupt a company, crash a plane or otherwise lead to loss of life. These are not reversible, but as you state it just may happen that in your domain certain things aren't important and some are. But it gives good reason why all operational security and/or business rules should be abstracted from the code as much as possible.
If you define security as the mechanism you use to protect the integrity of the system from malicious agents, then write checking does fall under this umbrella. The way I see it, though, is that a lot of the time there is not an obvious way to know the difference between a malicious write and a non-malicious write. If Joe User pulls up his employment record and makes an innocent change that the system lets him make due to a programming mistake, is this a security issue? He wasn't being malicious, so by the above definition, this is not a security issue. Its not a validation issue...so...I guess it is a security issue afterall, at least under your definition.
In the code, at least for me, its pretty hard to detect whether an action is malicious or not. So, as far as I know as a programmer, an action is just an action, thats it. When determining how to protect against different actions I can come up with some possible dangers:
(A) Illegal data (I can see it is wrong just by looking at it, like the -100 $/H. Validation layers/business rules can take care of this.
(B) Wrong data. The only way I know of preventing this one is to only trust certain users to enter the data. This is where Write/Delete/Insert permission checking comes in. I can't think of any other reason to implement this permission checking other than to prevent wrong data (although if someone tells me otherwise, I'll admit I'm wrong!)
(C) Exposing sensitive data. I said before that in all the cases where I have had to think about a security system, this is the most dangerous possibility. There is no undoing this. Ever (unless you "take care of" poor Joe User)
(There are more dangers, I know!)
Making a mistake in (A) can be just as bad as making a mistake in (B) as far as I see it, as both are about protecting correctness of the data. Incorrect data is incorrect...doesn't really matter why. Because of this, I see (A) and (B) as being very similar and subject to the same amount of care.
(C) Seems to be in a league of its own, though. This isn't about correctness of data, this is about preventing a fundamentally unfixable situation from happening.
IMHO, security concerns are seen regularly in both the technology domain and the business domain. This can make it difficult problem to classify and solve. For example:
“Clinicians are logged out if they do not use the system for 5 minutes, or if they close the browser.” - mostly technology domain
“Only administrators can view address details for high risk patients.” - mostly business domain
This has caused me confusion when I've tried to implement security for my applications. Just as I start to think that security can be wrapped up into a "one size fits all" framework like data access or logging, I then meet a project that drags me through a arduos "business logic" requirements session and is clearly very different to other systems.
Having done a few security implementations, I'm now thinking it's one of those cross cutting concerns that is deeply tied to both busines logic and infrastructure. Maybe seeing it this way might make life easier, rather than tyring to force it into one "camp"?
I almost expect the domain model to tell the story of how security is seen by the business, after all, I spend long enough discussing security with the customer so it must be part of the domain (unlike data access)!
Simiarly, I think want to see security present at the framework or infrastructure level, perhaps as a set of components and classes that can collaborate with the business layer to make enforcing security a breeze throughout the layers of the application.
Does any of that make sense!?
@Tobin,
It makes sense to me. However,
"I almost expect the domain model to tell the story of how security is seen by the business, after all, I spend long enough discussing security with the customer so it must be part of the domain (unlike data access)! "
Sometimes I discuss data access aspects with the customer, for example what concurrency strategies that should be used which is usually directly tied to the business meaning of the data.
Does that mean you expect to discover the implementations of these concurrency strategy desicions by directly examining the domain model?
@Mats,
Long time no speak! You still in the lovely Sweden?
Hmmm, that's a good question. I could do with more high-concurrency experience to get a firmer opinion, but from what I have experienced, I think concurrency strategies can be communicated through the domain model quite nicely.
Ruby on Rails lets me specify some locking strategies in the domain model. This "feels" like a good place for it.
Domain Driven Design also suggests using aggregates for scoping transactions and applying locking boundaries.
What do you think Mats?
On my last project, we had a dual security model of role-based and acl-based security. Underlying each was a permission set which mapped the functional permissions, e.g., 'user.create' is the permission to actually create users. So, you had a role like 'UserManager' with a mapping to some permissions {'user.create', 'user.delete', etc...}. Authorization for roles was something like Authorize(principal, permissions[]).
More interesting was trying to implement the ACL type security. Basically, we thought of it like Ayende did (Windows ACL) but in our case, the resources were Entities and stored in the DB. We wanted to standardize this support to all entities so we decoupled the ACL definitions into their own model (with a corresponding data model). Then extended the Entities to include a reference to an ACL (EntityID, Blah, AclID in the db). In the domain, we introduced a couple of interfaces, IAclControllable (which enabled a resource to have an ACL) and IAclPermissible (which enabled an entity to be privileged on a resource).
To complete the picture, we used the same underlying permission set to control access to the resources. So you might have a Cat (IAclControllable) entity on which an Owner (IAclPermissible) might be given permissions[] { 'cat.feed', 'cat.water' }. Permissions were, themselves, encapsulated into a Policy, which was linked (one-to-may) to an AclEntry (the owner entity).
So the Authorization algorithm for a resource is something like 1) is the requesting principal in a role that can perform the requested action? 2) if so, is the requesting identity in the ACL of the requested resource? 2a) if not, check the default policy for the resource 2b) if so, does the identity have the permission.
This works well in the domain. We, too, had inheritance issues to deal with which was not bad in the domain model.
You used a client wrapper to configure the permissions on an entity (adding entries to the acl).
The really problem with this design is query security. Like Ayende's problem, you can query only the entities you have permissions to use. So you might have query criteria like 'select cats where owner has permission to { 'feed', 'water' }'. When you factor in inheritance of permissions (permissions inherited from group membership) you have some pretty nasty queries to deal with.
Something like select all cats where owner has permission to 'feed' and 'water' or owner is in a group with permission to 'feed' and 'water' or the default policy allows anyone to 'feed' and 'water'.
We ended up buiding our own Query Objects to emit the complex SQL queries. Some of the clauses were really scary (i.e., pages). We were surprised SQL Server handled them as well as it did. After some optimization, it worked fine, but we cringed a bit over those queries...
Anyway, that was one 'generic' solution. It doesn't handle temporal business rules (e.g., grant access between 9am - 5pm) but it handled the permissioning system well. As for whether this is business logic or security logic, I vote this, as implemented, is security logic because it's fairly cross-cutting and reusuable. Next time, I would look to attach the auth checks via attributes so it reduces the noise a bit. As it stands, the inelegant part is the query system. This may be inevitable when storing your ACL definitions in the DB. I'd love to hear some comments on this approach or ways to integrate resource list security from within the domain layer without needing a 3rd party tool.
Comment preview