2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

🔥 remove forceAdminSSL and urlSSL, add admin url (#7937)

* 🔥  kill apiUrl helper, use urlFor helper instead

More consistency of creating urls.
Creates an easier ability to add config changes.

Attention: urlFor function is getting a little nesty, BUT that is for now wanted to make easier and centralised changes to the configs.
The url util need's refactoring anyway.

* 🔥  urlSSL

Remove all urlSSL usages.
Add TODO's for the next commit to re-add logic for deleted logic.

e.g.

- cors helper generated an array of url's to allow requests from the defined config url's -> will be replaced by the admin url if available
- theme handler prefered the urlSSL in case it was defined -> will be replaced by using the urlFor helper to get the blog url (based on the request secure flag)

The changes in this commit doesn't have to be right, but it helped going step by step.
The next commit is the more interesting one.

* 🔥    remove forceAdminSSL, add new admin url and adapt logic

I wanted to remove the forceAdminSSL as separate commit, but was hard to realise.
That's why both changes are in one commit:

1. remove forceAdminSSL
2. add admin.url option

- fix TODO's from last commits
- rewrite the ssl middleware!
- create some private helper functions in the url helper to realise the changes
- rename some wordings and functions e.g. base === blog (we have so much different wordings)
- i would like to do more, but this would end in a non readable PR
- this commit contains the most important changes to offer admin.url option

* 🤖  adapt tests

IMPORTANT
- all changes in the routing tests were needed, because each routing test did not start the ghost server
- they just required the ghost application, which resulted in a random server port
- having a random server port results in a redirect, caused by the ssl/redirect middleware

* 😎  rename check-ssl middleware

* 🎨  fix theme-handler because of master rebase
This commit is contained in:
Katharina Irrgang 2017-02-03 19:13:22 +01:00 committed by Hannah Wolfe
parent ff7c3a1cf0
commit a68592a6b9
37 changed files with 1527 additions and 1462 deletions

View file

@ -8,7 +8,7 @@ var debug = require('debug')('ghost:admin'),
// Global/shared middleware?
cacheControl = require('../middleware/cache-control'),
checkSSL = require('../middleware/check-ssl'),
urlRedirects = require('../middleware/url-redirects'),
errorHandler = require('../middleware//error-handler'),
maintenance = require('../middleware/maintenance'),
prettyURLs = require('../middleware//pretty-urls'),
@ -46,7 +46,7 @@ module.exports = function setupAdminApp() {
// Force SSL if required
// must happen AFTER asset loading and BEFORE routing
adminApp.use(checkSSL);
adminApp.use(urlRedirects);
// Add in all trailing slashes & remove uppercase
// must happen AFTER asset loading and BEFORE routing

View file

@ -21,7 +21,7 @@ var debug = require('debug')('ghost:api'),
// Shared
bodyParser = require('body-parser'), // global, shared
cacheControl = require('../middleware/cache-control'), // global, shared
checkSSL = require('../middleware/check-ssl'),
urlRedirects = require('../middleware/url-redirects'),
prettyURLs = require('../middleware/pretty-urls'),
maintenance = require('../middleware/maintenance'), // global, shared
errorHandler = require('../middleware/error-handler'), // global, shared
@ -235,7 +235,7 @@ module.exports = function setupApiApp() {
// Force SSL if required
// must happen AFTER asset loading and BEFORE routing
apiApp.use(checkSSL);
apiApp.use(urlRedirects);
// Add in all trailing slashes & remove uppercase
// must happen AFTER asset loading and BEFORE routing

View file

@ -14,7 +14,7 @@ var debug = require('debug')('ghost:blog'),
// local middleware
cacheControl = require('../middleware/cache-control'),
checkSSL = require('../middleware/check-ssl'),
urlRedirects = require('../middleware/url-redirects'),
errorHandler = require('../middleware/error-handler'),
maintenance = require('../middleware/maintenance'),
prettyURLs = require('../middleware/pretty-urls'),
@ -75,7 +75,7 @@ module.exports = function setupBlogApp() {
// Force SSL if required
// must happen AFTER asset loading and BEFORE routing
blogApp.use(checkSSL);
blogApp.use(urlRedirects);
// Add in all trailing slashes & remove uppercase
// must happen AFTER asset loading and BEFORE routing

View file

@ -1,7 +1,5 @@
{
"url": "http://localhost:2368",
"urlSSL": false,
"forceAdminSSL": false,
"server": {
"host": "127.0.0.1",
"port": 2368

View file

@ -170,7 +170,7 @@ function init(options) {
return scheduling.init({
schedulerUrl: config.get('scheduling').schedulerUrl,
active: config.get('scheduling').active,
apiUrl: utils.url.apiUrl(),
apiUrl: utils.url.urlFor('api', true),
internalPath: config.get('paths').internalSchedulingPath,
contentPath: config.getContentPath('scheduling')
});

View file

@ -3,7 +3,6 @@ var cors = require('cors'),
url = require('url'),
os = require('os'),
utils = require('../../utils'),
config = require('../../config'),
whitelist = [],
ENABLE_CORS = {origin: true, maxAge: 86400},
DISABLE_CORS = {origin: false};
@ -33,10 +32,14 @@ function getIPs() {
}
function getUrls() {
var urls = [url.parse(utils.url.urlFor('home', true)).hostname];
var blogHost = url.parse(utils.url.urlFor('home', true)).hostname,
adminHost = url.parse(utils.url.urlFor('admin', true)).hostname,
urls = [];
if (config.get('urlSSL')) {
urls.push(url.parse(config.get('urlSSL')).hostname);
urls.push(blogHost);
if (adminHost !== blogHost) {
urls.push(adminHost);
}
return urls;

View file

@ -1,61 +0,0 @@
var config = require('../config'),
url = require('url'),
utils = require('../utils'),
checkSSL;
function isSSLrequired(isAdmin, configUrl, forceAdminSSL) {
var forceSSL = url.parse(configUrl).protocol === 'https:' ? true : false;
if (forceSSL || (isAdmin && forceAdminSSL)) {
return true;
}
return false;
}
// The guts of checkSSL. Indicate forbidden or redirect according to configuration.
// Required args: forceAdminSSL, url and urlSSL should be passed from config. reqURL from req.url
function sslForbiddenOrRedirect(opt) {
var forceAdminSSL = opt.forceAdminSSL,
reqUrl = url.parse(opt.reqUrl), // expected to be relative-to-root
baseUrl = url.parse(opt.configUrlSSL || opt.configUrl),
response = {
// Check if forceAdminSSL: { redirect: false } is set, which means
// we should just deny non-SSL access rather than redirect
isForbidden: (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect),
redirectUrl: function redirectUrl(query) {
return url.format({
protocol: 'https:',
hostname: baseUrl.hostname,
port: baseUrl.port,
pathname: reqUrl.pathname,
query: query
});
}
};
return response;
}
// Check to see if we should use SSL
// and redirect if needed
checkSSL = function checkSSL(req, res, next) {
if (isSSLrequired(res.isAdmin, utils.url.urlFor('home', true), config.get('forceAdminSSL'))) {
if (!req.secure) {
var response = sslForbiddenOrRedirect({
forceAdminSSL: config.get('forceAdminSSL'),
configUrlSSL: config.get('urlSSL'),
configUrl: utils.url.urlFor('home', true),
reqUrl: req.originalUrl || req.url
});
if (response.isForbidden) {
return res.sendStatus(403);
} else {
return res.redirect(301, response.redirectUrl(req.query));
}
}
}
next();
};
module.exports = checkSSL;

View file

@ -28,7 +28,7 @@ function serveSharedFile(file, type, maxAge) {
if (type === 'text/xsl' || type === 'text/plain' || type === 'application/javascript') {
buf = buf.toString().replace(blogRegex, utils.url.urlFor('home', true).replace(/\/$/, ''));
buf = buf.toString().replace(apiRegex, utils.url.apiUrl({cors: true}));
buf = buf.toString().replace(apiRegex, utils.url.urlFor('api', {cors: true}, true));
}
content = {
headers: {

View file

@ -8,6 +8,7 @@ var _ = require('lodash'),
utils = require('../utils'),
logging = require('../logging'),
errors = require('../errors'),
utils = require('../utils'),
i18n = require('../i18n'),
themeHandler;
@ -18,7 +19,7 @@ themeHandler = {
var themeData = {
title: settingsCache.get('title'),
description: settingsCache.get('description'),
url: utils.url.urlFor('home', true),
url: utils.url.urlFor('home', {secure: req.secure}, true),
facebook: settingsCache.get('facebook'),
twitter: settingsCache.get('twitter'),
timezone: settingsCache.get('activeTimezone'),
@ -32,11 +33,6 @@ themeHandler = {
labsData = _.cloneDeep(settingsCache.get('labs')),
blogApp = req.app;
if (req.secure && config.get('urlSSL')) {
// For secure requests override .url property with the SSL version
themeData.url = config.get('urlSSL').replace(/\/$/, '');
}
hbs.updateTemplateOptions({
data: {
blog: themeData,
@ -49,7 +45,7 @@ themeHandler = {
}
// Pass 'secure' flag to the view engine
// so that templates can choose 'url' vs 'urlSSL'
// so that templates can choose to render https or http 'url', see url utility
res.locals.secure = req.secure;
next();

View file

@ -0,0 +1,68 @@
var url = require('url'),
debug = require('debug')('ghost:redirects'),
utils = require('../utils'),
urlRedirects;
function redirectUrl(options) {
var redirectTo = options.redirectTo,
path = options.path,
query = options.query,
parts = url.parse(redirectTo);
return url.format({
protocol: parts.protocol,
hostname: parts.hostname,
port: parts.port,
pathname: path,
query: query
});
}
/**
* SSL AND REDIRECTS
*/
urlRedirects = function urlRedirects(req, res, next) {
var requestedUrl = req.originalUrl || req.url,
requestedHost = req.get('host'),
targetHostWithProtocol,
targetHostWithoutProtocol;
if (res.isAdmin) {
targetHostWithProtocol = utils.url.urlFor('admin', true);
targetHostWithoutProtocol = utils.url.urlFor('admin', {cors: true}, true);
} else {
targetHostWithProtocol = utils.url.urlFor('home', true);
targetHostWithoutProtocol = utils.url.urlFor('home', {cors: true}, true);
}
debug('requestedUrl', requestedUrl);
debug('requestedHost', requestedHost);
debug('targetHost', targetHostWithoutProtocol);
// CASE: custom admin url is configured, but user requests blog domain
// CASE: exception: localhost is always allowed
if (!targetHostWithoutProtocol.match(new RegExp(requestedHost))) {
debug('redirect because host does not match');
return res.redirect(301, redirectUrl({
redirectTo: targetHostWithProtocol,
path: requestedUrl,
query: req.query
}));
}
// CASE: correct admin url, but not the correct protocol
if (utils.url.isSSL(targetHostWithProtocol) && !req.secure) {
debug('redirect because protocol does not match');
return res.redirect(301, redirectUrl({
redirectTo: targetHostWithProtocol,
path: requestedUrl,
query: req.query
}));
}
next();
};
module.exports = urlRedirects;

View file

@ -6,39 +6,37 @@ var moment = require('moment-timezone'),
url = require('url'),
config = require('./../config'),
settingsCache = require('./../api/settings').cache,
// @TODO: unify this with routes.apiBaseUrl
apiPath = '/ghost/api/v0.1',
// @TODO: unify this with the path in server/app.js
API_PATH = '/ghost/api/v0.1/',
STATIC_IMAGE_URL_PREFIX = 'content/images';
/** getBaseUrl
* Returns the base URL of the blog as set in the config. If called with secure options, returns the ssl URL.
/**
* Returns the base URL of the blog as set in the config.
*
* Secure:
* If the request is secure, we want to force returning the blog url as https.
* Imagine Ghost runs with http, but nginx allows SSL connections.
*
* @param {boolean} secure
* @return {string} URL returns the url as defined in config, but always with a trailing `/`
*/
function getBaseUrl(secure) {
var base;
function getBlogUrl(secure) {
var blogUrl;
// CASE: a specified SSL URL is configured (e. g. https://secure.blog.org/)
// see: https://github.com/TryGhost/Ghost/issues/6270#issuecomment-168939865
if (secure && config.get('urlSSL')) {
base = config.get('urlSSL');
if (secure) {
blogUrl = config.get('url').replace('http://', 'https://');
} else {
// CASE: no specified SSL URL configured, but user request is secure. In this case we force SSL
// and therefore replace the protocol.
if (secure) {
base = config.get('url').replace('http://', 'https://');
} else {
base = config.get('url');
}
blogUrl = config.get('url');
}
if (!base.match(/\/$/)) {
base += '/';
if (!blogUrl.match(/\/$/)) {
blogUrl += '/';
}
return base;
return blogUrl;
}
/** getSubdir
/**
* Returns a subdirectory URL, if defined so in the config.
* @return {string} URL a subdirectory if configured.
*/
@ -59,11 +57,25 @@ function getSubdir() {
return subdir;
}
function getProtectedSlugs() {
var subdir = getSubdir();
function deduplicateSubDir(url) {
var subDir = getSubdir(),
subDirRegex;
if (!_.isEmpty(subdir)) {
return config.get('slugs').protected.concat([subdir.split('/').pop()]);
if (!subDir) {
return url;
}
subDir = subDir.replace(/^\/|\/+$/, '');
subDirRegex = new RegExp(subDir + '\/' + subDir + '\/');
return url.replace(subDirRegex, subDir + '/');
}
function getProtectedSlugs() {
var subDir = getSubdir();
if (!_.isEmpty(subDir)) {
return config.get('slugs').protected.concat([subDir.split('/').pop()]);
} else {
return config.get('slugs').protected;
}
@ -77,8 +89,6 @@ function getProtectedSlugs() {
function urlJoin() {
var args = Array.prototype.slice.call(arguments),
prefixDoubleSlash = false,
subdir = getSubdir().replace(/^\/|\/+$/, ''),
subdirRegex,
url;
// Remove empty item at the beginning
@ -102,13 +112,28 @@ function urlJoin() {
url = url.replace(/^\//, '//');
}
// Deduplicate subdirectory
if (subdir) {
subdirRegex = new RegExp(subdir + '\/' + subdir + '\/');
url = url.replace(subdirRegex, subdir + '/');
url = deduplicateSubDir(url);
return url;
}
/**
* admin:url is optional
*/
function getAdminUrl() {
var adminUrl = config.get('admin:url'),
subDir = getSubdir();
if (!adminUrl) {
return;
}
return url;
if (!adminUrl.match(/\/$/)) {
adminUrl += '/';
}
adminUrl = urlJoin(adminUrl, subDir, '/');
adminUrl = deduplicateSubDir(adminUrl);
return adminUrl;
}
// ## createUrl
@ -122,7 +147,7 @@ function urlJoin() {
// Parameters:
// - urlPath - string which must start and end with a slash
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
// - secure (optional, default:false) - boolean whether or not to use urlSSL or url config
// - secure (optional, default:false) - boolean whether or not to force SSL
// Returns:
// - a URL which always ends with a slash
function createUrl(urlPath, absolute, secure) {
@ -132,7 +157,7 @@ function createUrl(urlPath, absolute, secure) {
// create base of url, always ends without a slash
if (absolute) {
base = getBaseUrl(secure);
base = getBlogUrl(secure);
} else {
base = getSubdir();
}
@ -193,7 +218,7 @@ function urlPathForPost(post) {
// - data (optional) - a json object containing data needed to generate a url
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
// This is probably not the right place for this, but it's the best place for now
// @TODO: rewrite, very hard to read!
// @TODO: rewrite, very hard to read, create private functions!
function urlFor(context, data, absolute) {
var urlPath = '/',
secure, imagePathRe,
@ -204,7 +229,7 @@ function urlFor(context, data, absolute) {
knownPaths = {
home: '/',
rss: '/rss/',
api: apiPath,
api: API_PATH,
sitemap_xsl: '/sitemap.xsl'
};
@ -239,7 +264,7 @@ function urlFor(context, data, absolute) {
if (absolute) {
// Remove the sub-directory from the URL because ghostConfig will add it back.
urlPath = urlPath.replace(new RegExp('^' + getSubdir()), '');
baseUrl = getBaseUrl(secure).replace(/\/$/, '');
baseUrl = getBlogUrl(secure).replace(/\/$/, '');
urlPath = baseUrl + urlPath;
}
@ -247,7 +272,7 @@ function urlFor(context, data, absolute) {
} else if (context === 'nav' && data.nav) {
urlPath = data.nav.url;
secure = data.nav.secure || secure;
baseUrl = getBaseUrl(secure);
baseUrl = getBlogUrl(secure);
hostname = baseUrl.split('//')[1] + getSubdir();
if (urlPath.indexOf(hostname) > -1
@ -266,20 +291,35 @@ function urlFor(context, data, absolute) {
}
}
} else if (context === 'home' && absolute) {
urlPath = getBaseUrl(secure);
// other objects are recognised but not yet supported
} else if (context === 'admin') {
if (config.get('forceAdminSSL')) {
urlPath = getBaseUrl(true);
} else {
urlPath = getBaseUrl();
urlPath = getBlogUrl(secure);
// CASE: with or without protocol?
// @TODO: rename cors
if (data && data.cors) {
urlPath = urlPath.replace(/^.*?:\/\//g, '//');
}
} else if (context === 'admin') {
urlPath = getAdminUrl() || getBlogUrl();
if (absolute) {
urlPath += 'ghost/';
} else {
urlPath = '/ghost/';
}
} else if (context === 'api') {
urlPath = getAdminUrl() || getBlogUrl();
// CASE: with or without protocol?
// @TODO: rename cors
if (data && data.cors) {
urlPath = urlPath.replace(/^.*?:\/\//g, '//');
}
if (absolute) {
urlPath = urlPath.replace(/\/$/, '') + API_PATH;
} else {
urlPath = API_PATH;
}
} else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) {
// trying to create a url for a named path
urlPath = knownPaths[context] || '/';
@ -294,41 +334,17 @@ function urlFor(context, data, absolute) {
return createUrl(urlPath, absolute, secure);
}
/**
* CASE: generate api url for CORS
* - we delete the http protocol if your blog runs with http and https (configured by nginx)
* - in that case your config.js configures Ghost with http and no admin ssl force
* - the browser then reads the protocol dynamically
*/
function apiUrl(options) {
options = options || {cors: false};
// @TODO unify this with urlFor
var url;
if (config.get('forceAdminSSL')) {
url = (config.get('urlSSL') || getBaseUrl(true)).replace(/^.*?:\/\//g, 'https://');
} else if (config.get('urlSSL')) {
url = config.get('urlSSL').replace(/^.*?:\/\//g, 'https://');
} else if (config.get('url').match(/^https:/)) {
url = config.get('url');
} else {
if (options.cors === false) {
url = config.get('url');
} else {
url = config.get('url').replace(/^.*?:\/\//g, '//');
}
}
return url.replace(/\/$/, '') + apiPath + '/';
function isSSL(urlToParse) {
var protocol = url.parse(urlToParse).protocol;
return protocol === 'https:';
}
module.exports.getProtectedSlugs = getProtectedSlugs;
module.exports.getSubdir = getSubdir;
module.exports.urlJoin = urlJoin;
module.exports.urlFor = urlFor;
module.exports.isSSL = isSSL;
module.exports.urlPathForPost = urlPathForPost;
module.exports.apiUrl = apiUrl;
/**
* If you request **any** image in Ghost, it get's served via

View file

@ -1,15 +1,14 @@
// # Frontend Route tests
// As it stands, these tests depend on the database, and as such are integration tests.
// Mocking out the models to not touch the DB would turn these into unit tests, and should probably be done in future,
// But then again testing real code, rather than mock code, might be more useful...
var request = require('supertest'),
should = require('should'),
testUtils = require('../../utils'),
ghost = testUtils.startGhost,
i18n = require('../../../../core/server/i18n'),
config = require('../../../../core/server/config');
var request = require('supertest'),
should = require('should'),
testUtils = require('../../utils'),
ghost = testUtils.startGhost,
i18n = require('../../../../core/server/i18n'),
config = require('../../../../core/server/config');
i18n.init();
@ -42,192 +41,164 @@ describe('Admin Routing', function () {
before(testUtils.teardown);
before(function (done) {
ghost().then(function (ghostServer) {
// Setup the request object with the ghost express app
request = request(ghostServer.rootApp);
done();
}).catch(function (e) {
console.log('Ghost Error: ', e);
console.log(e.stack);
done(e);
});
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
});
describe('Assets', function () {
it('should return 404 for unknown assets', function (done) {
request.get('/ghost/assets/not-found.js')
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(doEnd(done));
});
it('should retrieve built assets', function (done) {
request.get('/ghost/assets/vendor.js')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(200)
.end(doEnd(done));
});
});
describe('Legacy Redirects', function () {
it('should redirect /logout/ to /ghost/signout/', function (done) {
request.get('/logout/')
.expect('Location', '/ghost/signout/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /signout/ to /ghost/signout/', function (done) {
request.get('/signout/')
.expect('Location', '/ghost/signout/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /signup/ to /ghost/signup/', function (done) {
request.get('/signup/')
.expect('Location', '/ghost/signup/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
// Admin aliases
it('should redirect /signin/ to /ghost/', function (done) {
request.get('/signin/')
.expect('Location', '/ghost/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /admin/ to /ghost/', function (done) {
request.get('/admin/')
.expect('Location', '/ghost/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /GHOST/ to /ghost/', function (done) {
request.get('/GHOST/')
.expect('Location', '/ghost/')
.expect(301)
.end(doEndNoAuth(done));
});
});
// we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy
describe('Require HTTPS - no redirect', function () {
var forkedGhost, request;
describe('NO FORK', function () {
var ghostServer;
before(function (done) {
testUtils.fork.ghost({
forceAdminSSL: {redirect: false},
urlSSL: 'https://localhost/'
}, 'testhttps')
.then(function (child) {
forkedGhost = child;
request = require('supertest');
request = request(config.get('url').replace(/\/$/, ''));
})
.then(done)
.catch(done);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = request(config.get('url'));
done();
}).catch(function (e) {
console.log('Ghost Error: ', e);
console.log(e.stack);
done(e);
});
});
after(function (done) {
if (forkedGhost) {
forkedGhost.kill(done);
} else {
done(new Error('No forked ghost process exists, test setup must have failed.'));
}
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('should block admin access over non-HTTPS', function (done) {
request.get('/ghost/')
.expect(403)
.end(doEnd(done));
describe('Assets', function () {
it('should return 404 for unknown assets', function (done) {
request.get('/ghost/assets/not-found.js')
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(doEnd(done));
});
it('should retrieve built assets', function (done) {
request.get('/ghost/assets/vendor.js')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(200)
.end(doEnd(done));
});
});
it('should allow admin access over HTTPS', function (done) {
request.get('/ghost/setup/')
.set('X-Forwarded-Proto', 'https')
.expect(200)
.end(doEnd(done));
describe('Legacy Redirects', function () {
it('should redirect /logout/ to /ghost/signout/', function (done) {
request.get('/logout/')
.expect('Location', '/ghost/signout/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /signout/ to /ghost/signout/', function (done) {
request.get('/signout/')
.expect('Location', '/ghost/signout/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /signup/ to /ghost/signup/', function (done) {
request.get('/signup/')
.expect('Location', '/ghost/signup/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
// Admin aliases
it('should redirect /signin/ to /ghost/', function (done) {
request.get('/signin/')
.expect('Location', '/ghost/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /admin/ to /ghost/', function (done) {
request.get('/admin/')
.expect('Location', '/ghost/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEndNoAuth(done));
});
it('should redirect /GHOST/ to /ghost/', function (done) {
request.get('/GHOST/')
.expect('Location', '/ghost/')
.expect(301)
.end(doEndNoAuth(done));
});
});
describe('Ghost Admin Setup', function () {
it('should redirect from /ghost/ to /ghost/setup/ when no user/not installed yet', function (done) {
request.get('/ghost/')
.expect('Location', /ghost\/setup/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(302)
.end(doEnd(done));
});
it('should redirect from /ghost/signin/ to /ghost/setup/ when no user', function (done) {
request.get('/ghost/signin/')
.expect('Location', /ghost\/setup/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(302)
.end(doEnd(done));
});
it('should respond with html for /ghost/setup/', function (done) {
request.get('/ghost/setup/')
.expect('Content-Type', /html/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(doEnd(done));
});
});
});
describe('Require HTTPS - redirect', function () {
var forkedGhost, request;
before(function (done) {
testUtils.fork.ghost({
forceAdminSSL: {redirect: true},
urlSSL: 'https://localhost/'
}, 'testhttps')
.then(function (child) {
forkedGhost = child;
request = require('supertest');
request = request(config.get('url').replace(/\/$/, ''));
}).then(done)
.catch(done);
});
describe('FORK', function () {
// we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy
describe('Require HTTPS - redirect', function () {
var forkedGhost, request;
after(function (done) {
if (forkedGhost) {
forkedGhost.kill(done);
} else {
done(new Error('No forked ghost process exists, test setup must have failed.'));
}
});
before(function (done) {
testUtils.fork.ghost({
url: 'https://localhost/',
server: {
port: 2390
}
}, 'testhttps')
.then(function (child) {
forkedGhost = child;
request = require('supertest');
request = request('http://localhost:2390');
}).then(done)
.catch(done);
});
it('should redirect admin access over non-HTTPS', function (done) {
request.get('/ghost/')
.expect('Location', /^https:\/\/localhost\/ghost\//)
.expect(301)
.end(doEnd(done));
});
after(function (done) {
if (forkedGhost) {
forkedGhost.kill(done);
} else {
done(new Error('No forked ghost process exists, test setup must have failed.'));
}
});
it('should allow admin access over HTTPS', function (done) {
request.get('/ghost/setup/')
.set('X-Forwarded-Proto', 'https')
.expect(200)
.end(doEnd(done));
});
});
it('should redirect admin access over non-HTTPS', function (done) {
request.get('/ghost/')
.expect('Location', /^https:\/\/localhost:2390\/ghost\//)
.expect(301)
.end(doEnd(done));
});
describe('Ghost Admin Setup', function () {
it('should redirect from /ghost/ to /ghost/setup/ when no user/not installed yet', function (done) {
request.get('/ghost/')
.expect('Location', /ghost\/setup/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(302)
.end(doEnd(done));
});
it('should redirect from /ghost/signin/ to /ghost/setup/ when no user', function (done) {
request.get('/ghost/signin/')
.expect('Location', /ghost\/setup/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(302)
.end(doEnd(done));
});
it('should respond with html for /ghost/setup/', function (done) {
request.get('/ghost/setup/')
.expect('Content-Type', /html/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(doEnd(done));
it('should allow admin access over HTTPS', function (done) {
request.get('/ghost/setup/')
.set('X-Forwarded-Proto', 'https')
.expect(200)
.end(doEnd(done));
});
});
});
});

View file

@ -10,13 +10,16 @@ var supertest = require('supertest'),
request;
describe('Authentication API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -31,10 +34,11 @@ describe('Authentication API', function () {
});
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('can authenticate', function (done) {

View file

@ -1,29 +1,34 @@
var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Configuration API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
return testUtils.doAuth(request, 'posts');
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
accesstoken = token;
done();
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('success', function () {

View file

@ -2,17 +2,21 @@ var supertest = require('supertest'),
should = require('should'),
path = require('path'),
testUtils = require('../../../utils'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('DB API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -21,10 +25,11 @@ describe('DB API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('attaches the Content-Disposition header on export', function (done) {

View file

@ -6,24 +6,31 @@
var supertest = require('supertest'),
should = require('should'),
testUtils = require('../../../utils'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
require('should-http');
describe('Unauthorized', function () {
before(function (done) {
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
var ghostServer;
before(function (done) {
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
done();
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
})
.catch();
});
it('returns 401 error for known endpoint', function (done) {
@ -65,12 +72,16 @@ describe('Unauthorized', function () {
});
describe('Authorized API', function () {
var accesstoken;
var accesstoken, ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -79,10 +90,11 @@ describe('Authorized API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('serves a JSON 404 for an unknown endpoint', function (done) {

View file

@ -1,17 +1,21 @@
var testUtils = require('../../../utils'),
supertest = require('supertest'),
should = require('should'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Notifications API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -20,10 +24,11 @@ describe('Notifications API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('Add', function () {

View file

@ -3,17 +3,21 @@ var testUtils = require('../../../utils'),
supertest = require('supertest'),
_ = require('lodash'),
ObjectId = require('bson-objectid'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Post API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request, 'posts');
}).then(function (token) {
@ -22,10 +26,11 @@ describe('Post API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('Browse', function () {

View file

@ -2,6 +2,7 @@ var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
_ = require('lodash'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
@ -10,13 +11,16 @@ describe('Public API', function () {
settings: [
{key: 'labs', value: {publicAPI: true}}
]
};
}, ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request, 'posts', 'tags');
}).then(function (token) {
@ -36,10 +40,11 @@ describe('Public API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('browse posts', function (done) {

View file

@ -1,17 +1,21 @@
var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Settings API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -20,10 +24,11 @@ describe('Settings API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
// TODO: currently includes values of type=core

View file

@ -1,17 +1,21 @@
var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Slug API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -20,10 +24,11 @@ describe('Slug API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('should be able to get a post slug', function (done) {

View file

@ -13,33 +13,41 @@ var supertest = require('supertest'),
describe('Spam Prevention API', function () {
var author,
owner = testUtils.DataGenerator.Content.users[0];
owner = testUtils.DataGenerator.Content.users[0],
ghostServer;
before(function (done) {
ghost()
.then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
.then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
})
.then(function () {
request = supertest.agent(config.get('url'));
// in functional tests we start Ghost and the database get's migrated/seeded
// no need to add or care about any missing data (settings, permissions) except of extra data we would like to add or override
// override Ghost fixture owner and add some extra users
return testUtils.setup('owner:post')();
}).then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[1]
});
}).then(function (user) {
author = user;
done();
})
.catch(done);
// in functional tests we start Ghost and the database get's migrated/seeded
// no need to add or care about any missing data (settings, permissions) except of extra data we would like to add or override
// override Ghost fixture owner and add some extra users
return testUtils.setup('owner:post')();
})
.then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[1]
});
})
.then(function (user) {
author = user;
done();
})
.catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
afterEach(function (done) {

View file

@ -1,17 +1,21 @@
var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
describe('Tag API', function () {
var accesstoken = '';
var accesstoken = '', ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request, 'posts');
}).then(function (token) {
@ -20,10 +24,11 @@ describe('Tag API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
it('can retrieve all tags', function (done) {

View file

@ -22,11 +22,14 @@ describe('Themes API', function () {
.attach(fieldName, themePath);
},
editor: null
};
}, ghostServer;
before(function (done) {
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -47,7 +50,7 @@ describe('Themes API', function () {
}).catch(done);
});
after(function (done) {
after(function () {
// clean successful uploaded themes
fs.removeSync(config.getContentPath('themes') + '/valid');
fs.removeSync(config.getContentPath('themes') + '/casper.zip');
@ -55,10 +58,10 @@ describe('Themes API', function () {
// gscan creates /test/tmp in test mode
fs.removeSync(config.get('paths').appRoot + '/test');
testUtils.clearData()
return testUtils.clearData()
.then(function () {
done();
}).catch(done);
return ghostServer.stop();
});
});
describe('success cases', function () {

View file

@ -12,13 +12,16 @@ var testUtils = require('../../../utils'),
describe('Upload Icon API', function () {
var accesstoken = '',
getIconDimensions,
icons = [];
icons = [], ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -27,14 +30,15 @@ describe('Upload Icon API', function () {
}).catch(done);
});
after(function (done) {
after(function () {
icons.forEach(function (icon) {
fs.removeSync(config.get('paths').appRoot + icon);
});
testUtils.clearData().then(function () {
done();
}).catch(done);
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('success cases for icons', function () {

View file

@ -10,13 +10,17 @@ var testUtils = require('../../../utils'),
describe('Upload API', function () {
var accesstoken = '',
images = [];
images = [],
ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
return testUtils.doAuth(request);
}).then(function (token) {
@ -25,14 +29,15 @@ describe('Upload API', function () {
}).catch(done);
});
after(function (done) {
after(function () {
images.forEach(function (image) {
fs.removeSync(config.get('paths').appRoot + image);
});
testUtils.clearData().then(function () {
done();
}).catch(done);
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('success cases', function () {

View file

@ -2,6 +2,7 @@ var testUtils = require('../../../utils'),
should = require('should'),
supertest = require('supertest'),
ObjectId = require('bson-objectid'),
config = require('../../../../../core/server/config'),
ghost = testUtils.startGhost,
request;
@ -9,13 +10,16 @@ describe('User API', function () {
var ownerAccessToken = '',
editorAccessToken = '',
authorAccessToken = '',
editor, author;
editor, author, ghostServer;
before(function (done) {
// starting ghost automatically populates the db
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
ghost().then(function (ghostServer) {
request = supertest.agent(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = supertest.agent(config.get('url'));
}).then(function () {
// create editor
return testUtils.createUser({
@ -51,10 +55,11 @@ describe('User API', function () {
}).catch(done);
});
after(function (done) {
testUtils.clearData().then(function () {
done();
}).catch(done);
after(function () {
return testUtils.clearData()
.then(function () {
return ghostServer.stop();
});
});
describe('As Owner', function () {

View file

@ -7,9 +7,12 @@ var request = require('supertest'),
should = require('should'),
cheerio = require('cheerio'),
testUtils = require('../../utils'),
config = require('../../../../core/server/config'),
ghost = testUtils.startGhost;
describe('Channel Routes', function () {
var ghostServer;
function doEnd(done) {
return function (err, res) {
if (err) {
@ -26,10 +29,11 @@ describe('Channel Routes', function () {
}
before(function (done) {
ghost().then(function (ghostServer) {
// Setup the request object with the ghost express app
request = request(ghostServer.rootApp);
ghost().then(function (_ghostServer) {
ghostServer = _ghostServer;
return ghostServer.start();
}).then(function () {
request = request(config.get('url'));
done();
}).catch(function (e) {
console.log('Ghost Error: ', e);
@ -40,6 +44,10 @@ describe('Channel Routes', function () {
after(testUtils.teardown);
after(function () {
return ghostServer.stop();
});
describe('Index', function () {
it('should respond with html', function (done) {
request.get('/')

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: '',
clientSecret: '',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
ghostUrl.url.api().should.equal('//testblog.com/ghost/api/v0.1/');
@ -40,7 +40,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: '',
clientSecret: '',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
ghostUrl.url.api('a/', '/b', '/c/').should.equal('//testblog.com/ghost/api/v0.1/a/b/c/');
@ -50,7 +50,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
ghostUrl.url.api().should.equal('//testblog.com/ghost/api/v0.1/?client_id=ghost-frontend&client_secret=notasecret');
@ -60,7 +60,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
var rendered = ghostUrl.url.api({a: 'string', b: 5, c: 'en coded'});
@ -98,7 +98,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2});
@ -114,10 +114,11 @@ describe('Ghost Ajax Helper', function () {
configUtils.set({
url: 'https://testblog.com/'
});
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', true)
});
var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2});
@ -136,7 +137,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', true)
});
var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2});
@ -156,7 +157,7 @@ describe('Ghost Ajax Helper', function () {
ghostUrl.init({
clientId: 'ghost-frontend',
clientSecret: 'notasecret',
url: utils.url.apiUrl({cors: true})
url: utils.url.urlFor('api', {cors: true}, true)
});
var rendered = ghostUrl.url.api('posts', {limit: 3}),

View file

@ -159,11 +159,14 @@ describe('cors', function () {
done();
});
it('should be enabled if the origin matches config.urlSSL', function (done) {
var origin = 'https://secure.blog';
it('should be enabled if the origin matches config.url', function (done) {
var origin = 'http://admin:2222';
configUtils.set({
url: 'http://my.blog',
urlSSL: origin
url: 'https://blog',
admin: {
url: origin
}
});
req.get = sinon.stub().withArgs('origin').returns(origin);

View file

@ -1,226 +0,0 @@
var sinon = require('sinon'),
should = require('should'),
configUtils = require('../../utils/configUtils'),
checkSSL = require('../../../server/middleware/check-ssl');
should.equal(true, true);
describe('checkSSL', function () {
var res, req, next, sandbox;
beforeEach(function () {
sandbox = sinon.sandbox.create();
req = {};
res = {};
next = sandbox.spy();
configUtils.set({
url: 'http://default.com:2368/'
});
});
afterEach(function () {
sandbox.restore();
configUtils.restore();
});
it('should not require SSL (frontend)', function (done) {
req.originalUrl = '/';
checkSSL(req, res, next);
next.called.should.be.true();
next.calledWith().should.be.true();
done();
});
it('should require SSL (frontend)', function (done) {
req.originalUrl = '/';
req.secure = true;
checkSSL(req, res, next);
next.called.should.be.true();
next.calledWith().should.be.true();
done();
});
it('should not require SSL (admin)', function (done) {
req.originalUrl = '/ghost';
res.isAdmin = true;
checkSSL(req, res, next);
next.called.should.be.true();
next.calledWith().should.be.true();
done();
});
it('should not redirect with SSL (admin)', function (done) {
req.originalUrl = '/ghost';
res.isAdmin = true;
res.secure = true;
checkSSL(req, res, next);
next.called.should.be.true();
next.calledWith().should.be.true();
done();
});
it('should not redirect with force admin SSL (admin)', function (done) {
req.originalUrl = '/ghost';
res.isAdmin = true;
req.secure = true;
configUtils.set({
url: 'http://default.com:2368/',
forceAdminSSL: true
});
checkSSL(req, res, next);
next.called.should.be.true();
next.calledWith().should.be.true();
done();
});
it('should redirect with force admin SSL (admin)', function (done) {
req.originalUrl = '/ghost/';
res.isAdmin = true;
res.redirect = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/',
urlSSL: '',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://default.com:2368/ghost/');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should redirect to subdirectory with force admin SSL (admin)', function (done) {
req.originalUrl = '/blog/ghost/';
res.isAdmin = true;
res.redirect = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/blog/',
urlSSL: '',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://default.com:2368/blog/ghost/');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should redirect and keep query with force admin SSL (admin)', function (done) {
req.originalUrl = '/ghost/';
req.query = {
test: 'true'
};
res.isAdmin = true;
res.redirect = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/',
urlSSL: '',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://default.com:2368/ghost/?test=true');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should redirect with with config.url being SSL (frontend)', function (done) {
req.originalUrl = '/';
req.secure = false;
res.redirect = {};
configUtils.set({
url: 'https://default.com:2368',
urlSSL: '',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://default.com:2368/');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should redirect to urlSSL (admin)', function (done) {
req.originalUrl = '/ghost/';
res.isAdmin = true;
res.redirect = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/',
urlSSL: 'https://ssl-domain.com:2368/',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://ssl-domain.com:2368/ghost/');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should not redirect if redirect:false (admin)', function (done) {
req.originalUrl = '/ghost/';
res.isAdmin = true;
res.sendStatus = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/',
forceAdminSSL: {
redirect: false
}
});
sandbox.stub(res, 'sendStatus', function (statusCode) {
statusCode.should.eql(403);
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
it('should redirect to correct path with force admin SSL (admin as subapp)', function (done) {
req.url = '/';
req.originalUrl = '/ghost/';
res.isAdmin = true;
res.redirect = {};
req.secure = false;
configUtils.set({
url: 'http://default.com:2368/',
urlSSL: '',
forceAdminSSL: true
});
sandbox.stub(res, 'redirect', function (statusCode, url) {
statusCode.should.eql(301);
url.should.not.be.empty();
url.should.eql('https://default.com:2368/ghost/');
return;
});
checkSSL(req, res, next);
next.called.should.be.false();
done();
});
});

View file

@ -67,23 +67,6 @@ describe('Theme Handler', function () {
next.called.should.be.true();
});
it('handles secure context', function () {
var themeOptSpy = sandbox.stub(hbs, 'updateTemplateOptions');
req.secure = true;
res.locals = {};
configUtils.set({urlSSL: 'https://secure.blog'});
themeHandler.configHbsForContext(req, res, next);
themeOptSpy.calledOnce.should.be.true();
themeOptSpy.firstCall.args[0].should.be.an.Object().and.have.property('data');
themeOptSpy.firstCall.args[0].data.should.be.an.Object().and.have.property('blog');
themeOptSpy.firstCall.args[0].data.blog.should.be.an.Object().and.have.property('url');
themeOptSpy.firstCall.args[0].data.blog.url.should.eql('https://secure.blog');
res.locals.secure.should.equal(true);
next.called.should.be.true();
});
it('sets view path', function () {
req.secure = true;
res.locals = {};

View file

@ -0,0 +1,196 @@
var sinon = require('sinon'),
should = require('should'),
configUtils = require('../../utils/configUtils'),
urlRedirects = require('../../../server/middleware/url-redirects'),
sandbox = sinon.sandbox.create();
should.equal(true, true);
describe('checkSSL', function () {
var res, req, next, host;
beforeEach(function () {
req = {
get: function get() {
return host;
}
};
res = {
redirect: sandbox.spy()
};
next = sandbox.spy();
});
afterEach(function () {
sandbox.restore();
configUtils.restore();
host = null;
});
it('blog is http, requester uses http', function (done) {
configUtils.set({
url: 'http://default.com:2368/'
});
host = 'default.com:2368';
req.originalUrl = '/';
urlRedirects(req, res, next);
next.called.should.be.true();
res.redirect.called.should.be.false();
next.calledWith().should.be.true();
done();
});
it('blog is https, requester uses https', function (done) {
configUtils.set({
url: 'https://default.com:2368/'
});
host = 'default.com:2368';
req.originalUrl = '/';
req.secure = true;
urlRedirects(req, res, next);
next.called.should.be.true();
res.redirect.called.should.be.false();
next.calledWith().should.be.true();
done();
});
it('blog is https, requester uses http [redirect]', function (done) {
configUtils.set({
url: 'https://default.com:2368/'
});
host = 'default.com:2368';
req.originalUrl = '/';
urlRedirects(req, res, next);
next.called.should.be.false();
res.redirect.called.should.be.true();
done();
});
it('blog is http, requester uses https', function (done) {
configUtils.set({
url: 'http://default.com:2368/'
});
host = 'default.com:2368';
req.originalUrl = '/';
req.secure = true;
urlRedirects(req, res, next);
next.called.should.be.true();
res.redirect.called.should.be.false();
done();
});
it('blog is http, requester uses https', function (done) {
configUtils.set({
url: 'http://default.com:2368/'
});
host = 'default.com:2368/';
req.originalUrl = '/';
req.secure = true;
urlRedirects(req, res, next);
next.called.should.be.true();
res.redirect.called.should.be.false();
done();
});
it('admin is blog url and http, requester is http', function (done) {
configUtils.set({
url: 'http://default.com:2368'
});
host = 'default.com:2368';
res.isAdmin = true;
req.originalUrl = '/ghost';
urlRedirects(req, res, next);
next.called.should.be.true();
res.redirect.called.should.be.false();
done();
});
it('admin is custom url and https, requester is http [redirect]', function (done) {
configUtils.set({
url: 'http://default.com:2368',
admin: {
url: 'https://default.com:2368'
}
});
host = 'default.com:2368';
res.isAdmin = true;
req.originalUrl = '/ghost';
urlRedirects(req, res, next);
next.called.should.be.false();
res.redirect.calledWith(301, 'https://default.com:2368/ghost').should.be.true();
done();
});
it('admin is custom url and https, requester is http [redirect]', function (done) {
configUtils.set({
url: 'http://default.com:2368',
admin: {
url: 'https://admin.default.com:2368'
}
});
host = 'default.com:2368';
res.isAdmin = true;
req.originalUrl = '/ghost';
urlRedirects(req, res, next);
next.called.should.be.false();
res.redirect.calledWith(301, 'https://admin.default.com:2368/ghost').should.be.true();
done();
});
it('subdirectory [redirect]', function (done) {
configUtils.set({
url: 'http://default.com:2368/blog',
admin: {
url: 'https://admin.default.com:2368'
}
});
host = 'default.com:2368';
res.isAdmin = true;
req.originalUrl = '/blog/ghost';
urlRedirects(req, res, next);
next.called.should.be.false();
res.redirect.calledWith(301, 'https://admin.default.com:2368/blog/ghost').should.be.true();
done();
});
it('keeps query [redirect]', function (done) {
configUtils.set({
url: 'http://default.com:2368',
admin: {
url: 'https://admin.default.com:2368'
}
});
host = 'default.com:2368';
res.isAdmin = true;
req.originalUrl = '/ghost';
req.query = {
test: true
};
urlRedirects(req, res, next);
next.called.should.be.false();
res.redirect.calledWith(301, 'https://admin.default.com:2368/ghost?test=true').should.be.true();
done();
});
});

View file

@ -195,7 +195,7 @@ describe('Slack', function () {
isPostStub.returns(false);
settingsObj.settings[0] = slackObjWithUrl;
configUtils.set('forceAdminSSL', true);
configUtils.set('url', 'https://myblog.com');
// assertions
makeRequestAssertions = function (requestOptions, requestData) {

View file

@ -104,22 +104,22 @@ describe('Url', function () {
it('should return home url when asked for', function () {
var testContext = 'home';
configUtils.set({url: 'http://my-ghost-blog.com', urlSSL: 'https://my-ghost-blog.com'});
configUtils.set({url: 'http://my-ghost-blog.com'});
utils.url.urlFor(testContext).should.equal('/');
utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/');
utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/');
configUtils.set({url: 'http://my-ghost-blog.com/', urlSSL: 'https://my-ghost-blog.com/'});
configUtils.set({url: 'http://my-ghost-blog.com/'});
utils.url.urlFor(testContext).should.equal('/');
utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/');
utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/');
configUtils.set({url: 'http://my-ghost-blog.com/blog', urlSSL: 'https://my-ghost-blog.com/blog'});
configUtils.set({url: 'http://my-ghost-blog.com/blog'});
utils.url.urlFor(testContext).should.equal('/blog/');
utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/');
utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/');
configUtils.set({url: 'http://my-ghost-blog.com/blog/', urlSSL: 'https://my-ghost-blog.com/blog/'});
configUtils.set({url: 'http://my-ghost-blog.com/blog/'});
utils.url.urlFor(testContext).should.equal('/blog/');
utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/');
utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/');
@ -245,7 +245,7 @@ describe('Url', function () {
var testContext = 'nav',
testData;
configUtils.set({url: 'http://my-ghost-blog.com', urlSSL: 'https://my-ghost-blog.com'});
configUtils.set({url: 'http://my-ghost-blog.com'});
testData = {nav: {url: 'http://my-ghost-blog.com/short-and-sweet/'}};
utils.url.urlFor(testContext, testData).should.equal('http://my-ghost-blog.com/short-and-sweet/');
@ -286,13 +286,10 @@ describe('Url', function () {
utils.url.urlFor(testContext, testData).should.equal('mailto:marshmallow@my-ghost-blog.com');
});
it('should return other known paths when requested', function () {
it('sitemap: should return other known paths when requested', function () {
configUtils.set({url: 'http://my-ghost-blog.com'});
utils.url.urlFor('sitemap_xsl').should.equal('/sitemap.xsl');
utils.url.urlFor('sitemap_xsl', true).should.equal('http://my-ghost-blog.com/sitemap.xsl');
utils.url.urlFor('api').should.equal('/ghost/api/v0.1');
utils.url.urlFor('api', true).should.equal('http://my-ghost-blog.com/ghost/api/v0.1');
});
it('admin: relative', function () {
@ -303,7 +300,7 @@ describe('Url', function () {
utils.url.urlFor('admin').should.equal('/ghost/');
});
it('admin: forceAdminSSL is false', function () {
it('admin: url is http', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});
@ -311,22 +308,15 @@ describe('Url', function () {
utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/ghost/');
});
it('admin: forceAdminSSL is true', function () {
it('admin: custom admin url is set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
forceAdminSSL: true
admin: {
url: 'https://admin.my-ghost-blog.com'
}
});
utils.url.urlFor('admin', true).should.equal('https://my-ghost-blog.com/ghost/');
});
it('admin: forceAdminSSL is true', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
forceAdminSSL: true
});
utils.url.urlFor('admin', true).should.equal('https://my-ghost-blog.com/ghost/');
utils.url.urlFor('admin', true).should.equal('https://admin.my-ghost-blog.com/ghost/');
});
it('admin: blog is on subdir', function () {
@ -337,6 +327,14 @@ describe('Url', function () {
utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog/'
});
utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog'
@ -344,6 +342,93 @@ describe('Url', function () {
utils.url.urlFor('admin').should.equal('/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog',
admin: {
url: 'http://something.com'
}
});
utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog',
admin: {
url: 'http://something.com/blog'
}
});
utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog',
admin: {
url: 'http://something.com/blog/'
}
});
utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/');
});
it('admin: blog is on subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog/',
admin: {
url: 'http://something.com/blog'
}
});
utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/');
});
it('api: should return admin url is set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
admin: {
url: 'https://something.de'
}
});
utils.url.urlFor('api', true).should.eql('https://something.de/ghost/api/v0.1/');
});
it('api: url has subdir', function () {
configUtils.set({
url: 'http://my-ghost-blog.com/blog'
});
utils.url.urlFor('api', true).should.eql('http://my-ghost-blog.com/blog/ghost/api/v0.1/');
});
it('api: should return http if config.url is http', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});
utils.url.urlFor('api', true).should.eql('http://my-ghost-blog.com/ghost/api/v0.1/');
});
it('api: should return https if config.url is https', function () {
configUtils.set({
url: 'https://my-ghost-blog.com'
});
utils.url.urlFor('api', true).should.eql('https://my-ghost-blog.com/ghost/api/v0.1/');
});
it('api: cors should return no protocol', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});
utils.url.urlFor('api', {cors: true}, true).should.eql('//my-ghost-blog.com/ghost/api/v0.1/');
});
});
describe('urlPathForPost', function () {
@ -432,77 +517,4 @@ describe('Url', function () {
utils.url.urlPathForPost(testData).should.equal(postLink);
});
});
describe('apiUrl', function () {
it('should return https config.url if forceAdminSSL set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
forceAdminSSL: true
});
utils.url.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/');
});
it('should return https config.urlSSL if forceAdminSSL set and urlSSL is misconfigured', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'http://other-ghost-blog.com',
forceAdminSSL: true
});
utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});
it('should return https config.urlSSL if forceAdminSSL set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'https://other-ghost-blog.com',
forceAdminSSL: true
});
utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});
it('should return https config.urlSSL if set and misconfigured & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'http://other-ghost-blog.com'
});
utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});
it('should return https config.urlSSL if set & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'https://other-ghost-blog.com'
});
utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});
it('should return https config.url if config.url is https & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'https://my-ghost-blog.com'
});
utils.url.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/');
});
it('CORS: should return no protocol config.url if config.url is NOT https & forceAdminSSL/urlSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});
utils.url.apiUrl({cors: true}).should.eql('//my-ghost-blog.com/ghost/api/v0.1/');
});
it('should return protocol config.url if config.url is NOT https & forceAdminSSL/urlSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});
utils.url.apiUrl().should.eql('http://my-ghost-blog.com/ghost/api/v0.1/');
});
});
});

View file

@ -27,6 +27,12 @@ configUtils.set = function () {
* nconf keeps this as a reference and then it can happen that the defaultConfig get's overridden by new values
*/
configUtils.restore = function () {
/**
* we have to reset the whole config object
* config keys, which get set via a test and do not exist in the config files, won't get reseted
*/
config.reset();
_.each(configUtils.defaultConfig, function (value, key) {
config.set(key, _.cloneDeep(value));
});