Macto, Authorization decisions
Authorization is one of the few ubiquitous requirements, you are going to have to handle them in pretty much every system that you are going to build.
The users are the staff, and the securables are the inmates. The problem is that we have fairly different authorization requirements for different parts of the system.
For example, any authenticated user can lookup pretty much any inmate’s data (except for medical records, of course), but changing release date is something that only Legal can do. Only the staff on the enclosure that the inmate is located in can Sign Out the inmate. Actually releasing an inmate requires Legal and On Duty Officer approval, etc.
However, during weekends, the on duty staff assume responsibility for the entire prison. That is, an officer from enclosure C can Sign Out an inmate from enclosure A if that officer is the on duty officer.
There are a few more complications, but we will ignore them for now. One thing that is fairly clear, we have fairly complex authorization requirements, and they are different for each part of the system. For that matter, the way we make security decisions itself is different.
And since authorization decisions are synchronous (you can make them async, sort of, but at very high cost), performance is a critical concern. This is especially true because there is a strong tendency to call authorization decisions many times.
Given that, and given the complexity inherit to authorization, I think that we can skip the entire problem entirely by changing the rules of the game.
Most authentication systems would have you do something like:
auth.IsAllowed(CurrentUser, "/Inmate/Move", inmate);
And rely on the system to do its magic this way. The problem in this manner is that we provide the authorization system with very little information with which it can work. That means that in order to make authorization decisions, the system has to have a way to access other data (such as in what enclosures the current user is in charge of, where the inmate is, what is its status, etc).
The problem then become more an issue of data retrieval complexity rather than the authorization rules complexity. I think that we can avoid this by designing the system with more flexibility by providing the required data to the authorization system explicitly.
What do I mean? Well, just take a look:
auth.IsAllowed(
new SignOutAuthorization
{
OfficerRank = CurrentUser.Rank,
OfficerRoles = CurrenUser.GetAllCurrentRoles(),
OfficerEnclosures = CurrentUser.GetEnclosuresUserIsResponibleFor(),
InmateEnclosure = inmate.Enclosure,
InmateStatus = inmate.Status,
}
);
In other words, we are explicitly providing the authorization system with all the data that it needs for a particular task. That means, in turn, that we can now execute the authorization decision completely locally, without having to go somewhere to fetch the data. It also open the option of using a DSL to build the authorization rules, which will make things more dynamic and easier to work with.
Comments
We have a very similar authentication model, but we use an IActionAuthorization to pass to the IAuthorizationManager instance. IActionAuhorization has only one method IsAllowed which returns a boolean.
Our first version was inspired by the ActionCatalogService, part of SCSF.
I don't really see the point in making the authorization decision asynchronous, as you need a (positive) result to perform the action.
So the security module implementation breaks down to two - data validation and personalization.
I started writing this comment as a "con" (no pun intended), but after re-reading the previous statement - I like it more and more. Nice!
Arielr,
I am not sure that I am following what you mean, data validation & personalization.
__The problem is that we have fairly different authorization requirements for different parts of the system.
that means that our "users" e.g. staff has different responsibility (with each comes different power), so my question is, "roles" wouldn't help us here? So "Legal" would be one role of our Staff and we would just check whether autheticated (logged on) user have "action method" right.
Or would there be too much roles (enclosure one, two, three....) even when you can combine them (Legal and On Duty)?
That means you would have to "assign" roles dynamicaly to a staff, so you've chosen another approach to a problem?
__One thing that is fairly clear, we have fairly complex authorization requirements, and they are different for each part of the system.
well I agree it is complex...
cowgaR,
Roles would work, yes. You would need to dynamically assign people to and from roles.
And you will have a lot of roles.
Moreover, given the following piece of data, what should the auth sys do?
user: Operations, Captain, Enclosure 1 & 2
op: /Inmate/SignOut
inmate: Enclosure 3, Administrative
And:
user: Operations, Captain, Enclosure 1 & 2, On Duty
op: /Legal/ReleaseDateChange
inmate: Enclosure 3, Administrative
That is, what is the code that would satisfy both questions?
Since we have different logic chains around, we can end up with a very complex system, and that would be... bad.
it's lame (must be ;-) but this would be:
sign in for today - assigned roles (3 or 4)
..../Legal/ReleaseDateChange
check wheter current staff (me logged in) belongs to all required roles
but now that I'm thinking I see that the action (and thus required roles) is dependant on inmate in a question, so it is becomming complex...
but I know you are trying to show me some other thing I am not getting here ;)
cowgaR,
Try to write it down in pseudo code. The first sample should fail, the second should pass.
I think it will make it easier to understand the issue
"Validation and Personalization" are the basic infra building blocks you use for the security module.
Client and server side operations, which use personalization data for the current user, may just as well be used for security decisions.
Client and server side operations, which perform data validations on operations, may just as well be used for security validations.
It's the safe exact infra; different flavors.
I think the problem with your approach is that the calling code has to know exactly which facts the authorization system is basing its decision on.
What if your authorization system now also needs to look at the day of week to decide (like in the requirements at the top of your post). Do you explicitly pass in the current day of week and change every call-site?
Also that puts alot of responisbility on the caller site for a security critical component.
What I do like about the approach is that state is explicit and the authorization component does not have to know how to retrieve additional data from your application. That also makes it very easy to unit test.
So if you had multiple different ways a user could be authorized, do each of them get their own implementation? I'm not sure if I follow yet (maybe should just shuttup and wait for code).
Thomas,
I agree that this is a problem.
That is why we have strongly typed it. Notice SignOutAuthorization ?
We would have similar stuff to that on each possible operation. And that explicitly specify what we need to pass.
And each operation can only use the stuff that is passed to it.
Things like current date time are something that is globally and cheaply accessible, so I wouldn't worry about that.
Eyston,
There are multiple ways that the user get authorized.
For example, the logic for signing out an inmate and the logic for changing legal info is completely different and require different data.
I mean if there are multiple ways to be authenticated for SignOutAuthorization that are kind of orthogonal. Like criteria A or criteria B.
authenticated = authorized, typo
Eyston,
I am not sure what you mean by implementation.
For a single operation, the set of data that gets passed in is well known.
There may be multiple rules operation on that, though.
auth.IsAllowed(
);
That's the type of code I like to see in an application!!
I can't tell if you are sarcastic or not.
To illustrate Thomas' objection, how would the authorization task be instantiated to handle the following rule:
The officer can release the inmate M-F 8am - 5pm OR anytime if the On Duty staff includes an officer of rank "Captain" or higher.
To my thinking, this is a lot for the calling client to know about, whereas, if you place an authorization service between the calling client and the evaluation, you could check the staff schedule to see if a Captain is present.
Maybe this is solved by placing the configuration of the SignOutAuthorization behind a creation method, something that is isolates the specialized knowledge needed. But then, is that an Authorization Service by another name?
Are you suggesting that a rule is fully encapsulated by a single authorization task or do you envision tasks as composable (a tree) to support AND/OR logic?
I like the class instantiation as in SignOutAuthorization for an authorization request. I am uncertain about the API consumer having to
figure out how to fill the parameters. What would be the problem with
auth.IsAllowed(new SignOutAuthorization { User = CurrentUser, Inmate = inmate });
?
Why do you create the SignOutAuthorization with properties instead of constructor arguments?
With a constructor, when the SignOutAuthorization has new requirements, at least you break the build if you forget to update all your usages.
I also find it weird that the caller has to know every needed info to authorize an action...
Peter,
I have a good example of a similar auth service in my book, you might want to check that out.
Auth Rules are composed of a set of scripts that are combined together.
In this case, I would say something like:
allow if Status is OnDutyStuff and Rank >= Captain
allow if CurrentTimeIsBusinessHours
Date stuff is handled internally, not externally.
Frank,
The problem is that now the auth class needs to figure out where to get the info from. It is much better to let the calling code make this decision, since it can add any required info to the eager loaded parts.
David,
For real code, I would probably use a ctor, I used props here because it allows me to name the stuff so you can more easily see them.
Most non-trivial applications tend to need something like this. You need to give context to the authentication system to determine the applicable roles. Representing that context, and making the rules become the challenges. I'm looking forward to see how you use a DSL to do this.
DoniG
Well, you only need to abstract the data retrieval a bit more, and you basically have Claims-based security. That's generally the direction Identity and Authorization are moving these days, so it makes sense, but you might want to take a look at Windows Identity Foundation and its IClaimsPrincipal interface instead of reinventing the wheel ;)
I'm not sure if I'm twisting Peter's example, but his statement was "if the On Duty staff includes an officer of rank "Captain" or higher". I read that as the authorization could succeed if any officer on duty was a "Captain" or higher, not necessarily the current user.
Isn't this open for all kinds of bugs? It's easy to send something like that...
would not
allow for a much saver operation?
Jan,
a) in real world situation, you would tend to get the values from a variety of places, not just this two
b) I am writing code that protect against Murphy, not Micheveli
c) see my comment to David
I agree with Mark Seemann, this seems to be what its called "Claims-based security/authorization" The user supplies his claims and the system looks what it can do for the user for that operation request with those claims. Did you have chance to look at recently named Windows Identity Foundation formerly was Geneva something.
Hi Ayende, this is a really great insight into the full development process, thanks!
Are you planning on making the source code available too?
James,
Yes.
Are you going to use Rhino Security as base for your app? Or build authorization totally from scratch?
Somewhat of an old post but I was wondering how you convey reasons for authorization failure, currently you say.. can I do 'this', but all you get back is a yes or not.. how would you tell the user why they were not authorized for the action, and how would you handle localization?
The system we have returns so called 'authorization reports', which state are you authorized, and if you aren't they can include a list of 'problem' instances, we send the instances to a localized service which generates the error strings - the strings may often need to bind in information like.. if the erorr is 'suspended' you may want to include the date of suspension, and the date when suspension will end, so each problem type will also have instance properties that hold such information.
How would you handle this?
Stephen,
Usually, an Authenticate response result in a AuthenticationResult response, often that is an enum.
Comment preview