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:
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);
|
||||
});
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user