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:
119
node_modules/postman-runtime/lib/authorizer/apikey.js
generated
vendored
Normal file
119
node_modules/postman-runtime/lib/authorizer/apikey.js
generated
vendored
Normal 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();
|
||||
}
|
||||
};
|
95
node_modules/postman-runtime/lib/authorizer/auth-interface.js
generated
vendored
Normal file
95
node_modules/postman-runtime/lib/authorizer/auth-interface.js
generated
vendored
Normal 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
303
node_modules/postman-runtime/lib/authorizer/aws4.js
generated
vendored
Normal 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
77
node_modules/postman-runtime/lib/authorizer/basic.js
generated
vendored
Normal 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
83
node_modules/postman-runtime/lib/authorizer/bearer.js
generated
vendored
Normal 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
498
node_modules/postman-runtime/lib/authorizer/digest.js
generated
vendored
Normal 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
316
node_modules/postman-runtime/lib/authorizer/edgegrid.js
generated
vendored
Normal 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 SHA–256 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 SHA–256 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
264
node_modules/postman-runtime/lib/authorizer/hawk.js
generated
vendored
Normal 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
239
node_modules/postman-runtime/lib/authorizer/index.js
generated
vendored
Normal 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
62
node_modules/postman-runtime/lib/authorizer/noauth.js
generated
vendored
Normal 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
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();
|
||||
}
|
||||
};
|
494
node_modules/postman-runtime/lib/authorizer/oauth1.js
generated
vendored
Normal file
494
node_modules/postman-runtime/lib/authorizer/oauth1.js
generated
vendored
Normal 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
133
node_modules/postman-runtime/lib/authorizer/oauth2.js
generated
vendored
Normal 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();
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user