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:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

119
node_modules/postman-runtime/lib/authorizer/apikey.js generated vendored Normal file
View File

@@ -0,0 +1,119 @@
var _ = require('lodash'),
TARGETS = {
header: 'header',
query: 'query'
};
/**
* This module negotiates the following
*
* auth: {
* key: 'string',
* value: 'string',
* in: 'string~enum header, query',
*
* // @todo implement:
* privateKey: 'string',
* privateValue: 'string'
* }
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'apikey',
version: '0.0.1'
},
updates: [
{
property: '*',
type: 'header'
},
{
property: '*',
type: 'url.param'
}
]
},
/**
* 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();
},
/**
* Verifies whether the request has required parameters
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
return done(null, Boolean(auth.get('key') || auth.get('value')));
},
/**
* Verifies whether the auth succeeded
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs the request
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var target = TARGETS[auth.get('in')] || TARGETS.header,
key = auth.get('key'),
value = auth.get('value'),
lkey = _.lowerCase(key); // needed for header case insensitive matches
// either key or value should be present
if (!(key || value)) {
return done();
}
if (target === TARGETS.header) {
request.headers.remove(function (header) {
return header && (_.lowerCase(header.key) === lkey);
});
request.headers.add({
key: key,
value: value,
system: true
});
}
else if (target === TARGETS.query) {
request.url.query.remove(function (query) {
return query && (query.key === key);
});
request.url.query.add({
key: key,
value: value,
system: true
});
}
return done();
}
};

View File

@@ -0,0 +1,95 @@
var _ = require('lodash'),
EMPTY = '',
createAuthInterface;
/**
* Creates a wrapper around RequestAuth and provides getters and setters helper functions
*
* @constructs AuthInterface
* @param {RequestAuth} auth
* @param {Object} protocolProfileBehavior - Protocol profile behaviors
* @return {AuthInterface}
* @throws {Error}
*/
createAuthInterface = function (auth, protocolProfileBehavior) {
if (!(auth && auth.parameters && auth.parameters())) {
throw new Error('runtime~createAuthInterface: invalid auth');
}
return /** @lends AuthInterface.prototype **/{
/**
* @private
* @property {protocolProfileBehavior} - Protocol profile behaviors
*/
_protocolProfileBehavior: protocolProfileBehavior || {},
/**
* @param {String|Array<String>} keys
* @return {*} Returns a value for a key or an object having all keys & values depending on the input
* @example
* get('foo') // bar
* get(['foo', 'alpha']) // {foo: 'bar', 'alpha': 'beta'}
*/
get: function (keys) {
var paramVariable;
if (_.isString(keys)) {
paramVariable = auth.parameters().one(keys);
return paramVariable && paramVariable.get();
}
if (_.isArray(keys)) {
return _.transform(keys, function (paramObject, key) {
paramVariable = auth.parameters().one(key);
paramVariable && (paramObject[key] = paramVariable.get());
return paramObject;
}, {});
}
return undefined;
},
/**
* @param {String|Object} key
* @param {*} [value]
* @return {AuthInterface}
* @example
* set('foo', 'bar')
* set({foo: 'bar', 'alpha': 'beta'})
* @throws {Error}
*/
set: function (key, value) {
var modifiedParams = {},
parameters;
if (_.isObject(key)) {
modifiedParams = key;
}
else if (_.isString(key)) {
modifiedParams[key] = value;
}
else {
throw new Error('runtime~AuthInterface: set should be called with `key` as a string or object');
}
parameters = auth.parameters();
_.forEach(modifiedParams, function (value, key) {
var param = parameters.one(key);
if (!param) {
return parameters.add({key: key, value: value, system: true});
}
// Update if the param is a system property or an empty user property (null, undefined or empty string)
if (param.system || param.value === EMPTY || _.isNil(param.value) || _.isNaN(param.value)) {
return param.update({key: key, value: value, system: true});
}
});
return this;
}
};
};
module.exports = createAuthInterface;

303
node_modules/postman-runtime/lib/authorizer/aws4.js generated vendored Normal file
View File

@@ -0,0 +1,303 @@
var _ = require('lodash'),
aws4 = require('aws4'),
crypto = require('crypto'),
sdk = require('postman-collection'),
urlEncoder = require('postman-url-encoder'),
bodyBuilder = require('../requester/core-body-builder'),
RequestBody = sdk.RequestBody,
X_AMZ_PREFIX = 'X-Amz-',
BODY_HASH_HEADER = 'X-Amz-Content-Sha256',
/**
* Calculates body hash with given algorithm and digestEncoding.
*
* @todo This function can also be used in Digest auth so that it works correctly for urlencoded and file body types
*
* @param {RequestBody} body
* @param {String} algorithm
* @param {String} digestEncoding
* @param {Function} callback
*/
computeBodyHash = function (body, algorithm, digestEncoding, callback) {
if (!(body && algorithm && digestEncoding) || body.isEmpty()) { return callback(); }
var hash = crypto.createHash(algorithm),
originalReadStream,
rawBody,
urlencodedBody,
graphqlBody;
if (body.mode === RequestBody.MODES.raw) {
rawBody = bodyBuilder.raw(body.raw).body;
hash.update(rawBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.urlencoded) {
urlencodedBody = bodyBuilder.urlencoded(body.urlencoded).form;
urlencodedBody = urlEncoder.encodeQueryString(urlencodedBody);
hash.update(urlencodedBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.file) {
originalReadStream = _.get(body, 'file.content');
if (!originalReadStream) {
return callback();
}
return originalReadStream.cloneReadStream(function (err, clonedStream) {
if (err) { return callback(); }
clonedStream.on('data', function (chunk) {
hash.update(chunk);
});
clonedStream.on('end', function () {
callback(hash.digest(digestEncoding));
});
});
}
if (body.mode === RequestBody.MODES.graphql) {
graphqlBody = bodyBuilder.graphql(body.graphql).body;
hash.update(graphqlBody);
return callback(hash.digest(digestEncoding));
}
// @todo: formdata body type requires adding new data to form instead of setting headers for AWS auth.
// Figure out how to do that. See below link:
// AWS auth with formdata: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
// ensure that callback is called if body.mode doesn't match with any of the above modes
return callback();
};
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'awsv4',
version: '1.0.0'
},
updates: [
{
property: 'Host',
type: 'header'
},
{
property: 'Authorization',
type: 'header'
},
{
property: 'X-Amz-Date',
type: 'header'
},
{
property: 'X-Amz-Security-Token',
type: 'header'
},
{
property: 'X-Amz-Content-Sha256',
type: 'header'
},
{
property: 'X-Amz-Security-Token',
type: 'url.param'
},
{
property: 'X-Amz-Expires',
type: 'url.param'
},
{
property: 'X-Amz-Date',
type: 'url.param'
},
{
property: 'X-Amz-Algorithm',
type: 'url.param'
},
{
property: 'X-Amz-Credential',
type: 'url.param'
},
{
property: 'X-Amz-SignedHeaders',
type: 'url.param'
},
{
property: 'X-Amz-Signature',
type: 'url.param'
}
]
},
/**
* Initializes a item (fetches all required parameters, etc) before the actual authorization step.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authInitHookCallback} done
*/
init: function (auth, response, done) {
done(null);
},
/**
* Checks the item, and fetches any parameters that are not already provided.
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
done(null, true);
},
/**
* Verifies whether the request was successful after being sent.
*
* @param {AuthInterface} auth
* @param {Requester} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Generates the signature and adds auth data to the request as additional headers/query params.
* AWS v4 auth mandates that a content type header be present in each request.
*
* @param {Request} request request to add auth data
* @param {Object} params data required for auth
* @param {Object} params.credentials Should contain the AWS credentials, "accessKeyId" and "secretAccessKey"
* @param {String} params.host Contains the host name for the request
* @param {String} params.path Contains the complete path, with query string as well, e.g: /something/kane?hi=ho
* @param {String} params.service The name of the AWS service
* @param {String} params.region AWS region
* @param {String} params.method Request method
* @param {String} params.body Stringified request body
* @param {Object} params.headers Each key should be a header key, and the value should be a header value
* @param {Boolean} params.signQuery Add auth data to query params if true, otherwise add it to headers
*/
addAuthDataToRequest: function (request, params) {
var signedData = aws4.sign(params, params.credentials);
if (params.signQuery) {
_.forEach(sdk.Url.parse(signedData.path).query, function (param) {
// only add additional AWS specific params to request
if (_.startsWith(param.key, X_AMZ_PREFIX) && !request.url.query.has(param.key)) {
param.system = true;
request.url.query.add(param);
}
});
}
_.forEach(signedData.headers, function (value, key) {
request.upsertHeader({
key: key,
value: value,
system: true
});
});
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var self = this,
params = auth.get([
'accessKey',
'secretKey',
'sessionToken',
'service',
'region',
'addAuthDataToQuery'
]),
url = urlEncoder.toNodeUrl(request.url),
dataToSign;
// Clean up the request (if needed)
request.removeHeader('Authorization', {ignoreCase: true});
request.removeHeader('X-Amz-Date', {ignoreCase: true});
request.removeHeader('X-Amz-Security-Token', {ignoreCase: true});
request.removeHeader('X-Amz-Content-Sha256', {ignoreCase: true});
// Not removing `X-Amz-Expires` from params here allowing user to override
// default value
request.removeQueryParams([
'X-Amz-Security-Token',
'X-Amz-Date',
'X-Amz-Algorithm',
'X-Amz-Credential',
'X-Amz-SignedHeaders',
'X-Amz-Signature'
]);
dataToSign = {
credentials: {
accessKeyId: params.accessKey,
secretAccessKey: params.secretKey,
sessionToken: params.sessionToken || undefined
},
host: url.host,
path: url.path, // path = pathname + query
service: params.service || 'execute-api', // AWS API Gateway is the default service.
region: params.region || 'us-east-1',
method: request.method,
body: undefined, // no need to give body since we are setting 'X-Amz-Content-Sha256' header
headers: _.transform(request.getHeaders({enabled: true}), function (accumulator, value, key) {
accumulator[key] = value;
}, {}),
signQuery: params.addAuthDataToQuery
};
// Removed the code which was adding content-type header if it is not there in the request. Because
// aws4 does not require content-type header. It is only mandatory to include content-type header in signature
// calculation if it is there in the request.
// Refer: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#canonical-request
// body hash is not required when adding auth data to qury params
// @see: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
if (params.addAuthDataToQuery) {
self.addAuthDataToRequest(request, dataToSign);
return done();
}
// aws4 module can't calculate body hash for body with ReadStream.
// So we calculate it our self and set 'X-Amz-Content-Sha256' header which will be used by aws4 module
// to calculate the signature.
computeBodyHash(request.body, 'sha256', 'hex', function (bodyHash) {
if (bodyHash) {
request.upsertHeader({
key: BODY_HASH_HEADER,
value: bodyHash,
system: true
});
dataToSign.headers[BODY_HASH_HEADER] = bodyHash;
}
self.addAuthDataToRequest(request, dataToSign);
return done();
});
}
};

77
node_modules/postman-runtime/lib/authorizer/basic.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'basic',
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.
*
* @todo - add support for prompting a user for basic auth credentials if not already provided
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
done(null, true);
},
/**
* Verifies whether the basic auth succeeded.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var username = auth.get('username') || '',
password = auth.get('password') || '';
request.removeHeader('Authorization', {ignoreCase: true});
request.addHeader({
key: 'Authorization',
value: 'Basic ' + Buffer.from(`${username}:${password}`, 'utf8').toString('base64'),
system: true
});
return done();
}
};

83
node_modules/postman-runtime/lib/authorizer/bearer.js generated vendored Normal file
View File

@@ -0,0 +1,83 @@
var BEARER_AUTH_PREFIX = 'Bearer ';
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'bearer',
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();
},
/**
* Verifies whether the request has required parameters
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
return done(null, Boolean(auth.get('token')));
},
/**
* Verifies whether the auth succeeded
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs the request
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var token = auth.get('token');
if (!token) {
return done(); // Nothing to do if required parameters are not present.
}
// @TODO Should we support adding to query params and/or body also?
// According to the RFC#6750 they are supported but not recommended!
request.removeHeader('Authorization', {ignoreCase: true});
request.addHeader({
key: 'Authorization',
value: BEARER_AUTH_PREFIX + token,
system: true
});
return done();
}
};

498
node_modules/postman-runtime/lib/authorizer/digest.js generated vendored Normal file
View File

@@ -0,0 +1,498 @@
var _ = require('lodash'),
crypto = require('crypto'),
urlEncoder = require('postman-url-encoder'),
RequestBody = require('postman-collection').RequestBody,
bodyBuilder = require('../requester/core-body-builder'),
EMPTY = '',
ONE = '00000001',
DISABLE_RETRY_REQUEST = 'disableRetryRequest',
WWW_AUTHENTICATE = 'www-authenticate',
DIGEST_PREFIX = 'Digest ',
QOP = 'qop',
AUTH = 'auth',
COLON = ':',
QUOTE = '"',
SESS = '-sess',
AUTH_INT = 'auth-int',
AUTHORIZATION = 'Authorization',
MD5_SESS = 'MD5-sess',
ASCII_SOURCE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
ASCII_SOURCE_LENGTH = ASCII_SOURCE.length,
USERNAME_EQUALS_QUOTE = 'username="',
REALM_EQUALS_QUOTE = 'realm="',
NONCE_EQUALS_QUOTE = 'nonce="',
URI_EQUALS_QUOTE = 'uri="',
ALGORITHM_EQUALS_QUOTE = 'algorithm="',
CNONCE_EQUALS_QUOTE = 'cnonce="',
RESPONSE_EQUALS_QUOTE = 'response="',
OPAQUE_EQUALS_QUOTE = 'opaque="',
QOP_EQUALS = 'qop=',
NC_EQUALS = 'nc=',
ALGO = {
MD5: 'MD5',
MD5_SESS: 'MD5-sess',
SHA_256: 'SHA-256',
SHA_256_SESS: 'SHA-256-sess',
SHA_512_256: 'SHA-512-256',
SHA_512_256_SESS: 'SHA-512-256-sess'
},
AUTH_PARAMETERS = [
'algorithm',
'username',
'realm',
'password',
'method',
'nonce',
'nonceCount',
'clientNonce',
'opaque',
'qop',
'uri'
],
nonceRegex = /nonce="([^"]*)"/,
realmRegex = /realm="([^"]*)"/,
qopRegex = /qop="([^"]*)"/,
opaqueRegex = /opaque="([^"]*)"/,
_extractField,
SHA512_256,
nodeCrypto;
// Current Electron version(7.2.3) in Postman app uses OpenSSL 1.1.0
// which don't support `SHA-512-256`. Use external `js-sha512` module
// to handle this case.
if (!_.includes(crypto.getHashes(), 'sha512-256')) {
SHA512_256 = require('js-sha512').sha512_256;
nodeCrypto = crypto;
// create a wrapper class with similar interface to Node's crypto and use jsSHA
// to support SHA512-256 algorithm
crypto = function () {
this._hash = SHA512_256.create();
};
_.assign(crypto.prototype, {
update: function (data) {
this._hash.update(data);
return this;
},
digest: function () {
// we only need 'hex' digest for this auth
return this._hash.hex();
}
});
_.assign(crypto, {
createHash: function (hashAlgo) {
// return hash from js-sha for SHA512-256
if (hashAlgo === 'sha512-256') {
return new crypto();
}
// return Node's hash otherwise
return nodeCrypto.createHash(hashAlgo);
}
});
}
/**
* Generates a random string of given length
*
* @todo Move this to util.js. After moving use that for hawk auth too
* @param {Number} length
*/
function randomString (length) {
length = length || 6;
var result = [],
i;
for (i = 0; i < length; i++) {
result[i] = ASCII_SOURCE[(Math.random() * ASCII_SOURCE_LENGTH) | 0];
}
return result.join(EMPTY);
}
/**
* Extracts a Digest Auth field from a WWW-Authenticate header value using a given regexp.
*
* @param {String} string
* @param {RegExp} regexp
* @private
*/
_extractField = function (string, regexp) {
var match = string.match(regexp);
return match ? match[1] : EMPTY;
};
/**
* Returns the 'www-authenticate' header for Digest auth. Since a server can suport more than more auth-scheme,
* there can be more than one header with the same key. So need to loop over and check each one.
*
* @param {VariableList} headers
* @private
*/
function _getDigestAuthHeader (headers) {
return headers.find(function (property) {
return (property.key.toLowerCase() === WWW_AUTHENTICATE) && (_.startsWith(property.value, DIGEST_PREFIX));
});
}
/**
* Returns hex encoded hash of given data using given algorithm.
*
* @param {String} data string to calculate hash
* @param {String} algorithm hash algorithm
* @returns {String} hex encoded hash of given data
*/
function getHash (data, algorithm) {
return crypto.createHash(algorithm).update(data || EMPTY).digest('hex');
}
/**
* Calculates body hash with given algorithm and digestEncoding.
*
* @param {RequestBody} body Request body
* @param {String} algorithm Hash algorithm to use
* @param {String} digestEncoding Encoding of the hash
* @param {Function} callback Callback function that will be called with body hash
*/
function computeBodyHash (body, algorithm, digestEncoding, callback) {
if (!(algorithm && digestEncoding)) { return callback(); }
var hash = crypto.createHash(algorithm),
originalReadStream,
rawBody,
graphqlBody,
urlencodedBody;
// if body is not available, return hash of empty string
if (!body || body.isEmpty()) {
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.raw) {
rawBody = bodyBuilder.raw(body.raw).body;
hash.update(rawBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.urlencoded) {
urlencodedBody = bodyBuilder.urlencoded(body.urlencoded).form;
urlencodedBody = urlEncoder.encodeQueryString(urlencodedBody);
hash.update(urlencodedBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.file) {
originalReadStream = _.get(body, 'file.content');
if (!originalReadStream) {
return callback();
}
return originalReadStream.cloneReadStream(function (err, clonedStream) {
if (err) { return callback(); }
clonedStream.on('data', function (chunk) {
hash.update(chunk);
});
clonedStream.on('end', function () {
callback(hash.digest(digestEncoding));
});
});
}
if (body.mode === RequestBody.MODES.graphql) {
graphqlBody = bodyBuilder.graphql(body.graphql).body;
hash.update(graphqlBody);
return callback(hash.digest(digestEncoding));
}
// @todo: Figure out a way to calculate hash for formdata body type.
// ensure that callback is called if body.mode doesn't match with any of the above modes
return callback();
}
/**
* All the auth definition parameters excluding username and password should be stored and resued.
* @todo The current implementation would fail for the case when two requests to two different hosts inherits the same
* auth. In that case a retry would not be attempted for the second request (since all the parameters would be present
* in the auth definition though invalid).
*
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'digest',
version: '1.0.0'
},
updates: [
{
property: 'Authorization',
type: 'header'
},
{
property: 'nonce',
type: 'auth'
},
{
property: 'realm',
type: 'auth'
}
]
},
/**
* 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);
},
/**
* Checks whether the given item has all the required parameters in its request.
* Sanitizes the auth parameters if needed.
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
// ensure that all dynamic parameter values are present in the parameters
// if even one is absent, we return false.
done(null, Boolean(auth.get('nonce') && auth.get('realm')));
},
/**
* Verifies whether the request was successfully authorized after being sent.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
if (auth.get(DISABLE_RETRY_REQUEST) || !response) {
return done(null, true);
}
var code,
realm,
nonce,
qop,
opaque,
authHeader,
authParams = {};
code = response.code;
authHeader = _getDigestAuthHeader(response.headers);
// If code is forbidden or unauthorized, and an auth header exists,
// we can extract the realm & the nonce, and replay the request.
// todo: add response.is4XX, response.is5XX, etc in the SDK.
if ((code === 401 || code === 403) && authHeader) {
nonce = _extractField(authHeader.value, nonceRegex);
realm = _extractField(authHeader.value, realmRegex);
qop = _extractField(authHeader.value, qopRegex);
opaque = _extractField(authHeader.value, opaqueRegex);
authParams.nonce = nonce;
authParams.realm = realm;
opaque && (authParams.opaque = opaque);
qop && (authParams.qop = qop);
if (authParams.qop || auth.get(QOP)) {
authParams.clientNonce = randomString(8);
authParams.nonceCount = ONE;
}
// if all the auth parameters sent by server were already present in auth definition then we do not retry
if (_.every(authParams, function (value, key) { return auth.get(key); })) {
return done(null, true);
}
auth.set(authParams);
return done(null, false);
}
done(null, true);
},
/**
* Computes the Digest Authentication header from the given parameters.
*
* @param {Object} params
* @param {String} params.algorithm
* @param {String} params.username
* @param {String} params.realm
* @param {String} params.password
* @param {String} params.method
* @param {String} params.nonce
* @param {String} params.nonceCount
* @param {String} params.clientNonce
* @param {String} params.opaque
* @param {String} params.qop
* @param {String} params.uri
* @returns {String}
*/
computeHeader: function (params) {
var algorithm = params.algorithm,
hashAlgo = params.hashAlgo,
username = params.username,
realm = params.realm,
password = params.password,
method = params.method,
nonce = params.nonce,
nonceCount = params.nonceCount,
clientNonce = params.clientNonce,
opaque = params.opaque,
qop = params.qop,
uri = params.uri,
// RFC defined terms, http://tools.ietf.org/html/rfc2617#section-3
A0,
A1,
A2,
hashA1,
hashA2,
reqDigest,
headerParams;
if (_.endsWith(algorithm, SESS)) {
A0 = getHash(username + COLON + realm + COLON + password, hashAlgo);
A1 = A0 + COLON + nonce + COLON + clientNonce;
}
else {
A1 = username + COLON + realm + COLON + password;
}
if (qop === AUTH_INT) {
A2 = method + COLON + uri + COLON + params.bodyhash;
}
else {
A2 = method + COLON + uri;
}
hashA1 = getHash(A1, hashAlgo);
hashA2 = getHash(A2, hashAlgo);
if (qop === AUTH || qop === AUTH_INT) {
reqDigest = getHash([hashA1, nonce, nonceCount, clientNonce, qop, hashA2].join(COLON), hashAlgo);
}
else {
reqDigest = getHash([hashA1, nonce, hashA2].join(COLON), hashAlgo);
}
headerParams = [USERNAME_EQUALS_QUOTE + username + QUOTE,
REALM_EQUALS_QUOTE + realm + QUOTE,
NONCE_EQUALS_QUOTE + nonce + QUOTE,
URI_EQUALS_QUOTE + uri + QUOTE
];
algorithm && headerParams.push(ALGORITHM_EQUALS_QUOTE + algorithm + QUOTE);
if (qop === AUTH || qop === AUTH_INT) {
headerParams.push(QOP_EQUALS + qop);
}
if (qop === AUTH || qop === AUTH_INT || algorithm === MD5_SESS) {
nonceCount && headerParams.push(NC_EQUALS + nonceCount);
headerParams.push(CNONCE_EQUALS_QUOTE + clientNonce + QUOTE);
}
headerParams.push(RESPONSE_EQUALS_QUOTE + reqDigest + QUOTE);
opaque && headerParams.push(OPAQUE_EQUALS_QUOTE + opaque + QUOTE);
return DIGEST_PREFIX + headerParams.join(', ');
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var self = this,
params = auth.get(AUTH_PARAMETERS),
url = urlEncoder.toNodeUrl(request.url),
header;
if (!params.username || !params.realm) {
return done(); // Nothing to do if required parameters are not present.
}
request.removeHeader(AUTHORIZATION, {ignoreCase: true});
params.method = request.method;
params.uri = url.path;
switch (params.algorithm) {
case ALGO.SHA_256:
case ALGO.SHA_256_SESS:
params.hashAlgo = 'sha256';
break;
case ALGO.MD5:
case ALGO.MD5_SESS:
case EMPTY:
case undefined:
case null:
params.algorithm = params.algorithm || ALGO.MD5;
params.hashAlgo = 'md5';
break;
case ALGO.SHA_512_256:
case ALGO.SHA_512_256_SESS:
params.hashAlgo = 'sha512-256';
break;
default:
return done(new Error(`Unsupported digest algorithm: ${params.algorithm}`));
}
// calculate body hash for qop='auth-int'
if (params.qop === AUTH_INT) {
return computeBodyHash(request.body, params.hashAlgo, 'hex', function (bodyhash) {
params.bodyhash = bodyhash;
header = self.computeHeader(params);
request.addHeader({
key: AUTHORIZATION,
value: header,
system: true
});
return done();
});
}
header = self.computeHeader(params);
request.addHeader({
key: AUTHORIZATION,
value: header,
system: true
});
return done();
}
};

316
node_modules/postman-runtime/lib/authorizer/edgegrid.js generated vendored Normal file
View File

@@ -0,0 +1,316 @@
/**
* @fileOverview
*
* Implements the EdgeGrid authentication method.
* Specification document: https://developer.akamai.com/legacy/introduction/Client_Auth.html
* Sample impletentation by Akamai: https://github.com/akamai/AkamaiOPEN-edgegrid-node
*/
var _ = require('lodash'),
uuid = require('uuid/v4'),
crypto = require('crypto'),
sdk = require('postman-collection'),
RequestBody = sdk.RequestBody,
urlEncoder = require('postman-url-encoder'),
bodyBuilder = require('../requester/core-body-builder'),
EMPTY = '',
COLON = ':',
UTC_OFFSET = '+0000',
ZERO = '0',
DATE_TIME_SEPARATOR = 'T',
TAB = '\t',
SPACE = ' ',
SLASH = '/',
STRING = 'string',
SIGNING_ALGORITHM = 'EG1-HMAC-SHA256 ',
AUTHORIZATION = 'Authorization',
/**
* Returns current timestamp in the format described in EdgeGrid specification (yyyyMMddTHH:mm:ss+0000)
*
* @returns {String} UTC timestamp in format yyyyMMddTHH:mm:ss+0000
*/
getTimestamp = function () {
var date = new Date();
return date.getUTCFullYear() +
_.padStart(date.getUTCMonth() + 1, 2, ZERO) +
_.padStart(date.getUTCDate(), 2, ZERO) +
DATE_TIME_SEPARATOR +
_.padStart(date.getUTCHours(), 2, ZERO) +
COLON +
_.padStart(date.getUTCMinutes(), 2, ZERO) +
COLON +
_.padStart(date.getUTCSeconds(), 2, ZERO) +
UTC_OFFSET;
},
/**
* Creates a String containing a tab delimited set of headers.
*
* @param {String[]} headersToSign Headers to include in signature
* @param {Object} headers Request headers
* @returns {String} Canonicalized headers
*/
canonicalizeHeaders = function (headersToSign, headers) {
var formattedHeaders = [],
headerValue;
headersToSign.forEach(function (headerName) {
if (typeof headerName !== STRING) { return; }
// trim the header name to remove extra spaces from user input
headerName = headerName.trim().toLowerCase();
headerValue = headers[headerName];
// should not include empty headers as per the specification
if (typeof headerValue !== STRING || headerValue === EMPTY) { return; }
formattedHeaders.push(`${headerName}:${headerValue.trim().replace(/\s+/g, SPACE)}`);
});
return formattedHeaders.join(TAB);
},
/**
* Returns base64 encoding of the SHA256 HMAC of given data signed with given key
*
* @param {String} data Data to sign
* @param {String} key Key to use while signing the data
* @returns {String} Base64 encoded signature
*/
base64HmacSha256 = function (data, key) {
var encrypt = crypto.createHmac('sha256', key);
encrypt.update(data);
return encrypt.digest('base64');
},
/**
* Calculates body hash with given algorithm and digestEncoding.
*
* @param {RequestBody} body Request body
* @param {String} algorithm Hash algorithm to use
* @param {String} digestEncoding Encoding of the hash
* @param {Function} callback Callback function that will be called with body hash
*/
computeBodyHash = function (body, algorithm, digestEncoding, callback) {
if (!(body && algorithm && digestEncoding) || body.isEmpty()) { return callback(); }
var hash = crypto.createHash(algorithm),
originalReadStream,
rawBody,
urlencodedBody,
graphqlBody;
if (body.mode === RequestBody.MODES.raw) {
rawBody = bodyBuilder.raw(body.raw).body;
hash.update(rawBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.urlencoded) {
urlencodedBody = bodyBuilder.urlencoded(body.urlencoded).form;
urlencodedBody = urlEncoder.encodeQueryString(urlencodedBody);
hash.update(urlencodedBody);
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.file) {
originalReadStream = _.get(body, 'file.content');
if (!originalReadStream) {
return callback();
}
return originalReadStream.cloneReadStream(function (err, clonedStream) {
if (err) { return callback(); }
clonedStream.on('data', function (chunk) {
hash.update(chunk);
});
clonedStream.on('end', function () {
callback(hash.digest(digestEncoding));
});
});
}
if (body.mode === RequestBody.MODES.graphql) {
graphqlBody = bodyBuilder.graphql(body.graphql).body;
hash.update(graphqlBody);
return callback(hash.digest(digestEncoding));
}
// @todo: Figure out a way to calculate hash for formdata body type.
// ensure that callback is called if body.mode doesn't match with any of the above modes
return callback();
};
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'edgegrid',
version: '1.0.0'
},
updates: [
{
property: 'Authorization',
type: 'header'
}
]
},
/**
* Initializes a item (fetches all required parameters, etc) before the actual authorization step.
*
* @param {AuthInterface} auth AuthInterface instance created with request auth
* @param {Response} response Response of intermediate request (it any)
* @param {AuthHandlerInterface~authInitHookCallback} done Callback function called with error as first argument
*/
init: function (auth, response, done) {
done(null);
},
/**
* Checks the item, and fetches any parameters that are not already provided.
*
* @param {AuthInterface} auth AuthInterface instance created with request auth
* @param {AuthHandlerInterface~authPreHookCallback} done Callback function called with error, success and request
*/
pre: function (auth, done) {
// only check required auth params here
done(null, Boolean(auth.get('accessToken') && auth.get('clientToken') && auth.get('clientSecret')));
},
/**
* Verifies whether the request was successful after being sent.
*
* @param {AuthInterface} auth AuthInterface instance created with request auth
* @param {Requester} response Response of the request
* @param {AuthHandlerInterface~authPostHookCallback} done Callback function called with error and success
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Generates the signature, and returns the Authorization header.
*
* @param {Object} params Auth parameters to use in header calculation
* @param {String} params.accessToken Access token provided by service provider
* @param {String} params.clientToken Client token provided by service provider
* @param {String} params.clientSecret Client secret provided by service provider
* @param {String} params.nonce Nonce to include in authorization header
* @param {String} params.timestamp Timestamp as defined in protocol specification
* @param {String} [params.bodyHash] Base64-encoded SHA256 hash of request body for POST request
* @param {Object[]} params.headers Request headers
* @param {String[]} params.headersToSign Ordered list of headers to include in signature
* @param {String} params.method Request method
* @param {Url} params.url Node's URL object
* @returns {String} Authorization header
*/
computeHeader: function (params) {
var authHeader = SIGNING_ALGORITHM,
signingKey = base64HmacSha256(params.timestamp, params.clientSecret),
dataToSign;
authHeader += `client_token=${params.clientToken};`;
authHeader += `access_token=${params.accessToken};`;
authHeader += `timestamp=${params.timestamp};`;
authHeader += `nonce=${params.nonce};`;
dataToSign = [
params.method,
// trim to convert 'http:' from Node's URL object to 'http'
_.trimEnd(params.url.protocol, COLON),
params.baseURL || params.url.host,
params.url.path || SLASH,
canonicalizeHeaders(params.headersToSign, params.headers),
params.bodyHash || EMPTY,
authHeader
].join(TAB);
return authHeader + 'signature=' + base64HmacSha256(dataToSign, signingKey);
},
/**
* Signs a request.
*
* @param {AuthInterface} auth AuthInterface instance created with request auth
* @param {Request} request Request to be sent
* @param {AuthHandlerInterface~authSignHookCallback} done Callback function
*/
sign: function (auth, request, done) {
var params = auth.get([
'accessToken',
'clientToken',
'clientSecret',
'baseURL',
'nonce',
'timestamp',
'headersToSign'
]),
url = urlEncoder.toNodeUrl(request.url),
self = this;
if (!(params.accessToken && params.clientToken && params.clientSecret)) {
return done(); // Nothing to do if required parameters are not present.
}
request.removeHeader(AUTHORIZATION, {ignoreCase: true});
// Extract host from provided baseURL.
params.baseURL = params.baseURL && urlEncoder.toNodeUrl(params.baseURL).host;
params.nonce = params.nonce || uuid();
params.timestamp = params.timestamp || getTimestamp();
params.url = url;
params.method = request.method;
// ensure that headers are case-insensitive as specified in the documentation
params.headers = request.getHeaders({enabled: true, ignoreCase: true});
if (typeof params.headersToSign === STRING) {
params.headersToSign = params.headersToSign.split(',');
}
else if (!_.isArray(params.headersToSign)) {
params.headersToSign = [];
}
// only calculate body hash for POST requests according to specification
if (request.method === 'POST') {
return computeBodyHash(request.body, 'sha256', 'base64', function (bodyHash) {
params.bodyHash = bodyHash;
request.addHeader({
key: AUTHORIZATION,
value: self.computeHeader(params),
system: true
});
return done();
});
}
request.addHeader({
key: AUTHORIZATION,
value: self.computeHeader(params),
system: true
});
return done();
}
};

264
node_modules/postman-runtime/lib/authorizer/hawk.js generated vendored Normal file
View File

@@ -0,0 +1,264 @@
var url = require('url'),
_ = require('lodash'),
crypto = require('crypto'),
Hawk = require('postman-request/lib/hawk'),
RequestBody = require('postman-collection').RequestBody,
bodyBuilder = require('../requester/core-body-builder'),
urlEncoder = require('postman-url-encoder'),
ASCII_SOURCE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
ASCII_SOURCE_LENGTH = ASCII_SOURCE.length,
AUTHORIZATION = 'Authorization',
EMPTY = '';
/**
* Generates a random string of given length (useful for nonce generation, etc).
*
* @param {Number} length
*/
function randomString (length) {
length = length || 6;
var result = [],
i;
for (i = 0; i < length; i++) {
result[i] = ASCII_SOURCE[(Math.random() * ASCII_SOURCE_LENGTH) | 0];
}
return result.join(EMPTY);
}
/**
* Calculates body hash with given algorithm and digestEncoding.
* REFER: https://github.com/postmanlabs/postman-request/blob/master/lib/hawk.js#L12
*
* @param {RequestBody} body
* @param {String} algorithm
* @param {String} digestEncoding
* @param {String} contentType
* @param {Function} callback
*/
function computeBodyHash (body, algorithm, digestEncoding, contentType, callback) {
if (!(body && algorithm && digestEncoding) || body.isEmpty()) { return callback(); }
var hash = crypto.createHash(algorithm),
originalReadStream,
rawBody,
urlencodedBody,
graphqlBody;
hash.update('hawk.1.payload\n');
hash.update((contentType ? contentType.split(';')[0].trim().toLowerCase() : '') + '\n');
if (body.mode === RequestBody.MODES.raw) {
rawBody = bodyBuilder.raw(body.raw).body;
hash.update(rawBody);
hash.update('\n');
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.urlencoded) {
urlencodedBody = bodyBuilder.urlencoded(body.urlencoded).form;
urlencodedBody = urlEncoder.encodeQueryString(urlencodedBody);
hash.update(urlencodedBody);
hash.update('\n');
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.file) {
originalReadStream = _.get(body, 'file.content');
if (!originalReadStream) {
return callback();
}
return originalReadStream.cloneReadStream(function (err, clonedStream) {
if (err) { return callback(); }
clonedStream.on('data', function (chunk) {
hash.update(chunk);
});
clonedStream.on('end', function () {
hash.update('\n');
callback(hash.digest(digestEncoding));
});
});
}
if (body.mode === RequestBody.MODES.graphql) {
graphqlBody = bodyBuilder.graphql(body.graphql).body;
hash.update(graphqlBody);
hash.update('\n');
return callback(hash.digest(digestEncoding));
}
// @todo: Figure out a way to calculate hash for formdata body type.
// ensure that callback is called if body.mode doesn't match with any of the above modes
return callback();
}
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'hawk',
version: '1.0.0'
},
updates: [
{
property: 'Authorization',
type: 'header'
},
{
property: 'nonce',
type: 'auth'
},
{
property: 'timestamp',
type: 'auth'
}
]
},
/**
* 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);
},
/**
* Checks the item, and fetches any parameters that are not already provided.
* Sanitizes the auth parameters if needed.
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
!auth.get('nonce') && auth.set('nonce', randomString(6));
!_.parseInt(auth.get('timestamp')) && auth.set('timestamp', Math.floor(Date.now() / 1e3));
done(null, true);
},
/**
* Verifies whether the request was successfully authorized after being sent.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Computes signature and Auth header for a request.
*
* @param {Object} params
* @param {Object} params.credentials Contains hawk auth credentials, "id", "key" and "algorithm"
* @param {String} params.nonce
* @param {String} params.ext Extra data that may be associated with the request.
* @param {String} params.app Application ID used in Oz authorization protocol
* @param {String} params.dlg Delegation information (used in the Oz protocol)
* @param {String} params.user User id
* @param {String} params.url Complete request URL
* @param {String} params.method Request method
*
* @returns {*}
*/
computeHeader: function (params) {
return Hawk.header(url.parse(params.url), params.method, params);
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var params = auth.get([
'authId',
'authKey',
'algorithm',
'nonce',
'timestamp',
'extraData',
'app',
'delegation',
'user',
'includePayloadHash'
]),
contentType = request.headers.get('content-type'),
self = this,
signRequest = function (bodyHash) {
// force toString to add a protocol to the URL.
var url = urlEncoder.toNodeUrl(request.url),
result = self.computeHeader({
credentials: {
id: params.authId,
key: params.authKey,
algorithm: params.algorithm
},
nonce: params.nonce,
timestamp: params.timestamp,
ext: params.extraData,
app: params.app,
dlg: params.delegation,
user: params.user,
url: url.href,
method: request.method,
hash: bodyHash
});
request.addHeader({
key: AUTHORIZATION,
value: result,
system: true
});
return done();
};
if (!params.authId || !params.authKey) {
return done(); // Nothing to do if required parameters are not present.
}
request.removeHeader(AUTHORIZATION, {ignoreCase: true});
// @note: Payload verification is optional in hawk auth according to specifications (see below link). If user
// opt-in for payload verification, `Content-Type` header must be specified explicitely otherwise
// authentication might fail because we automatically add `Content-Type` header after auth handlers which
// is not accounted while calculating payload hash for hawk auth.
// documentation: https://github.com/hapijs/hawk#payload-validation
// issue: https://github.com/postmanlabs/postman-app-support/issues/6550
//
// @todo: Change flow of auto adding `Content-Type` header to happen before auth handlers
if (!params.includePayloadHash) {
return signRequest(); // sign request without calculating payload hash
}
computeBodyHash(request.body, params.algorithm, 'base64', contentType, signRequest);
}
};

239
node_modules/postman-runtime/lib/authorizer/index.js generated vendored Normal file
View File

@@ -0,0 +1,239 @@
var _ = require('lodash'),
sdk = require('postman-collection'),
createAuthInterface = require('./auth-interface'),
AUTH_TYPE_PROP = '__auth_type',
AuthLoader,
authorizeRequest;
/**
* This object manages loading and finding Handlers for auth.
*
* @type AuthLoader
*/
AuthLoader = {
/**
* Houses list of available Authentication handlers.
*
* @property {Object}
*/
handlers: {},
/**
* Finds the Handler for an Auth type.
*
* @param name
*
* @returns {AuthHandler}
*/
getHandler: function (name) {
return AuthLoader.handlers[name];
},
/**
* Adds a Handler for use with given Auth type.
*
* @param Handler
* @param name
*/
addHandler: function (Handler, name) {
if (!_.isFunction(Handler.init)) {
throw new Error('The handler for "' + name + '" does not have an "init" function, which is necessary');
}
if (!_.isFunction(Handler.pre)) {
throw new Error('The handler for "' + name + '" does not have a "pre" function, which is necessary');
}
if (!_.isFunction(Handler.post)) {
throw new Error('The handler for "' + name + '" does not have a "post" function, which is necessary');
}
if (!_.isFunction(Handler.sign)) {
throw new Error('The handler for "' + name + '" does not have a "sign" function, which is necessary');
}
Object.defineProperty(Handler, AUTH_TYPE_PROP, {
value: name,
configurable: false,
enumerable: false,
writable: false
});
AuthLoader.handlers[name] = Handler;
},
/**
* Removes the Handler for the Auth type.
*
* @param name
*/
removeHandler: function (name) {
AuthLoader.handlers[name] && (delete AuthLoader.handlers[name]);
}
};
// Create a Handler from each Signer that the SDK provides. Basically, we augment the signers with extra
// helper functions which take over the job of preparing a request for signing.
_.forEach({
noauth: require('./noauth'),
awsv4: require('./aws4'),
basic: require('./basic'),
bearer: require('./bearer'),
digest: require('./digest'),
hawk: require('./hawk'),
oauth1: require('./oauth1'),
oauth2: require('./oauth2'),
ntlm: require('./ntlm'),
apikey: require('./apikey'),
edgegrid: require('./edgegrid')
}, AuthLoader.addHandler);
/**
* Creates a copy of request, with the appropriate auth headers or parameters added.
*
* @note This function does not take care of resolving variables.
*
* @param {Request} request
* @param done
*
* @returns {Request}
*/
authorizeRequest = function (request, done) {
if (!request.auth) {
return done();
}
var clonedReq = new sdk.Request(request.toJSON()),
auth = clonedReq.auth,
authInterface = createAuthInterface(auth),
handler = AuthLoader.getHandler(auth.type);
if (handler) {
handler.sign(authInterface, clonedReq, function () { return done(null, clonedReq); });
}
else {
return done(new Error('runtime~authorizeRequest: could not find handler for auth type ' + auth.type));
}
};
module.exports = {
AuthLoader: AuthLoader,
authorizeRequest: authorizeRequest
};
// Interface
/**
* Interface for implementing auth handlers
*
* @interface AuthHandlerInterface
*/
// Interface functions
/**
* Defines the behaviour of an Auth Handler. This way the handler allows to statically analyse
* any changes the Handler will make ahead of time.
*
* @member {AuthHandlerInterface~AuthManifest} AuthHandlerInterface#manifest
*/
/**
* This hook decides whether all the required parameters are present in the auth or not.
* What happens next is dependent upon how the `done` callback is called.
* Check {@link AuthHandlerInterface~authPreHookCallback} for all the possible ways the callback can be called.
*
* @function
* @name AuthHandlerInterface#pre
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
* Callback function which takes error, success, and request as arguments
*/
/**
* This hook is called with the response from the intermediate request, which was requested from the
* [pre]{@link AuthHandlerInterface#pre} hook.
* Here the `auth` can be modified using the response. After this [pre]{@link AuthHandlerInterface#pre} hook will be
* called again to verify the required parameters.
*
* @function
* @name AuthHandlerInterface#init
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authInitHookCallback} done Callback function which takes error as the only argument
*/
/**
* This hook signs the `request` using the `auth`.
*
* @function
* @name AuthHandlerInterface#sign
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done Callback function which takes error as the only argument
*/
/**
* This hook is called after the request is made. It receives the response using which it can determine whether
* it was a failure or success. It can also modify the `auth` and ask to replay the `request`.
* For this it has to call the [done]{@link AuthHandlerInterface~authPostHookCallback} callback with `success` as false.
*
* @function
* @name AuthHandlerInterface#post
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done Callback function which takes error and success as arguments
*/
// Callbacks
/**
* This callback is called in the `pre` hook of the auth handler
* Depending on what parameters are passed in this callback, one of the following flows will be executed:
* 1. return (err): The request will be stopped and the error will be bubbled up
* 2. return (null, true): The request will be signed and sent
* 3. return (null, false): The request will be sent without being signed
* 4. return (null, false, `request`):
* - send the intermediate request
* - invoke the auth's [init]{@link AuthHandlerInterface#init} hook with the response of the intermediate request
* - invoke the auth's [pre]{@link AuthHandlerInterface#pre} hook
* @callback AuthHandlerInterface~authPreHookCallback
* @param {?Error} err
* @param {Boolean} success Defines whether the [pre]{@link AuthHandlerInterface#pre} hook was successful.
* @param {Request~definition|String} [request] It can be either request definition or request URL
*/
/**
* This callback is called in the `init` hook of the auth handler
* @callback AuthHandlerInterface~authInitHookCallback
* @param {?Error} err
*/
/**
* This callback is called in the `sign` hook of the auth handler
* @callback AuthHandlerInterface~authSignHookCallback
* @param {?Error} err
*/
/**
* This callback is called in the `post` hook of the auth handler
* @callback AuthHandlerInterface~authPostHookCallback
* @param {?Error} err
* @param {Boolean} success Defines whether the request was successful or not. If not, it will be replayed.
*/
/**
* Structure of an Auth Manifest. See {@link AuthHandlerInterface#manifest} for description.
*
* @typedef {Object} AuthHandlerInterface~AuthManifest
*
* @property {Object} info
* @property {String} info.name
* @property {String} info.version
* @property {Array<Object>} updates
*/

62
node_modules/postman-runtime/lib/authorizer/noauth.js generated vendored Normal file
View File

@@ -0,0 +1,62 @@
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
* @todo runtime needs to make sure AuthHandler
* cannot mutate any property on Request that it has not declared on the manifest.
*/
manifest: {
info: {
name: 'noauth',
version: '1.0.0'
},
updates: []
},
/**
* 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);
},
/**
* Checks whether the given item has all the required parameters in its request.
* Sanitizes the auth parameters if needed.
*
* @param {AuthInterface} auth
* @param {AuthHandlerInterface~authPreHookCallback} done
*/
pre: function (auth, done) {
done(null, true);
},
/**
* Verifies whether the request was successfully authorized after being sent.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
return done();
}
};

276
node_modules/postman-runtime/lib/authorizer/ntlm.js generated vendored Normal file
View 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();
}
};

494
node_modules/postman-runtime/lib/authorizer/oauth1.js generated vendored Normal file
View File

@@ -0,0 +1,494 @@
var _ = require('lodash'),
crypto = require('crypto'),
oAuth1 = require('node-oauth1'),
urlEncoder = require('postman-url-encoder'),
RequestBody = require('postman-collection').RequestBody,
bodyBuilder = require('../requester/core-body-builder'),
EMPTY = '',
RSA = 'RSA',
HYPHEN = '-',
PROTOCOL_HTTP = 'http',
PROTOCOL_SEPARATOR = '://',
HTTPS_PORT = '443',
HTTP_PORT = '80',
OAUTH1_PARAMS = {
oauthConsumerKey: 'oauth_consumer_key',
oauthToken: 'oauth_token',
oauthSignatureMethod: 'oauth_signature_method',
oauthTimestamp: 'oauth_timestamp',
oauthNonce: 'oauth_nonce',
oauthVersion: 'oauth_version',
oauthSignature: 'oauth_signature',
oauthCallback: 'oauth_callback',
oauthVerifier: 'oauth_verifier',
oauthBodyHash: 'oauth_body_hash'
};
/**
* Returns a OAuth1.0-a compatible representation of the request URL, also called "Base URL".
* For details, http://oauth.net/core/1.0a/#anchor13
*
* todo: should we ignore the auth parameters of the URL or not? (the standard does not mention them)
* we currently are.
*
* @private
* @param {Url} url - Node's URL object
* @returns {String}
*/
function getOAuth1BaseUrl (url) {
var port = url.port ? url.port : undefined,
host = ((port === HTTP_PORT ||
port === HTTPS_PORT ||
port === undefined) && url.hostname) || url.host,
path = url.path,
// trim to convert 'http:' from Node's URL object to 'http'
protocol = _.trimEnd(url.protocol || PROTOCOL_HTTP, PROTOCOL_SEPARATOR);
protocol = (_.endsWith(protocol, PROTOCOL_SEPARATOR) ? protocol : protocol + PROTOCOL_SEPARATOR);
return protocol.toLowerCase() + host.toLowerCase() + path;
}
/**
* Query parameters are encoded with WHATWG encoding in the request. OAuth1.0
* requires the query params to be encoded with the RFC-3986 standard. This
* function decodes the query parameters and encodes them to the required RFC-3986
* standard. For details: https://oauth.net/core/1.0a/#encoding_parameters
*
* @param {Request} request - request to update query parameters
* @param {Object} url - Node.js like url object
*/
function updateQueryParamEncoding (request, url) {
// early bailout if no query is set.
if (!url.query) {
return;
}
const queryParams = oAuth1.decodeForm(url.query);
// clear all query parameters
request.url.query.clear();
_.forEach(queryParams, function (param) {
request.url.query.add({
key: param[0] && oAuth1.percentEncode(param[0]),
value: param[1] && oAuth1.percentEncode(param[1])
});
});
}
/**
* Calculates body hash with given algorithm and digestEncoding.
*
* @param {RequestBody} body Request body
* @param {String} algorithm Hash algorithm to use
* @param {String} digestEncoding Encoding of the hash
* @param {Function} callback Callback function that will be called with body hash
*/
function computeBodyHash (body, algorithm, digestEncoding, callback) {
if (!(algorithm && digestEncoding)) { return callback(); }
var hash = crypto.createHash(algorithm),
originalReadStream,
rawBody,
graphqlBody;
// if body is not available, return hash of empty string
if (!body || body.isEmpty()) {
return callback(hash.digest(digestEncoding));
}
if (body.mode === RequestBody.MODES.raw) {
rawBody = bodyBuilder.raw(body.raw).body;
hash.update(rawBody);
return callback(hash.digest(digestEncoding));
}
// calculations for url-encoded body are not done here unlike other
// auths(i.e. AWS/HAWK) because it is not required for OAuth1.0
if (body.mode === RequestBody.MODES.file) {
originalReadStream = _.get(body, 'file.content');
if (!originalReadStream) {
return callback();
}
return originalReadStream.cloneReadStream(function (err, clonedStream) {
if (err) { return callback(); }
clonedStream.on('data', function (chunk) {
hash.update(chunk);
});
clonedStream.on('end', function () {
callback(hash.digest(digestEncoding));
});
});
}
if (body.mode === RequestBody.MODES.graphql) {
graphqlBody = bodyBuilder.graphql(body.graphql).body;
hash.update(graphqlBody);
return callback(hash.digest(digestEncoding));
}
// @todo: Figure out a way to calculate hash for formdata body type.
// ensure that callback is called if body.mode doesn't match with any of the above modes
return callback();
}
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'oauth1',
version: '1.0.0'
},
updates: [
{
property: 'Authorization',
type: 'header'
},
{
property: OAUTH1_PARAMS.oauthConsumerKey,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthToken,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthCallback,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthVerifier,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthBodyHash,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthSignatureMethod,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthTimestamp,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthNonce,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthVersion,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthSignature,
type: 'url.param'
},
{
property: OAUTH1_PARAMS.oauthConsumerKey,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthToken,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthCallback,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthVerifier,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthSignatureMethod,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthTimestamp,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthNonce,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthVersion,
type: 'body.urlencoded'
},
{
property: OAUTH1_PARAMS.oauthSignature,
type: 'body.urlencoded'
}
]
},
/**
* 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) {
done(null, true);
},
/**
* Verifies whether the basic auth succeeded.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Generates and adds oAuth1 data to the request. This function modifies the
* request passed in the argument.
*
* @param {Request} request - request to add oauth1 parameters
* @param {Object} params - oauth data to generate signature
* @param {Object} protocolProfileBehavior - Protocol profile behaviors
* @param {Function} done - callback function
*/
addAuthDataToRequest: function (request, params, protocolProfileBehavior, done) {
var url = urlEncoder.toNodeUrl(request.url),
signatureParams,
urlencodedBody,
bodyParams,
allParams,
signature,
message,
header,
accessor = {
consumerSecret: params.consumerSecret || EMPTY,
tokenSecret: params.tokenSecret || EMPTY,
privateKey: params.privateKey || EMPTY
},
disableUrlEncoding = protocolProfileBehavior && protocolProfileBehavior.disableUrlEncoding;
signatureParams = [
{system: true, key: OAUTH1_PARAMS.oauthConsumerKey, value: params.consumerKey},
{system: true, key: OAUTH1_PARAMS.oauthToken, value: params.token},
{system: true, key: OAUTH1_PARAMS.oauthSignatureMethod, value: params.signatureMethod},
{system: true, key: OAUTH1_PARAMS.oauthTimestamp, value: params.timestamp},
{system: true, key: OAUTH1_PARAMS.oauthNonce, value: params.nonce},
{system: true, key: OAUTH1_PARAMS.oauthVersion, value: params.version}
];
// bodyHash, callback and verifier parameters are part of extensions of the original OAuth1 spec.
// So we only include those in signature if they are non-empty, ignoring the addEmptyParamsToSign setting.
// Otherwise it causes problem for servers that don't support the respective OAuth1 extensions.
// Issue: https://github.com/postmanlabs/postman-app-support/issues/8737
if (params.bodyHash) {
signatureParams.push({system: true, key: OAUTH1_PARAMS.oauthBodyHash, value: params.bodyHash});
}
if (params.callback) {
signatureParams.push({system: true, key: OAUTH1_PARAMS.oauthCallback, value: params.callback});
}
if (params.verifier) {
signatureParams.push({system: true, key: OAUTH1_PARAMS.oauthVerifier, value: params.verifier});
}
// filter empty signature parameters
signatureParams = _.filter(signatureParams, function (param) {
return params.addEmptyParamsToSign || param.value;
});
urlencodedBody = request.body &&
request.body.mode === RequestBody.MODES.urlencoded &&
request.body.urlencoded;
// Body params only need to be included if they are URL encoded.
// http://oauth.net/core/1.0a/#anchor13
bodyParams = urlencodedBody ? urlencodedBody.filter(function (param) {
return !param.disabled;
}) : [];
allParams = [].concat(signatureParams, bodyParams);
message = {
action: getOAuth1BaseUrl(url),
method: request.method,
parameters: _.map(allParams, function (param) {
return [param.key, param.value];
})
};
try {
signature = oAuth1.SignatureMethod.sign(message, accessor);
}
catch (err) {
// handle invalid private key errors for RSA signatures
return done(err);
}
// Update the encoding for query parameters to RFC-3986 in accordance with the
// OAuth1.0a specification: https://oauth.net/core/1.0a/#encoding_parameters
// disableUrlEncoding option should be respected in authorization flow as well
if (disableUrlEncoding !== true) {
updateQueryParamEncoding(request, url);
}
signatureParams.push({system: true, key: OAUTH1_PARAMS.oauthSignature, value: signature});
// Add signature params to the request. The OAuth specification says
// that we should add parameters in the following order of preference:
// 1. Auth Header
// 2. Body parameters
// 3. Query parameters
//
// http://oauth.net/core/1.0/#consumer_req_param
if (params.addParamsToHeader) {
header = oAuth1.getAuthorizationHeader(params.realm, _.map(signatureParams, function (param) {
return [param.key, param.value];
}), params.disableHeaderEncoding);
request.addHeader({
key: 'Authorization',
value: header,
system: true
});
}
else if ((/PUT|POST/).test(request.method) && urlencodedBody) {
_.forEach(signatureParams, function (param) {
urlencodedBody.add(param);
});
}
else if (disableUrlEncoding === true) {
// disableUrlEncoding option should be respected in authorization flow as well
request.addQueryParams(signatureParams);
}
else {
_.forEach(signatureParams, function (param) {
request.url.query.add({
key: param.key && oAuth1.percentEncode(param.key),
value: param.value && oAuth1.percentEncode(param.value),
system: true
});
});
}
done();
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var self = this,
params = auth.get([
'consumerKey',
'consumerSecret',
'token',
'tokenSecret',
'privateKey',
'signatureMethod',
'callback',
'verifier',
'timestamp',
'nonce',
'version',
'realm',
'includeBodyHash',
'addParamsToHeader',
'addEmptyParamsToSign',
'disableHeaderEncoding'
]),
urlencodedBody = request.body,
signatureAlgo,
hashAlgo,
protocolProfileBehavior = auth._protocolProfileBehavior;
// extract hash and signature algorithm form signatureMethod
// signature methods are in this format: '<signatureAlgo>-<hashAlgo>' e.g. RSA-SHA1
hashAlgo = _.split(params.signatureMethod, HYPHEN);
signatureAlgo = _.upperCase(hashAlgo[0]);
hashAlgo = hashAlgo[1];
if (!params.consumerKey ||
(signatureAlgo !== RSA && !params.consumerSecret) ||
(signatureAlgo === RSA && !params.privateKey)) {
return done(); // Nothing to do if required parameters are not present.
}
// before this: urlencodedBody = request.body
// after this: urlencodedBody = request.body.urlencoded or undefined
urlencodedBody = (urlencodedBody &&
urlencodedBody.mode === RequestBody.MODES.urlencoded
) ? urlencodedBody.urlencoded : undefined;
// Remove existing headers and params (if any)
request.removeHeader('Authorization');
request.removeQueryParams(_.values(OAUTH1_PARAMS));
urlencodedBody && urlencodedBody.remove(function (param) {
return _.includes(_.values(OAUTH1_PARAMS), param.key);
});
// Generate a new nonce and timestamp
params.nonce = params.nonce || oAuth1.nonce(11).toString();
params.timestamp = params.timestamp || oAuth1.timestamp().toString();
// Ensure that empty parameters are not added to the signature
if (!params.addEmptyParamsToSign) {
params = _.reduce(params, function (accumulator, value, key) {
if (_.isString(value) && (value.trim() === EMPTY)) {
return accumulator;
}
accumulator[key] = value;
return accumulator;
}, {});
}
// Don't include body hash as defined in specification
// @see: https://tools.ietf.org/id/draft-eaton-oauth-bodyhash-00.html#when_to_include
if (urlencodedBody || !(params.includeBodyHash && hashAlgo)) {
return self.addAuthDataToRequest(request, params, protocolProfileBehavior, done);
}
computeBodyHash(request.body, hashAlgo, 'base64', function (bodyHash) {
params.bodyHash = bodyHash;
return self.addAuthDataToRequest(request, params, protocolProfileBehavior, done);
});
}
};

133
node_modules/postman-runtime/lib/authorizer/oauth2.js generated vendored Normal file
View File

@@ -0,0 +1,133 @@
var _ = require('lodash'),
HEADER = 'header',
QUERY_PARAMS = 'queryParams',
BEARER = 'bearer',
MAC = 'mac',
AUTHORIZATION = 'Authorization',
ACCESS_TOKEN = 'access_token',
AUTHORIZATION_PREFIX = 'Bearer',
OAUTH2_PARAMETERS = [
'accessToken',
'addTokenTo',
'tokenType',
'headerPrefix'
];
/**
* @implements {AuthHandlerInterface}
*/
module.exports = {
/**
* @property {AuthHandlerInterface~AuthManifest}
*/
manifest: {
info: {
name: 'oauth2',
version: '1.0.0'
},
updates: [
{
property: AUTHORIZATION,
type: 'header'
},
{
property: ACCESS_TOKEN,
type: 'url.param'
}
]
},
/**
* 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) {
done(null, Boolean(auth.get('accessToken')));
},
/**
* Verifies whether the basic auth succeeded.
*
* @param {AuthInterface} auth
* @param {Response} response
* @param {AuthHandlerInterface~authPostHookCallback} done
*/
post: function (auth, response, done) {
done(null, true);
},
/**
* Signs a request.
*
* @param {AuthInterface} auth
* @param {Request} request
* @param {AuthHandlerInterface~authSignHookCallback} done
*/
sign: function (auth, request, done) {
var params = auth.get(OAUTH2_PARAMETERS),
tokenType;
// Validation
if (!params.accessToken) {
return done(); // Nothing to do if required parameters are not present.
}
// Defaults
params.addTokenTo = params.addTokenTo || HEADER; // Add token to header by default
params.tokenType = params.tokenType || BEARER; // Use `Bearer` token type by default
params.headerPrefix = _.isNil(params.headerPrefix) ?
AUTHORIZATION_PREFIX : _.trim(String(params.headerPrefix));
// add a space after prefix only if there is any prefix
params.headerPrefix && (params.headerPrefix += ' ');
// Some servers send 'Bearer' while others send 'bearer'
tokenType = _.toLower(params.tokenType);
// @TODO Add support for HMAC
if (tokenType === MAC) {
return done();
}
// treat every token types (other than MAC) as bearer token
// clean conflicting headers and query params
// @todo: we should be able to get conflicting params from auth manifest
// and clear them before the sign step for any auth
request.removeHeader(AUTHORIZATION, {ignoreCase: true});
request.removeQueryParams([ACCESS_TOKEN]);
if (params.addTokenTo === QUERY_PARAMS) {
request.addQueryParams({
key: ACCESS_TOKEN,
value: params.accessToken,
system: true
});
}
else if (params.addTokenTo === HEADER) {
request.addHeader({
key: AUTHORIZATION,
value: params.headerPrefix + params.accessToken,
system: true
});
}
return done();
}
};