Yesterday, I started looking for a better way to handle a security model in which access permissions are based on both the permissions granted to the user and context in which the access request was made (ie. the page request settings). At first, I tried to make the permissions check a responsibility of the User; but, after some analysis and feedback, it became clear that it was neither practical nor appropriate to leave these decisions up to the User. As such, I want to explore moving the responsibility of security into a security manager (or an offshoot of it).
Before we do that, let's just recap some of our security concerns:
Access checks can and will be made for the current request and for future requests (such as with layout rendering).
Access rights depend on an event, either current or future.
Access rights depend on the target of the event.
Access rights depend on the user permissions.
Ultimately, what this boils down to is that each access check depends on some combination of the following four elements:
- Request Settings
Given this information, we could easily imagine passing all four off to a security service for each access check:
SecurityService.CanAccess( Settings, User, Event [, Target ] )
Now, call me crazy, but because the User will be the same for every single access check, it seems very sloppy that we have to keep passing it in. Likewise, the Settings will be the same for every check made in a single page call. As such, what I would like to do is create a request-based security service that composes the given user and the Settings and allows for a smaller API:
<!--- Get request-based security service. --->
<cfset ReqeustSecurity = SecurityService.GetRequestSecurity(
Settings = REQUEST.Settings,
User = SESSION.User
<!--- Check permissions. --->
<cfif ReqeustSecurity.CanAccess( Event [, Target ] ) />
Even if the RequestSercurity object is nothing more than a "Man In The Middle" anti-pattern that calls SecurityService.CanAccess() on the singleton and passes all four elements in, I still think it's a slightly more elegant approach.
Unfortunately, I got to the office a bit late today, so that's all the time for exploration that I have. But, there's still so much more to be tried. For example, what is the Target? Is it an ID? Is it a query row? Is it an object? And what about caching? Can we cache access rights? If so, where? And does that apply to all events? Lots to be explored!
Want to use code from this post? Check out the license.