mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-04 12:44:57 +03:00
250d571fe6
no issue - add mocha option to jshintrc, no need to define globals in files anymore - call client grunt commands in case of jshint/jscs
527 lines
19 KiB
JavaScript
527 lines
19 KiB
JavaScript
var path = require('path'),
|
|
EventEmitter = require('events').EventEmitter,
|
|
should = require('should'),
|
|
sinon = require('sinon'),
|
|
_ = require('lodash'),
|
|
Promise = require('bluebird'),
|
|
helpers = require('../../server/helpers'),
|
|
filters = require('../../server/filters'),
|
|
i18n = require('../../server/i18n'),
|
|
|
|
// Stuff we are testing
|
|
AppProxy = require('../../server/apps/proxy'),
|
|
AppSandbox = require('../../server/apps/sandbox'),
|
|
AppDependencies = require('../../server/apps/dependencies'),
|
|
AppPermissions = require('../../server/apps/permissions');
|
|
i18n.init();
|
|
|
|
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('requires a name to be passed', function () {
|
|
function makeWithoutName() {
|
|
return new AppProxy({});
|
|
}
|
|
|
|
makeWithoutName.should.throw('Must provide an app name for api context');
|
|
});
|
|
|
|
it('requires permissions to be passed', function () {
|
|
function makeWithoutPerms() {
|
|
return new AppProxy({
|
|
name: 'NoPerms'
|
|
});
|
|
}
|
|
|
|
makeWithoutPerms.should.throw('Must provide app permissions');
|
|
});
|
|
|
|
it('creates a ghost proxy', function () {
|
|
var appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
});
|
|
|
|
should.exist(appProxy.filters);
|
|
should.exist(appProxy.filters.register);
|
|
should.exist(appProxy.filters.deregister);
|
|
|
|
should.exist(appProxy.helpers);
|
|
should.exist(appProxy.helpers.register);
|
|
should.exist(appProxy.helpers.registerAsync);
|
|
|
|
should.exist(appProxy.api);
|
|
|
|
should.exist(appProxy.api.posts);
|
|
should.exist(appProxy.api.posts.browse);
|
|
should.exist(appProxy.api.posts.read);
|
|
should.exist(appProxy.api.posts.edit);
|
|
should.exist(appProxy.api.posts.add);
|
|
should.exist(appProxy.api.posts.destroy);
|
|
|
|
should.not.exist(appProxy.api.users);
|
|
|
|
should.exist(appProxy.api.tags);
|
|
should.exist(appProxy.api.tags.browse);
|
|
|
|
should.exist(appProxy.api.notifications);
|
|
should.exist(appProxy.api.notifications.browse);
|
|
should.exist(appProxy.api.notifications.add);
|
|
should.exist(appProxy.api.notifications.destroy);
|
|
|
|
should.exist(appProxy.api.settings);
|
|
should.exist(appProxy.api.settings.browse);
|
|
should.exist(appProxy.api.settings.read);
|
|
should.exist(appProxy.api.settings.edit);
|
|
});
|
|
|
|
it('allows filter registration with permission', function (done) {
|
|
var registerSpy = sandbox.spy(filters, 'registerFilter'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['testFilter'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
}),
|
|
fakePosts = [{id: 0}, {id: 1}],
|
|
filterStub = sandbox.spy(function (val) {
|
|
return val;
|
|
});
|
|
|
|
appProxy.filters.register('testFilter', 5, filterStub);
|
|
|
|
registerSpy.called.should.equal(true);
|
|
|
|
filterStub.called.should.equal(false);
|
|
|
|
filters.doFilter('testFilter', fakePosts)
|
|
.then(function () {
|
|
filterStub.called.should.equal(true);
|
|
appProxy.filters.deregister('testFilter', 5, filterStub);
|
|
done();
|
|
})
|
|
.catch(done);
|
|
});
|
|
|
|
it('does not allow filter registration without permission', function () {
|
|
var registerSpy = sandbox.spy(filters, 'registerFilter'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
}),
|
|
filterStub = sandbox.stub().returns('test result');
|
|
|
|
function registerFilterWithoutPermission() {
|
|
appProxy.filters.register('superSecretFilter', 5, filterStub);
|
|
}
|
|
|
|
registerFilterWithoutPermission.should.throw('The App "TestApp" attempted to perform an action or access' +
|
|
' a resource (filters.superSecretFilter) without permission.');
|
|
|
|
registerSpy.called.should.equal(false);
|
|
});
|
|
|
|
it('allows filter deregistration with permission', function (done) {
|
|
var registerSpy = sandbox.spy(filters, 'deregisterFilter'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostsRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
}),
|
|
fakePosts = [{id: 0}, {id: 1}],
|
|
filterStub = sandbox.stub().returns(fakePosts);
|
|
|
|
appProxy.filters.deregister('prePostsRender', 5, filterStub);
|
|
|
|
registerSpy.called.should.equal(true);
|
|
|
|
filterStub.called.should.equal(false);
|
|
|
|
filters.doFilter('prePostsRender', fakePosts)
|
|
.then(function () {
|
|
filterStub.called.should.equal(false);
|
|
done();
|
|
})
|
|
.catch(done);
|
|
});
|
|
|
|
it('does not allow filter deregistration without permission', function () {
|
|
var registerSpy = sandbox.spy(filters, 'deregisterFilter'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
}),
|
|
filterStub = sandbox.stub().returns('test result');
|
|
|
|
function deregisterFilterWithoutPermission() {
|
|
appProxy.filters.deregister('superSecretFilter', 5, filterStub);
|
|
}
|
|
|
|
deregisterFilterWithoutPermission.should.throw('The App "TestApp" attempted to perform an action or ' +
|
|
'access a resource (filters.superSecretFilter) without permission.');
|
|
|
|
registerSpy.called.should.equal(false);
|
|
});
|
|
|
|
it('allows helper registration with permission', function () {
|
|
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
});
|
|
|
|
appProxy.helpers.register('myTestHelper', sandbox.stub().returns('test result'));
|
|
|
|
registerSpy.called.should.equal(true);
|
|
});
|
|
|
|
it('does not allow helper registration without permission', function () {
|
|
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {
|
|
filters: ['prePostRender'],
|
|
helpers: ['myTestHelper'],
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
});
|
|
|
|
function registerWithoutPermissions() {
|
|
appProxy.helpers.register('otherHelper', sandbox.stub().returns('test result'));
|
|
}
|
|
|
|
registerWithoutPermissions.should.throw('The App "TestApp" attempted to perform an action or access a ' +
|
|
'resource (helpers.otherHelper) without permission.');
|
|
|
|
registerSpy.called.should.equal(false);
|
|
});
|
|
|
|
it('does allow INTERNAL app to register helper without permission', function () {
|
|
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {},
|
|
internal: true
|
|
});
|
|
|
|
function registerWithoutPermissions() {
|
|
appProxy.helpers.register('otherHelper', sandbox.stub().returns('test result'));
|
|
}
|
|
|
|
registerWithoutPermissions.should.not.throw('The App "TestApp" attempted to perform an action or access a ' +
|
|
'resource (helpers.otherHelper) without permission.');
|
|
|
|
registerSpy.called.should.equal(true);
|
|
});
|
|
});
|
|
|
|
describe('Sandbox', function () {
|
|
it('loads apps in a sandbox', function () {
|
|
var appBox = new AppSandbox(),
|
|
appPath = path.resolve(__dirname, '..', 'utils', 'fixtures', 'app', 'good.js'),
|
|
GoodApp,
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {}
|
|
}),
|
|
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'),
|
|
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,
|
|
appProxy = new AppProxy({
|
|
name: 'TestApp',
|
|
permissions: {}
|
|
}),
|
|
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,
|
|
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,
|
|
loadApp = function () {
|
|
BadApp = appBox.loadApp(badAppPath);
|
|
};
|
|
|
|
loadApp.should.throw(/^Unsafe App require[\w\W]*example$/);
|
|
});
|
|
|
|
it('does allow INTERNAL apps to require modules relatively outside their directory', function () {
|
|
var appBox = new AppSandbox({internal: true}),
|
|
badAppPath = path.join(__dirname, '..', 'utils', 'fixtures', 'app', 'badoutside.js'),
|
|
InternalApp,
|
|
loadApp = function () {
|
|
InternalApp = appBox.loadApp(badAppPath);
|
|
};
|
|
|
|
InternalApp = appBox.loadApp(badAppPath);
|
|
|
|
loadApp.should.not.throw(/^Unsafe App require[\w\W]*example$/);
|
|
|
|
InternalApp.should.be.a.Function();
|
|
});
|
|
});
|
|
|
|
describe('Dependencies', function () {
|
|
it('can install by package.json', function (done) {
|
|
var deps = new AppDependencies(process.cwd()),
|
|
fakeEmitter = new EventEmitter();
|
|
|
|
deps.spawnCommand = sandbox.stub().returns(fakeEmitter);
|
|
|
|
deps.install().then(function () {
|
|
deps.spawnCommand.calledWith('npm').should.equal(true);
|
|
done();
|
|
}).catch(done);
|
|
|
|
_.delay(function () {
|
|
fakeEmitter.emit('exit');
|
|
}, 30);
|
|
});
|
|
it('does not install when no package.json', function (done) {
|
|
var deps = new AppDependencies(__dirname),
|
|
fakeEmitter = new EventEmitter();
|
|
|
|
deps.spawnCommand = sandbox.stub().returns(fakeEmitter);
|
|
|
|
deps.install().then(function () {
|
|
deps.spawnCommand.called.should.equal(false);
|
|
done();
|
|
}).catch(done);
|
|
|
|
_.defer(function () {
|
|
fakeEmitter.emit('exit');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Permissions', function () {
|
|
var noGhostPackageJson = {
|
|
name: 'myapp',
|
|
version: '0.0.1',
|
|
description: 'My example app',
|
|
main: 'index.js',
|
|
scripts: {
|
|
test: 'echo \'Error: no test specified\' && exit 1'
|
|
},
|
|
author: 'Ghost',
|
|
license: 'MIT',
|
|
dependencies: {
|
|
'ghost-app': '0.0.1'
|
|
}
|
|
},
|
|
validGhostPackageJson = {
|
|
name: 'myapp',
|
|
version: '0.0.1',
|
|
description: 'My example app',
|
|
main: 'index.js',
|
|
scripts: {
|
|
test: 'echo \'Error: no test specified\' && exit 1'
|
|
},
|
|
author: 'Ghost',
|
|
license: 'MIT',
|
|
dependencies: {
|
|
'ghost-app': '0.0.1'
|
|
},
|
|
ghost: {
|
|
permissions: {
|
|
posts: ['browse', 'read', 'edit', 'add', 'delete'],
|
|
users: ['browse', 'read', 'edit', 'add', 'delete'],
|
|
settings: ['browse', 'read', 'edit', 'add', 'delete']
|
|
}
|
|
}
|
|
};
|
|
|
|
it('has default permissions to read and browse posts', function () {
|
|
should.exist(AppPermissions.DefaultPermissions);
|
|
|
|
should.exist(AppPermissions.DefaultPermissions.posts);
|
|
|
|
AppPermissions.DefaultPermissions.posts.should.containEql('browse');
|
|
AppPermissions.DefaultPermissions.posts.should.containEql('read');
|
|
|
|
// Make it hurt to add more so additional checks are added here
|
|
_.keys(AppPermissions.DefaultPermissions).length.should.equal(1);
|
|
});
|
|
it('uses default permissions if no package.json', function (done) {
|
|
var perms = new AppPermissions('test');
|
|
|
|
// No package.json in this directory
|
|
sandbox.stub(perms, 'checkPackageContentsExists').returns(Promise.resolve(false));
|
|
|
|
perms.read().then(function (readPerms) {
|
|
should.exist(readPerms);
|
|
|
|
readPerms.should.equal(AppPermissions.DefaultPermissions);
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
it('uses default permissions if no ghost object in package.json', function (done) {
|
|
var perms = new AppPermissions('test'),
|
|
noGhostPackageJsonContents = JSON.stringify(noGhostPackageJson, null, 2);
|
|
|
|
// package.json IS in this directory
|
|
sandbox.stub(perms, 'checkPackageContentsExists').returns(Promise.resolve(true));
|
|
// no ghost property on package
|
|
sandbox.stub(perms, 'getPackageContents').returns(Promise.resolve(noGhostPackageJsonContents));
|
|
|
|
perms.read().then(function (readPerms) {
|
|
should.exist(readPerms);
|
|
|
|
readPerms.should.equal(AppPermissions.DefaultPermissions);
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
it('rejects when reading malformed package.json', function (done) {
|
|
var perms = new AppPermissions('test');
|
|
|
|
// package.json IS in this directory
|
|
sandbox.stub(perms, 'checkPackageContentsExists').returns(Promise.resolve(true));
|
|
// malformed JSON on package
|
|
sandbox.stub(perms, 'getPackageContents').returns(Promise.reject(new Error('package.json file is malformed')));
|
|
|
|
perms.read().then(function (readPerms) {
|
|
/*jshint unused:false*/
|
|
done(new Error('should not resolve'));
|
|
}).catch(function (err) {
|
|
err.message.should.equal('package.json file is malformed');
|
|
done();
|
|
});
|
|
});
|
|
it('reads from package.json in root of app directory', function (done) {
|
|
var perms = new AppPermissions('test'),
|
|
validGhostPackageJsonContents = validGhostPackageJson;
|
|
|
|
// package.json IS in this directory
|
|
sandbox.stub(perms, 'checkPackageContentsExists').returns(Promise.resolve(true));
|
|
// valid ghost property on package
|
|
sandbox.stub(perms, 'getPackageContents').returns(Promise.resolve(validGhostPackageJsonContents));
|
|
|
|
perms.read().then(function (readPerms) {
|
|
should.exist(readPerms);
|
|
|
|
readPerms.should.not.equal(AppPermissions.DefaultPermissions);
|
|
|
|
should.exist(readPerms.posts);
|
|
readPerms.posts.length.should.equal(5);
|
|
|
|
should.exist(readPerms.users);
|
|
readPerms.users.length.should.equal(5);
|
|
|
|
should.exist(readPerms.settings);
|
|
readPerms.settings.length.should.equal(5);
|
|
|
|
_.keys(readPerms).length.should.equal(3);
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
});
|