I am trying to use the REST interface of AWS S3 for a web service which stores and retrieves file pieces in a simmilar way git does (via hash and a directory system based off of it). I am using the RestSharp client library to make these calls, as the AWS SDK is out of the question (the web service is actually required to work with AWS-like stores such as Hitachi HDS) and in general, as more storage platforms would be added, it was felt a standardised method would be best to perform over-the-wire communication.
The problem is that RestSharp may be adding some extra payload, as S3 is crying about having more than one data element to save.
The following code is the core storage logic, and it should be noted I am using Ninject to handle any dependancies.
public bool PutBytesInStore(string piecehash, byte[] data)
{
string method = "POST";
string hash;
using (var sha1 = new SHA1CryptoServiceProvider())
{
hash = Convert.ToBase64String(sha1.ComputeHash(data));
}
string contentType = "application/octet-stream";
string date = new DateTime().ToString("{EEE, dd MMM yyyy HH:mm:ss}");
string file = string.Format("pieces/{0}/{1}/{2}", piecehash.Substring(0, 2), piecehash.Substring(0, 6),
piecehash);
//Creating signature
var sBuilder = new StringBuilder();
sBuilder.Append(method).Append("\n");
sBuilder.Append(contentType).Append("\n");
sBuilder.Append(date).Append("\n");
sBuilder.Append(hash).Append("\n");
sBuilder.Append(file).Append("\n");
var signature = Convert.ToBase64String(new HMACSHA1(Encoding.UTF8.GetBytes(_password)).ComputeHash(Encoding.UTF8.GetBytes(sBuilder.ToString())));
_request.Method = Method.POST;
_request.AddFile(piecehash, data, piecehash);
_request.AddHeader("Date", date);
_request.AddHeader("Content-MD5", hash);
_request.AddHeader("Authorisation", string.Format("AWS {0}:{1}", _identifier, signature));
var response = _client.Execute(_request);
//Check responses for any errors
var xmlResponse = XDocument.Parse(response.Content);
switch (response.StatusCode)
{
case HttpStatusCode.Forbidden:
ErrorCodeHandler(xmlResponse);
break;
case HttpStatusCode.BadRequest:
ErrorCodeHandler(xmlResponse);
break;
case HttpStatusCode.Accepted:
return true;
default:
return false;
}
return false;
}
The problem lies with the response sent, which reads;
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>InvalidArgument</Code>
<Message>POST requires exactly one file upload per request.</Message>
<ArgumentValue>0</ArgumentValue>
<ArgumentName>file</ArgumentName>
<RequestId>SomeRequest</RequestId
<HostId>SomeID</HostId>
</Error>
The AWS API seems pretty sparse on this message, and I cant quite seem to be able to figure out why the RestSharp client would be adding more than two files to the payload.
Any help is greatly appreciated.
Its because of webkitboundary. pleas try your stuff on postman - webkitboundary is very important on uploading.
Related
I am currently using Forge Webhooks API to handle different events that might occur on a project. Everything works fine, except the payload signature check.
The reason why I want to check the payload is because the callback will end up on my API and I want to reject all requests that do not come from Forge's webhook service.
Steps I followed:
Add (register) secret key (token) on Forge. API Reference
Trigger an event that will eventually call my API for handling it.
Validating signature header. Followed this tutorial.
PROBLEM!!! My computedSignature is different from the signature received from Forge.
My C# code looks like this:
private const string SHA_HASH = "sha1hash";
var secretKeyBytes = Encoding.UTF8.GetBytes(ForgeAuthConfiguration.AntiForgeryToken);
using var hmac = new HMACSHA1(secretKeyBytes);
var computedHash = hmac.ComputeHash(request.Body.ReadAsBytes());
var computedSignature = $"{SHA_HASH}={computedHash.Aggregate("", (s, e) => s + $"{e:x2}", s => s)}";
For one example, Forge's request has this signature header: sha1hash=303c4e7d2a94ccfa559560dc2421cee8496d2d83
My C# code computes this signature: sha1hash=3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5
The same AntiForgeryToken was sent to Forge at step 1
Ok, I thought my C# code is broken, then I tried this online HMAC generator and for the given input, result is: 3bb8d41c3c1cb6c9652745f5996b4e7f832ca8d5 (same as C#)
Ok, maybe the online generator is broken, I tried their own code in node js and this is the result:
I have 3 ways of encrypting the SAME body using the SAME key and I get the SAME result every time. BUT those results are DIFFERENT from the signature provided by Forge, resulting in failing the check and rejecting a valid request...
Does anyone know what is happening with that signature?
Why is it different from my result if I follow their tutorial?
How are you validating your requests?
The code below is working at my side. Could you give it a try if it helps?
[HttpPost]
[Route("api/forge/callback/webhookbysig")]
public async Task<IActionResult> WebhookCallbackBySig()
{
try
{
var encoding = Encoding.UTF8;
byte[] rawBody = null;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
rawBody = encoding.GetBytes(reader.ReadToEnd());
}
var requestSignature = Request.Headers["x-adsk-signature"];
string myPrivateToken = Credentials.GetAppSetting("FORGE_WEBHOOK_PRIVATE_TOKEN");
var tokenBytes = encoding.GetBytes(myPrivateToken);
var hmacSha1 = new HMACSHA1(tokenBytes);
byte[] hashmessage = hmacSha1.ComputeHash(rawBody);
var calculatedSignature = "sha1hash=" + BitConverter.ToString(hashmessage).ToLower().Replace("-", "");
if (requestSignature.Equals(calculatedSignature))
{
System.Diagnostics.Debug.Write("Same!");
}
else
{
System.Diagnostics.Debug.Write("diff!");
}
}
catch(Exception ex)
{
}
// ALWAYS return ok (200)
return Ok();
}
If this does not help, please share with your webhook ID (better send email at forge.help#autodesk.com). We will ask engineer team to check it.
Problem
When I call "Request Sync" on the Google HomeGraph API I receive a "403 Forbidden" response.
Background
I'm writing a Smart Home Action, and have successfully implemented SYNC, QUERY and EXECUTE. Testing on my mobile I can see and interact with devices okay. I'm now trying to implement Request Sync, but can't appear to interact with the API. I am making what seems to be successful requests for an Access Token. The token always begins with "ya29.c." which in my naïve understanding suggests an empty header and payload (trying it on https://jwt.io). However, when testing it at https://accounts.google.com/o/oauth2/tokeninfo?access_token= it appears valid, showing both my service account unique ID and the scope I intended. When I make a call to the API, either manually posting the data, or via Google's own code, it gives me a blunt 403 error. I do not know where I can get any more information on this error other than the exception objects. I'm new to GCP and couldn't find any sort of log. Given I've tried different methods and all return a 403 I'm inclined to suspect the issue is more a account or credential-related than the code, but can't be certain.
API Key
(I'm no longer able to reproduce any errors relating to API keys being missing or invalid).
Although the documentation doesn't show it, I've seen some people use an API key. When I don't include the API key with a p12 certificate, or include an incorrect one it errors (either with API key missing, or API key invalid accordingly). I have created an unrestricted API key in IAM, and am using that. I can't appear to explicitly relate this to HomeGraph API, but it says that it can call any API.
Code
This example fetches an access token, then tries to call the API via POST with and without the API key. It then tries to authenticate and call the API via the Google library code. Each fails with a 403.
using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.HomeGraphService.v1;
using Google.Apis.HomeGraphService.v1.Data;
using Google.Apis.Services;
using Lambda.Core.Constants;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using static Google.Apis.HomeGraphService.v1.DevicesResource;
public class Example
{
public void RequestSync()
{
const string UrlWithoutKey = #"https://homegraph.googleapis.com/v1/devices:requestSync";
const string UrlWithKey = #"https://homegraph.googleapis.com/v1/devices:requestSync?key=" + OAuthConstants.GoogleApiKey;
string accessToken = this.GetAccessToken();
// Manual Attempt 1
try
{
string response = this.CallRequestSyncApiManually(accessToken, UrlWithoutKey);
}
catch (WebException ex)
{
// Receive 403, Forbidden
string msg = ex.Message;
}
// Manual Attempt 2
try
{
string response = this.CallRequestSyncApiManually(accessToken, UrlWithKey);
}
catch (WebException ex)
{
// Receive 403, Forbidden
string msg = ex.Message;
}
// SDK Attempt
try
{
this.CallRequestSyncApiWithSdk();
}
catch (GoogleApiException ex)
{
// Google.Apis.Requests.RequestError
// The caller does not have permission[403]
// Errors[Message[The caller does not have permission] Location[- ] Reason[forbidden] Domain[global]]
// at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response) in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 243
// at Google.Apis.Requests.ClientServiceRequest`1.Execute() in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 167
string msg = ex.Message;
}
}
private string GetAccessToken()
{
string defaultScope = "https://www.googleapis.com/auth/homegraph";
string serviceAccount = OAuthConstants.GoogleServiceAccountEmail; // "??????#??????.iam.gserviceaccount.com"
string certificateFile = OAuthConstants.CertificateFileName; // "??????.p12"
var oAuth2 = new GoogleOAuth2(defaultScope, serviceAccount, certificateFile); // As per https://stackoverflow.com/questions/26478694/how-to-produce-jwt-with-google-oauth2-compatible-algorithm-rsa-sha-256-using-sys
bool status = oAuth2.RequestAccessTokenAsync().Result;
// This access token at a glance appears invalid due to an empty header and payload,
// But verifies ok when tested here: https://accounts.google.com/o/oauth2/tokeninfo?access_token=
return oAuth2.AccessToken;
}
private string CallRequestSyncApiManually(string accessToken, string url)
{
string apiRequestBody = #"{""agentUserId"": """ + OAuthConstants.TestAgentUserId + #"""}";
var client = new HttpClient();
var request = (HttpWebRequest)WebRequest.Create(url);
var data = Encoding.ASCII.GetBytes(apiRequestBody);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/json";
request.ContentLength = data.Length;
request.Headers.Add("Authorization", $"Bearer {accessToken}");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
return responseString;
}
private void CallRequestSyncApiWithSdk()
{
var certificate = new X509Certificate2(OAuthConstants.CertificateFileName, OAuthConstants.CertSecret, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(OAuthConstants.GoogleServiceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/homegraph" },
}.FromCertificate(certificate));
var service = new HomeGraphServiceService(
new BaseClientService.Initializer()
{
// Complains if API key is not provided, even though we're using a certificate from a Service Account
ApiKey = OAuthConstants.GoogleApiKey,
HttpClientInitializer = credential,
ApplicationName = OAuthConstants.ApplicationName,
});
var request = new RequestSyncRequest(
service,
new RequestSyncDevicesRequest
{
AgentUserId = OAuthConstants.TestAgentUserId
});
request.Execute();
}
}
Account Configuration
Account screenshots. (I'm not allowed to post images yet, so they're links)
HomeGraph is enabled
My API Key is unrestricted
My Service Account has Owner & Service Account Token Creator enabled
Updates
I have tried skipping manually obtaining the access token, as per Devunwired's suggestion. Whilst this does eliminate the error I was getting from not providing the API key, I still end up with the 403. My reasoning for doing the access token part manually was part of debugging a 403 I was getting with the API call. That way I could at least see part of the process working. I'm happy to use the library version for the solution as the access token doesn't appear to be the issue.
public void GoogleLibraryJsonCredentialExample()
{
try
{
GoogleCredential credential;
using (var stream = new FileStream(OAuthConstants.JsonCredentialsFileName, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(new[] { OAuthConstants.GoogleScope });
}
var service = new HomeGraphServiceService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = OAuthConstants.ApplicationName,
});
var request = new RequestSyncRequest(
service,
new RequestSyncDevicesRequest
{
AgentUserId = OAuthConstants.TestAgentUserId
});
request.Execute();
}
catch (Exception ex)
{
// Receive 403, Forbidden
string msg = ex.Message;
}
}
Concerns
Is it possible that I need to be making the API call from a verified or white-listed domain? At the moment I'm running it from a console app running on my development machine. My understanding of domain verification is that it does not apply to incoming calls, and therefore shouldn't be the problem.
I am making what seems to be successful requests for an Access Token.
You should not need to manually request OAuth access tokens when using the Google client libraries. They generally handle this process internally using the credentials you provide from the GCP console.
Although the documentation doesn't show it, I've seen some people use an API key. Indeed, it is mandatory to include it for the SDK approach.
We do not recommend using the API key method to access the Home Graph API. You should be using service account credentials. API keys will technically work for the Request Sync method, but you will not be able to authenticate Report State using an API key.
The fact that you are receiving an error trying to build the HomeGraphServiceService without an API key may be indicative that the credential you are using isn't set up correctly (no private key or possibly missing scopes). The recommended method for supplying service account credentials is to download them in the JSON format rather than certificate, and the code to generate a credential from JSON should look something like this:
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}
You can find additional C# examples for authenticating APIs in the authentication guide.
The problem was nothing to do with my permission to talk to the HomeGraph API or that user. Instead it was where HomeGraph wanted to call my Smart Home Action, but the access token had expired. When attempting to refresh the token, an erroneous implementation on my part led to a blunt 403, which Google was then relaying back to me.
For those interested, the issue was that rather than omitting the expiry date for a token that should never expire, I was setting it to DateTime.MaxValue (subsequently sent through some further processing). Unfortunately when this is finally cast to an int, it is a value that exceeds int.Max. The subsequent time on the expiry was set to epoch (i.e. in the past), and therefore the token validation failed due to expiry.
For anyone else still having the same issue, double check your agentUserId matches exactly the value shown in your SYNC output payload. In my case I'd checked this.
Many thanks to anyone who's looked at this.
I am very new to dialogflow and WebAPIs, and having trouble with a simple dialogflow fulfillment webhook written in C# and hosted on Azure. I am using dialogflow V2.0 API version.
Currently my fulfillment works and returns a simple response but has no regard to the intent and parameters. I am trying to now parse the JSON get the intent do a simple select case and return the value of the parameters recevied. And this is giving me lot of trouble. The webhook link, my code and the error message returned in the "catch" block are given below
public JsonResult Post(string value)
{
try
{
dynamic obj = JsonConvert.DeserializeObject(value);
string Location = string.Empty;
switch (obj.intent.displayName)
{
case "getstock":
Location = obj.outContexts[0].parameters[0].Location;
break;
}
WebhookResponse r = new WebhookResponse();
r.fulfillmentText = string.Format("The stock at {0} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you", Location);
r.source = "API.AI";
Response.ContentType = "application/json";
return Json(r);
} catch(Exception e)
{
WebhookResponse err = new WebhookResponse();
err.fulfillmentText = e.Message;
return Json(err);
}
}
The error message :
Value cannot be null.
Parameter name: value
The above function is called via POST, you can use POSTMAN and you will get the JSON response.
Moreover i am using ASP.Net Web Api with Visual Studio 2017 with Controllers
First install the nuget package Google.Apis.Dialogflow.v2 and its dependencies. It'll save you a lot of work later on as it has dialogflow response/request c# objects which will make navigating the object graph easier.
Second add the using for the package using Google.Apis.Dialogflow.v2.Data;
Change your method to something like
public GoogleCloudDialogflowV2WebhookResponse Post(GoogleCloudDialogflowV2WebhookRequest obj)
{
string Location = string.Empty;
switch (obj.QueryResult.Intent.DisplayName)
{
case "getstock":
Location = obj.QueryResult.Parameters["Location"].ToString();
break;
}
var response = new GoogleCloudDialogflowV2WebhookResponse()
{
FulfillmentText = $"The stock at {Location} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you",
Source = "API.AI"
};
return response;
}
I think your main issue in your code is "obj.outContexts[0]" outContexts isn't where you'll find your parameters, and unless you've setup an output content this will be null. You need to look in queryResult for your parameters.
I am trying to post an album to picasa, but always get "bad request" response.
Should I use HttpRequest class instead?
System.Net.WebClient wc = new System.Net.WebClient();
wc.Headers.Add("Authorization", "AuthSub token=\"" + token + "\"");
wc.Headers.Add("GData-Version", "2");
string data = "<entry xmlns='http://www.w3.org/2005/Atom' " +
"xmlns:media='http://search.yahoo.com/mrss/' " +
"xmlns:gphoto='http://schemas.google.com/photos/2007'>" +
"<title type='text'>" + name + "</title>" +
"<summary type='text'>" + descr + "</summary>" +
"<gphoto:location>asd</gphoto:location>" +
"<gphoto:access>" + access + "</gphoto:access>" +
"<gphoto:timestamp>1152255600000</gphoto:timestamp>" +
"<media:group>" +
"<media:keywords>adds</media:keywords>" +
"</media:group>" +
"<category scheme='http://schemas.google.com/g/2005#kind' " +
"term='http://schemas.google.com/photos/2007#album'></category>" +
"</entry>";
try
{
string response = wc.UploadString("https://picasaweb.google.com/data/feed/api/user/default", "post", data);
return response;
}
catch (Exception e)
{
return e.ToString();
}
Google makes a handy api for picasa [.net] integration:
http://code.google.com/apis/picasaweb/docs/1.0/developers_guide_dotnet.html
No sense writing all that code by hand!
Here is some code (vb.net, but it's straightforward):
Public Shared Function CreateAlbum(ByVal albumTitle As String) As AlbumAccessor
Dim newAlbum As New AlbumEntry()
newAlbum.Title.Text = albumTitle
Dim ac As New AlbumAccessor(newAlbum)
ac.Access = "public"
Dim feedUri As New Uri(PicasaQuery.CreatePicasaUri(ConfigurationManager.AppSettings("GData_Email")))
Dim albumEntry As PicasaEntry = CreateAuthenticatedRequest().Insert(feedUri, newAlbum)
Return New AlbumAccessor(albumEntry)
End Function
Public Shared Function CreateAuthenticatedRequest() As PicasaService
Dim service As New PicasaService(ConfigurationManager.AppSettings("GData_AppName"))
service.setUserCredentials(ConfigurationManager.AppSettings("GData_Email"), ConfigurationManager.AppSettings("GData_Password"))
Return service
End Function
I know this is older so you may already have an answer. I also know that Google does make an API but with .net it only works for the first version of Picasa and you are trying to work with version two, as am I. I came across your post and thought I would offer an answer for you in case you are still trying to work this out or someone else comes across the post and wants an answer.
I see a couple of things that may be causing your issue. The first is that you seem to be mixing and matching the authentication protocol with the version. For the second version of the Google Picasa API, I believe you need to be using OAuth2 protocol, not AuthSub protocol. I haven't tried with AuthSub. The second issue is that I do not believe you have enough information in your headers (missing content-length, content-type, and host[although you may not need host when using a webclient]). One way that I've found to make sure that my requests are working well (and honestly has been a lifesaver) is to go to the OAuth2Playground on Google: Oauth2Playground. Here you can create your tokens and requests and easily see their headers and post information when successful requests are made.
Here is a snippet of code that I wrote which allows for creation of an album. In order to create, you must have an authenticated token with an access code (you would want to first get user permissions and store their refresh token and then refresh to get the session access_token) The access_token is passed in the authorization line of the header. It also parses the response and gets a success variable from the response as well as the albumid. The entire xml feed for the album is returned on response, so you could go into the detail of reading that in and working with it directly if you wanted)
public bool CreatePicasaAlbum(GoogleUtility.Picasa.AlbumEntry.entry a, IGoogleOauth2AccessToken token)
{
TcpClient client = new TcpClient(picasaweb.google.com, 443);
Stream netStream = client.GetStream();
SslStream sslStream = new SslStream(netStream);
sslStream.AuthenticateAsClient(picasaweb.google.com);
byte[] contentAsBytes = Encoding.ASCII.GetBytes(a.toXmlPostString());
string data = a.toXmlPostString();
StringBuilder msg = new StringBuilder();
msg.AppendLine("POST /data/feed/api/user/default HTTP/1.1");
msg.AppendLine("Host: picasaweb.google.com");
msg.AppendLine("Gdata-version: 2");
msg.AppendLine("Content-Length: " + data.Length);
msg.AppendLine("Content-Type: application/atom+xml");
msg.AppendLine(string.Format(GetUserInfoDataString(), token.access_token));
msg.AppendLine("");
byte[] headerAsBytes = Encoding.ASCII.GetBytes(msg.ToString());
sslStream.Write(headerAsBytes);
sslStream.Write(contentAsBytes);
StreamReader reader = new StreamReader(sslStream);
bool success = false;
string albumID = "";
while (reader.Peek() > 0)
{
string line = reader.ReadLine();
if (line.Contains("HTTP/1.1 201 Created")) { success = true; }
if (line.Contains("Location: https") && string.IsNullOrWhiteSpace(albumID))
{
var aiIndex = line.LastIndexOf("/");
albumID = line.Substring(aiIndex + 1);
}
System.Diagnostics.Debug.WriteLine(line);
if (line == null) break;
}
return success;
}
/// <summary>
/// User Info Data String for Authorization on TCP requests
/// [Authorization: OAuth {0}"]
/// </summary>
/// <returns></returns>
private string GetUserInfoDataString()
{
return "Authorization: OAuth {0}";
}
Sorry, I should add that I created an object that returns the feed string for the album entry xml as you had above. The feed xml matches the documentation. I leave the timestamp blank as the default stamp is when you create it, and I've not figured out what if anything can be put in category so I leave that blank too.
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/' xmlns:gphoto='http://schemas.google.com/photos/2007'>
<title type='text'>Created from code</title>
<summary type='text'>Code created this album</summary>
<gphoto:location>somewhere</gphoto:location>
<gphoto:access>public</gphoto:access>
<gphoto:timestamp></gphoto:timestamp>
<media:group>
<media:keywords>test, album, fun</media:keywords>
</media:group>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#album'>
</category>
</entry>
One other edit: The IGoogleOauth2AccessToken is another class I created to house the token details. What you really need passed in is the access_token string that you get when you refresh the OAuth2 token. My token housing code just has the access_code, token_type, and expires as part of the object. You just need the access token string for the authorization.
I'm struggling with the final part of getting my first bit of code working with the AWS - I have got this far, I attached the web reference in VS and this have this
amazon.AWSECommerceService service = new amazon.AWSECommerceService();
// prepare an ItemSearch request
amazon.ItemSearchRequest request = new amazon.ItemSearchRequest();
request.SearchIndex = "DVD";
request.Title = "scream";
request.ResponseGroup = new string[] { "Small" };
amazon.ItemSearch itemSearch = new amazon.ItemSearch();
itemSearch.AssociateTag = "";
itemSearch.Request = new ItemSearchRequest[] { request };
itemSearch.AWSAccessKeyId = ConfigurationManager.AppSettings["AwsAccessKeyId"];
itemSearch.Request = new ItemSearchRequest[] { request };
ItemSearchResponse response = service.ItemSearch(itemSearch);
// write out the results
foreach (var item in response.Items[0].Item)
{
Response.Write(item.ItemAttributes.Title + "<br>");
}
I get the error
The request must contain the parameter Signature.
I know you have to 'sign' requests now, but can't figure out 'where' I would do this or how? any help greatly appreciated?
You have to add to the SOAP request headers including your Amazon access key ID, a timestamp, and the SHA256 hash of the request operation and the timestamp. To accomplish that, you would need access to the SOAP message just before it is going to be sent out. There's a walkthrough and a sample project I put together at http://flyingpies.wordpress.com/2009/08/01/17/.
For the record:
Another reason to get this error is due to keywords with spaces in it.
Example:
'http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=xxx&AssociateTag=usernetmax-20&Version=2011-08-01&Operation=ItemSearch&ResponseGroup=Medium,Offers&SearchIndex=All&Keywords=Baby
Stroller&MerchantId=All&Condition=All&Availability=Available&ItemPage=1&Timestamp=2012-05-16T02:17:32Z&Signature=ye5c2jo99cr3%2BPXVkMyXX8vMhTC21UO4XfHpA21%2BUCs%3D'
It should be:
'http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=xxx&AssociateTag=usernetmax-20&Version=2011-08-01&Operation=ItemSearch&ResponseGroup=Medium,Offers&SearchIndex=All&Keywords=Baby%20Stroller&MerchantId=All&Condition=All&Availability=Available&ItemPage=1&Timestamp=2012-05-16T02:17:32Z&Signature=ye5c2jo99cr3%2BPXVkMyXX8vMhTC21UO4XfHpA21%2BUCs%3D'
PHP solution:
$Keywords = str_replace(' ', '%20', $Keywords);
or
$Keywords = urlencode($Keywords);