Interceptors:Security
<< Back to Dashboard | << Interceptors Viewer
|
Security Interceptor: Securing Your Applications
Introduction
The topic of security is always an interesting and essential one. However, most MVC framework offer very loose security when it comes down to an even-oriented architecture. Interceptors and the security interceptor change this. With the ColdBox security interceptor you will be able to secure all your public and private events from execution. There is a default criterion to meet for event security that the interceptor will provide to you. However, as we all know, every application has different requirements and since we are keen on extensibility, the interceptor can be configured to work with whatever security authentications and permissions you might have.
The interceptor wraps itself around the preProcess execution of your request and also (if configured) on any runEvent() methods that get executed internally. The interceptor is based on a custom rules engine that will validate a request against a set of rules that you define and then if a rule is matched, it will try to see if the user is authenticated and in a specific criteria (as specified by you). The two available authentication algorithms are the following:
- (default) By using cflogging, cfloginuser, cflogout
- Security Validation Object that you create and implement.
The default security is based on what ColdFusion gives you for basic security using their security tags. You basically use cfloginuser to log in a user and set their appropriate roles in the system. The interceptor can then match to these roles via the security rules you have created. The second method of authentication is based on your logic. You will be able to register a validation object with the interceptor. Once a rule is matched, the interceptor will call your validation object, send in the rule and ask if the user can access it or not. It will be up to your logic to determine if the rule is satisfied or not. It all sounds very complex and internally it is. However, the implementation is very straightforward. So let's begin.
Features
- It secures all incoming events that get intercepted at the preProcess interception point
- If enabled, it can secure all internal event executions
- Security rules are created and can be stored in the following mediums:
- xml (ColdBox Default)
- Any Database
- Any Model Object
- IoC bean (Coming as a query from an IoC bean)
- ColdBox Cache (Placing a query of rules into the cache)
- A rule engine has been built to provide security for multiple events
- The rules can be configured to use regular expressions or simple snippets
- Can use CF authentication security
- Can Use your own Security Validation by creating a security validation object.
IoC = Inversion of Control from ColdSpring or LightWire
How it works?
The basics of the security validation is that you define a set of rules, much like how you define a firewall. The most important part of a rule is the securelist element. This element is where you define the snippet or regex of events you want to secure. If an incoming event matches to this securelist, then the event will be checked against a whitelist list. If it is there, then the request continues as normal. However, if the event is not whitelisted it means it must be secured. Therfore, the interceptor will either use internal CF security to validate the roles attached to the rule and if the user validates the request is responded to. Now, if you are using your own security algorithm, the interceptor will call the registered user validator object and pass in a structure that represents the rule that matched. Then the validator object will be in charge of determining if the user is authorized and in whatever criteria your system has implemented. Then the validator object must return a boolean variable telling the interceptor whether to continue the request or redirect the user for login.
Declaring the Interceptor
You will declare the security interceptor in your application configuration file: coldbox.xml.cfm. Just look for the interceptor declarations and add the following:
<Interceptor class="coldbox.system.interceptors.security"> <-- <Properties go here --> </Interceptor>
This snippet declares that you will be using the security interceptor, but now we need some properties.
Global Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| useRegex | boolean | false | true | So you can decide to use or not regular expressions on pattern matching |
| useRoutes | boolean | false | false | Whether the interceptor should redirect using the SES routes or normal events via setNextEvent. |
| queryChecks | boolean | false | true | Flag that tells the interceptor to validate the columns in the security rules. If you are doing custom columns then set this to false. |
| preEventSecurity | boolean | false | false | This turns on the preEvent execution point that will make sure that before any event is fired internally, that its verified against the security rules |
| debugMode | boolean | false | false | Logs all activities as the events are matched and verified |
| rulesSource | string | true | --- | Where to look for the rules as described above, this value has to be a choice from the following list xml,db,model,ioc or ocm. |
| validator | string | false | --- | The class path of the validator object to use. The interceptor will create the object for you and cache it internally. If the object has an init() method, the interceptor will call it for you. |
| validatorModel | string | false | --- | The model name of the security validator to use for custom validations. The interceptor will call getModel() with the name of this property |
| validatorIOC | string | false | --- | The bean name of the security validator to use for custom validations. The interceptor will ask the IoC plugin for the bean according to this property |
XML Properties
The following are properties used when the source of the rules is xml
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rulesFile | string | true if rulesSource = xml | --- | The location of the security rules xml file |
DB Properties
The following are properties used when the source of the rules is db or coming from the database.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rulesDSN | string | true if rulesSource = db | --- | The dsn to use if the rules are coming from a database |
| rulesTable | string | true if rulesSource = db | --- | The table where the rules are |
| rulesSQL | string | false | select* from rulesTable | The custom SQL statement to use to retrieve the rules according to the rulesTable property. If not set, the default of select* from rulesTable will be used. |
| rulesOrderBy | string | false | --- | The column to order the rules by. If not chosen, the interceptor will not order the query, just select it. |
Model Properties
The following are properties used when the source of the rules is model or coming from a model object
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rulesModel | string | true if rulesSource = model | --- | The model object name that has the rules |
| rulesModelMethod | string | true if rulesSource = model | --- | The method in the model object, that will be called and return a query of rules |
| rulesModelArgs | string | false | --- | A comma-delimited list of arguments to send into the model's method. This is an optional argument and if not set, the method will be called with no arguments |
IOC Properties
The following are properties used when the source of the rules is ioc or coming from an IoC factory object.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rulesBean | string | true if rulesSource = ioc | --- | The bean name to ask the IoC plugin for that has the rules |
| rulesBeanMethod | string | true if rulesSource = ioc | --- | The method in the bean to call that will return a query of rules |
| rulesBeanArgs | string | false | --- | A comma-delimited list of arguments to send into the method. This is an optional argument and if not set, the method will be called with no arguments |
OCM Properties
The following are properties used when the source of the rules is ocm or coming from the coldbox cache.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rulesOCMKey | string | true if rulesSource = ocm | --- | The cache key to use to retrieve the rules from the ColdBox cache |
Sample Declarations
Let's look at some sample full declarations:
<Interceptor class="coldbox.system.interceptors.security"> <Property name="useRoutes">true</Property> <Property name="queryChecks">false</Property> <Property name="rulesSource">model</Property> <Property name="rulesModel">SecurityService</Property> <Property name="rulesModelMethod">getSecurityRules</Property> <Property name="validatorModel">SecurityService</Property> </Interceptor> <Interceptor class="coldbox.system.interceptors.security"> <Property name="useRoutes">true</Property> <Property name="queryChecks">false</Property> <Property name="rulesSource">ioc</Property> <Property name="rulesBean">SecurityService</Property> <Property name="rulesBeanMethod">getSecurityRules</Property> <Property name="validatorIOC">SecurityService</Property> </Interceptor> <Interceptor class="coldbox.system.interceptors.security"> <Property name="rulesSource">xml</Property> <Property name="rulesFile">/applications/coldbox/testing/tests/resources/security.xml.cfm</Property> <Property name="debugMode">true</Property> <Property name="preEventSecurity">true</Property> </Interceptor> <Interceptor class="coldbox.system.interceptors.security"> <Property name="rulesSource">ocm</Property> <Property name="rulesOCMkey">qSecurityRules</Property> </Interceptor> <Interceptor class="coldbox.system.interceptors.security"> <Property name="rulesSource">ioc</Property> <Property name="rulesBean">security</Property> <Property name="rulesBeanMethod">getRules</Property> <Property name="validator">applications.coldbox.testing.testmodel.security</Property> </Interceptor> <Interceptor class="coldbox.system.interceptors.security"> <Property name="rulesSource">db</Property> <Property name="rulesDSN">coldboxframework_com</Property> <Property name="rulesTable">securityrules</Property> <Property name="rulesSQL"></Property> <Property name="rulesOrderBy">securityrule_id</Property> </Interceptor>
Note: If you want to register your own validator instead of the interceptor creating it or taking it from the IoC plugin or Models, please use the registerValidator() method and do not declare the validator, validatorIOC or validatorModel properties.
Rules
Now that we have seen all the properties of this interceptor, how to configure it and declare it, let's look at how the rules work. All the security rules follows the following format (However, you can append as many columns as you like for your own custom validations and the interceptor will register all the columns you pass to it, except from the xml file. Also note that you will need to set the queryChecks property to false):
| Element | Type | Description |
|---|---|---|
| whitelist | varchar | A comma delimited list of events or patterns to whitelist or to bypass security on |
| securelist | varchar | A comma delimited list of events or patterns to secure |
| roles | varchar | A comma delimited list of roles that can access these secure events |
| permissions | varchar | A comma delimited list of permissions that can access these secure events |
| redirect | varchar | An event or route to redirect if the user is not in a role |
You might be asking, why is the element permissions here, if the default ColdFusion security doesn't work with permissions but with roles. Well, in anticipation to customizations and permissions based systems, the permissions element exists. Its there already if you need to use it. However, the default interceptor security does not use it, just the roles element. A few guidelines:
- Test your regular expressions
- Distinguish between routes and normal links
- Order of declaration of the rules is important as they are fired in order
- Test your whitelist events, if not you might be producing endless loops of security redirections
- Make sure implicit events are whitelisted if you are securing the entire application events
- As best practice, create your own validator object.
Important: The basic rules table is provided. However, it is very denormalized and for simple applications. If your applications will deal with many permissions and roles, I suggest you build your DB according to your business rules and then just create a method that can join the rules into the above format. You can easily do this in SQL or create a view for your security rules. If you are doing this, then your security implementations are advanced and you know what you are doing (Hopefully). So please note that the way you store the rules in the DB is your priority and consideration.
Sample XML Rules
<?xml version="1.0" encoding="ISO-8859-1"?> <-- < Declare as many rule elements as you want, order is important Remember that the securelist can contain a list of regular expression if you want ex: All events in the user handler user\..* ex: All events .* ex: All events that start with admin ^admin If you are not using regular expression, just write the text that can be found in an event. --> <rules> <rule> <whitelist>user\.login,user\.logout,^main.*</whitelist> <securelist>^user\..*, ^admin</securelist> <roles>admin</roles> <permissions>read,write</permissions> <redirect>user.login</redirect> </rule> <rule> <whitelist></whitelist> <securelist>^moderator</securelist> <roles>admin,moderator</roles> <permissions>read</permissions> <redirect>user.login</redirect> </rule> </rules>
Important: Please remember to white list your main events (implicit), login and logout events if you will be securing the entire application.
First Rule Analysis
As you can see from the sample, the first rule has the following elements
whitelist : user\.login,user\.logout,^main.*
This means that the following events will not be verified for security: user.login, user.logout and any event that starts with main will be let through, if they match the secure list pattern.
securelist : ^user\..*, ^admin
This means that any event that starts with the word user. will be secured and anything that starts with the word admin will also be secured, unless the incoming event matches a pattern in the whitelist element.
roles : admin
This means that only a user with admin role will be allowed to visit the securelist events.
Permissions : read,write
This probably means that I am doing my own security validation and apart from having the user have a role of admin, he/she must also have the read and write permissions. My own validator will validate this logic.
redirect : user.login
From the syntax, I can tell that if the user does not validate, the interceptor will redirect via normal setNextEvent to the user.login event.
Second Rule Analysis
The second rule has the following elements:
whitelist : empty
No white listed events are defined.
securelist : ^moderator
This means that any event that starts with the word moderator will be secured and validated against the user's credentials.
roles : admin, moderator
This means that users with roles of admin and moderator can execute events that are in the securelist.
Permissions : read
This probably means that I am doing my own security validation and apart from having the user have a role of admin or moderator, he/she must also have the read permission. My own validator will validate this logic.
redirect : user.login
From the syntax, I can tell that if the user does not validate, the interceptor will redirect via normal setNextEvent to the user.login event.
_securedURL key
When the security interceptor is about to relocate via routes or normal event syntax to the redirect event, it will save the url you are on in a variable called: _securedURL. This key will be persisted in the flash memory of the framework and when the user get's relocated to the redirect event, this key will exist in the request collection. You can then use this to store where they came from and do proper redirections. So always remember to use this key if you want to provide a seamless login experience to your users.
Default Security
This interceptor will try to use ColdFusion's cflogin + cfloginuser authentication by default. However, if you are using your own authentication mechanisms you can still use this interceptor by implementing a Security Validator Object (See next section). Below we can see a sample on how to use the cflogin tag:
Ex:
<cflogin> Your login logic here <--- Log in the user with appropriate credentials ---> <cfloginuser name="name" password="password" roles="ROLES HERE"> </cflogin> <--- Some Real sample ---> <cflogin> <cfif getUserService().authenticate(rc.username,rc.password)> <cfloginuser name="#rc.username#" password="#rc.password#" roles="#getUserService().getRoles(rc.username)#" /> </cfif> </cflogin>
For more information about cflogin, cfloginuser and cflogout, please visit the Adobe ColdFusion Live Docs at http://www.adobe.com/go/livedocs_cf8docs_cfml_reference
Custom Security: Validator Object
A security validator object is a simple cfc that implements the following function:
userValidator(rule:struct,messagebox:coldbox.system.plugins.messagebox,controller:coldbox.system.controller) : boolean
This function must return a boolean variable and it must validate a user according to the rule that just ran by testing the fields that get sent in as a rule. Where this method exists is up to you. The method also receives a reference to the messagebox plugin so you can set messages or anything you like for display purposes if the security fails. It will also receive a reference to the current ColdBox controller. You can use the controller to call other plugins, persist keys, or anything you like (please see the controller API). The important note here is that the rule structure contains all the elements/columns you defined in your xml or query. If you are using the default elements/columns you would get the following structure:
- rule
- roles
- permissions
- securelist
- whitelist
- redirect
However, the important elements are roles & permissions because that is what the validator is trying to do. In your method you will validate the roles, permissions or anything additional against your user's object or whatever it is you use to have a user logged in.
Below is a real life example:
<--- User Validator for security ---> <cffunction name="userValidator" access="public" returntype="boolean" output="false" hint="Verifies that the user is in any permission"> <---************************************************************** ---> <cfargument name="rule" required="true" type="struct" hint="The rule to verify"> <cfargument name="messagebox" type="any" required="true" hint="The ColdBox messagebox plugin. You can use to set a redirection message"/> <cfargument name="controller" type="any" required="true" hint="The coldbox controller" /> <---************************************************************** ---> <--- Local call to get the user object from the session ---> <cfset var oUser = getUserSession()> <--- The results boolean variable I will return ---> <cfset var results = false> <--- The permission I am checkin ---> <cfset var thisPermission = ""> <--- Authorized Check, if true, then see if user is valid. This column is an additional column in my query ---> <cfif arguments.rule['authorize_check'] and oUser.getisAuthorized()> <--- I first check if the user is authorized or not if set in the db rules ---> <cfset results = true> </cfif> <--- Loop Over Permissions to see if my user is in any of them. ---> <cfloop list="#arguments.rule['permissions']#" index="thisPermission"> <--- My user object has a method called check permission that I call with a permission to validate ---> <cfif oUser.checkPermission( thisPermission ) > <--- This permission existed, I only need one to match as per my business logic, so let's return and move on ---> <cfset results = true> <cfbreak> </cfif> </cfloop> <--- Messagebox will be set with a custom message ---> <cfif not results> <cfset arguments.messagebox.setMessage("warning","You are not authorized to view this page.")> </cfif> <--- I now return whether the user can view the incoming rule or not ---> <cfreturn results> </cffunction>
Declaring the Validator
You have four ways to declare the security validator:
1) This validator object can be set as a property in the interceptor declaration (validator=) as an instantiation path. The interceptor will create it, cache it and try to execute it every time a rule matches.
<Property name="validator">mypath.model.myvalidator</Property>
If the object has an init(controller) method, the interceptor will call it
2) You can register the validator via the registerValidator() method on the security interceptor. This must be called from the application start handler or other interceptors as long as it executes before any preProcess execution occurs, else it will fail validation:
<cfset myValidator = createObject("component","model.myvalidator").init(getConfigBean(), getDatasource('mysite') )> <--- The following is in my application start handler ---> <cfset getInterceptor('coldbox.system.interceptors.security').registerValidator(myValidator)>
That validator object can exists and come from anywhere using the mentioned technique above.
3) Use the validatorModel property, in which the interceptor grabs the validator by calling a getModel(name) method. The value of the property is the name of the model object the framework will try to construct for you.
<Property name="validatorModel">myValidator</Property>
4) Use the validatorIOC property, in which the interceptor grabs the validator from the ioc plugin, whether you are using coldspring or lightwire. The value of the property is the name of the bean.
<Property name="validatorIOC">myValidator</Property>
Conclusion
You have learned all about the security validator and its flexibility. It gives you plenty of options on how to secure your applications built on a rock solid rule engine. The regular expression matching mechanism can create extraordinary rules for you and save you lots of time. However, please note that regular expressions are tricky and I recommend testing them thoroughly. Now, get back to coding and secure your ColdBox Application.
Glyn Jackson said
at 03:42:42 PM 05-Oct-2010

SideBar
User Login 




Comments (