From 65ab92f7019bbe469345ceeed9a771350c1088f2 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 28 Jun 2017 14:54:34 -0400 Subject: [PATCH] Add SpoofedNodeHttpImpl Session spoofing is needed for making authenticated requests. --- src/dapi/DataApiManager.js | 3 +- src/dapi/http/HttpImpl.js | 6 +- src/dapi/http/NodeHttpImpl.js | 7 +- src/dapi/http/SpoofedNodeHttpImpl.js | 78 +++++++++++++++++++++ src/dapi/http/XhrHttpImpl.js | 6 ++ test/dapi/http/HttpDataApiTest.js | 10 ++- test/dapi/http/SpoofedNodeHttpImplTest.js | 84 +++++++++++++++++++++++ 7 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/dapi/http/SpoofedNodeHttpImpl.js create mode 100644 test/dapi/http/SpoofedNodeHttpImplTest.js diff --git a/src/dapi/DataApiManager.js b/src/dapi/DataApiManager.js index 0ebc862..f67960c 100644 --- a/src/dapi/DataApiManager.js +++ b/src/dapi/DataApiManager.js @@ -98,9 +98,10 @@ module.exports = Class( 'DataApiManager' ) 'private _apis': {}, - __construct: function( api_factory ) + __construct: function( api_factory, apis ) { this._dataApiFactory = api_factory; + this.setApis( apis || {} ); }, diff --git a/src/dapi/http/HttpImpl.js b/src/dapi/http/HttpImpl.js index a9bee06..62d0cf2 100644 --- a/src/dapi/http/HttpImpl.js +++ b/src/dapi/http/HttpImpl.js @@ -47,5 +47,9 @@ module.exports = Interface( 'HttpImpl', * * @return {HttpImpl} self */ - 'public requestData': [ 'url', 'method', 'data', 'callback' ] + 'public requestData': [ 'url', 'method', 'data', 'callback' ], + + // TODO: temporary to work around class extension bug; see + // SpoofedNodeHttpImpl + 'public setOptions': [], } ); diff --git a/src/dapi/http/NodeHttpImpl.js b/src/dapi/http/NodeHttpImpl.js index 541b332..35e687d 100644 --- a/src/dapi/http/NodeHttpImpl.js +++ b/src/dapi/http/NodeHttpImpl.js @@ -98,7 +98,7 @@ module.exports = Class( 'NodeHttpImpl' ) throw Error( `No handler for ${protocol}` ); } - this._setOptions( options, method, data ); + this.setOptions( options, method, data ); let forbid_end = false; @@ -152,13 +152,16 @@ module.exports = Class( 'NodeHttpImpl' ) /** * Set request options * + * TODO: public to work around a class extension trait bug; make + * protected once fixed + * * @param {Object} options request options * @param {string} method HTTP method * @param {string} data request data * * @return {Object} request headers */ - 'private _setOptions'( options, method, data ) + 'virtual public setOptions'( options, method, data ) { const { headers = {} } = options; diff --git a/src/dapi/http/SpoofedNodeHttpImpl.js b/src/dapi/http/SpoofedNodeHttpImpl.js new file mode 100644 index 0000000..ac68812 --- /dev/null +++ b/src/dapi/http/SpoofedNodeHttpImpl.js @@ -0,0 +1,78 @@ +/** + * Node-based HTTP client with session spoofing + * + * Copyright (C) 2017 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +'use strict'; + +const { Trait } = require( 'easejs' ); +const HttpImpl = require( './HttpImpl' ); + + +/** + * Spoof user session during request + * + * TODO: Implementing HttpImpl instead of overriding NodeHttpImpl to work + * around a class extension bug; change once fixed. + */ +module.exports = Trait( 'SpoofedNodeHttpImpl' ) + .implement( HttpImpl ) + .extend( +{ + /** + * Session to spoof + * @type {UserSession} + */ + 'private _request': null, + + + /** + * Use session for spoofing requests + * + * @param {UserSession} session session to spoof + */ + __mixin( session ) + { + this._request = session; + }, + + + /** + * Set request options to spoof session + * + * @param {Object} options request options + * @param {string} method HTTP method + * @param {string} data request data + * + * @return {Object} request headers + */ + 'virtual abstract override public setOptions'( options, method, data ) + { + const cookie = this._request.getSessionIdName() + '=' + + this._request.getSessionId(); + + options.headers = { + 'User-Agent': this._request.getUserAgent(), + 'X-Forwarded-For': this._request.getRemoteAddr(), + 'Cookie': cookie, + }; + + return this.__super( options, method, data ); + } +} ); diff --git a/src/dapi/http/XhrHttpImpl.js b/src/dapi/http/XhrHttpImpl.js index 20e0f19..28eccac 100644 --- a/src/dapi/http/XhrHttpImpl.js +++ b/src/dapi/http/XhrHttpImpl.js @@ -279,5 +279,11 @@ module.exports = Class( 'XhrHttpImpl' ) e.status = req.status; callback( e, req.responseText ); + }, + + + 'public setOptions'() + { + // TOOD: remove (see HttpImpl) } } ); diff --git a/test/dapi/http/HttpDataApiTest.js b/test/dapi/http/HttpDataApiTest.js index 12e8d82..e402ae4 100644 --- a/test/dapi/http/HttpDataApiTest.js +++ b/test/dapi/http/HttpDataApiTest.js @@ -27,7 +27,11 @@ var dapi = require( '../../../' ).dapi, dummy_url = 'http://foo', dummy_impl = Class .implement( dapi.http.HttpImpl ) - .extend( { requestData: function( _, __, ___, ____ ) {} } )(), + .extend( + { + requestData: function( _, __, ___, ____ ) {}, + setOptions() {}, + } )(), dummy_sut = Sut( dummy_url, 'GET', dummy_impl ); @@ -86,7 +90,9 @@ describe( 'HttpDataApi', function() { this.provided = arguments; c( this.err, this.data ); - } + }, + + setOptions() {}, } )(); diff --git a/test/dapi/http/SpoofedNodeHttpImplTest.js b/test/dapi/http/SpoofedNodeHttpImplTest.js new file mode 100644 index 0000000..adc8039 --- /dev/null +++ b/test/dapi/http/SpoofedNodeHttpImplTest.js @@ -0,0 +1,84 @@ +/** + * Tests Node-based HTTP client with session spoofing + * + * Copyright (C) 2017 R-T Specialty, LLC. + * + * This file is part of the Liza Data Collection Framework. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +'use strict'; + +const { expect } = require( 'chai' ); +const { + SpoofedNodeHttpImpl: Sut, + NodeHttpImpl, +} = require( '../../../' ).dapi.http; + + +describe( 'SpoofNodeHttpImpl', () => +{ + it( "adds session headers", done => + { + const user_agent = 'Agent Foo'; + const forward_for = '::1'; + const sessname = 'FOOSESSID'; + const sessid = '12345'; + + const protos = { + http: { + request( given ) + { + expect( given.headers[ 'User-Agent' ] ) + .to.equal( user_agent ); + expect( given.headers[ 'X-Forwarded-For' ] ) + .to.equal( forward_for ); + + expect( given.headers.Cookie ) + .to.contain( sessname + '=' + sessid ); + + done(); + }, + }, + }; + + const url = { + parse: () => ( { + protocol: 'http', + } ) + }; + + const session = getStubSession( { + agent: user_agent, + forward_for: forward_for, + sessname: sessname, + sessid: sessid, + } ); + + const given = NodeHttpImpl.use( Sut( session ) )( protos, url ) + .requestData( '', '', {}, ()=>{} ); + } ); +} ); + + +function getStubSession( { agent, forward_for, sessname, sessid } ) +{ + return { + getUserAgent: () => agent, + getRemoteAddr: () => forward_for, + getSessionIdName: () => sessname, + getSessionId: () => sessid, + }; +}