We are excited to bring you another release for cbSecurity packed with security goodness: version 2.3.0. This version focuses on security contexts from ANY layer of a ColdBox application. It will also enhance your functional skills as well, as it introduces some nice semantics for securing your code.

# Install
install cbsecurity

# Update
update cbsecurity

What's New With 2.3.0

This release focuses on bringing a strong focus on protecting every development layer of a ColdBox application. It introduces the cbSecurity model that can be used by any layer and provides you with a great functional API to secure your code.

Explicit Authorizations

There will be times where you will need authorization checks outside of the incoming request rules or the handler annotations. This can be from within interceptors, models, layouts or even views. For this, we have provided the cbSecurity model so you can do explicit authorization checks anywhere you like.

cbSecurity Model

You can inject our model or you can use our handy cbsecure() mixin (interceptors/handlers/layouts/views) and then call the appropriate security functions:

// Mixin: Handlers/Interceptors/Layouts/Views
cbsecure()

// Injection
property name="cbSecurity" inject="@cbSecurity"
All security methods will call the application's configured Authentication Service to retrieve the currently logged in user. If the user is not logged in an immediate `NoUserLoggedIn` exception will be thrown by all methods.

The secure() Methods

Now that you have access to the model, you can use the following method to verify explicit permissions and authorize access. This method will throw an exception if the user does not validate the incoming permissions context (NotAuthorized).

// Verify the currently logged in user has those permission, 
// else throw a NotAuthorized exception
cbSecurity.secure( permissions, [message] );
cbsecure().secure( permissions, [message] );
  • The permission can be an array, string or list of the permissions to validate.
  • The message is a custom error message to be used in the message string of the exception thrown.

You also have two more authorization methods that will verify certain permission conditions for you:

// Authorize that the user has ALL of the incoming permissions
cbSecurity.secureAll( permissions, [message] );
// Authorize that the user has NONE of the incoming permissions
cbSecurity.secureNone( permissions, [message] );

Conditional Authorizations Using when()

There are also cases where you want to execute a piece of code by determining if the user has access to do so. For example, only a USER_ADMIN can change people's roles or you want to filter some data for certain users. For this, we have created the when() method with the following signature:

when( permissions, success, fail )
  • The permissions is a permission array or list that will be Or'ed
  • The success is a closure/lambda or UDF that will execute if the permissions validate.
  • The fail is a closure/lambda or UDF that will execute if the permissions DID not validate, much like an else statement

Both closures/functions takes in a user which is the currently authenticated user, the called in permissions and can return anything.

// Lambda approach
( user, permissions ) => { 
    // your code here
};
// UDF/Closure
function( user, permissions ){ 
    // your code here
}

You can also chain the when() calls if needed, to create beautiful security contexts. So if we go back to our admin examples, we can do something like this:

var oAuthor = authorService.getOrFail( rc.authorId );
prc.data = userService.getData();

// Run Security Contexts
cbSecure()
    // Only user admin can change to the incoming role
    .when( "USER_ADMIN", ( user ) => oAuthor.setRole( roleService.get( rc.roleID ) ) )
    // The system admin can set a super admin
    .when( "SYSTEM_ADMIN", ( user ) => oAuthor.setRole( roleService.getSystemAdmin() ) )
    // Filter the data to be shown to the user
    .when( "USER_READ_ONLY", ( user ) => prc.data.filter( ( i ) => !i.isClassified ) )

// Calling with a fail closure
cbSecurity.when(
    "USER_ADMIN",
    ( user ) => user.setRole( "admin" ), //success
    ( user ) => relocate( "Invaliduser" ) //fail
);

We have also added the following whenX() methods to serve your needs when evaluating the permissions:

// When all permissions must exist in the user
whenAll( permissoins, success, fail)
// When none of the permissions exist in the user
whenNone( permissions, success, fail )

Verification Methods

If you just want to validate if a user has certain permissions or maybe no permissions at all or if a passed user is the same as the logged in user, then you can use the following boolean methods that only do verification.

Please note that you could potentially do these type of methods by leveraging the currently logged in user and it's `hasPermission()` method. However, these methods provide abstraction and can easily be mocked!
// Checks the user has one or at least one permission if the 
// permission is a list or array
boolean cbSecurity.has( permission );
// The user must have ALL the permissions
boolean cbSecurity.all( permission );
// The user must NOT have any of the permissions
boolean cbSecurity.none( permission );
// Verify if the passed in user is the same as the logged in user
boolean cbSecurity.sameUser( user );

These are great to have a unified and abstracted way to verifying permissions or if the passed user is the same as the logged in user. Here are some examples:

View Layer


    This is only visible to user admins!



    Impersonate User



    This is You!

Other Layers:

if( cbSecurity.has( "PERM" ) ){
    auditUser();
}

if( cbSecurity.sameUser( prc.incomingUser ) ){
    // you can change your gravatar
}
Please note that we do user equality by calling the `getId()` method of the authenticated user and the incoming user. This is part of our `IAuthUser` interface requirements.

Authorization Contexts

There are also times where you need to validate custom conditions and block access to certain areas. This way, you can implement your own custom security logic and leverage cbSecurity for blockage. You will accomplish this via the secureWhen() method:

secureWhen( context, [errorMessage] )

The context can be a closure/lambda/udf or a boolean evaluation:

// Using as a closure/lambda
cbSecurity.secureWhen( ( user ) => !user.isConfirmed() )
cbSecurity.secureWhen( ( user ) => !oEntry.canPublish( user ) )

// Using a boolean evaluation
cbSecurity.secureWhen( cbSecurity.none( "AUTHOR_ADMIN" ) && !cbSecurity.sameUser( oAuthor )  )
cbSecurity.whenNone( "AUTHOR_ADMIN", ( user ) => relocate() );

The closure/udf will receive the currently authenticated user as the first argument.

( user ) => {}
function( user );

Securing Views

You can also use our handy event.secureView() method in the request context to pivot between views according to user permissions.

cbSecurity injects the `secureView()` method into the request context via the `preProcess` interception point.
event.secureView( permissions, successView, failView )

This will allow you to set the successView if the user has the permissions or the failView if they don't.

cbSecurity Method Summary

Blocking Methods

When certain permission context is met, if not throws NotAuthorized

  • secure( permissions, [message] )
  • secureAll( permissions, [message] )
  • secureNone( permissions, [message] )
  • secureWhen( context, [message] )
  • guard() alias to secure()

Action Context Methods

When certain permission context is met, execute the success function/closure, else if a fail closure is defined, execute that instead.

  • when( permissions, success, fail )
  • whenAll( permissions, success, fail )
  • whenNone( permissions, success, fail )

Verification Methods

Verify permissions or user equality

  • has( permissions ):boolean
  • all( permissions ):boolean
  • none( permissions ):boolean
  • sameUser( user ):boolean

Request Context Methods

  • secureView( permissions, successView, failView )