Skip to content

Commit

Permalink
feat(cors): Dynamically determine allowed origins
Browse files Browse the repository at this point in the history
  • Loading branch information
elpete committed Aug 8, 2019
1 parent ee5e50c commit 97cae05
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 16 deletions.
4 changes: 3 additions & 1 deletion ModuleConfig.cfc
Expand Up @@ -6,7 +6,9 @@ component {

function configure() {
settings = {
allowOrigins = "*",
allowOrigins = function( event ) {
return event.getHTTPHeader( "Origin", "*" );
},
allowMethods = [ "DELETE", "GET", "PATCH", "POST", "PUT", "OPTIONS" ],
allowHeaders = [ "Content-Type", "X-Auth-Token", "Origin", "Authorization" ],
maxAge = 60 * 60 * 24, // 1 day
Expand Down
21 changes: 15 additions & 6 deletions interceptors/CORS.cfc
Expand Up @@ -38,8 +38,11 @@ component {
}

var allowedOrigins = settings.allowOrigins;
if ( ! isSimpleValue( settings.allowOrigins ) ) {
allowedOrigins = arrayToList( settings.allowOrigins, ", " );
if ( isClosure( allowedOrigins ) || isCustomFunction( allowedOrigins ) ) {
allowedOrigins = allowedOrigins( event );
}
if ( ! isSimpleValue( allowedOrigins ) ) {
allowedOrigins = arrayToList( allowedOrigins, ", " );
}
log.debug( "Setting the 'Access-Control-Allow-Origin' header to #allowedOrigins#." );
event.setHTTPHeader( name = "Access-Control-Allow-Origin", value = allowedOrigins );
Expand Down Expand Up @@ -99,8 +102,11 @@ component {

if ( ! structKeyExists( currentHeaders, "Access-Control-Allow-Origin" ) ) {
var allowedOrigins = settings.allowOrigins;
if ( ! isSimpleValue( settings.allowOrigins ) ) {
allowedOrigins = arrayToList( settings.allowOrigins, ", " );
if ( isClosure( allowedOrigins ) || isCustomFunction( allowedOrigins ) ) {
allowedOrigins = allowedOrigins( event );
}
if ( ! isSimpleValue( allowedOrigins ) ) {
allowedOrigins = arrayToList( allowedOrigins, ", " );
}
log.debug( "Setting the 'Access-Control-Allow-Origin' header to #allowedOrigins#." );
event.setHTTPHeader( name = "Access-Control-Allow-Origin", value = allowedOrigins );
Expand Down Expand Up @@ -141,11 +147,14 @@ component {
}

var allowedOrigins = settings.allowOrigins;
if ( isClosure( allowedOrigins ) || isCustomFunction( allowedOrigins ) ) {
allowedOrigins = allowedOrigins( event );
}
if ( isSimpleValue( allowedOrigins ) ) {
if ( settings.allowOrigins == "*" ) {
if ( allowedOrigins == "*" ) {
return true;
}
allowedOrigins = listToArray( settings.allowOrigins, "," );
allowedOrigins = listToArray( allowedOrigins, "," );
}
return arrayContains( allowedOrigins, event.getHTTPHeader( "Origin", "" ) );
}
Expand Down
105 changes: 96 additions & 9 deletions tests/specs/integration/CORSSpec.cfc
Expand Up @@ -19,27 +19,33 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
prepareMock( getRequestContext() )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.index", renderResults = true );

var responseHeaders = getHeaders( event );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin", "The 'Access-Control-Allow-Origin' should be set." );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*", "The 'Access-Control-Allow-Origin' should be set to '*'." );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com", "The 'Access-Control-Allow-Origin' should be set to 'example.com'." );
} );

it( "sets the correct headers for an options request", function() {
prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" );
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );;
var event = execute( route = "/", renderResults = true );

var responseHeaders = getHeaders( event );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Methods" );
expect( responseHeaders[ "Access-Control-Allow-Methods" ] ).toBe( "DELETE, GET, PATCH, POST, PUT, OPTIONS" );
Expand All @@ -59,13 +65,16 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "GET" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

var responseHeaders = getHeaders( event );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Credentials" );
expect( responseHeaders[ "Access-Control-Allow-Credentials" ] ).toBe( "true" );
Expand All @@ -78,6 +87,45 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
it( "can configure the allowed origins", function() {
getController().getConfigSettings().modules.cors.settings.allowOrigins = "example.com";

prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

var responseHeaders = getHeaders( event );

expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );
} );

it( "does not add headers if the origin is not allowed", function() {
getController().getConfigSettings().modules.cors.settings.allowOrigins = "example2.com";

prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

var responseHeaders = getHeaders( event );

expect( responseHeaders ).toBeEmpty( "The response should have no headers because it is not allowed" );
} );

it( "can accept a function for the allowed origins", function() {
getController().getConfigSettings().modules.cors.settings.allowOrigins = function( event ) {
return event.getHTTPHeader( "Origin", "" );
};

prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
Expand Down Expand Up @@ -114,6 +162,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

Expand All @@ -130,6 +181,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

Expand All @@ -146,6 +200,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

Expand All @@ -162,6 +219,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

Expand All @@ -181,6 +241,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$results( "Content-Type, X-Auth-Token, Origin" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( route = "/", renderResults = true );

Expand All @@ -197,6 +260,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.index", renderResults = true );
var responseHeaders = getHeaders( event );
Expand All @@ -208,18 +274,24 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.doSomething", renderResults = true );
var responseHeaders = getHeaders( event );
expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );

setup();

prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.doSomethingElse", renderResults = true );
var responseHeaders = getHeaders( event );
Expand All @@ -236,6 +308,9 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.index", renderResults = true );
var responseHeaders = getHeaders( event );
Expand All @@ -247,23 +322,29 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.doSomething", renderResults = true );
var responseHeaders = getHeaders( event );
expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );

setup();

prepareMock( getRequestContext() )
.$( "getHTTPMethod", "OPTIONS" )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" );
var event = execute( event = "Main.doSomethingElse", renderResults = true );
var responseHeaders = getHeaders( event );
expect( responseHeaders ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeaders[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );
} );

it( "skips over events that are cached", function() {
Expand All @@ -273,17 +354,23 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" )
.$( "getEventCacheableKey", {} );
var eventOne = execute( event = "Main.cached", renderResults = true );
var responseHeadersOne = getHeaders( eventOne );
expect( responseHeadersOne ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeadersOne[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeadersOne[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );

setup();
prepareMock( getRequestContext() )
.$( "getHTTPHeader" )
.$args( "Origin", "" )
.$results( "example.com" )
.$( "getHTTPHeader" )
.$args( "Origin", "*" )
.$results( "example.com" )
.$( "getEventCacheableKey", {
"provider": "template",
"cacheable": true,
Expand All @@ -295,7 +382,7 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
var eventTwo = execute( event = "Main.cached", renderResults = true );
var responseHeadersTwo = getHeaders( eventTwo );
expect( responseHeadersTwo ).toHaveKey( "Access-Control-Allow-Origin" );
expect( responseHeadersTwo[ "Access-Control-Allow-Origin" ] ).toBe( "*" );
expect( responseHeadersTwo[ "Access-Control-Allow-Origin" ] ).toBe( "example.com" );
} );
} );
}
Expand Down

0 comments on commit 97cae05

Please sign in to comment.