Skip to content
thofrey edited this page Jul 23, 2013 · 2 revisions

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>
Clone this wiki locally