Skip to content

Commit

Permalink
Merge pull request #6 from Mastercard/fixing-encoding
Browse files Browse the repository at this point in the history
Fixing NodeJs OAuth signer to handle already encoded params
  • Loading branch information
danny-gallagher authored May 7, 2019
2 parents 05dbbeb + de660e8 commit 3a8221e
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 9 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mastercard-oauth1-signer",
"version": "1.1.0",
"version": "1.1.1",
"description": "Zero dependency library for generating a Mastercard API compliant OAuth signature.",
"main": "src/OAuth.js",
"scripts": {
Expand Down
19 changes: 17 additions & 2 deletions src/OAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ OAuth.getSignatureBaseString = function getSignatureBaseString(httpMethod, baseU
// Uppercase HTTP method
`${httpMethod.toUpperCase()}&` +
// Base URI
`${encodeURIComponent(baseUri)}&` +
`${OAuth.getEncodedString(baseUri)}&` +
// OAuth parameter string
`${encodeURIComponent(paramString)}`;
`${OAuth.getEncodedString(paramString)}`;

return sbs.replace(/!/, "%21");
};
Expand Down Expand Up @@ -273,3 +273,18 @@ OAuth.toOAuthParamString = function toOAuthParamString(queryParamsMap, oauthPara

return allParams;
};

/**
* Get encoded Uri from passed parameter
* Also checks whether Uri is already encoded
* Decodes Url first as URI builders encode some parts of the passed parameters
*
* @param {String} stringParam Param that's checked for encoding and returned accordingly
*/
OAuth.getEncodedString = function getEncodedString(stringParam) {
if(decodeURIComponent(stringParam) === stringParam) {
return encodeURIComponent(stringParam);
} else {
return encodeURIComponent(decodeURIComponent(stringParam));
}
};
49 changes: 46 additions & 3 deletions test/OAuth-extractQueryParams.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const extractQueryParams = require("../src/OAuth").extractQueryParams;

describe("OAuth Signer", function() {
describe("#extractQueryParams()", function() {
const href="HTTPS://SANDBOX.api.mastercard.com/merchantid/v1/merchantid?MerchantId=GOOGLE%20LTD%20ADWORDS%20%28CC%40GOOGLE.COM%29&Format=XML&Type=ExactMatch&Format=JSON";
const href="https://sandbox.api.mastercard.com/merchantid/v1/merchantid?MerchantId=GOOGLE%20LTD%20ADWORDS%20%28CC%40GOOGLE.COM%29&Format=XML&Type=ExactMatch&Format=JSON&EmptyVal=";
const queryParams = extractQueryParams(href);

it("Should return a Map", function() {
Expand All @@ -12,10 +12,10 @@ describe("OAuth Signer", function() {

it("Query parameter keys should be sorted", function() {
const mapKeysArray = Array.from(queryParams.keys());
assert.deepEqual(mapKeysArray, ["Format", "MerchantId", "Type"]);
assert.deepEqual(mapKeysArray, ["EmptyVal", "Format", "MerchantId", "Type"]);
});

it("Query parameter values should be sorted. Values for parameters with the same name are added into a list.", function() {
it("Query parameter values should be sorted. Parameters should support duplicate keys", function() {
// Format
const valuesFormat = queryParams.get("Format");
const valuesFormatArray = Array.from(valuesFormat.values());
Expand All @@ -37,5 +37,48 @@ describe("OAuth Signer", function() {
assert.equal(valuesType.size, 1);
assert.deepEqual(valuesTypeArray, ["ExactMatch"]);
});

it("Query should support empty parameters", function() {
// Blank Value
const emptyVal = queryParams.get("EmptyVal");
const emptyValArray = Array.from(emptyVal.values());
assert.deepEqual(emptyValArray, [""])
});

it("Should support RFC Example", function () {
const rfcHref = "https://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b";
const rfcQueryParams = extractQueryParams(rfcHref);

assert.equal(4, rfcQueryParams.size);

assert.deepEqual(Array.from(rfcQueryParams.get("b5").values()), ["%3D%253D"]);
assert.deepEqual(Array.from(rfcQueryParams.get("a3").values()), ["a"]);
assert.deepEqual(Array.from(rfcQueryParams.get("c%40").values()), [""]);
assert.deepEqual(Array.from(rfcQueryParams.get("a2").values()), ["r%20b"]);
});

it("Should not encode params when URI created from string with non-encoded params", function() {
const nonEncodedUri = "https://example.com/request?colon=:&plus=+&comma=,";

const nonEncodedParams = extractQueryParams(nonEncodedUri);

assert.equal(3, nonEncodedParams.size);

assert.deepEqual([":"], Array.from(nonEncodedParams.get("colon").values()));
assert.deepEqual(["+"], Array.from(nonEncodedParams.get("plus").values()));
assert.deepEqual([","], Array.from(nonEncodedParams.get("comma").values()));
});

it("Should encode params when URI created from string with encoded params", function() {
const encodedUri = "https://example.com/request?colon=%3A&plus=%2B&comma=%2C";

const encodedParams = extractQueryParams(encodedUri);

assert.equal(3, encodedParams.size);

assert.deepEqual(["%3A"], Array.from(encodedParams.get("colon").values()));
assert.deepEqual(["%2B"], Array.from(encodedParams.get("plus").values()));
assert.deepEqual(["%2C"], Array.from(encodedParams.get("comma").values()));
});
});
});
25 changes: 23 additions & 2 deletions test/OAuth-getSignatureBaseString.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
const assert = require("assert");
const OAuth = require("../src/OAuth");
const getSignatureBaseString = require("../src/OAuth").getSignatureBaseString;
const toOauthParamString = require("../src/OAuth").toOAuthParamString;
const extractQueryParams = require("../src/OAuth").extractQueryParams;

describe("OAuth Signer", function() {
describe("#getSignatureBaseString()", function() {
it("Creates a correctly constructed and escaped signature base string", function() {
const httpMethod = "GET";
const baseUri = "https://sandbox.api.mastercard.com/merchantid/v1/merchantid";
const paramString = "Format=JSON&Format=XML&MerchantId=GOOGLE%20LTD%20ADWORDS%20%28CC%40GOOGLE.COM%29&Type=ExactMatch&oauth_consumer_key=aaa!aaa&oauth_nonce=uTeLPs6K&oauth_signature_method=RSA-SHA256&oauth_timestamp=1524771555&oauth_version=1.0";
const paramString = "Format=JSON&Format=XML&MerchantId=GOOGLE%20LTD%20ADWORDS%20CC%40GOOGLE.COM&Type=ExactMatch&oauth_consumer_key=aaa!aaa&oauth_nonce=uTeLPs6K&oauth_signature_method=RSA-SHA256&oauth_timestamp=1524771555&oauth_version=1.0";
const sbs = getSignatureBaseString(httpMethod, baseUri, paramString);

assert.deepEqual(sbs, "GET&https%3A%2F%2Fsandbox.api.mastercard.com%2Fmerchantid%2Fv1%2Fmerchantid&Format%3DJSON%26Format%3DXML%26MerchantId%3DGOOGLE%2520LTD%2520ADWORDS%2520%2528CC%2540GOOGLE.COM%2529%26Type%3DExactMatch%26oauth_consumer_key%3Daaa%21aaa%26oauth_nonce%3DuTeLPs6K%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1524771555%26oauth_version%3D1.0");
assert.deepEqual(sbs, "GET&https%3A%2F%2Fsandbox.api.mastercard.com%2Fmerchantid%2Fv1%2Fmerchantid&Format%3DJSON%26Format%3DXML%26MerchantId%3DGOOGLE%20LTD%20ADWORDS%20CC%40GOOGLE.COM%26Type%3DExactMatch%26oauth_consumer_key%3Daaa%21aaa%26oauth_nonce%3DuTeLPs6K%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1524771555%26oauth_version%3D1.0");
});

it("Should create expected base string when query params are encoded", function() {
const encodedUri = "https://example.com/?param=token1%3Atoken2";
const encodedParams = extractQueryParams(encodedUri);
const paramString = toOauthParamString(encodedParams, new Map());
const baseString = getSignatureBaseString("GET", "https://example.com", paramString);

assert.deepEqual("GET&https%3A%2F%2Fexample.com&param%3Dtoken1%3Atoken2", baseString);
});

it("Should create expected base string when query params are not encoded", function() {

const nonEncodedUri = "https://example.com/?param=token1:token2";
const nonEncodedParams = extractQueryParams(nonEncodedUri);
const paramString = toOauthParamString(nonEncodedParams, new Map());
const baseString = getSignatureBaseString("GET", "https://example.com", paramString);

assert.deepEqual("GET&https%3A%2F%2Fexample.com&param%3Dtoken1%3Atoken2", baseString);
});
});
});

0 comments on commit 3a8221e

Please sign in to comment.