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.