Copyright © 2004–2010 OpenSourcery, LLC. This work is licensed under a Creative Commons Attribution 3.0 United States License.
Recently I was given the task of overhauling the permissions system of eleMentalClinic. We needed a way to specify which clinicians could view which clients. We needed to ensure client security by preventing unauthorized clinicians from viewing certain clients.
The original eMC permissions system was very simple. In the database there was a list of roles. There was also a table that listed client->role relations. Roles were identified by name. Roles were only used to verify a clinician had access to a given controller.
This refactor was a multi-step process. The first step was to write a new role+permissions system that worked with the controller-access logic, but also allowed for clinician+client associations. The second step was to retrofit security check logic to the application.
The first step is fairly simple. Creating roles and associations like what we needed for eMC is a common problem with several common solutions. I ended up migrating to a new system where we had system roles, personnel roles, role memberships, and role-client associations. Memberships are all role-role associations.
The second step is significantly more difficult, specially when you consider the size and complexity of eMC. One thought is to have the dispatcher intercept the client_id parameter and run a check against the current_user. The problem with this solution comes on pages where multiple clients are displayed, many without a request.
The next possibly solution is to modify the client object to require authorization in order to build. The problem here is that the current_user is known to the controller, but not globally. Making the current_user a global did not sound like a good idea, in addition this would break a majority of the tests in the eMC test suite, a suite that is 2.9mb. If we had to refactor the client object, and all the resulting test failures, this task could take months.
I began toying with the idea of code that could retrofit the permissions into eMC at runtime. A magical module that would be able to provide the security check with all the information it needs. A module that could ensure security without the need to rewrite any of eMC's existing object code. Enter Package-Watchdog. Package-Watchdog is a Perl module I wrote to do just that.
With Package-Watchdog you can 'watch' subroutines within a specific package, and 'forbid' them from using subroutines in another package. The way it works is simple in concept, wrap code around the watched sub that rewrites the forbidden subs. During normal use the forbidden subs act as normal, However once inside a watched sub the forbidden ones are altered. This continues all the way down the stack, you could have a chain of subs as deep as you want, if any of them call the forbidden one the watchdog is triggered.
By default Package-Watchdog will die when a forbidden sub is invoked within a watched sub. However Package-Watchdog is written so that you can provide a custom reaction subroutine. This custom reaction subroutine is passed all the information relevant to the watch. This includes the names of the subs called, the parameters they were called with, and the watchdog helper objects that manage them.
In eMC a watchdog object is instantiated just before the controller. The watchdog is told to watch the controller's subroutines, and forbid the subroutines in the client object that are used to build it. The watchdog is provided a custom reaction sub. This custom reaction sub gets the controller via arguments, and the current user from the controller. The client_id being loaded is provided via the forbidden arguments. The forbidden sub can then take this information and run a security check. If the check passes the program continues, otherwise it throws a security exception.
The main goal of the watchdog is to absolutely prevent displaying client information to an unauthorized clinician. The watchdog left the application badly broken, pages such as schedule would always throw security exceptions. This is still considered a win, it means no accidental displaying of client information.Now it becomes a task of fixing the security exceptions, and as an added bonus we can now see where they are so they don't slip by.
As one might expect this could slow down an application considerably, and at first it did. I added caching to the permissions system, and also to the security check logic. The application now runs as fast as it always has in my experience.
Tagged as: package-watchdog, permissions, retrofit, role