Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,379 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Sandboxed Hooks | ||
Sandboxed hooks cen be used for running untrusted hook code. In each hook file you can use following functions: | ||
|
||
`before(transcactionName, function)` | ||
|
||
`after(transactionName, function)` | ||
|
||
`beforeAll(function)` | ||
|
||
`afterAll(function)` | ||
|
||
`beforeEach(function)` | ||
|
||
`afterEach(function)` | ||
|
||
|
||
- [Transasction]() object is passed as a first argument to the hook function. | ||
- Sandboxed hooks doesn't have asynchronous API. Loading of hooks and each hook is ran in it's own isolated, sandboxed context. | ||
- Hook maximum execution time is 500ms. | ||
- Memory limit is 1M | ||
- Inside each hook you can access `stash` object variable which is passed between contexts of each hook function execution. | ||
- Hook code is evaluated as `use strict` | ||
- Sandboxed mode does not support CoffeScript hooks | ||
|
||
|
||
## Examples | ||
|
||
## CLI switch | ||
|
||
``` | ||
$ dredd blueprint.md http://localhost:3000 --hokfiles path/to/hookfile.js --sandbox | ||
``` | ||
|
||
## JS API | ||
|
||
```javascript | ||
Dredd = require('dredd'); | ||
configuration = { | ||
server: "http://localhost", | ||
options: { | ||
path: "./test/fixtures/single-get.apib", | ||
sandbox: true, | ||
hookfiles: './test/fixtures/sandboxed-hook.js', | ||
} | ||
}; | ||
dredd = new Dredd(configuration); | ||
|
||
dred.run(function(error, stats){ | ||
// your callback code here | ||
}); | ||
``` | ||
|
||
|
||
### Stashing example | ||
```javascript | ||
|
||
after('First action', function(transaction){ | ||
stash['id'] = JSON.parse(transaction.real.response); | ||
}) | ||
|
||
before('Second action', funciton(transaction){ | ||
newBody = JSON.parse(transaction.request.body); | ||
newBody[id] = stash['id']; | ||
transasction.request.body = JSON.stringify(newBody); | ||
}) | ||
|
||
``` | ||
|
||
|
||
### Throwing an exception, hook function context is not shared | ||
```javascript | ||
var myObject = {}; | ||
|
||
after('First action', function(transaction){ | ||
myObject['id'] = JSON.parse(transaction.real.response); | ||
}) | ||
|
||
before('Second action', funciton(transaction){ | ||
newBody = JSON.parse(transaction.request.body); | ||
newBody[id] = myObject['id']; | ||
transasction.request.body = JSON.stringify(newBody); | ||
}) | ||
|
||
``` | ||
|
||
This will explode with: `ReferenceError: myOjcet is not defined` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,93 @@ | ||
path = require 'path' | ||
|
||
require 'coffee-script/register' | ||
path = require 'path' | ||
proxyquire = require('proxyquire').noCallThru() | ||
glob = require 'glob' | ||
fs = require 'fs' | ||
async = require 'async' | ||
|
||
Hooks = require './hooks' | ||
logger = require './logger' | ||
sandboxHooksCode = require './sandbox-hooks-code' | ||
mergeSandboxedHooks = require './merge-sandboxed-hooks' | ||
|
||
addHooks = (runner, transactions, emitter, customConfig) -> | ||
|
||
hooks = new Hooks() | ||
hooks.transactions ?= {} | ||
|
||
addHooks = (runner, transactions, callback) -> | ||
|
||
runner.hooks = new Hooks() | ||
runner.hooks.transactions ?= {} | ||
|
||
customConfigCwd = runner?.configuration?.custom?.cwd | ||
|
||
for transaction in transactions | ||
hooks.transactions[transaction.name] = transaction | ||
runner.hooks.transactions[transaction.name] = transaction | ||
|
||
pattern = runner?.configuration?.options?.hookfiles | ||
if pattern | ||
if not pattern | ||
if runner.configuration.hooksData? | ||
if runner.configuration.options.sandbox == true | ||
if typeof(runner.configuration.hooksData) != 'object' or Array.isArray(runner.configuration.hooksData) != false | ||
return callback(new Error("hooksData option must be an object e.g. {'filename.js':'console.log(\"Hey!\")'}")) | ||
|
||
# run code in sandbox | ||
async.eachSeries Object.keys(runner.configuration.hooksData), (key, next) -> | ||
data = runner.configuration.hooksData[key] | ||
|
||
# run code in sandbox | ||
sandboxHooksCode data, (sandboxError, result) -> | ||
return next(sandboxError) if sandboxError | ||
|
||
# merge stringified hooks | ||
runner.hooks = mergeSandboxedHooks(runner.hooks, result) | ||
next() | ||
|
||
, callback | ||
else | ||
msg = """ | ||
Not sandboxed hooks loading from strings is not implemented, | ||
Sandbox mode must me on for loading hooks from strings" | ||
""" | ||
callback(new Error(msg)) | ||
else | ||
return callback() | ||
else | ||
files = glob.sync pattern | ||
|
||
logger.info 'Found Hookfiles: ' + files | ||
|
||
try | ||
# Running in not sendboxed mode | ||
if not runner.configuration.options.sandbox == true | ||
try | ||
for file in files | ||
proxyquire path.resolve((customConfigCwd or process.cwd()), file), { | ||
'hooks': runner.hooks | ||
} | ||
return callback() | ||
catch error | ||
logger.warn 'Skipping hook loading...' | ||
logger.warn 'Error reading hook files (' + files + ')' | ||
logger.warn 'This probably means one or more of your hookfiles is invalid.' | ||
logger.warn 'Message: ' + error.message if error.message? | ||
logger.warn 'Stack: ' + error.stack if error.stack? | ||
return callback() | ||
|
||
# Running in sendboxed mode | ||
else | ||
logger.info 'Loading hookfiles in sandboxed context' + files | ||
for file in files | ||
proxyquire path.resolve((customConfig?.cwd or process.cwd()), file), { | ||
'hooks': hooks | ||
} | ||
catch error | ||
logger.warn 'Skipping hook loading...' | ||
logger.warn 'Error reading hook files (' + files + ')' | ||
logger.warn 'This probably means one or more of your hookfiles is invalid.' | ||
logger.warn 'Message: ' + error.message if error.message? | ||
logger.warn 'Stack: ' + error.stack if error.stack? | ||
return | ||
|
||
runner.hooks ?= hooks | ||
|
||
return hooks | ||
resolvedPath = path.resolve((customConfigCwd or process.cwd()), file) | ||
|
||
# load hook file content | ||
fs.readFile resolvedPath, 'utf8', (readingError, data) -> | ||
return callback readingError if readingError | ||
|
||
# run code in sandbox | ||
sandboxHooksCode data, (sandboxError, result) -> | ||
return callback(sandboxError) if sandboxError | ||
|
||
runner.hooks = mergeSandboxedHooks(runner.hooks, result) | ||
|
||
callback() | ||
|
||
|
||
module.exports = addHooks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
clone = require 'clone' | ||
|
||
mergeSendboxedHooks = (original, toMerge) -> | ||
|
||
newHooks = clone original | ||
|
||
for target, functions of toMerge | ||
if Array.isArray functions | ||
newHooks[target] = newHooks[target].concat functions | ||
else if typeof(functions) == "object" and not Array.isArray functions | ||
for transactionName, funcArray of functions | ||
newHooks[target][transactionName] = [] if not newHooks[target][transactionName] | ||
newHooks[target][transactionName] = newHooks[target][transactionName].concat funcArray | ||
|
||
return newHooks | ||
|
||
module.exports = mergeSendboxedHooks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{Pitboss} = require 'pitboss' | ||
Hooks = require './hooks' | ||
|
||
sandboxHooksCode = (hooksCode, callback) -> | ||
hooks = new Hooks | ||
wrappedCode = """ | ||
var _hooks = new _Hooks(); | ||
var before = _hooks.before; | ||
var after = _hooks.after; | ||
var beforeAll = _hooks.beforeAll; | ||
var afterAll = _hooks.afterAll; | ||
var beforeEach = _hooks.beforeEach; | ||
var afterEach = _hooks.afterEach; | ||
#{hooksCode} | ||
try { | ||
var output = _hooks.dumpHooksFunctionsToStrings() | ||
} catch(e) { | ||
console.log(e.message) | ||
console.log(e.stack) | ||
throw(e) | ||
} | ||
output | ||
""" | ||
|
||
pitboss = new Pitboss wrappedCode | ||
pitboss.run {libraries: {"_Hooks": '../../../lib/hooks', "console", "console"}}, (err, result) -> | ||
return callback err if err | ||
callback(undefined, result) | ||
|
||
module.exports = sandboxHooksCode |
Oops, something went wrong.