mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
commit
389fb7d1e0
13 changed files with 366 additions and 82 deletions
|
@ -4,10 +4,9 @@ var path = require('path'),
|
||||||
when = require('when'),
|
when = require('when'),
|
||||||
appProxy = require('./proxy'),
|
appProxy = require('./proxy'),
|
||||||
config = require('../config'),
|
config = require('../config'),
|
||||||
|
AppSandbox = require('./sandbox'),
|
||||||
loader;
|
loader;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Get a relative path to the given apps root, defaults
|
// Get a relative path to the given apps root, defaults
|
||||||
// to be relative to __dirname
|
// to be relative to __dirname
|
||||||
function getAppRelativePath(name, relativeTo) {
|
function getAppRelativePath(name, relativeTo) {
|
||||||
|
@ -16,10 +15,16 @@ function getAppRelativePath(name, relativeTo) {
|
||||||
return path.relative(relativeTo, path.join(config.paths().appPath, name));
|
return path.relative(relativeTo, path.join(config.paths().appPath, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load apps through a psuedo sandbox
|
||||||
|
function loadApp(appPath) {
|
||||||
|
var sandbox = new AppSandbox();
|
||||||
|
|
||||||
|
return sandbox.loadApp(appPath);
|
||||||
|
}
|
||||||
|
|
||||||
function getAppByName(name) {
|
function getAppByName(name) {
|
||||||
// Grab the app class to instantiate
|
// Grab the app class to instantiate
|
||||||
var AppClass = require(getAppRelativePath(name)),
|
var AppClass = loadApp(getAppRelativePath(name)),
|
||||||
app;
|
app;
|
||||||
|
|
||||||
// Check for an actual class, otherwise just use whatever was returned
|
// Check for an actual class, otherwise just use whatever was returned
|
||||||
|
|
91
core/server/apps/sandbox.js
Normal file
91
core/server/apps/sandbox.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
Module = require('module'),
|
||||||
|
_ = require('underscore');
|
||||||
|
|
||||||
|
function AppSandbox(opts) {
|
||||||
|
this.opts = _.defaults(opts || {}, AppSandbox.defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSandbox.prototype.loadApp = function loadAppSandboxed(appPath) {
|
||||||
|
var appFile = require.resolve(appPath),
|
||||||
|
appBase = path.dirname(appFile);
|
||||||
|
|
||||||
|
this.opts.appRoot = appBase;
|
||||||
|
|
||||||
|
return this.loadModule(appPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) {
|
||||||
|
// Set loaded modules parent to this
|
||||||
|
var self = this,
|
||||||
|
moduleDir = path.dirname(modulePath),
|
||||||
|
parentModulePath = self.opts.parent || module.parent,
|
||||||
|
appRoot = self.opts.appRoot || moduleDir,
|
||||||
|
currentModule,
|
||||||
|
nodeRequire;
|
||||||
|
|
||||||
|
// Resolve the modules path
|
||||||
|
modulePath = Module._resolveFilename(modulePath, parentModulePath);
|
||||||
|
|
||||||
|
// Instantiate a Node Module class
|
||||||
|
currentModule = new Module(modulePath, parentModulePath);
|
||||||
|
|
||||||
|
// Grab the original modules require function
|
||||||
|
nodeRequire = currentModule.require;
|
||||||
|
|
||||||
|
// Set a new proxy require function
|
||||||
|
currentModule.require = function requireProxy(module) {
|
||||||
|
// check whitelist, plugin config, etc.
|
||||||
|
if (_.contains(self.opts.blacklist, module)) {
|
||||||
|
throw new Error("Unsafe App require: " + module);
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstTwo = module.slice(0, 2),
|
||||||
|
resolvedPath,
|
||||||
|
relPath,
|
||||||
|
innerBox,
|
||||||
|
newOpts;
|
||||||
|
|
||||||
|
// Load relative modules with their own sandbox
|
||||||
|
if (firstTwo === './' || firstTwo === '..') {
|
||||||
|
// Get the path relative to the modules directory
|
||||||
|
resolvedPath = path.resolve(moduleDir, module);
|
||||||
|
|
||||||
|
// Check relative path from the appRoot for outside requires
|
||||||
|
relPath = path.relative(appRoot, resolvedPath);
|
||||||
|
if (relPath.slice(0, 2) === '..') {
|
||||||
|
throw new Error('Unsafe App require: ' + relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign as new module path
|
||||||
|
module = resolvedPath;
|
||||||
|
|
||||||
|
// Pass down the same options
|
||||||
|
newOpts = _.extend({}, self.opts);
|
||||||
|
|
||||||
|
// Make sure the appRoot and parent are appropriate
|
||||||
|
newOpts.appRoot = appRoot;
|
||||||
|
newOpts.parent = currentModule.parent;
|
||||||
|
|
||||||
|
// Create the inner sandbox for loading this module.
|
||||||
|
innerBox = new AppSandbox(newOpts);
|
||||||
|
|
||||||
|
return innerBox.loadModule(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original require method for white listed named modules
|
||||||
|
return nodeRequire.call(currentModule, module);
|
||||||
|
};
|
||||||
|
|
||||||
|
currentModule.load(currentModule.id);
|
||||||
|
|
||||||
|
return currentModule.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
AppSandbox.defaults = {
|
||||||
|
blacklist: ['knex', 'fs', 'http', 'sqlite3', 'pg', 'mysql', 'ghost']
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = AppSandbox;
|
|
@ -1,79 +0,0 @@
|
||||||
/*globals describe, beforeEach, afterEach, before, it*/
|
|
||||||
var should = require('should'),
|
|
||||||
sinon = require('sinon'),
|
|
||||||
_ = require("underscore"),
|
|
||||||
helpers = require('../../server/helpers'),
|
|
||||||
filters = require('../../server/filters'),
|
|
||||||
|
|
||||||
// Stuff we are testing
|
|
||||||
appProxy = require('../../server/apps/proxy');
|
|
||||||
|
|
||||||
describe('App Proxy', function () {
|
|
||||||
|
|
||||||
var sandbox,
|
|
||||||
fakeApi;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
|
|
||||||
fakeApi = {
|
|
||||||
posts: {
|
|
||||||
browse: sandbox.stub(),
|
|
||||||
read: sandbox.stub(),
|
|
||||||
edit: sandbox.stub(),
|
|
||||||
add: sandbox.stub(),
|
|
||||||
destroy: sandbox.stub()
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
browse: sandbox.stub(),
|
|
||||||
read: sandbox.stub(),
|
|
||||||
edit: sandbox.stub()
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
all: sandbox.stub()
|
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
destroy: sandbox.stub(),
|
|
||||||
add: sandbox.stub()
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
browse: sandbox.stub(),
|
|
||||||
read: sandbox.stub(),
|
|
||||||
add: sandbox.stub()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates a ghost proxy', function () {
|
|
||||||
should.exist(appProxy.filters);
|
|
||||||
appProxy.filters.register.should.equal(filters.registerFilter);
|
|
||||||
appProxy.filters.unregister.should.equal(filters.unregisterFilter);
|
|
||||||
|
|
||||||
should.exist(appProxy.helpers);
|
|
||||||
appProxy.helpers.register.should.equal(helpers.registerThemeHelper);
|
|
||||||
appProxy.helpers.registerAsync.should.equal(helpers.registerAsyncThemeHelper);
|
|
||||||
|
|
||||||
should.exist(appProxy.api);
|
|
||||||
|
|
||||||
should.exist(appProxy.api.posts);
|
|
||||||
should.not.exist(appProxy.api.posts.edit);
|
|
||||||
should.not.exist(appProxy.api.posts.add);
|
|
||||||
should.not.exist(appProxy.api.posts.destroy);
|
|
||||||
|
|
||||||
should.not.exist(appProxy.api.users);
|
|
||||||
|
|
||||||
should.exist(appProxy.api.tags);
|
|
||||||
|
|
||||||
should.exist(appProxy.api.notifications);
|
|
||||||
should.not.exist(appProxy.api.notifications.destroy);
|
|
||||||
|
|
||||||
should.exist(appProxy.api.settings);
|
|
||||||
should.not.exist(appProxy.api.settings.browse);
|
|
||||||
should.not.exist(appProxy.api.settings.add);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
157
core/test/unit/apps_spec.js
Normal file
157
core/test/unit/apps_spec.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*globals describe, beforeEach, afterEach, before, it*/
|
||||||
|
var fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
should = require('should'),
|
||||||
|
sinon = require('sinon'),
|
||||||
|
_ = require("underscore"),
|
||||||
|
helpers = require('../../server/helpers'),
|
||||||
|
filters = require('../../server/filters'),
|
||||||
|
|
||||||
|
// Stuff we are testing
|
||||||
|
appProxy = require('../../server/apps/proxy'),
|
||||||
|
AppSandbox = require('../../server/apps/sandbox');
|
||||||
|
|
||||||
|
describe('Apps', function () {
|
||||||
|
|
||||||
|
var sandbox,
|
||||||
|
fakeApi;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
|
fakeApi = {
|
||||||
|
posts: {
|
||||||
|
browse: sandbox.stub(),
|
||||||
|
read: sandbox.stub(),
|
||||||
|
edit: sandbox.stub(),
|
||||||
|
add: sandbox.stub(),
|
||||||
|
destroy: sandbox.stub()
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
browse: sandbox.stub(),
|
||||||
|
read: sandbox.stub(),
|
||||||
|
edit: sandbox.stub()
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
all: sandbox.stub()
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
destroy: sandbox.stub(),
|
||||||
|
add: sandbox.stub()
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
browse: sandbox.stub(),
|
||||||
|
read: sandbox.stub(),
|
||||||
|
add: sandbox.stub()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Proxy', function () {
|
||||||
|
it('creates a ghost proxy', function () {
|
||||||
|
should.exist(appProxy.filters);
|
||||||
|
appProxy.filters.register.should.equal(filters.registerFilter);
|
||||||
|
appProxy.filters.unregister.should.equal(filters.unregisterFilter);
|
||||||
|
|
||||||
|
should.exist(appProxy.helpers);
|
||||||
|
appProxy.helpers.register.should.equal(helpers.registerThemeHelper);
|
||||||
|
appProxy.helpers.registerAsync.should.equal(helpers.registerAsyncThemeHelper);
|
||||||
|
|
||||||
|
should.exist(appProxy.api);
|
||||||
|
|
||||||
|
should.exist(appProxy.api.posts);
|
||||||
|
should.not.exist(appProxy.api.posts.edit);
|
||||||
|
should.not.exist(appProxy.api.posts.add);
|
||||||
|
should.not.exist(appProxy.api.posts.destroy);
|
||||||
|
|
||||||
|
should.not.exist(appProxy.api.users);
|
||||||
|
|
||||||
|
should.exist(appProxy.api.tags);
|
||||||
|
|
||||||
|
should.exist(appProxy.api.notifications);
|
||||||
|
should.not.exist(appProxy.api.notifications.destroy);
|
||||||
|
|
||||||
|
should.exist(appProxy.api.settings);
|
||||||
|
should.not.exist(appProxy.api.settings.browse);
|
||||||
|
should.not.exist(appProxy.api.settings.add);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Sandbox', function () {
|
||||||
|
it('loads apps in a sandbox', function () {
|
||||||
|
var appBox = new AppSandbox(),
|
||||||
|
appPath = path.resolve(__dirname, '..', 'utils', 'fixtures', 'app', 'good.js'),
|
||||||
|
GoodApp,
|
||||||
|
app;
|
||||||
|
|
||||||
|
GoodApp = appBox.loadApp(appPath);
|
||||||
|
|
||||||
|
should.exist(GoodApp);
|
||||||
|
|
||||||
|
app = new GoodApp(appProxy);
|
||||||
|
|
||||||
|
app.install(appProxy);
|
||||||
|
|
||||||
|
app.app.something.should.equal(42);
|
||||||
|
app.app.util.util().should.equal(42);
|
||||||
|
app.app.nested.other.should.equal(42);
|
||||||
|
app.app.path.should.equal(appPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow apps to require blacklisted modules at top level', function () {
|
||||||
|
var appBox = new AppSandbox(),
|
||||||
|
badAppPath = path.join(__dirname, '..', 'utils', 'fixtures', 'app', 'badtop.js'),
|
||||||
|
BadApp,
|
||||||
|
app,
|
||||||
|
loadApp = function () {
|
||||||
|
appBox.loadApp(badAppPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadApp.should.throw('Unsafe App require: knex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow apps to require blacklisted modules at install', function () {
|
||||||
|
var appBox = new AppSandbox(),
|
||||||
|
badAppPath = path.join(__dirname, '..', 'utils', 'fixtures', 'app', 'badinstall.js'),
|
||||||
|
BadApp,
|
||||||
|
app,
|
||||||
|
installApp = function () {
|
||||||
|
app.install(appProxy);
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp = appBox.loadApp(badAppPath);
|
||||||
|
|
||||||
|
app = new BadApp(appProxy);
|
||||||
|
|
||||||
|
installApp.should.throw('Unsafe App require: knex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow apps to require blacklisted modules from other requires', function () {
|
||||||
|
var appBox = new AppSandbox(),
|
||||||
|
badAppPath = path.join(__dirname, '..', 'utils', 'fixtures', 'app', 'badrequire.js'),
|
||||||
|
BadApp,
|
||||||
|
app,
|
||||||
|
loadApp = function () {
|
||||||
|
BadApp = appBox.loadApp(badAppPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadApp.should.throw('Unsafe App require: knex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow apps to require modules relatively outside their directory', function () {
|
||||||
|
var appBox = new AppSandbox(),
|
||||||
|
badAppPath = path.join(__dirname, '..', 'utils', 'fixtures', 'app', 'badoutside.js'),
|
||||||
|
BadApp,
|
||||||
|
app,
|
||||||
|
loadApp = function () {
|
||||||
|
BadApp = appBox.loadApp(badAppPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadApp.should.throw('Unsafe App require: ../example');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
16
core/test/utils/fixtures/app/badinstall.js
Normal file
16
core/test/utils/fixtures/app/badinstall.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
return knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
6
core/test/utils/fixtures/app/badlib.js
Normal file
6
core/test/utils/fixtures/app/badlib.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
knex: knex
|
||||||
|
};
|
16
core/test/utils/fixtures/app/badoutside.js
Normal file
16
core/test/utils/fixtures/app/badoutside.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
var lib = require('../example');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return lib.answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
16
core/test/utils/fixtures/app/badrequire.js
Normal file
16
core/test/utils/fixtures/app/badrequire.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
var lib = require('./badlib');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return lib.knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
16
core/test/utils/fixtures/app/badtop.js
Normal file
16
core/test/utils/fixtures/app/badtop.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
24
core/test/utils/fixtures/app/good.js
Normal file
24
core/test/utils/fixtures/app/good.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
util = require('./goodlib.js'),
|
||||||
|
nested = require('./nested/goodnested');
|
||||||
|
|
||||||
|
function GoodApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
GoodApp.prototype.install = function () {
|
||||||
|
// Goes through app to do data
|
||||||
|
this.app.something = 42;
|
||||||
|
this.app.util = util;
|
||||||
|
this.app.nested = nested;
|
||||||
|
this.app.path = path.join(__dirname, 'good.js');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
GoodApp.prototype.activate = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = GoodApp;
|
6
core/test/utils/fixtures/app/goodlib.js
Normal file
6
core/test/utils/fixtures/app/goodlib.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
util: function () {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
};
|
6
core/test/utils/fixtures/app/nested/goodnested.js
Normal file
6
core/test/utils/fixtures/app/nested/goodnested.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
var lib = require('../goodlib.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
other: 42
|
||||||
|
};
|
4
core/test/utils/fixtures/example.js
Normal file
4
core/test/utils/fixtures/example.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
answer: 42
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue