Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support & Tests for X-Forwarded-Path #320

Merged
merged 2 commits into from
Aug 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions lib/http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
} else {
this.useXForwardedHostHeader = true;
}
if(typeof options.useXForwardedPathHeader !== 'undefined') {
this.useXForwardedPathHeader = options.useXForwardedPathHeader ? true : false;
} else {
this.useXForwardedPathHeader = true;
}
this.idCounter = 0;
this.zetta = zettaInstance;
this.peerRegistry = zettaInstance.peerRegistry;
Expand Down Expand Up @@ -78,7 +83,7 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
/^\/peers\/(.+)$/, // /peers/123123...
/^\/peer-management$/, // /peer-management
];

function match(request) {
return ValidWSUrls.some(function(re) {
return re.test(request.url);
Expand All @@ -87,7 +92,7 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {

this.wss = new WebSocketServer({ noServer: true });
this.server.on('upgrade', function(request, socket, headers) {

var sendError = function(code) {
// Check any custom websocket paths from extentions
var finish = function() {
Expand All @@ -108,7 +113,7 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
finish();
}
};

if (/^\/peers\/(.+)$/.exec(request.url)) {
async.eachSeries(self._wsHooks.peerConnect, function(handler, next) {
return handler(request, socket, headers, next);
Expand All @@ -118,7 +123,7 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
}

// Handle Peer Request
self.wss.handleUpgrade(request, socket, headers, function(ws) {
self.wss.handleUpgrade(request, socket, headers, function(ws) {
self.setupPeerSocket(ws);
});
});
Expand All @@ -135,7 +140,7 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
var query = [
{ name: self.zetta.id, topic: '_peer/connect' },
{ name: self.zetta.id, topic: '_peer/disconnect' }];

var client = new EventSocket(ws, query);
self.eventBroker.client(client);
} else {
Expand All @@ -147,10 +152,14 @@ var ZettaHttpServer = module.exports = function(zettaInstance, options) {
// 404
sendError(404);
}

});

this.cloud = titan({useXForwardedHostHeader: this.useXForwardedHostHeader})
var titanOpts = {
useXForwardedHostHeader: this.useXForwardedHostHeader,
useXForwardedPathHeader: this.useXForwardedPathHeader
};
this.cloud = titan(titanOpts)
.format({ engines: [Siren], override: { 'application/json': Siren }, directory: path.join(__dirname, './api_formats') })
.add(RootResource, zettaInstance)
.add(PeersManagementResource, zettaInstance)
Expand Down Expand Up @@ -284,7 +293,7 @@ ZettaHttpServer.prototype.setupPeerSocket = function(ws) {
// Include ._env and ._loader on websocket, allows argo formatters to work used in virtual_device build actions.
var host = ws.upgradeReq.headers['host']
self.wireUpWebSocketForEvent(ws, host, '/servers/' + name);

if (self.peers[name] && self.peers[name].state !== PeerSocket.DISCONNECTED) {
// peer already connected or connecting
ws.close(4000, 'peer already connected');
Expand All @@ -294,7 +303,7 @@ ZettaHttpServer.prototype.setupPeerSocket = function(ws) {
} else {
var peer = new PeerSocket(ws, name, self.peerRegistry);
self.peers[name] = peer;

// Events coming from the peers pubsub using push streams
peer.on('zetta-events', function(topic, data) {
self.zetta.pubsub.publish(name + '/' + topic, data, true); // Set fromRemote flag to true
Expand Down Expand Up @@ -500,7 +509,7 @@ ZettaHttpServer.prototype.proxyToPeer = function(env, next) {

var agent = env.zettaAgent || peer.agent;

var opts = {
var opts = {
method: req.method,
headers: req.headers,
path: req.url,
Expand All @@ -518,7 +527,7 @@ ZettaHttpServer.prototype.proxyToPeer = function(env, next) {
Object.keys(response.headers).forEach(function(header) {
res.setHeader(header, response.headers[header]);
});

res.statusCode = response.statusCode;

if (!opts.pipe) {
Expand Down
135 changes: 119 additions & 16 deletions test/test_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function checkDeviceOnRootUri(entity) {
assert(entity.class.indexOf('device') >= 0);
assert(entity.class.indexOf(entity.properties.type) >= 0);
assert.deepEqual(entity.rel, ["http://rels.zettajs.io/device"]);

assert(entity.properties.name);
assert(entity.properties.type);
assert(entity.properties.state);
Expand Down Expand Up @@ -102,6 +102,27 @@ describe('Zetta Api', function() {
});
})

it('updates href path using x-forwarded-path header', function(done) {
var app = zetta({ registry: reg, peerRegistry: peerRegistry })
.silent()
.name('local')
._run(function(err) {
if (err) {
return done(err);
}
var rootPath = '/api/v1';
request(getHttpServer(app))
.get('/')
.set('x-forwarded-path', rootPath)
.expect(getBody(function(res, body) {
var self = body.links.filter(function(link) { return link.rel.indexOf('self') >= 0; })[0];
var resultPath = require('url').parse(self.href).pathname;
assert.equal(resultPath.substr(0, rootPath.length), rootPath);
}))
.end(done);
});
})

it('allow for x-forwarded-host header to be disabled', function(done) {
var app = zetta({ registry: reg, peerRegistry: peerRegistry, useXForwardedHostHeader: false })
.silent()
Expand All @@ -122,6 +143,31 @@ describe('Zetta Api', function() {
});
})

it('allow for x-forwarded-path header to be disabled', function(done) {
var app = zetta({ registry: reg, peerRegistry: peerRegistry, useXForwardedPathHeader: false })
.silent()
.name('local')
._run(function(err) {
if (err) {
return done(err);
}

var rootPath = '/api/v1';

request(getHttpServer(app))
.get('/')
.set('x-forwarded-path', rootPath)
.expect(getBody(function(res, body) {
var self = body.links.filter(function(link) { return link.rel.indexOf('self') >= 0; })[0];
var resultPath = require('url').parse(self.href).pathname;
var resultPathSub = resultPath.substr(0,rootPath.length);
assert.notEqual(resultPathSub, rootPath);
assert.equal(resultPathSub, '/');
}))
.end(done);
});
})

describe('/servers/<peer id> ', function() {
var app = null;
var url = null;
Expand Down Expand Up @@ -203,7 +249,7 @@ describe('Zetta Api', function() {
it('should have monitor log link formatted correctly for HTTP requests', function(done) {
request(getHttpServer(app))
.get(url)
.expect(getBody(function(res, body) {
.expect(getBody(function(res, body) {
var link = body.links.filter(function(l) {
return l.rel.indexOf('monitor') > -1;
})[0];
Expand Down Expand Up @@ -298,7 +344,7 @@ describe('Zetta Api', function() {

response.on('end', done);
}).end();
});
});

it('should have valid entities', function(done) {
request(getHttpServer(app))
Expand Down Expand Up @@ -359,7 +405,7 @@ describe('Zetta Api', function() {
reg.find(query, function(err, machines) {
assert.equal(machines.length, 1);
assert.equal(machines[0].type, 'testdriver');
assert.equal(machines[0].id, '12345');
assert.equal(machines[0].id, '12345');
done();
});
})(res);
Expand Down Expand Up @@ -527,10 +573,10 @@ describe('Zetta Api', function() {
.expect(getBody(function(err, body) {
assert.equal(body.entities.length, 1);
var entity = body.entities[0];
assert.equal(entity.properties.id, '1');
assert.equal(entity.properties.id, '1');
}))
.end(done);
});
.end(done);
});
});

describe('#link', function() {
Expand All @@ -548,6 +594,35 @@ describe('Zetta Api', function() {
.expect('Location', /^http.+/)
.end(done);
});

it('should return Location header whose value honors forwarded host', function(done) {
request(getHttpServer(app))
.post('/peer-management')
.set('x-forwarded-host', 'google.com')
.send('url=http://testurl')
.expect('Location', /^http.+/)
.expect(function(res){
var loc = res.headers['location'];
var locHost = require('url').parse(loc).hostname;
assert.equal(locHost, 'google.com');
})
.end(done);
});

it('should return Location header whose value honors forwarded path', function(done) {
var rootPath = '/ipa/1v';
request(getHttpServer(app))
.post('/peer-management')
.set('x-forwarded-path', rootPath)
.send('url=http://testurl')
.expect('Location', /^http.+/)
.expect(function(res){
var loc = res.headers['location'];
var locPath = require('url').parse(loc).pathname;
assert.equal(locPath.substr(0,rootPath.length), rootPath);
})
.end(done);
});
});

describe('#show', function() {
Expand Down Expand Up @@ -606,6 +681,34 @@ describe('Zetta Api', function() {
}))
.end(done);
});

it('should replace url host in all device links using forwarded host', function(done) {
var rootPath = '/alpha/v1';
request(getHttpServer(app))
.get('/devices')
.set('x-forwarded-host', 'google.ca')
.expect(getBody(function(res, body) {
body.links.forEach(function(link){
var linkHost = require('url').parse(link.href).hostname;
assert.equal(linkHost, 'google.ca');
});
}))
.end(done);
});

it('should inject path in all device links using forwared root path', function(done) {
var rootPath = '/alpha/v1';
request(getHttpServer(app))
.get('/devices')
.set('x-forwarded-path', rootPath)
.expect(getBody(function(res, body) {
body.links.forEach(function(link){
var linkPath = require('url').parse(link.href).pathname;
assert.equal(linkPath.substr(0,rootPath.length), rootPath);
});
}))
.end(done);
});
});


Expand Down Expand Up @@ -730,7 +833,7 @@ describe('Zetta Api', function() {
it('disabling a stream should remove it from the API.', function(done) {
Object.keys(app.runtime._jsDevices).forEach(function(name) {
var device = app.runtime._jsDevices[name];
device.disableStream('foo');
device.disableStream('foo');
});

request(getHttpServer(app))
Expand All @@ -749,7 +852,7 @@ describe('Zetta Api', function() {
var device = null;
Object.keys(app.runtime._jsDevices).forEach(function(name) {
device = app.runtime._jsDevices[name];
device.disableStream('foo');
device.disableStream('foo');
device.enableStream('foo');
});

Expand Down Expand Up @@ -872,13 +975,13 @@ describe('Zetta Api', function() {
var createTransitionArgTest = function(action, testType, input) {
it('api should decode transition args to ' + testType + ' for ' + action, function(done) {
var device = app.runtime._jsDevices[Object.keys(app.runtime._jsDevices)[0]];

var orig = device._transitions[action].handler;
device._transitions[action].handler = function(x) {
assert.equal(typeof x, testType);
orig.apply(device, arguments);
};

request(getHttpServer(app))
.post(url)
.type('form')
Expand All @@ -887,7 +990,7 @@ describe('Zetta Api', function() {
.end(done);
});
};

createTransitionArgTest('test-number', 'number', 123)
createTransitionArgTest('test-text', 'string', 'Hello');
createTransitionArgTest('test-none', 'string', 'Anything');
Expand Down Expand Up @@ -970,7 +1073,7 @@ describe('Zetta Api', function() {
var device = app.runtime._jsDevices[deviceKey];

var remoteDestroy = function(cb) {
cb(null, false);
cb(null, false);
}

device._remoteDestroy = remoteDestroy.bind(device);
Expand All @@ -990,7 +1093,7 @@ describe('Zetta Api', function() {
var device = app.runtime._jsDevices[deviceKey];

var remoteDestroy = function(cb) {
cb(new Error('Oof! Ouch!'));
cb(new Error('Oof! Ouch!'));
}

device._remoteDestroy = remoteDestroy.bind(device);
Expand All @@ -1010,7 +1113,7 @@ describe('Zetta Api', function() {
var device = app.runtime._jsDevices[deviceKey];

var remoteDestroy = function(cb) {
cb(null, true);
cb(null, true);
}

device._remoteDestroy = remoteDestroy.bind(device);
Expand All @@ -1034,7 +1137,7 @@ describe('Zetta Api', function() {
assert.equal(res.statusCode, 200);
assert.equal(body.properties.foo, 0);
}))
.end(done);
.end(done);
});

it('should return a 404 when updating a non-existent device', function(done) {
Expand Down
3 changes: 3 additions & 0 deletions zetta.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ var Zetta = module.exports = function(opts) {
if(typeof opts.useXForwardedHostHeader !== 'undefined') {
httpOptions.useXForwardedHostHeader = opts.useXForwardedHostHeader;
}
if(typeof opts.useXForwardedPathHeader !== 'undefined') {
httpOptions.useXForwardedPathHeader = opts.useXForwardedPathHeader;
}

if (typeof opts.tls === 'object') {
Object.keys(opts.tls).forEach(function(k) {
Expand Down