I am trying to send a PUT request to an Amazon compatible storage, and I keep getting the following error:
"<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your Secret Access Key and signing method. For more information, see REST Authentication and SOAP Authentication for details.</Message><RequestId>0af9f985:155f11613d1:1732:13</RequestId></Error>"
Can someone please tell me how to generate the correct signature using the Secret Access Key and other parameters, in Javascript. I know that I am using the correct credentials, because all other operations supported by Amazon S3 are working, using the Amazon S3 SDK. I went through this link:
http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html, but I couldn't find what the method is expecting for each parameters.
Adding the code, that I have written. This is pretty much the same as you see in the Javascript SDK, only the method canonicalizedResource, is more simpler:
private stringToSign(request: any) {
var parts = [];
parts.push(request.headers['method']);
parts.push(request.headers['Content-MD5'] || '');
parts.push(request.headers['Content-Type'] || '');
parts.push(request.headers['presigned-expires'] || '');
var headers = this.canonicalizedAmzHeaders(request);
if (headers) parts.push(headers);
parts.push(this.canonicalizedResource(request));
return parts.join('\n');;
}
private canonicalizedAmzHeaders(request: any) {
var amzHeaders = [];
AWS.util.each(request.headers, function(name) {
if (name.match(/^x-amz-/i))
amzHeaders.push(name);
});
amzHeaders.sort(function(a, b) {
return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
});
var parts = [];
AWS.util.each(amzHeaders, function(name) {
parts.push(amzHeaders[name].toLowerCase() + ':' + request.headers[amzHeaders[name]]);
});
return parts.join('\n');
}
private canonicalizedResource(request) {
return request.path || '/';
}
I call the method like:
var signature = this.sign(AWS.config.credentials.secretAccessKey, this.stringToSign(request));
Can you make sure your system time is up to date? I have had issues when my system time is out of sync. AWS rejects those API calls.
Related
I want to make a website by implementing the use of sparql in vue js. The scenario I want is to create a special place to write Sparql Query and then execute it with vue.js. Is all that possible? if possible how should i start it? if not possible, is there any other alternative than using vue.js? Please help.
I am not a JS pro by any means. Anyway, for a similar problem, I used axios for the HTTP request.
This worked fine for my use case. Anyway, you will find a precise description of the JSON format at https
var params = new URLSearchParams();
// queryArtistsByNameTpl contains the actual SPARQL query,
// with $artistName as a placeholder
let similarGroupsQuery = queryArtistsByNameTpl.replace("$artistName", this.artistName);
params.append('query', similarGroupsQuery);
let getArtistsHandler = sparqlResponseHandler(this, 'artistList');
axios.post(sparqlEndPoint, params).then(getArtistsHandler);
function sparqlResponseHandler(currentObj, currList) {
return function(response) {
const rows = response.data.results.bindings;
currentObj[currList] = [];
if (rows.length > 0) {
rows.forEach(function(item, index) {
var record = {};
for (var prop in item) {
if (item.hasOwnProperty(prop)) {
record[prop] = item[prop].value;
}
}
currentObj[currList].push(record);
})
} else {
console.log("no data from SPARQL end point");
}
}
}
The JSON resturned by SPARQl endpoints is specified in https://www.w3.org/TR/sparql11-results-json/ , which is rather short and very understandable.
The code can certainly be improved by someone more knowledgeable then me, but it was fine for the tech demo we used it for.
Hi trying to get a signUrl from S3, for some reason making the call with % isn't parse correctly by my code. I get a 404 not found.
This is the ajax request:
https://stage.musicmarkers.com/website/api/admin/get-signed-url/thumbnail/magazine%2F2BE.gif/image%2Fgif
My API:
app.get('/website/api/admin/get-signed-url/thumbnail/:key/:type', auth.getMember, directives.noCache, getThumbnailSingedUrl);
My function:
function getThumbnailSingedUrl(req, res) {
if (!isAdmin(req, res)) {
return;
}
var key = req.params.key || '';
var type = req.params.type || '';
ThumbnailBucketFacade.getSignedUrl(
'putObject',
key,
type,
function onGotSignedUrl(error, result) {
if (error) {
RestResponse.serverError(res, error);
} else {
RestResponse.ok(res, result);
}
}
);
}
Making the call in a dev environment works.
Making the call without % environment works.
Same code exactly in a different project works.
Any ideas?
I believe what you have is encoded URI. So you need to decode it before using it:
const key = req.params.key && decodeURIComponent(req.params.key) || '';
const type = req.params.type && decodeURIComponent(req.params.type) || '';
More on decoreURIComponent here.
This is also backward compatible, so you don't have to worry that a plain string will get mangled.
So eventually it was a configuration issue at 'nginx', the 'nginx' router
was configured to add '/' at the end of the site name. That made all the
other slashes scrambled and ultimately to the call not to be recognise.
Thank's for those helping.
I am working to manage some Google Drive files with Google Apps Script. One piece of this project is reviewing properties on files, so I am using the Drive API rather than DriveApp. Additionally, Google Apps Script currently has access to the Drive REST API v2 instead of v3.
I've successfully set a property (id) and am able to pull the files with the property set.
console = Logger;
function Files (folderId) {
var optionalArgs,response
;
optionalArgs = {
q:'"'+folderId+'" in parents',
spaces:"drive"
}
do {
response = Drive.Files.list(optionalArgs);
response.items.forEach(function (file) {
console.log(file.properties);
var id = file.properties.find(function (property) {
return property.key === 'id';
});
this[id.value] = file;
},this);
} while (optionalArgs.pageToken = response.nextPageToken);
}
When running this function, I am able to see the file properties in the log
[{visibility=PUBLIC, kind=drive#property, etag="3GjDSTzy841RsmcBo4Ir-DLlp20/HGzJl78t8I2IehiAlaGXTkm2-C4", value=9e18766b-1cc9-4c1b-8003-b241f43db304, key=id}]
but get
TypeError :Cannot call method "find" of undefined.
I am unable to iterate through this resulting array. Using JSON.parse on it trys to convert it to an object, which is problematic for files with multiple properties. Using JSON.parse(JSON.stringify()) on it results in
SyntaxError: Unexpected token: u
which I understand is resulting from the value being undefined. I could work with that, if my log wasn't telling me otherwise.
"nextPageToken(string) The page token for the next page of files. This will be absent if the end of the files list has been reached."
Quote from: https://developers.google.com/drive/v2/reference/files/list#examples
so this line assigns an undefined value to optionalArgs.pageToken
while (optionalArgs.pageToken = response.nextPageToken);
and does not terminate the loop. A better way is to assign the value of optionalArgs.pageToken inside the loop and break the loop if its value is undefined like this:
console = Logger;
function Files (folderId) {
var optionalArgs,response
;
optionalArgs = {
q:'"'+folderId+'" in parents',
spaces:"drive"
}
do {
response = Drive.Files.list(optionalArgs);
response.items.forEach(function (file) {
console.log(file.properties);
var id = file.properties.find(function (property) {
return property.key === 'id';
});
this[id.value] = file;
},this);
// Assign the value inside the loop
optionalArgs.pageToken = response.nextPageToken
} while (optionalArgs.pageToken != undefined) // check to see if it undefined here and break the loop.
}
You can use v3 in Google Apps Script by selecting the version in the Advance Google Service option:
Just note that you have to follow how to request in Google Drive v3 but you can still use v2 because all of the example is still in v2.
Regarding your error, using their code in the sample code for Listing folders.
function listRootFolders() {
var query = '"root" in parents and trashed = false and ' +
'mimeType = "application/vnd.google-apps.folder"';
var folders, pageToken;
do {
folders = Drive.Files.list({
q: query,
maxResults: 100,
pageToken: pageToken
});
if (folders.items && folders.items.length > 0) {
for (var i = 0; i < folders.items.length; i++) {
var folder = folders.items[i];
Logger.log('%s (ID: %s)', folder.title, folder.id);
}
} else {
Logger.log('No folders found.');
}
pageToken = folders.nextPageToken;
} while (pageToken);
}
You can also read more code implementation of the above documentation.
Hope this helps.
Is there a way to send signed version of secret key to AWS JS SDK, instead of sending secret Key adn access key in plain text. I am using Salesforce and would like to create a signature in Salesforce and send it to Javascript (or VF page) the signature which can then be used in AWS SDK callouts, instead of giving the secret key on the client side.
It's good that you see the obvious problem with embedding your credentials into the client.
Amazon Security Token Service (STS) is one solution to this issue.
Your application back-end systems send a request for temporary credentials to STS. These credentials allow the entity possessing them to perform only actions authorized by the token, which is a third attribute added to the usual (access key id, access key secret) tuple, and the authority is valid only until the token expires. Note that the token doesn't use your access key and secret, it actually comes with its own access key and secret, all of which are short-lived and "disposable."
In a sense, this is fairly well described by the phrase you used, "send signed version of secret key." It's a disposable delegation of authority, in a sense.
See also http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html.
I am finally able to make a call to STS from Salesforce using Apex Code, all thanks to Michael. Which then returns the Session Token, Temp Access Key and Temp Secret Key. Following is the gist of the code that I used to make a call to AWS STS.
public void testGetSessionToken() {
method = HttpMethod.XGET;
createSigningKey(AWS_SECRET_KEY_ID ) ;
service = 'sts';
resource = '/';
// Create all of the bits and pieces using all utility functions above
HttpRequest request = new HttpRequest();
request.setMethod(method.name().removeStart('X'));
setQueryParam('Version', '2011-06-15');
setQueryParam('Action', 'AssumeRole');
setQUeryParam('RoleArn', 'arn:aws:iam::669117241099:role/TestRole');
setQueryParam('RoleSessionName', 'Swai');
String formatedDate = requestTime.formatGmt('yyyyMMdd') + 'T' + '03' + requestTime.formatGmt('mmss') + 'Z';
setHeader('X-Amz-Date', formatedDate );
setHeader('host', endpoint.getHost());
finalEndpoint = 'https://sts.amazonaws.com' + resource + '?Version=2011-06-15&Action=AssumeRole&RoleArn=arn:aws:iam::559117241080:role/TestRole&RoleSessionName=Swai';
request.setEndpoint(finalEndpoint);
String[] headerKeys = new String[0];
String authHeader = createAuthorizationHeader(); //This is the method that gets the signature)
request.setHeader( 'host', 'sts.amazonaws.com');
request.setHeader( 'X-Amz-Date', formatedDate );
request.setHeader( 'Authorization', authHeader);
HttpResponse response = new Http().send(request);
// Parse XML to get SessionToken
XMLStreamReader xmlrdr = response.getXmlStreamReader();
while (xmlrdr.hasNext()) {
if (xmlrdr.getEventType() == XMLTag.START_ELEMENT ) {
if (xmlrdr.getLocalName() == 'SessionToken') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageSessionToken = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
} else if (xmlrdr.getLocalName() == 'AccessKeyId') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageTempAccessKeyId = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
} else if (xmlrdr.getLocalName() == 'SecretAccessKey') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageTempSecretAccessKey = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
}
else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
}
Thanks a lot Michael for being so helpful and for your prompt responses. I am posting the solution that I have here so that it can benefit others, and hopefully not be stuck for this long.
I am having what I hope is an easy to solve problem with the Soundcloud API using JavaScript:
unauthorized, the following code works fine:
var group = 'https://soundcloud.com/groups/chilled';
SC.initialize({
client_id: 'MY_CLIENT_ID',
redirect_uri: 'http://localhost:49957/tn/callback.html'
});
// Resolve works fine and gives number ID of group
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
after I authorise a user:
SC.connect().then(function () {
return SC.get('/me');
}).then(function (me) {
authUser = me.id
});
// Resolve no longer works and i get 401 unauthorised
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
can anyone help me to understand what I am doing wrong - I can't seem to find an example to follow anywhere. Driving me potty!
Many thanks in advance,
James
For anyone else out there facing the same issues, I have answered my own question:
Firstly, I was not properly logged in due to an error on Soundcloud's sample code in their callback.html used to return to the site after oAuth client side login flow. In Soundcloud's sample callback.html, the code:
<body onload="window.opener.setTimeout(window.opener.SC.connectCallback, 1)">
must be altered to:
<body onload="window.setTimeout(window.opener.SC.connectCallback, 1)">
This allows the popup to close properly and completes the login flow if the application settings are correctly configured to the same domain (localhost or production, but not a mix of the two).
Further to this callback, i have added the following code:
var params = getSearchParameters();
window.opener.oAuthToken = params.code;
function getSearchParameters() {
var prmstr = window.location.search.substr(1);
return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}
function transformToAssocArray(prmstr) {
var params = {};
var prmarr = prmstr.split("&");
for (var i = 0; i < prmarr.length; i++) {
var tmparr = prmarr[i].split("=");
params[tmparr[0]] = tmparr[1];
}
return params;
}
In my subsequent pages, I can get any data as a sub-resource of the '/me/...' endpoint now, but anything I used to be able to interrogate via public access is still in accessible. Since I can now iterate through the logged in users groups, I no longer have to resolve the name of a group via the public resource '/resolve/', so my issue is not solved, but avoided in my current development.