Skip to content

Commit

Permalink
Add Support & Tests for X-Forwarded-Root (#320)
Browse files Browse the repository at this point in the history
Add Support & Tests for X-Forwarded-Path
  • Loading branch information
wooliet authored and AdamMagaluk committed Aug 31, 2016
1 parent f498c58 commit e8e307f
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 27 deletions.
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

0 comments on commit e8e307f

Please sign in to comment.