mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 12:53:13 +03:00
64ed246d03
no issue - added an `externalRequest` lib - uses same underlying `got` module as our `request` lib - uses `got`'s `beforeRequest` and `beforeRedirect` hooks to perform it's own dns resolution for each url that's encountered and aborts with an error if it resolves to a private IP address block - includes a bypass for Ghost's configured url so that requests to it's own hostname+port are not blocked - updated v2 and canary oembed controllers to use the `externalRequest` lib
333 lines
12 KiB
JavaScript
333 lines
12 KiB
JavaScript
const sinon = require('sinon');
|
|
const should = require('should');
|
|
const rewire = require('rewire');
|
|
const nock = require('nock');
|
|
const externalRequest = rewire('../../../core/server/lib/request-external');
|
|
const configUtils = require('../../utils/configUtils');
|
|
|
|
// for sinon stubs
|
|
const dnsPromises = require('dns').promises;
|
|
|
|
describe('External Request', function () {
|
|
describe('with private ip', function () {
|
|
beforeEach(function () {
|
|
sinon.stub(dnsPromises, 'lookup').callsFake(function () {
|
|
return Promise.resolve({address: '192.168.0.1'});
|
|
});
|
|
});
|
|
|
|
afterEach(function () {
|
|
configUtils.restore();
|
|
sinon.restore();
|
|
nock.cleanAll();
|
|
});
|
|
|
|
it('allows configured hostname', function () {
|
|
configUtils.set('url', 'http://example.com');
|
|
|
|
const url = 'http://example.com/endpoint/';
|
|
const expectedResponse = {
|
|
body: 'Response body',
|
|
url: 'http://example.com/endpoint/',
|
|
statusCode: 200
|
|
};
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://example.com')
|
|
.get('/endpoint/')
|
|
.reply(200, 'Response body');
|
|
|
|
return externalRequest(url, options).then(function (res) {
|
|
requestMock.isDone().should.be.true();
|
|
should.exist(res);
|
|
should.exist(res.body);
|
|
res.body.should.be.equal(expectedResponse.body);
|
|
should.exist(res.url);
|
|
res.statusCode.should.be.equal(expectedResponse.statusCode);
|
|
should.exist(res.statusCode);
|
|
res.url.should.be.equal(expectedResponse.url);
|
|
});
|
|
});
|
|
|
|
it('allows configured hostname+port', function () {
|
|
configUtils.set('url', 'http://example.com:2368');
|
|
|
|
const url = 'http://example.com:2368/endpoint/';
|
|
const expectedResponse = {
|
|
body: 'Response body',
|
|
url: 'http://example.com:2368/endpoint/',
|
|
statusCode: 200
|
|
};
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://example.com:2368')
|
|
.get('/endpoint/')
|
|
.reply(200, 'Response body');
|
|
|
|
return externalRequest(url, options).then(function (res) {
|
|
requestMock.isDone().should.be.true();
|
|
should.exist(res);
|
|
should.exist(res.body);
|
|
res.body.should.be.equal(expectedResponse.body);
|
|
should.exist(res.url);
|
|
res.statusCode.should.be.equal(expectedResponse.statusCode);
|
|
should.exist(res.statusCode);
|
|
res.url.should.be.equal(expectedResponse.url);
|
|
});
|
|
});
|
|
|
|
it('blocks configured hostname with incorrect port', function () {
|
|
configUtils.set('url', 'http://example.com');
|
|
|
|
const url = 'http://example.com:1234/endpoint/';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have rejected with non-permitted IP message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL resolves to a non-permitted private IP block');
|
|
});
|
|
});
|
|
|
|
it('blocks configured hostname+port with incorrect port', function () {
|
|
configUtils.set('url', 'http://example.com:2368');
|
|
|
|
const url = 'http://example.com:1234/endpoint/';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have rejected with non-permitted IP message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL resolves to a non-permitted private IP block');
|
|
});
|
|
});
|
|
|
|
it('blocks on request', function () {
|
|
const url = 'http://some-website.com/';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://some-website.com')
|
|
.get('/files/')
|
|
.reply(200, 'Response');
|
|
|
|
return externalRequest(url, options).then(function () {
|
|
throw new Error('Request should have rejected with non-permitted IP message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL resolves to a non-permitted private IP block');
|
|
requestMock.isDone().should.be.false();
|
|
});
|
|
});
|
|
|
|
it('blocks on redirect', function () {
|
|
configUtils.set('url', 'http://some-website.com');
|
|
|
|
const url = 'http://some-website.com/endpoint/';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://some-website.com')
|
|
.get('/endpoint/')
|
|
.reply(301, 'Oops, got redirected',
|
|
{
|
|
location: 'http://someredirectedurl.com/files/'
|
|
});
|
|
|
|
const secondRequestMock = nock('http://someredirectedurl.com')
|
|
.get('/files/')
|
|
.reply(200, 'Redirected response');
|
|
|
|
return externalRequest(url, options).then(function () {
|
|
throw new Error('Request should have rejected with non-permitted IP message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL resolves to a non-permitted private IP block');
|
|
requestMock.isDone().should.be.true();
|
|
secondRequestMock.isDone().should.be.false();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('general behaviour', function () {
|
|
beforeEach(function () {
|
|
sinon.stub(dnsPromises, 'lookup').callsFake(function (host) {
|
|
return Promise.resolve({address: '123.123.123.123'});
|
|
});
|
|
});
|
|
|
|
afterEach(function () {
|
|
configUtils.restore();
|
|
sinon.restore();
|
|
nock.cleanAll();
|
|
});
|
|
|
|
it('[success] should return response for http request', function () {
|
|
const url = 'http://some-website.com/endpoint/';
|
|
const expectedResponse = {
|
|
body: 'Response body',
|
|
url: 'http://some-website.com/endpoint/',
|
|
statusCode: 200
|
|
};
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://some-website.com')
|
|
.get('/endpoint/')
|
|
.reply(200, 'Response body');
|
|
|
|
return externalRequest(url, options).then(function (res) {
|
|
requestMock.isDone().should.be.true();
|
|
should.exist(res);
|
|
should.exist(res.body);
|
|
res.body.should.be.equal(expectedResponse.body);
|
|
should.exist(res.url);
|
|
res.statusCode.should.be.equal(expectedResponse.statusCode);
|
|
should.exist(res.statusCode);
|
|
res.url.should.be.equal(expectedResponse.url);
|
|
});
|
|
});
|
|
|
|
it('[success] can handle redirect', function () {
|
|
const url = 'http://some-website.com/endpoint/';
|
|
const expectedResponse = {
|
|
body: 'Redirected response',
|
|
url: 'http://someredirectedurl.com/files/',
|
|
statusCode: 200
|
|
};
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://some-website.com')
|
|
.get('/endpoint/')
|
|
.reply(301, 'Oops, got redirected',
|
|
{
|
|
location: 'http://someredirectedurl.com/files/'
|
|
});
|
|
|
|
const secondRequestMock = nock('http://someredirectedurl.com')
|
|
.get('/files/')
|
|
.reply(200, 'Redirected response');
|
|
|
|
return externalRequest(url, options).then(function (res) {
|
|
requestMock.isDone().should.be.true();
|
|
secondRequestMock.isDone().should.be.true();
|
|
should.exist(res);
|
|
should.exist(res.body);
|
|
res.body.should.be.equal(expectedResponse.body);
|
|
should.exist(res.url);
|
|
res.statusCode.should.be.equal(expectedResponse.statusCode);
|
|
should.exist(res.statusCode);
|
|
res.url.should.be.equal(expectedResponse.url);
|
|
});
|
|
});
|
|
|
|
it('[failure] can handle invalid url', function () {
|
|
const url = 'test';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have rejected with invalid url message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL empty or invalid.');
|
|
});
|
|
});
|
|
|
|
it('[failure] can handle empty url', function () {
|
|
const url = '';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have rejected with invalid url message');
|
|
}, (err) => {
|
|
should.exist(err);
|
|
err.message.should.be.equal('URL empty or invalid.');
|
|
});
|
|
});
|
|
|
|
it('[failure] can handle an error with statuscode not 200', function () {
|
|
const url = 'http://nofilehere.com/files/test.txt';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://nofilehere.com')
|
|
.get('/files/test.txt')
|
|
.reply(404);
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have errored');
|
|
}, (err) => {
|
|
requestMock.isDone().should.be.true();
|
|
should.exist(err);
|
|
err.statusMessage.should.be.equal('Not Found');
|
|
});
|
|
});
|
|
|
|
it('[failure] returns error if request errors', function () {
|
|
const url = 'http://nofilehere.com/files/test.txt';
|
|
const options = {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0'
|
|
}
|
|
};
|
|
|
|
const requestMock = nock('http://nofilehere.com')
|
|
.get('/files/test.txt')
|
|
.times(3) // 1 original request + 2 default retries
|
|
.reply(500, {message: 'something awful happened', code: 'AWFUL_ERROR'});
|
|
|
|
return externalRequest(url, options).then(() => {
|
|
throw new Error('Request should have errored with an awful error');
|
|
}, (err) => {
|
|
requestMock.isDone().should.be.true();
|
|
should.exist(err);
|
|
err.statusMessage.should.be.equal('Internal Server Error');
|
|
err.body.should.match(/something awful happened/);
|
|
err.body.should.match(/AWFUL_ERROR/);
|
|
});
|
|
});
|
|
});
|
|
});
|