Skip to content
This repository was archived by the owner on Sep 2, 2020. It is now read-only.

Commit f4ab223

Browse files
Adrian-Sanchezelpete
authored andcommitted
feat: Ability to use local override handlers if they exist
cbguard will first check the local handler for an `onAuthenticationFailure` or `onAuthorizationFailure` function. If it exists, it will be called. BREAKING CHANGE: Dropped support for Lucee 4.5 and ACF 11 Special thanks to @Adrian-Sanchez for this PR.
1 parent c7e1aa4 commit f4ab223

File tree

9 files changed

+158
-47
lines changed

9 files changed

+158
-47
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ jdk:
55
env:
66
matrix:
77
- ENGINE=lucee@5
8-
- ENGINE=lucee@4.5
98
- ENGINE=adobe@2018
109
- ENGINE=adobe@2016
11-
- ENGINE=adobe@11
1210
before_install:
1311
- curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
1412
- sudo echo "deb http://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list

README.md

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# cbguard
22

3-
[![Master Branch Build Status](https://img.shields.io/travis/elpete/cbguard/master.svg?style=flat-square&label=master)](https://travis-ci.org/elpete/cbguard)
3+
[![Master Branch Build Status](https://img.shields.io/travis/coldbox-modules/cbguard/master.svg?style=flat-square&label=master)](https://travis-ci.org/coldbox-modules/cbguard)
44

55
## Annotation driven guards for authentication and authorization in ColdBox
66

@@ -93,15 +93,15 @@ When a user is denied access to a action, an event of your choosing is executed
9393

9494
This is the event that is executed when the user is not logged in and is attempting to execute a secured action, whether or not that handler or action has permissions.
9595

96-
1. `authorizationOverrideEvent` (Default: same as `authenticationOverrideEvent`)
96+
2. `authorizationOverrideEvent` (Default: same as `authenticationOverrideEvent`)
9797

9898
This is the event that is executed when the user is logged in and is attempting to execute a secured action but does not have the requisite permissions.
9999

100-
1. `authenticationAjaxOverrideEvent` (Default: `Main.onAuthenticationFailure`)
100+
3. `authenticationAjaxOverrideEvent` (Default: `Main.onAuthenticationFailure`)
101101

102102
This is the event that is executed when the user is not logged in and is attempting to execute a secured action via ajax (`event.isAjax()`), whether or not that handler or action has permissions. By default, this will execute the same action that is configured for `authenticationOverrideEvent`.
103103

104-
1. `authorizationAjaxOverrideEvent` (Default: same as `authorizationOverrideEvent`)
104+
4. `authorizationAjaxOverrideEvent` (Default: same as `authorizationOverrideEvent`)
105105

106106
This is the event that is executed when the user is logged in and is attempting to execute a secured action via ajax (`event.isAjax()`) but does not have the requisite permissions. By default, this will execute the same action that is configured for `authorizationOverrideEvent`.
107107

@@ -160,7 +160,7 @@ moduleSettings = {
160160
The default `authenticationService` for `cbguard` is `AuthenticationService@cbauth`. `cbauth` follows the `AuthenticationServiceInterface` out of the box.
161161

162162

163-
### Advanced Setup
163+
### config/ColdBox.cfc Settings
164164

165165
You can change the method names called on the `AuthenticationService` and the returned `User` if you need to. We highly discourage this use case, as it makes it harder to utilize the `cbguard` conventions across projects. However, should the need arise, you can modify the method names as follows:
166166

@@ -219,6 +219,43 @@ component {
219219
}
220220
```
221221

222+
### Local Handler Overrides
223+
224+
If an `onAuthenticationFailure` or `onAuthorizationFailure` method exists on the handler being
225+
secured, it will be used in the case of an authentication or authorization failure event,
226+
respectively.
227+
228+
```
229+
// handlers/Admin.cfc
230+
component secured {
231+
232+
function index( event, rc, prc ) {
233+
event.setView( "admin/index" );
234+
}
235+
236+
function secret( event, rc, prc ) secured="superadmin" {
237+
event.setView( "admin/secret" );
238+
}
239+
240+
function onAuthenticationFailure( event, rc, prc ) {
241+
relocate( "/login" );
242+
}
243+
244+
function onAuthenticationFailure( event, rc, prc ) {
245+
flash.put( "authorizationError", "You don't have the correct permissions to access that resource." );
246+
redirectBack(); // from the redirectBack module
247+
}
248+
249+
}
250+
```
251+
252+
### Override Order
253+
cbguard will process your authorization and authentication failures in the following order:
254+
1. Inline handler methods (`onAuthenticationFailure` & `onAuthorizationFailure` within your handlers).
255+
2. cbguard settings in the ModuleConfig of the handler's module. (Overrides in `modules_app/api/ModuleConfig.cfc` when the handler is in the module, i.e. `modules_app/api/handlers/Main.cfc`.)
256+
3. Overrides in `config/ColdBox.cfc` using `moduleSettings`.
257+
4. Default settings for the module.
258+
222259
## `autoRegisterInterceptor`
223260

224261
If you need more control over the order of your interceptors you can

interceptors/SecuredEventInterceptor.cfc

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,17 @@ component extends="coldbox.system.Interceptor"{
7575
}
7676

7777
if ( ! invoke( props.authenticationService, props.methodNames[ "isLoggedIn" ] ) ) {
78+
// Override the coldbox.cfc global onAuthenticationFailure if it exists in the handler.
79+
// Per docs, they will override for Ajax requests also.
7880
var eventType = event.isAjax() ? "authenticationAjaxOverrideEvent" : "authenticationOverrideEvent";
79-
var relocateEvent = props[ eventType ];
81+
var relocateEvent = getOverrideEvent( handlerMetadata, event, props, eventType );
8082
var overrideAction = props.overrideActions[ eventType ];
83+
84+
// If the override is within the same handler that is being secured,
85+
// we have to override the event instead of relocating. Prevents a circle of death.
86+
if ( event.getCurrentHandler() == handlerService.getHandlerBean( relocateEvent ).getHandler() ) {
87+
overrideAction = "override";
88+
}
8189
switch ( overrideAction ) {
8290
case "relocate":
8391
relocate( relocateEvent );
@@ -111,9 +119,17 @@ component extends="coldbox.system.Interceptor"{
111119
}
112120
}
113121

122+
// At this point, we know the user did NOT have any of the required permissions,
123+
// so we will fire the appropriate authorization failure events
114124
var eventType = event.isAjax() ? "authorizationAjaxOverrideEvent" : "authorizationOverrideEvent";
115-
var relocateEvent = props[ eventType ];
125+
var relocateEvent = getOverrideEvent( handlerMetadata, event, props, eventType );
116126
var overrideAction = props.overrideActions[ eventType ];
127+
128+
// If the override is within the same handler that is being secured,
129+
// we have to override the event instead of relocating. Prevents a circle of death.
130+
if ( event.getCurrentHandler() == handlerService.getHandlerBean( relocateEvent ).getHandler() ) {
131+
overrideAction = 'override';
132+
}
117133
switch ( overrideAction ) {
118134
case "relocate":
119135
relocate( relocateEvent );
@@ -168,10 +184,9 @@ component extends="coldbox.system.Interceptor"{
168184
if ( ! structKeyExists( targetActionMetadata, "secured" ) || targetActionMetadata.secured == false ) {
169185
return false;
170186
}
171-
172187
if ( ! invoke( props.authenticationService, props.methodNames[ "isLoggedIn" ] ) ) {
173188
var eventType = event.isAjax() ? "authenticationAjaxOverrideEvent" : "authenticationOverrideEvent";
174-
var relocateEvent = props[ eventType ];
189+
var relocateEvent = getOverrideEvent( handlerMetadata, event, props, eventType );
175190
var overrideAction = props.overrideActions[ eventType ];
176191
switch ( overrideAction ) {
177192
case "relocate":
@@ -206,9 +221,12 @@ component extends="coldbox.system.Interceptor"{
206221
}
207222
}
208223

224+
// Override the coldbox.cfc global onAuthorizationFailure if it exists in the handler.
225+
// Per docs, they will override for Ajax requests also.
209226
var eventType = event.isAjax() ? "authorizationAjaxOverrideEvent" : "authorizationOverrideEvent";
210-
var relocateEvent = props[ eventType ];
227+
var relocateEvent = getOverrideEvent( handlerMetadata, event, props, eventType );
211228
var overrideAction = props.overrideActions[ eventType ];
229+
212230
switch ( overrideAction ) {
213231
case "relocate":
214232
relocate( relocateEvent );
@@ -224,5 +242,25 @@ component extends="coldbox.system.Interceptor"{
224242
}
225243
return true;
226244
}
227-
245+
/**
246+
* Override the coldbox.cfc global on[eventType]Failure if it exists in the handler.
247+
*/
248+
private function getOverrideEvent( handlerMetadata, event, props, eventType ) {
249+
var handlerOverrides = arrayFilter( arguments.handlerMetadata.functions, function( func ) {
250+
// In case some other override comes up in the future, using switch
251+
switch( eventType ) {
252+
case "authenticationOverrideEvent":
253+
case "authenticationAjaxOverrideEvent":
254+
return func.name == "onAuthenticationFailure";
255+
case "authorizationOverrideEvent":
256+
case "authorizationAjaxOverrideEvent":
257+
return func.name == "onAuthorizationFailure";
258+
default:
259+
return false;
260+
}
261+
} );
262+
return handlerOverrides.isEmpty() ?
263+
arguments.props[ eventType ] :
264+
arguments.event.getCurrentHandler() & "." & handlerOverrides[ 1 ].name;
265+
}
228266
}

server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"app":{
3-
"cfengine":"adobe@11"
3+
"cfengine":"adobe@2016"
44
}
55
}

tests/resources/ModuleIntegrationSpec.cfc

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
component extends="coldbox.system.testing.BaseTestCase" {
22

3+
property name="authenticationService" inject="AuthenticationService";
4+
35
function beforeAll() {
46
super.beforeAll();
57

68
getController().getModuleService()
79
.registerAndActivateModule( "cbguard", "testingModuleRoot" );
10+
11+
getWireBox().autowire( this );
812
}
913

1014
/**
11-
* @beforeEach
12-
*/
15+
* @beforeEach
16+
*/
1317
function setupIntegrationTest() {
1418
setup();
1519
}
1620

21+
/**
22+
* @beforeEach
23+
*/
24+
function autoLogOut() {
25+
authenticationService.logout();
26+
}
27+
1728
function withSwappedSettings( applyOverrides, callback ) {
1829
var newSettings = duplicate( getController().getConfigSettings().modules.cbguard.settings );
1930
applyOverrides( newSettings );
@@ -28,4 +39,25 @@ component extends="coldbox.system.testing.BaseTestCase" {
2839
}
2940
}
3041

42+
private function createUser( overrides = {} ) {
43+
var props = {
44+
id = 1,
45+
email = "johndoe@example.com",
46+
username = "johndoe",
47+
permissions = []
48+
};
49+
structAppend( props, overrides, true );
50+
return tap( getInstance( "User" ), function( user ) {
51+
user.setId( props.id );
52+
user.setEmail( props.email );
53+
user.setUsername( props.username );
54+
user.setPermissions( props.permissions );
55+
} );
56+
}
57+
58+
private function tap( variable, callback ) {
59+
callback( variable );
60+
return variable;
61+
}
62+
3163
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
component secured {
2+
3+
function index( event, rc, prc ) {
4+
event.noRender();
5+
}
6+
7+
function secret( event, rc, prc ) secured="superadmin" {
8+
event.noRender();
9+
}
10+
11+
function onAuthenticationFailure( event, rc, prc ) {
12+
event.noRender();
13+
}
14+
15+
function onAuthorizationFailure( event, rc, prc ) {
16+
event.noRender();
17+
}
18+
19+
}

tests/specs/integration/APIRedirectsSpec.cfc

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
11
component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
22

3-
property name="authenticationService" inject="AuthenticationService";
43
property name="interceptorService" inject="coldbox:interceptorService";
54

6-
function beforeAll() {
7-
super.beforeAll();
8-
getWireBox().autowire( this );
9-
}
10-
115
function run() {
126
describe( "API Redirect Specs", function() {
13-
beforeEach( function() {
14-
authenticationService.logout();
15-
} );
16-
177
it( "redirects the user to the normal authentication failure event if no API authentication event is set", function() {
188
var securedEventInterceptor = interceptorService.getInterceptor( "SecuredEventInterceptor" );
199
var authenticationFailureRedirect = securedEventInterceptor.getProperty( "authenticationOverrideEvent" );

tests/specs/integration/AuthorizationSpec.cfc

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,4 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
7373
} );
7474
}
7575

76-
private function createUser( overrides = {} ) {
77-
var props = {
78-
id = 1,
79-
email = "johndoe@example.com",
80-
username = "johndoe",
81-
permissions = []
82-
};
83-
structAppend( props, overrides, true );
84-
return tap( getInstance( "User" ), function( user ) {
85-
user.setId( props.id );
86-
user.setEmail( props.email );
87-
user.setUsername( props.username );
88-
user.setPermissions( props.permissions );
89-
} );
90-
}
91-
92-
private function tap( variable, callback ) {
93-
callback( variable );
94-
return variable;
95-
}
96-
9776
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
2+
3+
function run() {
4+
describe( "local handler overrides", function() {
5+
it( "looks for a local onAuthenticationFailure method on the handler for authentication failure events first", function() {
6+
var event = execute( event = "LocalOverrides.index" );
7+
expect( event.getValue( "event", "" ) ).toBe( "LocalOverrides.onAuthenticationFailure" );
8+
} );
9+
10+
it( "looks for a local onAuthorizationFailure method on the handler for authentication failure events first", function() {
11+
authenticationService.login( createUser( { permissions = [] } ) );
12+
var event = execute( event = "LocalOverrides.secret" );
13+
expect( event.getValue( "relocate_event", "" ) ).toBe( "LocalOverrides.onAuthorizationFailure" );
14+
} );
15+
} );
16+
}
17+
18+
}

0 commit comments

Comments
 (0)