So I'm building a custom security library, which interfaces with our database. It's supposed to provide basic access control for in-house apps: certain users can do X, others can't. My needs at the moment are pretty basic, but the library will eventually be used by several apps and control a lot of securables.
My basic object model is that a User is a member of zero or more Groups. Those Groups bestow zero or more Permissions. In reality these will all be one-to-many but I don't want to referentially enforce that. Permissions are grant-only (if no groups give you the permission, you don't have it, but there isn't a "Deny" that overrides a granted permission like in Windows RBS), and groups can nest (a Tier 2 user has the rights of a Tier 1, plus some new ones). When attempting to access a securable area of the program, the application will imperatively assert that a user has the requisite permission via examining its group hierarchy.
However, I want there to be several levels of redundancy built into the library. Of particular importance is that a user who doesn't have permission to change security settings shouldn't be able to. So, I want to make this security hierarchy readonly in most cases, so a needed but denied permission can't be added in-memory. Only when a user has proven they have permission to modify security settings by providing a readonly user with that permission, should the properties become settable from even a code level.
That's where I think I've overbuilt it. In order to do this, the domain has developed a split personality; two copies of each of the objects, one readonly, the other not. The readonly version is the one produced by default; any operation that would produce a writable version requires the currently logged-in user to have permission to make security changes. This is resulting in two Users (User and ConfigurableUser), two Groups, Two Permissions, each with two interfaces (the configurable deriving from the readonly)... you get the idea.
I've realized that basically the only people all this extra cruft would stop is other developers, who are generally trustworthy (it's an in-house app and we control all the resources this app makes use of, and the developers generally get admin rights on a lot). If I can trust that people who touch the code know what they're doing, the apps can be read-write and there won't be a problem with the possibility that permissions can be "elevated" via a clever code snippet.
Help me make this sane. Is there a different pattern I should follow? Am I right to distrust other developers? I prefer not to integrate with Windows security, but the option has been discussed; the main drawback would be that the access rights would be maintained in Active Directory for the entire company, and the manager of the users of this app shouldn't have that kind of access to the overall system security.
EDIT: Some good comments and answers from all. AD or other integrated security is not totally off the table, but we did discuss it before I started developing, and found some drawbacks. So, here are some more of my/our thoughts on why we even decided to go with a "custom" security setup:
Use of the app is entirely in-house. As such, security of this app is not so much about preventing outside attacks/hostile takeovers, but keeping Joe Officeworker from doing something he shouldn't according to business policy. If Joe ends up miraculously finding a way to make himself "god" in the app, his powers are still pretty limited, because the app itself has very limited access to the database and other resources, most of which he'd need to do his job anyway and are thus granted by virtue of being even the lowest-level user.
Despite that, if a user ever got his Windows password phished, cracked or keylogged, the attacker would gain some serious access to client data through the application for "free" if the app used integrated security instead of a traditional login. A separate security layer for the app provides at least a possibility of redundancy; they'd have to crack two sets of credentials to get in instead of one, and that second set of credentials is locked behind yet another layer of database security that the cracked user wouldn't have free access to.
Using Active Directory or other integrated security has a couple of problems from a development/administrative perspective.
The simple solution, given all this, is to contain security within the bounds of the app instead of tying in to network security. We get a security structure we can maintain with relative ease, that stops at the app.
EDIT 2: As an epilogue to this question, the solution I eventually arrived at was to keep my mutable object hierarchy, but then create a simple interface, IAuthUser, with readonly members for the user's basic info and permissions. IAuthUsers are created in only one place - during login - by copying the User retrieved using the credentials into a private class that implements the public interface. They are used for all authentication and authorization by interrogating the contained list of permissions projected from the user's group membership at startup. They are never turned back into mutable users, and thus are never saved back to the DB. Meanwhile, the mutable Users cannot be turned into IAuthUsers outside of the login process, so are useless for authorization, and they cannot be saved back to the DB without providing an IAuthUser that has permission to make the type of changes detected in the object (by comparing it to the version currently in the DB)
Instead of having a split hierarchy, I would have a "user can modify security settings" Permission, and assert that any time a user tries to edit something.
"Complexity is the worst enemy of security." --Bruce Schneier
For the sake of building a very secure system I would rely upon Active Directory for access control company wide. The most important part of your proposed security system is that its not realying on obscure functionality to fool attackers. Instead the use Active Directory means your access control system will be actively maintained by Microsoft. Which is the platform you are tied to.
This solution isn't bullet proof. You still have to worry about LDAP Injection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With