mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-23 22:11:09 +03:00
Don't allow a subdirectory called Ghost
fixes #1755 - adds extra validation to config loader - adds tests for config loader and validation
This commit is contained in:
parent
ab0ecf65db
commit
e2325dc969
@ -12,15 +12,20 @@ var fs = require('fs'),
|
||||
paths = require('./paths'),
|
||||
|
||||
appRoot = paths().appRoot,
|
||||
configexample = paths().configExample,
|
||||
configFile = process.env.GHOST_CONFIG || paths().config;
|
||||
configExample = paths().configExample,
|
||||
configFile = process.env.GHOST_CONFIG || paths().config,
|
||||
rejectMessage = 'Unable to load config';
|
||||
|
||||
function readConfigFile(envVal) {
|
||||
return require(configFile)[envVal];
|
||||
}
|
||||
|
||||
function writeConfigFile() {
|
||||
var written = when.defer();
|
||||
|
||||
/* Check for config file and copy from config.example.js
|
||||
if one doesn't exist. After that, start the server. */
|
||||
fs.exists(configexample, function checkTemplate(templateExists) {
|
||||
fs.exists(configExample, function checkTemplate(templateExists) {
|
||||
var read,
|
||||
write;
|
||||
|
||||
@ -29,7 +34,7 @@ function writeConfigFile() {
|
||||
}
|
||||
|
||||
// Copy config.example.js => config.js
|
||||
read = fs.createReadStream(configexample);
|
||||
read = fs.createReadStream(configExample);
|
||||
read.on('error', function (err) {
|
||||
/*jslint unparam:true*/
|
||||
return errors.logError(new Error('Could not open config.example.js for read.'), appRoot, 'Please check your deployment for config.js or config.example.js.');
|
||||
@ -50,14 +55,13 @@ function writeConfigFile() {
|
||||
|
||||
function validateConfigEnvironment() {
|
||||
var envVal = process.env.NODE_ENV || undefined,
|
||||
rejectMessage = 'Unable to load config',
|
||||
hasHostAndPort,
|
||||
hasSocket,
|
||||
config,
|
||||
parsedUrl;
|
||||
|
||||
try {
|
||||
config = require(configFile)[envVal];
|
||||
config = readConfigFile(envVal);
|
||||
} catch (ignore) {
|
||||
|
||||
}
|
||||
@ -76,8 +80,13 @@ function validateConfigEnvironment() {
|
||||
return when.reject(rejectMessage);
|
||||
}
|
||||
|
||||
if (/\/ghost(\/|$)/.test(parsedUrl.pathname)) {
|
||||
errors.logError(new Error('Your site url in config.js cannot contain a subdirectory called ghost.'), config.url, 'Please rename the subdirectory before restarting');
|
||||
return when.reject(rejectMessage);
|
||||
}
|
||||
|
||||
// Check that we have database values
|
||||
if (!config.database) {
|
||||
if (!config.database || !config.database.client) {
|
||||
errors.logError(new Error('Your database configuration in config.js is invalid.'), JSON.stringify(config.database), 'Please make sure this is a valid Bookshelf database configuration');
|
||||
return when.reject(rejectMessage);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ var _ = require('underscore'),
|
||||
path = require('path'),
|
||||
when = require('when'),
|
||||
errors = require('../errorHandling'),
|
||||
config = require('../config'),
|
||||
configPaths = require('../config/paths'),
|
||||
baseStore = require('./base'),
|
||||
|
||||
localFileStore;
|
||||
@ -20,7 +20,7 @@ localFileStore = _.extend(baseStore, {
|
||||
// - returns a promise which ultimately returns the full url to the uploaded image
|
||||
'save': function (image) {
|
||||
var saved = when.defer(),
|
||||
targetDir = this.getTargetDir(config.paths().imagesRelPath),
|
||||
targetDir = this.getTargetDir(configPaths().imagesRelPath),
|
||||
targetFilename;
|
||||
|
||||
this.getUniqueFileName(this, image, targetDir).then(function (filename) {
|
||||
@ -33,7 +33,7 @@ localFileStore = _.extend(baseStore, {
|
||||
}).then(function () {
|
||||
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
||||
// For local file system storage can use relative path so add a slash
|
||||
var fullUrl = (config.paths().subdir + '/' + targetFilename).replace(new RegExp('\\' + path.sep, 'g'), '/');
|
||||
var fullUrl = (configPaths().subdir + '/' + targetFilename).replace(new RegExp('\\' + path.sep, 'g'), '/');
|
||||
return saved.resolve(fullUrl);
|
||||
}).otherwise(function (e) {
|
||||
errors.logError(e);
|
||||
@ -56,7 +56,7 @@ localFileStore = _.extend(baseStore, {
|
||||
|
||||
// middleware for serving the files
|
||||
'serve': function () {
|
||||
return express['static'](config.paths().imagesPath);
|
||||
return express['static'](configPaths().imagesPath);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,14 +1,221 @@
|
||||
/*globals describe, it, beforeEach, afterEach */
|
||||
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
when = require('when'),
|
||||
path = require('path'),
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
when = require('when'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
_ = require('underscore'),
|
||||
rewire = require("rewire"),
|
||||
|
||||
config = require('../../server/config');
|
||||
// Thing we are testing
|
||||
defaultConfig = require('../../../config.example')[process.env.NODE_ENV],
|
||||
loader = rewire('../../server/config/loader'),
|
||||
theme = rewire('../../server/config/theme'),
|
||||
paths = rewire('../../server/config/paths');
|
||||
|
||||
describe('Config', function () {
|
||||
|
||||
describe('Loader', function () {
|
||||
var sandbox,
|
||||
rejectMessage = loader.__get__('rejectMessage'),
|
||||
overrideConfig = function (newConfig) {
|
||||
loader.__set__("readConfigFile", sandbox.stub().returns(
|
||||
_.extend({}, defaultConfig, newConfig)
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
loader = rewire('../../server/config/loader');
|
||||
sandbox.restore();
|
||||
|
||||
});
|
||||
|
||||
it('loads the config file if one exists', function (done) {
|
||||
// the test infrastructure is setup so that there is always config present,
|
||||
// but we want to overwrite the test to actually load config.example.js, so that any local changes
|
||||
// don't break the tests
|
||||
loader.__set__("configFile", path.join(paths().appRoot, 'config.example.js'));
|
||||
|
||||
loader().then(function (config) {
|
||||
config.url.should.equal(defaultConfig.url);
|
||||
config.database.client.should.equal(defaultConfig.database.client);
|
||||
config.database.connection.should.eql(defaultConfig.database.connection);
|
||||
config.server.host.should.equal(defaultConfig.server.host);
|
||||
config.server.port.should.equal(defaultConfig.server.port);
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('creates the config file if one does not exist', function (done) {
|
||||
|
||||
var deferred = when.defer(),
|
||||
// trick loader into thinking that the config file doesn't exist yet
|
||||
existsStub = sandbox.stub(fs, 'exists', function (file, cb) { return cb(false); }),
|
||||
// create a method which will return a pre-resolved promise
|
||||
resolvedPromise = sandbox.stub().returns(deferred.promise);
|
||||
|
||||
deferred.resolve();
|
||||
|
||||
// ensure that the file creation is a stub, the tests shouldn't really create a file
|
||||
loader.__set__("writeConfigFile", resolvedPromise);
|
||||
loader.__set__("validateConfigEnvironment", resolvedPromise);
|
||||
|
||||
loader().then(function () {
|
||||
existsStub.calledOnce.should.be.true;
|
||||
resolvedPromise.calledTwice.should.be.true;
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('accepts valid urls', function (done) {
|
||||
// replace the config file with invalid data
|
||||
overrideConfig({url: 'http://testurl.com'});
|
||||
|
||||
loader().then(function (localConfig) {
|
||||
localConfig.url.should.equal('http://testurl.com');
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'https://testurl.com'});
|
||||
return loader();
|
||||
}).then(function (localConfig) {
|
||||
localConfig.url.should.equal('https://testurl.com');
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'http://testurl.com/blog/'});
|
||||
return loader();
|
||||
}).then(function (localConfig) {
|
||||
localConfig.url.should.equal('http://testurl.com/blog/');
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'http://testurl.com/ghostly/'});
|
||||
return loader();
|
||||
}).then(function (localConfig) {
|
||||
localConfig.url.should.equal('http://testurl.com/ghostly/');
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: '//testurl.com'});
|
||||
return loader();
|
||||
}).then(function (localConfig) {
|
||||
localConfig.url.should.equal('//testurl.com');
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('rejects invalid urls', function (done) {
|
||||
// replace the config file with invalid data
|
||||
overrideConfig({url: 'notvalid'});
|
||||
|
||||
loader().otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'something.com'});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
done();
|
||||
}).then(function () {
|
||||
should.fail('no error was thrown when it should have been');
|
||||
done();
|
||||
}).then(done, null);
|
||||
});
|
||||
|
||||
it('does not permit subdirectories named ghost', function (done) {
|
||||
// replace the config file with invalid data
|
||||
overrideConfig({url: 'http://testurl.com/ghost/'});
|
||||
|
||||
loader().otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'http://testurl.com/ghost/blog/'});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({url: 'http://testurl.com/blog/ghost'});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
done();
|
||||
}).then(function () {
|
||||
should.fail('no error was thrown when it should have been');
|
||||
done();
|
||||
}).then(done, null);
|
||||
});
|
||||
|
||||
it('requires a database config', function (done) {
|
||||
// replace the config file with invalid data
|
||||
overrideConfig({database: null});
|
||||
|
||||
loader().otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({database: {}});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
done();
|
||||
}).then(function () {
|
||||
should.fail('no error was thrown when it should have been');
|
||||
done();
|
||||
}).then(done, null);
|
||||
});
|
||||
|
||||
|
||||
it('requires a socket or a host and port', function (done) {
|
||||
// replace the config file with invalid data
|
||||
overrideConfig({server: {socket: 'test'}});
|
||||
|
||||
loader().then(function (localConfig) {
|
||||
localConfig.server.socket.should.equal('test');
|
||||
|
||||
// Next test
|
||||
overrideConfig({server: null});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({server: {host: null}});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({server: {port: null}});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
// Next test
|
||||
overrideConfig({server: {host: null, port: null}});
|
||||
return loader();
|
||||
}).otherwise(function (error) {
|
||||
error.should.include(rejectMessage);
|
||||
|
||||
done();
|
||||
}).then(function () {
|
||||
should.fail('no error was thrown when it should have been');
|
||||
done();
|
||||
}).then(done, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Theme', function () {
|
||||
|
||||
var sandbox,
|
||||
@ -24,13 +231,13 @@ describe('Config', function () {
|
||||
return when({value: 'casper'});
|
||||
});
|
||||
|
||||
config.theme.update(settings, 'http://my-ghost-blog.com')
|
||||
theme.update(settings, 'http://my-ghost-blog.com')
|
||||
.then(done)
|
||||
.then(null, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
config.theme.update(settings, config().url)
|
||||
theme.update(settings, defaultConfig.url)
|
||||
.then(done)
|
||||
.then(null, done);
|
||||
|
||||
@ -38,14 +245,14 @@ describe('Config', function () {
|
||||
});
|
||||
|
||||
it('should have exactly the right keys', function () {
|
||||
var themeConfig = config.theme();
|
||||
var themeConfig = theme();
|
||||
|
||||
// This will fail if there are any extra keys
|
||||
themeConfig.should.have.keys('url', 'title', 'description', 'logo', 'cover');
|
||||
});
|
||||
|
||||
it('should have the correct values for each key', function () {
|
||||
var themeConfig = config.theme();
|
||||
var themeConfig = theme();
|
||||
|
||||
// Check values are as we expect
|
||||
themeConfig.should.have.property('url', 'http://my-ghost-blog.com');
|
||||
@ -67,7 +274,7 @@ describe('Config', function () {
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
config.paths.update(config().url)
|
||||
paths.update(defaultConfig.url)
|
||||
.then(done)
|
||||
.then(null, done);
|
||||
|
||||
@ -75,7 +282,7 @@ describe('Config', function () {
|
||||
});
|
||||
|
||||
it('should have exactly the right keys', function () {
|
||||
var pathConfig = config.paths();
|
||||
var pathConfig = paths();
|
||||
|
||||
// This will fail if there are any extra keys
|
||||
pathConfig.should.have.keys(
|
||||
@ -100,7 +307,7 @@ describe('Config', function () {
|
||||
});
|
||||
|
||||
it('should have the correct values for each key', function () {
|
||||
var pathConfig = config.paths(),
|
||||
var pathConfig = paths(),
|
||||
appRoot = path.resolve(__dirname, '../../../');
|
||||
|
||||
pathConfig.should.have.property('appRoot', appRoot);
|
||||
@ -108,32 +315,32 @@ describe('Config', function () {
|
||||
});
|
||||
|
||||
it('should not return a slash for subdir', function (done) {
|
||||
config.paths.update('http://my-ghost-blog.com').then(function () {
|
||||
config.paths().should.have.property('subdir', '');
|
||||
paths.update('http://my-ghost-blog.com').then(function () {
|
||||
paths().should.have.property('subdir', '');
|
||||
|
||||
return config.paths.update('http://my-ghost-blog.com/');
|
||||
return paths.update('http://my-ghost-blog.com/');
|
||||
}).then(function () {
|
||||
config.paths().should.have.property('subdir', '');
|
||||
paths().should.have.property('subdir', '');
|
||||
|
||||
done();
|
||||
}).otherwise(done);
|
||||
});
|
||||
|
||||
it('should handle subdirectories properly', function (done) {
|
||||
config.paths.update('http://my-ghost-blog.com/blog').then(function () {
|
||||
config.paths().should.have.property('subdir', '/blog');
|
||||
paths.update('http://my-ghost-blog.com/blog').then(function () {
|
||||
paths().should.have.property('subdir', '/blog');
|
||||
|
||||
return config.paths.update('http://my-ghost-blog.com/blog/');
|
||||
return paths.update('http://my-ghost-blog.com/blog/');
|
||||
}).then(function () {
|
||||
config.paths().should.have.property('subdir', '/blog');
|
||||
paths().should.have.property('subdir', '/blog');
|
||||
|
||||
return config.paths.update('http://my-ghost-blog.com/my/blog');
|
||||
return paths.update('http://my-ghost-blog.com/my/blog');
|
||||
}).then(function () {
|
||||
config.paths().should.have.property('subdir', '/my/blog');
|
||||
paths().should.have.property('subdir', '/my/blog');
|
||||
|
||||
return config.paths.update('http://my-ghost-blog.com/my/blog/');
|
||||
return paths.update('http://my-ghost-blog.com/my/blog/');
|
||||
}).then(function () {
|
||||
config.paths().should.have.property('subdir', '/my/blog');
|
||||
paths().should.have.property('subdir', '/my/blog');
|
||||
|
||||
done();
|
||||
}).otherwise(done);
|
||||
|
@ -77,6 +77,7 @@
|
||||
"grunt-update-submodules": "~0.2.0",
|
||||
"matchdep": "~0.3.0",
|
||||
"mocha": "~1.15.1",
|
||||
"rewire": "~2.0.0",
|
||||
"request": "~2.29.0",
|
||||
"require-dir": "~0.1.0",
|
||||
"should": "~2.1.1",
|
||||
|
Loading…
Reference in New Issue
Block a user