feat: ✨ Created a mini nodeJS server with NewMan for testing without PostMan GUI.
This will mimic a run in a CD/CI environment or docker container.
This commit is contained in:
276
node_modules/postman-runtime/lib/authorizer/ntlm.js
generated
vendored
Normal file
276
node_modules/postman-runtime/lib/authorizer/ntlm.js
generated
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* @fileOverview
|
||||
*
|
||||
* Implements the NTLM over HTTP specification: [MS-NTHT] https://msdn.microsoft.com/en-us/library/cc237488.aspx
|
||||
* Also see [MS-NLMP]: https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||
*
|
||||
* @note NTLM supports a number of different variations, where an actual TCP connection is signed etc. This file
|
||||
* does _not_ implement those cases.
|
||||
*/
|
||||
|
||||
var ntlmUtil = require('httpntlm').ntlm,
|
||||
_ = require('lodash'),
|
||||
|
||||
EMPTY = '',
|
||||
NTLM = 'NTLM',
|
||||
STATE = 'state',
|
||||
NEGOTIATE = 'negotiate',
|
||||
NTLM_HEADER = 'ntlmHeader',
|
||||
AUTHORIZATION = 'Authorization',
|
||||
WWW_AUTHENTICATE = 'www-authenticate',
|
||||
DISABLE_RETRY_REQUEST = 'disableRetryRequest',
|
||||
NTLM_PARAMETERS = {
|
||||
DOMAIN: 'domain',
|
||||
WORKSTATION: 'workstation',
|
||||
USERNAME: 'username',
|
||||
PASSWORD: 'password'
|
||||
},
|
||||
STATES = {
|
||||
INITIALIZED: 'INITIALIZED',
|
||||
T1_MSG_CREATED: 'T1_MSG_CREATED',
|
||||
T3_MSG_CREATED: 'T3_MSG_CREATED'
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the username to separate username and domain. It can handle two formats:
|
||||
* - Down-Level Logon name format `DOMAIN\USERNAME`
|
||||
* - User Principal Name format `USERNAME@DOMAIN`
|
||||
*
|
||||
* @private
|
||||
* @param {String} username - Username string to parse from
|
||||
* @return {Object} - An object with `username` and `domain` fields, which are `strings`.
|
||||
*/
|
||||
function parseParametersFromUsername (username) {
|
||||
var dllParams,
|
||||
upnParams;
|
||||
|
||||
if (!(username && typeof username === 'string')) {
|
||||
return {
|
||||
username: EMPTY,
|
||||
domain: EMPTY
|
||||
};
|
||||
}
|
||||
|
||||
dllParams = username.split('\\');
|
||||
upnParams = username.split('@');
|
||||
|
||||
// username should be either of the two formats, not both
|
||||
if (dllParams.length > 1 && upnParams.length > 1) {
|
||||
return {
|
||||
username,
|
||||
domain: EMPTY
|
||||
};
|
||||
}
|
||||
|
||||
// try to parse from "down level logon" format
|
||||
if (dllParams.length === 2 && dllParams[0] && dllParams[1]) {
|
||||
return {
|
||||
username: dllParams[1],
|
||||
domain: dllParams[0]
|
||||
};
|
||||
}
|
||||
|
||||
// try to parse from "user principal name" format
|
||||
if (upnParams.length === 2 && upnParams[0] && upnParams[1]) {
|
||||
return {
|
||||
username: upnParams[0],
|
||||
domain: upnParams[1]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
username,
|
||||
domain: EMPTY
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `WWW-Authenticate` header has NTLM challenge.
|
||||
*
|
||||
* @private
|
||||
* @param {*} headers - Postman headers instance
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function hasNTLMChallenge (headers) {
|
||||
// Case 1: multiple headers
|
||||
// - WWW-Authenticate: NTLM
|
||||
// - WWW-Authenticate: Negotiate
|
||||
if (headers.has(WWW_AUTHENTICATE, NTLM) || headers.has(WWW_AUTHENTICATE, NEGOTIATE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case 2: single header
|
||||
// - WWW-Authenticate: Negotiate, NTLM
|
||||
return String(headers.get(WWW_AUTHENTICATE)).includes(NTLM);
|
||||
}
|
||||
|
||||
/**
|
||||
* NTLM auth while authenticating requires negotiateMessage (type 1) and authenticateMessage (type 3) to be stored.
|
||||
* Also it needs to know which stage is it in (INITIALIZED, T1_MSG_CREATED and T3_MSG_CREATED).
|
||||
* After the first successful authentication, it just relies on the TCP connection, no other state is needed.
|
||||
* @todo Currenty we don't close the connection. So there is no way to de-authenticate.
|
||||
*
|
||||
* @implements {AuthHandlerInterface}
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* @property {AuthHandlerInterface~AuthManifest}
|
||||
*/
|
||||
manifest: {
|
||||
info: {
|
||||
name: 'ntlm',
|
||||
version: '1.0.0'
|
||||
},
|
||||
updates: [
|
||||
{
|
||||
property: 'Authorization',
|
||||
type: 'header'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes an item (extracts parameters from intermediate requests if any, etc)
|
||||
* before the actual authorization step.
|
||||
*
|
||||
* @param {AuthInterface} auth
|
||||
* @param {Response} response
|
||||
* @param {AuthHandlerInterface~authInitHookCallback} done
|
||||
*/
|
||||
init: function (auth, response, done) {
|
||||
done(null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies whether the request has valid basic auth credentials (which is always).
|
||||
* Sanitizes the auth parameters if needed.
|
||||
*
|
||||
* @param {AuthInterface} auth
|
||||
* @param {AuthHandlerInterface~authPreHookCallback} done
|
||||
*/
|
||||
pre: function (auth, done) {
|
||||
!auth.get(STATE) && auth.set(STATE, STATES.INITIALIZED);
|
||||
|
||||
done(null, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies whether the basic auth succeeded.
|
||||
*
|
||||
* @param {AuthInterface} auth
|
||||
* @param {Response} response
|
||||
* @param {AuthHandlerInterface~authPostHookCallback} done
|
||||
*/
|
||||
post: function (auth, response, done) {
|
||||
if (auth.get(DISABLE_RETRY_REQUEST)) {
|
||||
return done(null, true);
|
||||
}
|
||||
|
||||
var state = auth.get(STATE),
|
||||
domain = auth.get(NTLM_PARAMETERS.DOMAIN) || EMPTY,
|
||||
workstation = auth.get(NTLM_PARAMETERS.WORKSTATION) || EMPTY,
|
||||
username = auth.get(NTLM_PARAMETERS.USERNAME) || EMPTY,
|
||||
password = auth.get(NTLM_PARAMETERS.PASSWORD) || EMPTY,
|
||||
negotiateMessage, // type 1
|
||||
challengeMessage, // type 2
|
||||
authenticateMessage, // type 3
|
||||
ntlmType2Header,
|
||||
parsedParameters;
|
||||
|
||||
if (response.code !== 401 && response.code !== 403) {
|
||||
return done(null, true);
|
||||
}
|
||||
|
||||
// we try to extract domain from username if not specified.
|
||||
if (!domain) {
|
||||
parsedParameters = parseParametersFromUsername(username) || {};
|
||||
|
||||
username = parsedParameters.username;
|
||||
domain = parsedParameters.domain;
|
||||
}
|
||||
|
||||
if (state === STATES.INITIALIZED) {
|
||||
// Nothing to do if the server does not ask us for auth in the first place.
|
||||
if (!hasNTLMChallenge(response.headers)) {
|
||||
return done(null, true);
|
||||
}
|
||||
|
||||
// Create a type 1 message to send to the server
|
||||
negotiateMessage = ntlmUtil.createType1Message({
|
||||
domain: domain,
|
||||
workstation: workstation
|
||||
});
|
||||
|
||||
// Add the type 1 message as the auth header
|
||||
auth.set(NTLM_HEADER, negotiateMessage);
|
||||
|
||||
// Update the state
|
||||
auth.set(STATE, STATES.T1_MSG_CREATED);
|
||||
|
||||
// ask runtime to replay the request
|
||||
return done(null, false);
|
||||
}
|
||||
else if (state === STATES.T1_MSG_CREATED) {
|
||||
// At this point, we can assume that the type 1 message was sent to the server
|
||||
|
||||
// there can be multiple headers present with key `www-authenticate`.
|
||||
// iterate to get the one which has the NTLM hash. if multiple
|
||||
// headers have the NTLM hash, use the first one.
|
||||
ntlmType2Header = response.headers.find(function (header) {
|
||||
return String(header.key).toLowerCase() === WWW_AUTHENTICATE &&
|
||||
header.valueOf().startsWith('NTLM ');
|
||||
});
|
||||
|
||||
if (!ntlmType2Header) {
|
||||
return done(new Error('ntlm: server did not send NTLM type 2 message'));
|
||||
}
|
||||
|
||||
challengeMessage = ntlmUtil.parseType2Message(ntlmType2Header.valueOf(), _.noop);
|
||||
|
||||
if (!challengeMessage) {
|
||||
return done(new Error('ntlm: server did not correctly process authentication request'));
|
||||
}
|
||||
|
||||
authenticateMessage = ntlmUtil.createType3Message(challengeMessage, {
|
||||
domain: domain,
|
||||
workstation: workstation,
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
// Now create the type 3 message, and add it to the request
|
||||
auth.set(NTLM_HEADER, authenticateMessage);
|
||||
auth.set(STATE, STATES.T3_MSG_CREATED);
|
||||
|
||||
// ask runtime to replay the request
|
||||
return done(null, false);
|
||||
}
|
||||
else if (state === STATES.T3_MSG_CREATED) {
|
||||
// Means we have tried to authenticate, so we should stop here without worrying about anything
|
||||
return done(null, true);
|
||||
}
|
||||
|
||||
// We are in an undefined state
|
||||
return done(null, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Signs a request.
|
||||
*
|
||||
* @param {AuthInterface} auth
|
||||
* @param {Request} request
|
||||
* @param {AuthHandlerInterface~authSignHookCallback} done
|
||||
*/
|
||||
sign: function (auth, request, done) {
|
||||
var ntlmHeader = auth.get(NTLM_HEADER);
|
||||
|
||||
request.removeHeader(AUTHORIZATION, {ignoreCase: true});
|
||||
ntlmHeader && request.addHeader({
|
||||
key: AUTHORIZATION,
|
||||
value: ntlmHeader,
|
||||
system: true
|
||||
});
|
||||
|
||||
return done();
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user