Only this pageAll pages
Powered by GitBook
1 of 15

v1.x

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Introduction

WELCOME TO THE COLDBOX SECURITY MODULE

This module will provide your application with a security rule engine.

LICENSE Apache License, Version 2.0.

IMPORTANT LINKS

  • https://www.forgebox.io/view/cbsecurity

SYSTEM REQUIREMENTS

  • Lucee 4.5+

  • ColdFusion 9+

INSTRUCTIONS

Just drop into your modules folder or use CommandBox to install

box install cbsecurity

You will then need to configure the interceptor via the cbsecurity settings in your main ColdBox.cfc or you can also declare the interceptor manually by leveraging the class: cbsecurity.interceptors.Security. If you define the cbsecurity settings, then the module will load the interceptor automatically for you with those settings.

You can find all the documentation here: https://github.com/ColdBox/cbox-security/wiki

Security Rules

Security rules can come from xml, json, query, memory or custom locations. You will find some examples in this module's config folder.

Settings

Below are the security settings you can use for this module. Remember you must create the cbsecurity struct in your ColdBox.cfc:

cbsecurity = {
    // By default all rules are evulated as regular expressions
    useRegex = true,
    // Verify queries that they have all required columns, by default it is relaxed
    queryChecks = false,
    // Will verify rules of execute before ANY event. Be careful, can be intensive, usually the preProcess is enough.
    preEventSecurity = false,
    // The class path of a CFC that will validate rules, optional
    validator = "class.path",
    // The WireBox ID of the object to validate rules, optional
    validatorModel = "wireboxID",
    // The bean ID of the object in the ioc module that will validate the rules, optional
    validatorIOC = "beanID.from.ioc.module",
    // Where to look for security rules
    rulesSource = "xml,json,db,model,ioc,ocm",
    // The location of a rules file, aplies to XML and JSON only
    rulesFile = "path.to.file",
    // Rules DB Properties
    rulesDSN = "datasource",
    rulesTable = "table",
    rulesSQL = "select * from rulesTable",
    rulesOrderBy = "",
    // Model Rule Properties
    rulesModel = "wirebox.id",
    rulesModelMethod = "method",
    rulesModelArgs = "comma-delimmited list of args",
    // IOC properties
    rulesBean = "bean.id",
    rulesBeanMethod = "method",
    rulesBeanArgs = "comma-delimmited list of args",
    // Cache key that has rules in the 'default' provider
    rulesOCMKey = "key.from.default.provider"
}

Manual Interceptor Declaration

Here is a sample declaration you can use in your ColdBox.cfc:

// Security Interceptor declaration.
interceptors = [
    { class="cbsecurity.interceptors.Security",
      name="CBSecurity",
      properties={
        // please add the properties you want here to configure the security interceptor
        rulesFile = "/cbsecurity/config/security.json.cfm",
        rulesSource = "json"
     } }
];

Default Security

This module will try to use ColdFusion's cflogin + cfloginuserauthentication by default. However, if you are using your own authentication mechanisms you can still use this module by implementing a Security Validator Object (See next section). Below we can see a sample on how to use the cflogin tag:

Example:

<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 docs http://cfdocs.org/security-functions

JSON Properties

The following are properties used when the source of the rules is json

Property

Type

Required

Default

Description

rulesfile

string

true if rulesSource = JSON

---

The location of the security rules json file

interceptors = [
    {class="cbsecurity.interceptors.Security", name="ApplicationSecurity", properties={
        useRegex = true, rulesSource = "json", validatorModel = "SecurityService",
        rulesFile = "config/security.json.cfm"
    }}
];

IOC Properties

The following are properties used when the source of the rules is ioc or coming from an IoC module

Property

Type

Required

Default

Description

rulesBean

string

true if rulesSource = ioc

---

The bean name to ask the IoC module 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

interceptors = [
    {class="cbsecurity.interceptors.Security", name="ApplicationSecurity", properties={
        useRegex = true, rulesSource = "ioc", validatorModel = "SecurityService",
        rulesBean = "SecurityService", rulesBeanMethod = "getRules", rulesBeanArgs = "sorting=true"
    }}
];

OCM Properties

The following are properties used when the source of the rules is ocm or coming from the CacheBox

Property

Type

Required

Default

Description

rulesOCMKey

string

true

---

The cache key to use to retrieve the rules from the ColdBox default cache provider

interceptors = [
    {class="cbsecurity.interceptors.Security", name="ApplicationSecurity", properties={
        useRegex = true, rulesSource = "ocm", validatorModel = "SecurityService",
        rulesOCMKey = "qSecurityRules"
    }}
];

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
expressions 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 expressions, just write the text
that can be found in an event.
-->
<rules>
    <rule>
        <match>event</match>
        <whitelist>user\.login,user\.logout,^main.*</whitelist>
        <securelist>^user\..*, ^admin</securelist>
        <roles>admin</roles>
        <permissions>read,write</permissions>
        <redirect>user.login</redirect>
    </rule>

    <rule>
           <match>event</match>
        <whitelist></whitelist>
        <securelist>^moderator</securelist>
        <roles>admin,moderator</roles>
        <permissions>read</permissions>
        <redirect>user.login</redirect>
    </rule>

    <rule>
           <match>url</match>
        <whitelist></whitelist>
        <securelist>/secured.*</securelist>
        <roles>admin,paid_subscriber</roles>
        <permissions></permissions>
        <redirect>user.pay</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

<match>event</match>

So it will match the incoming event.

<whitelist>user\.login,user\.logout,^main.*</whitelist>

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</securelist>

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</roles>

This means that only a user with admin role will be allowed to visit the securelist events.

<permissions>read,write</permissions>

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</redirect>

Then if it does not validate it will use this redirect element to relocate via setNextEvent()

Second Rule Analysis

The second rule has the following elements:

<match>event</match>

So it will match the incoming event.

<whitelist></whitelist>

No white listed events are defined.

<securelist>^moderator</securelist>

This means that any event that starts with the word moderator will be secured and validated against the user's credentials.

<roles>admin,moderator</roles>

This means that users with roles of admin and moderator can execute events that are in the securelist.

<permissions>read</permissions>

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</redirect>

Then if it does not validate it will use this redirect element to relocate via setNextEvent()

Third Rule Analysis

The third rule has the following elements:

<match>URL</match>

So it will match the incoming URL pattern after the domain name and application location.

<whitelist></whitelist>

No white listed events are defined.

<securelist>/secured.*</securelist>

It will secure any incoming URI that starts with /secured

<roles>admin,paid_subscriber</roles>

This means that users with roles of admin and paid_subscriber can visit URLs with /secured in them

<permissions></permissions>

No permissions

<redirect>user.pay</redirect>

Then if it does not validate it will use this redirect element to relocate via setNextEvent()

_securedURL key

When the security module is about to relocate redirect element, it will save the incoming URL that was requested in a flash RAM 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 element, 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. You can easily place it in the login form:

#html.startForm(action=prc.xehDoLogin,name="loginForm",novalidate="novalidate")#
    <---< Secured URL --->
    #html.hiddenField(name="_securedURL",value=event.getValue('_securedURL',''))#

    #html.textfield(name="username",label="Username: ",size="40",required="required",class="textfield",value=prc.rememberMe)#
    #html.passwordField(name="password",label="Password: ",size="40",required="required",class="textfield")#
    
    <div id="loginButtonbar">
    #html.checkBox(name="rememberMe",value=true,checked=(len(prc.rememberMe)))# 
    #html.label(field="rememberMe",content="Remember Me  ",class="inline")#
    #html.submitButton(value="  Log In  ",class="buttonred")#
    </div>
    
    <br/>
    <img src="#prc.cbRoot#/includes/images/lock.png" alt="lostPassword" />
    <a href="#event.buildLink(prc.xehLostPassword)#">Lost your password?</a> 
    
#html.endForm()#

Custom Security Validator Object

A security validator object is a simple CFC that implements the following function:

boolean userValidator( rule:struct, controller:coldbox.system.web.Controller )

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. 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. 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="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>
    
    <!---  I now return whether the user can view the incoming rule or not --->
    <cfreturn results>
</cffunction>

Or, in cfscript:

/** 
 * User Validator for security
 * 
 * @hint Verifies that the user is in any permission
 * @rule.hint The rule to verify
 * @controller.hint The ColdBox controller
 */
public boolean function userValidator( required struct rule, required any controller ) {
    // Local call to get the user object from the session
    var user = getUserSession();

    // Authorized Check, if true, then see if user is valid. This column is an additional column in my query
    if ( arguments.rule['authorize_check'] and user.getIsAuthorized() ) {
        return true;
    }
    
    // Loop Over Permissions to see if my user is in any of them.
    var permissionsArray = ListToArray(arguments.rule['permissions']);
    for (var permission in permissionsArray) {
        // My user object has a method called check permission that I call with a permission to validate
        if ( user.checkPermission( permission ) ) {
            // This permission existed, I only need one to match as per my business logic, so let's return and move on
            return true;
        }
    }

    // If we got here, the user does not have permission
    return false;
}

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.

interceptors = [
    {class="cbsecurity.interceptors.Security", name="ApplicationSecurity", properties={
        useRegex = true, rulesSource = "db", validatorModel = "SecurityService",
        rulesDSN = "myApp", rulesTable = "securityRules", rulesOrderBy = "order asc"
    }}
];

Sample JSON Rules

[
    {
        "whitelist": "user\\.login,user\\.logout,^main.*",
        "securelist": "^user\\.*, ^admin",
        "match": "event",
        "roles": "admin",
        "permissions": "",
        "redirect": "user.login",
        "useSSL": false
    },
    {
        "whitelist": "",
        "securelist": "^shopping",
        "match": "url",
        "roles": "",
        "permissions": "shop,checkout",
        "redirect": "user.login",
        "useSSL": true
    }
]

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

interceptors = [
    {class="cbsecurity.interceptors.Security", name="ApplicationSecurity", properties={
        useRegex = true, rulesSource = "xml", validatorModel = "SecurityService",
        rulesFile = "config/security.xml.cfm"
    }}
];

Declaring the Interceptor

In order to enable ColdBox security you must register the Security interceptor in your parent or other module configuration's interceptors section:

interceptors = [
    { 
        class       = "cbSecurity.interceptors.Security", 
        name        = "ApplicationSecurity", 
        properties  = {
            // Security properties go here.
        }
    }
];

IMPORTANT If you are using SES or URL mappings in your ColdBox 4 application, make sure that you declare the security interceptor after the SES interceptor. Interceptors require order, so security needs for the URL to be translated first. In coldbox 5 SES is handled by the Routing service, so you don't need this SES interceptor.

Global Properties

Property

Type

Required

Default

Description

useRegex

boolean

false

true

By default all secure and white lists are matched using regular expressions. You can disable it if you like and use plain old string matching.

queryChecks

boolean

false

false

Flag that tells the interceptor to validate the columns in the security rules. This makes sure all columns have the same columns. By default it is in relaxed mode so all columns are used.

preEventSecurity

boolean

false

false

This turns on the preEventexecution point that will make sure that before any event is fired internally, that its verified against the security rules. Only use this if you really want to secure all internal events, else this can hinder performance.

ruleSource

string

true

---

Where to look for the rules as described above, this value has to be a choice from the following list xml,json,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 to be retrieved via

validatorIOC

string

false

---

The bean name of the security validator to use for custom validations. The interceptor will ask the IoC module for the bean according to this property

WireBox

How It Works

The basics of the security validation is that you define a set of rules, much like how you define a firewall. Each rule is composed of several elements: securelist, whitelist, roles, permissions, match, and redirect. However, each rule can be expanded by the developer as needed with custom elements, etc. Each rule will be evaluated in the order that it is declared and the follow validation via our flow diagram below.

  1. An incoming request or internal event reaches the first rule and the type of matching is determined: event or URI matching

  2. The incoming event or URI is matched against the whitelist element

    1. If matched, then the event is whitelisted so it continues to the next rule

    2. Else, continue

  3. The incoming event or URI is matched against the securelist element

    1. If not matched, then continue to next rule

    2. Else, continue validation

  4. Do we have a custom validator or not?

    1. Yes, validate against the custom validator

    2. No, validate against ColdFusion's logged in user roles or logged in credentials

  5. If validation fails, redirect the user via the redirect element

Overview

The topic of security is always an interesting and essential one. However, most MVC frameworks offer very loose security when it comes down to an event-oriented architecture. ColdBox Interceptors change this behavior as we have the ability to intercept requests in any point in time during our application executions. With this feature we introduce our Security Module that implements what we call ColdBox Security. With the ColdBox security module you will be able to secure all your public and private ColdBox events from execution and incoming URL patterns. However, as we all know, every application has different requirements and since we are keen on extensibility, the module can be configured to work with whatever security authentications and permissions you might have.

The module wraps itself around the preProcess execution of your request and also (if configured) on any `runEvent()`` methods that get executed internally. The module 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:

  1. (default) By using cflogin, cfloginuser, cflogout

  2. 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 module can then match to these roles via the security rules you have created.

The second method of authentication is based on your custom security logic. You will be able to register a validation object with the module. Once a rule is matched, the module 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.

Features

  • Secures incoming events and URIs

  • If enabled, secure internal event executions via preEvent() interceptions

  • Security rules can exist in:

    • XML File

    • JSON File

    • Database

    • Model/WireBox Object

    • IoC Module

    • ColdBox Cache (Placing a query of rules into the cache)

  • The rules can be configured to use regular expressions or simple snippets

  • Can use ColdFusion authentication security

  • Can Use your own Security Validation by creating a security validation object.

Resources

  • cflogin - http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7db9.html

  • cfloginuser - http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7c5c.html

  • cflogout - http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7c5b.html

  • security tags - http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec17576-7ffc.html#WSc3ff6d0ea77859461172e0811cbec22c24-7705

Security Rules

Now that we have seen all the properties of this module, 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:

Property

Type

Description

match

event or URI

Determines if it needs to match the incoming URI or the incoming event. By default it matches the incoming event.

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 or permission

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. It's 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 (http://gskinner.com/blog/archives/2008/03/regexr_free_onl.html)

  • Determine if you want to secure the incoming URL or event

  • 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 for more granular control

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.