-
Notifications
You must be signed in to change notification settings - Fork 24
Permissions Filter
A basic permissions filter in included with the Mach-II framework, which allows access to an event based upon a user's defined privileges or permissions. Let's see how this can be used as-is, then customize it a bit for greater flexibility.
Examine PermissionsFilter.cfc
in the framework's filters
folder. The code is well-commented, but it's important to look at the getUserPermissions
method. The filter is expecting to find a permissions
property in session scope. If your session is set up this way, you can use this filter directly. More likely, you'll need to modify to fit your application.
To use the filter without modification, add the declaration to the event-filters tag in your configuration file:
<event-filters>
<event-filter name="permissionsFilter" type="MachII.filters.PermissionsFilter"/>
</event-filters>
Let's suppose that when a user logs in, we look up the user's permissions and store them as a comma-delimited list in session scope. To control access to an event, we use this filter, passing the necessary permissions to access the event:
<event-handler event="admin.menu" access="public">
<filter name="permissionsFilter">
<parameter name="requiredPermissions" value="admin,godfather"/>
<parameter name="invalidEvent" value="security.access.denied"/>
</filter>
</event-handler>
If the user has both the "admin" and "godfather" roles in his session.permissions list, then the event-handler will continue, otherwise the invalidEvent
(defined here as 'security.access.denied') will be announced and execution of the current event will be aborted. In the invalidEvent handler you might display a page explaining that access has been denied, log the attempt to a database, notify an administrator via e-mail, or take any other appropriate action.
Let's customize this basic filter to give it a little more flexibility and to reflect our user setup. It may be possible to extend the existing filter and override the methods as necessary, but for the sake of simplicity of this example, we'll make a copy and store it in the filters
folder under our application root and modify our copy. We'll have to change the configuration file to reflect this:
<event-filters>
<event-filter name="permissionsFilter" type="myapp.filters.PermissionsFilter"/>
</event-filters>
In our application, we've used a session facade, so we'll modify the getUserPermissions
appropriately. In our case, the session facade has a getUserRoles()
method which returns a comma-delimited list of the roles, and a boolean getIsLoggedIn()
method. We've made the session facade available to our filter using ColdSpring dependency-injection.
<cffunction name="getUserPermissions" access="public" returntype="any">
<cfset var retVal = "" />
<cfif getSessionFacade().getIsLoggedIn()>
<cfset retVal = getSessionFacade().getRoles() />
</cfif>
<cfreturn retVal />
</cffunction>
We'd also like to make the filter handle the case where the user must have at least one of the required permissions, as well as the case where the user must have all of the required permissions, which is how the base filter is coded. Let's call the first case requiredPermissionsAny
and the second requiredPermissionsAll
to distinguish them. We use them in the same manner as before:
<event-handler event="admin.menu" access="public">
<filter name="permissionsFilter">
<parameter name="requiredPermissionsAny" value="admin,godfather"/>
<parameter name="invalidEvent" value="security.access.denied"/>
</filter>
</event-handler>
or
<event-handler event="admin.menu" access="public">
<filter name="permissionsFilter">
<parameter name="requiredPermissionsAll" value="admin,godfather"/>
<parameter name="invalidEvent" value="security.access.denied"/>
</filter>
</event-handler>
And we create the necessary methods in the filter to handle these:
<cffunction name="validatePermissionsAll" access="public" returntype="boolean">
<cfargument name="requiredPermissionsAll" type="string" required="true" />
<cfargument name="userPermissions" type="string" required="true" />
<cfset var isValidated = true />
<cfset var permission = 0 />
<cfloop index="permission" list="#requiredPermissionsAll#" delimiters=",">
<cfif ListFindNoCase(arguments.userPermissions,permission) EQ 0>
<cfset isValidated = false />
</cfif>
</cfloop>
<cfreturn isValidated />
</cffunction>
<cffunction name="validatePermissionsAny" access="public" returntype="boolean">
<cfargument name="requiredPermissionsAny" type="string" required="true" />
<cfargument name="userPermissions" type="string" required="true" />
<cfset var isValidated = false />
<cfset var permission = 0 />
<cfloop index="permission" list="#requiredPermissionsAny#" delimiters=",">
<cfif ListFindNoCase(arguments.userPermissions,permission) NEQ 0>
<cfset isValidated = true />
</cfif>
</cfloop>
<cfreturn isValidated />
</cffunction>