I'm trying to add additional parameters to my call to Amazon Selling Partner API.
The calls I'm doing at the moment is point at Amazon Get Orders
I'd like to add two parameters: MarketplaceIds(required, array) and OrderStatuses(optional, string; e.g.: "Shipped").
My question is:
shall this go only as part of the request or it also impacts on the canonical request?
and how to implement it?
This is my code so far:
function GetOrders(){
var access_token = AccessToken();
//Time variables
var currentDate = new Date();
var isoDate = currentDate.toISOString();
var isoString = isoDate.replace(/-/g, "").replace(/:/g, "").replace(/(\.\d{3})/, "");
var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
//API variables
var end_point = 'https://sellingpartnerapi-eu.amazon.com';
//Credential variables
var aws_region = "eu-west-1";
var service = "execute-api";
var termination_string = "aws4_request";
//CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
//CanonicalRequest components:
var httpRequestMethod = 'GET';
var canonicalURI = '/orders/v0/orders';
var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
var canonicalheaders = 'host:' + "sellingpartnerapi-eu.amazon.com" + '\n' + 'user-agent:' + 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)' + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';//;user-agent & host; al comienzo
var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");
requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
//Building the canonical request
var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + "marketplaceId=A1PA6795UKMFR9" + '\n' + canonicalheaders + '\n\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
var canonical_signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_string);
canonical_request = canonical_signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
//CredentialScope = Date + AWS region + Service + Termination string;
//StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoString + '\n' + credential_scope + '\n' + canonical_request;
var kSecret = ACCESS_KEY;
var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
var signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
var options = {
'method': 'GET',
'headers': {
//'host': end_point,
'user-agent': 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)',
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
'muteHttpExceptions': true
}
var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
Logger.log(getOrders);
}
I already have a variable marketplaceId on my canonicalQueryString parameter but using it as it is I'm getting the following response:
{
"errors": [
{
"message": "Missing or invalid request parameters: [MarketplaceIds]",
"code": "InvalidInput"
}
]
}
Change
var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
to
var canonicalQueryString = '?MarketplaceIds=A1PA6795UKMFR9';
Related
I'm trying to send a request to Amazon Selling Partner API.
I reached a point where I now need to include the marketplaceIds in the request to get past through this error:
{
"errors": [
{
"message": "Missing or invalid request parameters: [MarketplaceIds]",
"code": "InvalidInput"
}
]
}
However the hash I'm calculating is not the same that the API suggests:
My Full Canonical Request
GET
/orders/v0/orders
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBIMBLORaPVaVyqdJnWfDF_zMyAccessToken
x-amz-date:2021-03-17T00:39:12.108Z
host;user-agent;x-amz-access-token;x-amz-date
9a34e897d8be214423d5360dbbab5239cb298102312f94bf7d222f91e4770be9
Amazon's Canonical Request
{
"errors": [
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'GET
/orders/v0/orders
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBIMBLORaPVaVyqdJnWfDF_zMyAccessToken
x-amz-date:2021-03-17T00:39:12.108Z
host;user-agent;x-amz-access-token;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
The canonical request is composed of the following values (guide here)
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
The component I'm missing is the payload_hash and it could be because of 2 reasons:
The content I'm hashing is wrong
The way I'm hashing is wrong (or both)
My code:
I want to get my orders from Amazon API (guide here) and the only mandatory value is marketplaceIds:
A list of MarketplaceId values. Used to select orders that were placed
in the specified marketplaces.
function GetOrders(){
var access_token = AccessToken();
//API variables
var end_point = 'https://sellingpartnerapi-eu.amazon.com';
//Credential variables
var aws_region = "eu-west-1";
var service = "execute-api";
var termination_string = "aws4_request";
//CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
//CanonicalRequest components:
var httpRequestMethod = 'GET';
var canonicalURI = '/orders/v0/orders';
var canonicalQueryString = '';
var canonicalheaders = 'host:' + "sellingpartnerapi-eu.amazon.com" + '\n' + 'user-agent:' + 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)' + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate + '\n';
var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
//Building an object that contains MarketplaceIds array
var request_parameters = {
MarketplaceIds: ["A1PA6795UKMFR9"]
};
//Hashing the stringify object
var requestPayloadHashed = digestToHex(JSON.stringify(request_parameters));
//Building the canonical request
var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;
var canonical_signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_string);
canonical_request = canonical_signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
//Once the canonical request is completed we continue with the call to the API
var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoString + '\n' + credential_scope + '\n' + canonical_request;
var kSecret = ACCESS_KEY;
var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
var signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
var options = {
'method': 'GET',
'headers': {
'payload': request_parameters,
'user-agent': 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)',
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
'muteHttpExceptions': true
}
var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
Logger.log(getOrders);
}
I tried to guide myself following this example in Python (the POST call) however I might be missing several things.
I'm trying to create a request to Amazon Selling Partner API following this guide.
The first part: Creating an access has already been taken care of here.
The documentation of the API for Orders can be found here.
I'm trying to invoke the GET /orders/v0/orders operation.
Connecting to the API
The only mandatory parameter for this operation is the MarketplaceIds based on the documentation.
In order to get the orders we need to sign our request. Here is my code so far:
function GetOrders(){
var access_token = AccessToken();
//Time variables
var currentDate = new Date();
var isoDate = currentDate.toISOString();
var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
//API variables
var end_point = 'https://sellingpartnerapi-eu.amazon.com';
//Credential variables
var aws_region = "eu-west-1";
var service = "execute-api";
var termination_string = "aws4_request";
//CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
//CanonicalRequest components:
var httpRequestMethod = 'GET';
var canonicalURI = '/orders/v0/orders';
var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
var canonicalheaders = 'host:' + canonicalURI + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW
//Building the canonical request
var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
var canonical_signature = Utilities.computeHmacSha256Signature(canonical_string, ACCESS_KEY);
var canonical_request = canonical_string + '\n' + canonical_signature;
canonical_request = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_request);//NEW
//CredentialScope = Date + AWS region + Service + Termination string;
//StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;
var kSecret = ACCESS_KEY;
var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
var kRegion = Utilities.computeHmacSha256Signature(Utilities.newBlob(aws_region).getBytes(), kDate);
var kService = Utilities.computeHmacSha256Signature(Utilities.newBlob(service).getBytes(), kRegion);
var kSigning = Utilities.computeHmacSha256Signature(Utilities.newBlob(termination_string).getBytes(), kService);
kSigning = kSigning.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
Logger.log('kSigning: ' + kSigning);
var signature = Utilities.computeHmacSha256Signature(kSigning, string_to_sign);
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
var options = {
'method': 'GET',
'payload': {
'end_point': end_point,
'path': canonicalURI,
'query_string': canonicalQueryString
//Path parameter not needed
},
'headers': {
//'host': end_point,
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'user-agent': 'GAS Script 1.0 (Javascript)',
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
}
var getOrders = UrlFetchApp.fetch(end_point, options);
Logger.log(getOrders);
}
PROBLEMS
When running the script I get the following error:
Exception: Request failed for https://sellingpartnerapi-eu.amazon.com returned code 403. Truncated server response: {
{
"errors": [
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'POST
/
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBSomeAccessToken
x-amz-date:2021-03-10T02:44:01.727Z
host;user-agent;x-amz-access-token;x-amz-date
cf22942946358a7530d8b72df6333e859644aaebb08a1cd825a6af65a8561111'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T024401Z
20210310/eu-west-1/execute-api/aws4_request
c4c1dcea7026765f52c5265296f9e1cb91b6618928debbc04a393bac89ce8493'
",
"code": "InvalidSignature"
}
]
}
QUESTIONS
I have a big doubt about what is "Payload"
For this part of the code:
var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;
We have to incorporate a hashed version of the payload request requestPayloadHashed.
It also mentions:
If the payload is empty, use an empty string as the input to the hash
function.
For now I have just create that variable with a blank value
var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW
But I'm not sure if I'm ommiting something important there.
UPDATE #1
After applying Tanaike recommendations I got the following message:
{
"errors": [
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'GET
/orders/v0/orders
marketplaceId=A1PA6795UKMFR9
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBISomeAccessToken
x-amz-date:2021-03-10T03:00:14.411Z
host;user-agent;x-amz-access-token;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T030014Z
20210310/eu-west-1/execute-api/aws4_request
f1bbc99190ca5a9e9e068ad6a0b2ef6a7aed4a1232095ef8f3d77ad62d0e66ac'
",
"code": "InvalidSignature"
}
]
}
UPDATE #2
There is this website that help us do some testing with these connections:
https://mws.amazonservices.de/scratchpad/index.html
By using it I believe I have validated the Access Key ID and Secret Key, however, it is asking for a SellerId which is new to me and is also not mentioned in the API docs.
I'm wondering where it could go.
UPDATE #3
I implemented most of Tanaike recommendations and also tried to align what I was sending to the API to the error message I was getting:
This is the last version of the script:
function GetOrders(){
var access_token = AccessToken();
//Time variables
var currentDate = new Date();
var isoDate = currentDate.toISOString();
var isoString = isoDate.replace(/-/g, "").replace(/:/g, "").replace(/(\.\d{3})/, "");
var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
Logger.log('isoDate: ' + isoDate)
//API variables
var end_point = 'https://sellingpartnerapi-eu.amazon.com';
//Credential variables
var aws_region = "eu-west-1";
var service = "execute-api";
var termination_string = "aws4_request";
//CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
//CanonicalRequest components:
var httpRequestMethod = 'GET';
var canonicalURI = '/orders/v0/orders';
var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
var canonicalheaders = 'host:' + "sellingpartnerapi-eu.amazon.com" + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
var signedheaders = 'host;x-amz-access-token;x-amz-date';//;user-agent
var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW
//Building the canonical request
var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + "marketplaceId=A1PA6795UKMFR9" + '\n' + canonicalheaders + '\n\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
Logger.log('canonical_string: ' + canonical_string)
var canonical_signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_string);
canonical_request = canonical_signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
Logger.log("canonical_request: " + canonical_request)
//CredentialScope = Date + AWS region + Service + Termination string;
//StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoString + '\n' + credential_scope + '\n' + canonical_request;
Logger.log("string_to_sign: " + string_to_sign);
var kSecret = ACCESS_KEY;
var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
Logger.log('kSigning: ' + kSigning);
var signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
Logger.log('signature: ' + signature)
var options = {
'method': 'GET',
'headers': {
//'host': end_point,
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
//'user-agent': 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)',
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
'muteHttpExceptions': true
}
var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
Logger.log(getOrders);
}
I'm now getting an error related entirely to my access:
{
"errors": [
{
"message": "Access to requested resource is denied.",
"code": "Unauthorized",
"details": ""
}
]
}
However this is probably due to the fact that when I registered the application (guide here) I used the IAM user instead of the IAM role.
And it says in the guide that:
Important. When registering your application, the IAM ARN that you
provide must be for the IAM entity to which you attached the IAM
policy from Step 3. Create an IAM policy. In this workflow, that IAM
entity is the IAM role from Step 4. Create an IAM role. If you
register your application using your IAM user, be sure that the IAM
policy is attached to it. Otherwise your calls to the Selling Partner
API will fail. We recommend registering your application using an IAM
role, as shown in this workflow, to help you better control access to
your AWS resources.
So I'm going to go ahead and fix that and see if I get the authorization I need.
Modification points:
In the case of UrlFetchApp, when payload is used, even when method is GET, it is requested as the POST request. It seems that this is the current specification.
user-agent cannot be changed for UrlFetchApp.
As a precondition, when your values for authorizing are correct values for requesting to the endpoint, your script can be modified by reflected above points as follows.
I thought that your error message might be due to the difference between the method of "GET" and "POST". At first, please test the following modification. When an error occurs, please show it.
Modified script:
From:
var options = {
'method': 'GET',
'payload': {
'end_point': end_point,
'path': canonicalURI,
'query_string': canonicalQueryString
//Path parameter not needed
},
'headers': {
//'host': end_point,
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'user-agent': 'GAS Script 1.0 (Javascript)',
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
}
var getOrders = UrlFetchApp.fetch(end_point, options);
To:
var options = {
'method': 'GET',
'headers': {
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
}
var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
Reference:
fetch(url, params) of Class UrlFetchApp
Added:
From Signing AWS requests with Signature Version 4, I modified your script. When I saw your script, I noticed that the byte array is included in the string values. I thought that this might be also one of reasons of your issue. So I modified your script. Could you please confirm it? And when I saw the official document, I confirmed that when the byte array is used for Utilities.computeHmacSha256Signature, converting the string value to byte array is the same with the samples rather than converting the byte array to string value.
function sample() {
const hex = bytes => bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
const digestToHex = data => hex(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, data));
const toBytes = data => Utilities.newBlob(data).getBytes();
const ACCESS_ID = "MyAccessKey";
const ACCESS_KEY = "MyAccessSecret";
var access_token = "access_token"; // AccessToken();
//Time variables
var currentDate = new Date();
var isoDate = currentDate.toISOString();
var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
//API variables
var end_point = 'https://sellingpartnerapi-eu.amazon.com';
//Credential variables
var aws_region = "eu-west-1";
var service = "execute-api";
var termination_string = "aws4_request";
// 1. Create string to sign.
var httpRequestMethod = 'GET';
var canonicalURI = '/orders/v0/orders';
var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
var canonicalheaders = 'host:' + canonicalURI + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");
const canonical_request = digestToHex(canonicalRequest);
var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;
// 2. Create derived signing key.
var kSecret = ACCESS_KEY;
var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
// 3. Create signature.
const signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
// 4. Request.
var options = {
'method': 'GET',
'headers': {
'x-amz-access-token': access_token,
'x-amz-date': isoDate,
'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
},
}
var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
Logger.log(getOrders);
}
Note:
Unfortunately, I cannot test above modified script. So when you tested it and an error occurs, please confirm your values for authorizating again. And please show the error message.
In this modification, it supposes that your values for authorizating are the correct values. Please be careful this.
About const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");, when above script occurs an error, please test const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",""].join("\n");.
Reference:
Signing AWS requests with Signature Version 4
I am using Claudia-api-builder to create and deploy the.
https://github.com/claudiajs/example-projects/tree/master/custom-authorizers
My AWS custom authorizer looks like this :
let jwtDecode = require('jwt-decode');
var generatePolicy = function (authToken, methodArn) {
'use strict';
var tmp = methodArn.split(':'),
apiGatewayArnTmp = tmp[5].split('/'),
awsAccountId = tmp[4],
region = tmp[3],
restApiId = apiGatewayArnTmp[0],
stage = apiGatewayArnTmp[1];
let group = jwtDecode(authToken)["cognito:groups"];
if (group[0] === 'Admin') {
return {
'principalId': authToken.split('-')[0],
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Action': [
'execute-api:Invoke'
],
'Resource': [
'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/citizens',
'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/citizens/{citizenId}/personal-details'
]
}]
}
};
}
exports.auth = function testAuth(event, context, callback) {
'use strict';
console.log('got event', event);
/*
* {
* "type":"TOKEN",
* "authorizationToken":"<Incoming bearer token>",
* "methodArn":"arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>"
* }
*/
if (event && event.authorizationToken && event.methodArn) {
callback(null, generatePolicy(event.authorizationToken, event.methodArn));
} else {
callback('Unauthorized');
}
};
The first API from the resource is working fine, but when I am calling the 2nd API i:e :
'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/citizens/{citizenId}/personal-details'
It is giving me 403 Forbidden with :
{
"Message": "User is not authorized to access this resource"
}
In my case, Authorization Caching is also disabled
Any solution for this issue?
The resource should not be the path of the API Gateway method.
In fact it should be the Arn of the resource. You can get this from the AWS console by performing the following:
Open API Gateway
Select your API Gateway
Click the Resources option
Find your resource (This will be the GET method underneath citizens/{citizenId}/personal-details). Click on it.
There will be an Arn available for you.
When using path based parameters any parameter is replaced by an * so this would become the below.
'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/citizens/*/personal-details'
The solution for me was to make a wildcard resource instead of just passing in event.methodArn into the generateAllow()
a wild card resource looks like this, then you pass it into your generateAllow()
var tmp = event.methodArn.split(':');
var apiGatewayArnTmp = tmp[5].split('/');
var resource = tmp[0] + ':' + tmp[1] + ':' + tmp[2] + ':' + tmp[3] + ':' + tmp[4] + ':' + apiGatewayArnTmp[0] + '/*/*';
generatePolicy("1", 'Allow', resource)
a more in-depth explanation can be found here https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-lambda-authorization-errors/
I looked at all the responses on post AWS S3 - How to fix 'The request signature we calculated does not match the signature' error? but I still have issues with my Python API sign 4 error. It still returns me with error "The request signature we calculated does not match with the signature you provided".
Here are the details:
While I am able to sign successfully: https://abcdef.mycompany.com/issues/P1230 but can't get https://abcdef.mycompany.com/issues?q=assignedFolder%3A(abc123) signed, and keep getting error
import json, hashlib, hmac, datetime, uuid, sys, urllib3, boto3, time, urllib.parse, os, requests
def get_extract(docid):
method = 'GET'
service = 'myservice'
host = 'abcdef.mycompany.com'
region = 'us-east-1'
endpoint = 'https://abcdef.mycompany.com'
request_parameters = '?q=assignedFolder%3A(abc123)' ## docid is abc123: hard-coding for example purpose
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
access_key = 'mykey123'
secret_key = 'mykey123'
if access_key is None or secret_key is None:
print('No access key is available.')
sys.exit()
t = datetime.datetime.utcnow()
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
canonical_uri = '/issues'
canonical_querystring = request_parameters
canonical_headers = 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n'
signed_headers = 'host;x-amz-date'
payload_hash = hashlib.sha256(('').encode('utf-8')).hexdigest()
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
algorithm = 'AWS4-HMAC-SHA256'
credential_scope = datestamp + '/' + region + '/' + service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
signing_key = getSignatureKey(secret_key, datestamp, region, service)
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
headers = {'x-amz-date':amzdate,'Host': host,'Authorization':authorization_header}
request_url = endpoint + canonical_uri + canonical_querystring
http = urllib3.PoolManager()
res = http.request('GET',request_url,headers=headers)
print(len(res.data.decode('utf-8')))
print(res.data.decode('utf-8'))
Error:
<InvalidSignatureException>
<Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message>
</InvalidSignatureException>
Need some help with this request, have been struggling to get this working for last 4-5 days. Any help is appreciated
I'm trying to calculate the same situation as shown here
Following is a snippet of coffeescirpt that uses CryptoJS. The EncodedPolicy is correct according to the example.
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, "AWS4" + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
hmac.update("20130806")
hmac.update("us-east-1")
hmac.update("s3")
hmac.update("aws4_request")
hmac.update(EncodedPolicy)
console.log( hmac.finalize().toString() )
The output is "71df5c44d375c21856d92de15941bc0deaf1a845e197c7f46834663256092f56", but it is expected to be "21496b44de44ccb73d545f1a995c68214c9cb0d41c45a17a5daeec0b1a6db047".
UPDATE:
getSignatureKey = (key, dateStamp, regionName, serviceName, EncodedPolicy) ->
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, "AWS4" + key)
hmac.update( dateStamp )
dateKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, dateKey )
hmac.update( regionName )
regionKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, regionKey )
hmac.update( serviceName )
serviceKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, serviceKey )
hmac.update( "aws4_request" )
requestKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, requestKey )
hmac.update( EncodedPolicy )
return hmac.finalize()
Is correct at least up until requestKey. Still getting response from amazon saying signature they calculated does not match the one I provided.
Update2:
Below is all the relevant code.
SERVER CODE
#s3 = {}
#s3.key = "AWS-KEY"
#s3.privKey = "AWS-PRIV-KEY"
#s3.region = 'us-east-1'
#s3.service = 's3'
#s3.alg = 'AWS4-HMAC-SHA256'
#s3.bucket = 'bucket-name'
#s3.acl = 'public-read'
#s3.generate_policy = (path,date) ->
policy = {
"conditions": [
{'bucket': s3.bucket}
['starts-with', '$key', path]
{'acl': s3.acl}
{'success_action_status': '200'}
["content-length-range", 0, 100000]
['starts-with', '$Content-Type', 'image/jpeg']
]
"expiration": date.format("YYYY-MM-DDTHH:mm:ss.00\\Z")
}
return policy
getSignatureKey = (key, dateStamp, regionName, serviceName, stringToSign) ->
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, "AWS4" + key)
hmac.update( dateStamp )
dateKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, dateKey )
hmac.update( regionName )
regionKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, regionKey )
hmac.update( serviceName )
serviceKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, serviceKey )
hmac.update( "aws4_request" )
requestKey = hmac.finalize()
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, requestKey )
hmac.update( stringToSign )
return hmac.finalize()
stringToSign = (alg, date, creds, hashedReq) ->
return alg + '\n' + date.format( "YYYYMMDDTHHMMSS\\Z" ) + '\n' + creds + '\n' + hashedReq
canonReq = (date, creds, alg) ->
headers = "host: " + s3.bucket + ".s3.amazonaws.com" + '\n'
headers += "Content-type: image/jpeg; charset=utf-8" + '\n'
headers += "x-amz-date: " + date.format("YYYYMMDDTHHmmss\\Z") + '\n'
signedHeaders = "content-type;host;x-amz-date"
canonReq = "POST http://" + s3.bucket + ".s3.amazonaws.com/ HTTP/1.1" + '\n'
canonReq += "\\" + '\n'
canonReq += '\n'
canonReq += headers
canonReq += '\n'
canonReq += signedHeaders + '\n'
canonReq += CryptoJS.SHA256( signedHeaders )
#s3.generate_credentials = (path, date) ->
policy = new Buffer(JSON.stringify(s3.generate_policy(path, date )).replace('\n', "")).toString('base64')
creds = s3.key + '/' + date.format("YYYYMMDD") + '/' + s3.region + '/' + s3.service + '/' + 'aws4_request'
hashedCanonicalReq = CryptoJS.SHA256( canonReq(date) )
stringToSign = stringToSign( s3.alg, date, creds, hashedCanonicalReq )
sig = getSignatureKey(s3.privKey,date.format('YYYYMMDD'),s3.region,s3.service, stringToSign)
credentials = {
AWSkey: s3.key
key: path
policy: policy
bucket: s3.bucket
signature: sig.toString()
acl: s3.acl
date: date.format("YYYYMMDDTHHmmss\\Z")
alg: s3.alg
creds: creds
}
return credentials
#Meteor.methods
get_s3_credentials: () ->
if !Meteor.user()
return
path = "test"
date = moment().utc().add('minutes', 5)
credentials = s3.generate_credentials(path, date)
return credentials
CLIENT CODE
uploadFile = function() {
Meteor.call( 'get_s3_credentials', function(err, res) {
if( res ){
var file = document.getElementById('uploadFiles').files[0];
var fd = new FormData();
console.log( res );
fd.append('key',res.key);
fd.append('AWSAccessKeyId', res.AWSkey)
fd.append('acl',res.acl);
fd.append('signature',res.signature);
fd.append('policy', res.policy);
fd.append('success_action_status',200);
fd.append('Content-Type',"image/jpeg");
fd.append("file",file);
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://' + res.bucket + '.s3.amazonaws.com', true);
xhr.send(fd);
}else{
console.log( err );
}
});
}
Update:
Was able to get it working using aws-sdk (signed URL) and the client side code from http://www.recursiverobot.com/post/75281512146/demos-using-aws-with-node-js-and-an-angularjs-frontend#s3.