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

Fix - Auth state management for ntlm auth in runner #1361

Closed
wants to merge 5 commits into from
Closed
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
25 changes: 24 additions & 1 deletion lib/authorizer/auth-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ var _ = require('lodash'),
*
* @constructs AuthInterface
* @param {RequestAuth} auth -
* @param {Object} authState - auth state
* @param {Object} protocolProfileBehavior - Protocol profile behaviors
* @param {Object} [options] - authorizer options
* @return {AuthInterface}
* @throws {Error}
*/
createAuthInterface = function (auth, protocolProfileBehavior, options) {
createAuthInterface = function (auth, authState, protocolProfileBehavior, options) {
if (!(auth && auth.parameters && auth.parameters())) {
throw new Error('runtime~createAuthInterface: invalid auth');
}
Expand Down Expand Up @@ -109,6 +110,28 @@ createAuthInterface = function (auth, protocolProfileBehavior, options) {
});

return this;
},

/**
* @param {String} state - name of state
* @return {Object|undefined}
*/
getState: function (state) {
if (state && authState && authState[state]) {
return authState[state];
}
},

/**
* @param {String} state - name of state
* @param {Object|undefined} value - auth state value
*/
setState: function (state, value) {
if (!authState || !state) {
return;
}

authState[state] = value;
}
};
};
Expand Down
3 changes: 2 additions & 1 deletion lib/authorizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ authorizeRequest = function (request, done) {

var clonedReq = new sdk.Request(request.toJSON()),
auth = clonedReq.auth,
authInterface = createAuthInterface(auth),
authState = {}, // setting this as empty because dry run has no state
authInterface = createAuthInterface(auth, authState),
handler = AuthLoader.getHandler(auth.type);

if (handler) {
Expand Down
32 changes: 20 additions & 12 deletions lib/authorizer/ntlm.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ module.exports = {
* @param {AuthHandlerInterface~authPreHookCallback} done -
*/
pre: function (auth, done) {
!auth.get(STATE) && auth.set(STATE, STATES.INITIALIZED);
!auth.getState(STATE) && auth.setState(STATE, STATES.INITIALIZED);

done(null, true);
},
Expand All @@ -167,7 +167,7 @@ module.exports = {
return done(null, true);
}

var state = auth.get(STATE),
var state = auth.getState(STATE),
domain = auth.get(NTLM_PARAMETERS.DOMAIN) || EMPTY,
workstation = auth.get(NTLM_PARAMETERS.WORKSTATION) || EMPTY,
username = auth.get(NTLM_PARAMETERS.USERNAME) || EMPTY,
Expand Down Expand Up @@ -203,10 +203,10 @@ module.exports = {
});

// Add the type 1 message as the auth header
auth.set(NTLM_HEADER, negotiateMessage);
auth.setState(NTLM_HEADER, negotiateMessage);

// Update the state
auth.set(STATE, STATES.T1_MSG_CREATED);
auth.setState(STATE, STATES.T1_MSG_CREATED);

// ask runtime to replay the request
return done(null, false);
Expand Down Expand Up @@ -240,8 +240,8 @@ module.exports = {
});

// Now create the type 3 message, and add it to the request
auth.set(NTLM_HEADER, authenticateMessage);
auth.set(STATE, STATES.T3_MSG_CREATED);
auth.setState(NTLM_HEADER, authenticateMessage);
auth.setState(STATE, STATES.T3_MSG_CREATED);

// ask runtime to replay the request
return done(null, false);
Expand All @@ -263,14 +263,22 @@ module.exports = {
* @param {AuthHandlerInterface~authSignHookCallback} done -
*/
sign: function (auth, request, done) {
var ntlmHeader = auth.get(NTLM_HEADER);
var ntlmHeader = auth.getState(NTLM_HEADER),
authState = auth.getState(STATE);

request.removeHeader(AUTHORIZATION, { ignoreCase: true });
ntlmHeader && request.addHeader({
key: AUTHORIZATION,
value: ntlmHeader,
system: true
});

// when authState === INITIALIZED that means it's new req
// so no need to add AUTHORIZATION header
if (authState !== STATES.INITIALIZED) {
if (ntlmHeader) {
request.addHeader({
key: AUTHORIZATION,
value: ntlmHeader,
system: true
});
}
}

return done();
}
Expand Down
4 changes: 4 additions & 0 deletions lib/runner/create-item-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ module.exports = function (payload, defaults) {
// get a reference to the Auth instance from the item, so changes are synced back
context.auth = context.originalItem.getAuth();

// to store the auth state for a particular auth
// ex - authState = { state: 'T1_MSG_CREATED', ntlmHeader: 'abc' }
context.authState = (defaults && defaults.authState) ? defaults.authState : {};

// Make sure run is not errored out if older version of collection SDK is used.
// @todo remove this safety check in the next release
if (typeof context.originalItem.getProtocolProfileBehaviorResolved === FUNCTION) {
Expand Down
2 changes: 1 addition & 1 deletion lib/runner/request-helpers-postsend.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = [
originalAuth = context.originalItem.getAuth(),
originalAuthParams = originalAuth && originalAuth.parameters(),
authHandler = AuthLoader.getHandler(auth.type),
authInterface = createAuthInterface(auth);
authInterface = createAuthInterface(auth, context.authState);

// bail out if there is no matching auth handler for the type
if (!authHandler) {
Expand Down
1 change: 1 addition & 0 deletions lib/runner/request-helpers-presend.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ module.exports = [
}

authInterface = createAuthInterface(auth,
context.authState,
context.protocolProfileBehavior,
run.options.requester && run.options.requester.authorizer);

Expand Down
128 changes: 128 additions & 0 deletions test/integration/auth-methods/ntlm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,132 @@ var expect = require('chai').expect,
expect(response).to.have.property('code', 401);
});
});

describe('with NTLM auth set at collection level and 5 iterations', function () {
before(function (done) {
var localRunOptions = {
collection: {
item: {
name: 'NTLM Sample Request',
request: {
url: ntlmServerURL
}
},
auth: {
type: 'ntlm',
ntlm: {
username: USERNAME,
password: PASSWORD,
domain: DOMAIN,
workstation: WORKSTATION
}
}
},
iterationCount: 5
};

// perform the collection run
this.run(localRunOptions, function (err, results) {
testrun = results;
done(err);
});
});

it('should have completed the run', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.callCount': 1
});

var err = testrun.request.firstCall.args[0];

err && console.error(err.stack);
expect(err).to.be.null;

expect(testrun).to.nested.include({
'start.callCount': 1
});
});

it('should have sent the request 3 * 5 = 15 times', function () {
expect(testrun).to.nested.include({
'request.callCount': 15
});

var response0 = testrun.request.getCall(0).args[2],
response1 = testrun.request.getCall(1).args[2],
response2 = testrun.request.getCall(2).args[2],
response5 = testrun.request.getCall(5).args[2],
response8 = testrun.request.getCall(8).args[2],
response11 = testrun.request.getCall(11).args[2],
response14 = testrun.request.getCall(14).args[2];

expect(response0, 'iteration 1, request 1').to.have.property('code', 401);
expect(response1, 'iteration 1, request 2').to.have.property('code', 401);
expect(response2, 'iteration 1, request 3').to.have.property('code', 200);
expect(response5, 'iteration 2, request 3').to.have.property('code', 200);
expect(response8, 'iteration 3, request 3').to.have.property('code', 200);
expect(response11, 'iteration 4, request 3').to.have.property('code', 200);
expect(response14, 'iteration 5, request 3').to.have.property('code', 200);
});
});

describe('with NTLM auth set at collection level with 4 requests', function () {
before(function (done) {
var localRunOptions = {
collection: {
item: [
{ name: 'NTLM Sample Request 1',
request: {
url: ntlmServerURL
} },
{ name: 'NTLM Sample Request 2',
request: {
url: ntlmServerURL
} },
{ name: 'NTLM Sample Request 3',
request: {
url: ntlmServerURL
} },
{ name: 'NTLM Sample Request 4',
request: {
url: ntlmServerURL
} }
],
auth: {
type: 'ntlm',
ntlm: {
username: USERNAME,
password: PASSWORD,
domain: DOMAIN,
workstation: WORKSTATION
}
}
},
iterationCount: 5
};

// perform the collection run
this.run(localRunOptions, function (err, results) {
testrun = results;
done(err);
});
});

it('should have completed the run with 4 200 and other 401', function () {
var response0 = testrun.request.getCall(0).args[2],
response1 = testrun.request.getCall(1).args[2],
response2 = testrun.request.getCall(2).args[2],
response5 = testrun.request.getCall(5).args[2],
response8 = testrun.request.getCall(8).args[2],
response11 = testrun.request.getCall(11).args[2];

expect(response0, 'iteration 1, request 1').to.have.property('code', 401);
expect(response1, 'iteration 1, request 2').to.have.property('code', 401);
expect(response2, 'iteration 1, request 3').to.have.property('code', 200);
expect(response5, 'iteration 2, request 3').to.have.property('code', 200);
expect(response8, 'iteration 3, request 3').to.have.property('code', 200);
expect(response11, 'iteration 4, request 3').to.have.property('code', 200);
});
});
});
12 changes: 6 additions & 6 deletions test/unit/auth-handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1855,7 +1855,7 @@ describe('Auth Handler:', function () {
},
request = new Request(_rawReq),
auth = request.auth,
authInterface = createAuthInterface(auth, protocolProfileBehavior),
authInterface = createAuthInterface(auth, {}, protocolProfileBehavior),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down Expand Up @@ -1887,7 +1887,7 @@ describe('Auth Handler:', function () {
},
request = new Request(_rawReq),
auth = request.auth,
authInterface = createAuthInterface(auth, protocolProfileBehavior),
authInterface = createAuthInterface(auth, {}, protocolProfileBehavior),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down Expand Up @@ -1949,7 +1949,7 @@ describe('Auth Handler:', function () {
protocolProfileBehavior,
request = new Request(_rawReq),
auth = request.auth,
authInterface = createAuthInterface(auth, protocolProfileBehavior),
authInterface = createAuthInterface(auth, {}, protocolProfileBehavior),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down Expand Up @@ -1981,7 +1981,7 @@ describe('Auth Handler:', function () {
},
request = new Request(_rawReq),
auth = request.auth,
authInterface = createAuthInterface(auth, protocolProfileBehavior),
authInterface = createAuthInterface(auth, {}, protocolProfileBehavior),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down Expand Up @@ -2011,7 +2011,7 @@ describe('Auth Handler:', function () {
},
request = new Request(_rawReq),
auth = request.auth,
authInterface = createAuthInterface(auth, protocolProfileBehavior),
authInterface = createAuthInterface(auth, {}, protocolProfileBehavior),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down Expand Up @@ -2898,7 +2898,7 @@ describe('Auth Handler:', function () {
},
request = new Request(data),
auth = request.auth,
authInterface = createAuthInterface(auth),
authInterface = createAuthInterface(auth, {}),
handler = AuthLoader.getHandler(auth.type);

handler.sign(authInterface, request, _.noop);
Expand Down
9 changes: 9 additions & 0 deletions test/unit/auth-interface.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,13 @@ describe('AuthInterface', function () {
authInterface.set(true, newPassword);
}).to.throw(/runtime~AuthInterface: set should be called with `key` as a string or object/);
});

it('should set and get auth state', function () {
var fakeAuth = new sdk.RequestAuth(fakeAuthObj),
authInterface = createAuthInterface(fakeAuth, {});

expect(authInterface.getState('state')).to.equal(undefined);
authInterface.setState('state', 'T1_MSG_CREATED');
expect(authInterface.getState('state')).to.equal('T1_MSG_CREATED');
});
});
Loading