I m adding code to delete multiple objects from S3. POST request to delete returns 403 status. I am adding this code in legacy code and unfortunately cannot change to change AWS S3 library. Not able to figure out if I am missing a header or if the logic is incorrect.
public Response deleteMultipleObjects(Delete object) throws MalformedURLException, IOException, StorageException {
HttpURLConnection request = null;
try(ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out)) {
os.writeObject(object);
Calendar now = Calendar.getInstance();
Map<String, List<String>> headers = new HashMap<>();
headers.put(AWSAuthConnection.CONTENT_TYPE_HEADER, Arrays.asList("application/octet-stream"));
headers.put(AWSAuthConnection.CONTENT_LENGTH_HEADER, Arrays.asList(Integer.toString(out.toByteArray().length)));
headers.put(AWSAuthConnection.AMAZON_DATE_HEADER, Arrays.asList(convertDateToString(now, AWSAuthConnection.TIMESTAMP_FORMAT, AWSAuthConnection.TIME_ZONE_UTC)));
headers.put(AWSAuthConnection.AMAZON_CONTENT_SHA256_HEADER, Arrays.asList(buildHash(out.toByteArray(), SHA256_HASH, EncodingType.HEX)));
headers.put(AWSAuthConnection.CONTENT_MD5_HEADER, Arrays.asList(buildHash(out.toByteArray(), AWSAuthConnection.MD5_HASH, AWSAuthConnection.EncodingType.BASE64)));
request = makeRequest("POST", bucket+"/?delete", headers, null);
request.setDoOutput(true);
request.getOutputStream().write(object == null ? new byte[]{} : out.toByteArray());
return new Response(request);
}
}
private String buildHash(final byte[] contentToHash, final String hashMethod, final AWSAuthConnection.EncodingType encodingType) throws StorageException{
try {
MessageDigest digest = MessageDigest.getInstance(hashMethod);
byte[] contentHash = digest.digest(contentToHash);
if (encodingType == AWSAuthConnection.EncodingType.HEX) {
return Hex.encodeHexString(contentHash);
} else {
return Base64.encodeBase64String(contentHash);
}
} catch (NoSuchAlgorithmException e) {
throw new StorageException(StorageException.ERROR_INVALID_STORAGE_TYPE, e);
}
}
private HttpURLConnection makeRequest(String method, String resource, Map<String, List<String>> headers, StorageObject object) throws MalformedURLException, IOException {
URL url = makeURL(resource);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
connection.setUseCaches(true);
addHeaders(connection, headers);
if (object != null) addMetadataHeaders(connection, object.metadata);
addAuthHeader(connection, method, resource);
return connection;
}
private void addAuthHeader(HttpURLConnection connection, String method, String resource) {
String canonicalString =
Utils.makeCanonicalString(method, resource, connection.getRequestProperties());
String encodedCanonical = Utils.encode(this.info, canonicalString, false);
connection.setRequestProperty("Authorization", "AWS " + this.info + ":" + encodedCanonical);
}
Error from S3:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>.......</Error>
Has anyone faced this issue before? If you know a way to fix, please help
Related
I'm trying to send aמ HTTP POST request in order to send contacts information to a Mail Exchange Server, using their API (creating a new "subscriber"). I'm using Java and java.util.HttpURLConnection.
When I try signing the connection, I'm getting a null reference exception. If I try signing the connection prior to adding the setRequestProperty headers, I'm getting an Invalid Signature response from the server.
Using a GET request with the same general procedure works - which means, as far as I understand, that my signing method (and key values etc.) is OK.
The service I'm trying to use has some kind of a "SDK" available, written in .NET. I didn't try to use it but I do believe it to work (they declare so).
I tried to replicate their procedure. Below you can find my code, follow by theirs:
private static HttpURLConnection createAndSendOAuthPostRequestWithParams () throws MalformedURLException, IOException, Exception {
String url = "http://apisdomain/v1.0/lists/354467/subscribers";
// Here I set up the values given by the provider (API's admin) which I removed from the example
String clientKey = "";
String clientSecret = "";
String userKey = "";
String userSecret = "";
String postData = "NAME=TestSubscriber&EMAIL=test#gmail.com
byte[] postBody = postData.getBytes("UTF-8");
URL apiUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("content-length", String.valueOf(postBody.length));
connection.setRequestProperty("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
//OAuth
OAuthConsumer consumer = new DefaultOAuthConsumer (clientKey, clientSecret);
//consumer.setAdditionalParameters(parameters);
consumer.setTokenWithSecret(userKey, userSecret);
HttpRequest httpReq = consumer.sign(connection); //Where the exception occurs
if (!postBody.toString().isEmpty()) {
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(postBody);
outputStream.flush();
}
}
return connection;
}
From thier SDK:
using System.Text;
namespace ResponderSDK
{
using OAuth;
using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
class ResponderOAuth
{
/* Contains the last HTTP status code returned. */
public HttpStatusCode http_code;
/* Contains the last API call. */
public string url;
/* Set up the API root URL. */
public string host = "http://api.responder.co.il/v1.0/";
/* Set timeout default. */
public int timeout = 3000;
/* Set connect timeout. */
public int connect_timeout = 30;
/* Verify SSL Cert. */
public bool ssl_verifypeer = false;
/* Response format. */
public string format = "json";
/* Contains the last HTTP headers returned. */
public string http_info;
/* Set the useragent. */
public string useragent = "ResponderOAuth v0.1-beta";
/*debug info*/
public string headers_string;
public string base_string;
public string post_string;
/* Signature */
private OAuthSignatureMethod_HMAC_SHA1 signature;
/* OAuthConsumer */
private OAuthConsumer consumer;
/* Token */
private OAuthToken token;
public ResponderOAuth(string consumer_key, string consumer_secret, string oauth_token = null, string oauth_token_secret = null)
{
this.signature = new OAuthSignatureMethod_HMAC_SHA1();
this.consumer = new OAuthConsumer(consumer_key, consumer_secret);
if (!String.IsNullOrEmpty(oauth_token) && !String.IsNullOrEmpty(oauth_token_secret))
{
this.token = new OAuthToken(oauth_token, oauth_token_secret);
}
else
{
this.token = null;
}
}
public string http_request(string url, string method = "GET", ParametersArray parameters = null)
{
method = method.ToUpper();
if (url.LastIndexOf("https://") != 0 && url.LastIndexOf("http://") != 0)
{
url = String.Format("{0}{1}", this.host, url);
}
if (method.Equals("GET"))
parameters = null;
OAuthRequest request = OAuthRequest.from_consumer_and_token(this.consumer, this.token, method, url, parameters);
request.sign_request(this.signature, this.consumer, this.token);
this.base_string = request.base_string;
if (method.Equals("GET"))
return this.http(request.to_url(), "GET", request.to_header(), null);
else
return this.http(request.get_normalized_http_url(), method, request.to_header(), request.to_postdata());
}
private string http(string url, string method, WebHeaderCollection headers, string data = null)
{
List<string> new_http_info = new List<string>();
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(AcceptAllCertifications);
HttpWebRequest request = null;
if (!method.Equals("DELETE"))
request = (HttpWebRequest)WebRequest.Create(url);
else
{
if (!String.IsNullOrEmpty(data))
{
url = String.Format("{0}?{1}", url, data);
}
request = (HttpWebRequest)WebRequest.Create(url);
}
/* WebRequest settings */
((HttpWebRequest)request).ProtocolVersion = System.Net.HttpVersion.Version10;
((HttpWebRequest)request).UserAgent = this.useragent;
((HttpWebRequest)request).ContinueTimeout = this.connect_timeout;
((HttpWebRequest)request).Timeout = this.timeout;
((HttpWebRequest)request).Headers = headers;
((HttpWebRequest)request).UseDefaultCredentials = true;
((HttpWebRequest)request).PreAuthenticate = true;
((HttpWebRequest)request).Credentials = CredentialCache.DefaultCredentials;
this.headers_string = headers.ToString();
this.post_string = data;
byte[] dataByteArray = null;
if ((!String.IsNullOrEmpty(data) && method.Equals("POST")) || method.Equals("PUT"))
{
((HttpWebRequest)request).ContentType = "application/x-www-form-urlencoded";
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
dataByteArray = encoding.GetBytes(data);
((HttpWebRequest)request).ContentLength = dataByteArray.Length;
((HttpWebRequest)request).Expect = "";
}
switch (method)
{
case "POST":
((HttpWebRequest)request).Method = "POST";
if (!String.IsNullOrEmpty(data))
{
Stream dataPost = request.GetRequestStream();
dataPost.Write(dataByteArray, 0, dataByteArray.Length);
dataPost.Close();
}
break;
case "PUT":
((HttpWebRequest)request).Method = "PUT";
if (!String.IsNullOrEmpty(data))
{
Stream dataPost = request.GetRequestStream();
dataPost.Write(dataByteArray, 0, dataByteArray.Length);
dataPost.Close();
}
break;
case "DELETE":
((HttpWebRequest)request).Method = "DELETE";
break;
}
WebResponse response = request.GetResponse();
this.http_code = ((HttpWebResponse)response).StatusCode;
// Get the stream containing content returned by the server.
Stream dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
return reader.ReadToEnd();
}
}
}
If your input String format is json, you can change content-type to "application/json" and try signing in after adding the setRequestProperty headers.
I have a use case to authenticate OAuth1 request which is signed using RSA Private Key and verified at server end with RSA public key.
I found this library from Twitter which helps us authenticate/verify the Oauth signed requests. https://github.com/twitter/joauth
I want to leverage this library for verifying the request from Jersey or Spring MVC action method. The request from client would have been signed using private key. At my end I would use the public key of the client to verify the request. which means RSA-SHA1 algo.
Twitter joauth seem to be useful but I am missing the code that would transform HttpServletRequest to OAuthRequest
The library read-me file suggests this as facility but I could not find a code that does javax.servlet.http.HttpServletRequest --> com.twitter.joauth.OAuthRequest transformation.
The request verification happens in verify method which has following signature.
public VerifierResult verify(UnpackedRequest.OAuth1Request request, String tokenSecret, String consumerSecret);
Secondly I also want to know which is the most appropriate way to use/read RSA public key with twitter joauth when verify method takes String parameter ?
I have never used any library to authenticate users via Twitter. But I have just looked in the UnpackedRequest.OAuth1Request. You can create an instance of this class by filling all parameters. I have written Twitter OAuth Header creator, so you can just use it to fill those parameters or send POST requests directly without a library.
Here all classes what you need:
Signature - to generate an OAuth Signature.
public class Signature {
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public static String calculateRFC2104HMAC(String data, String key)
throws java.security.SignatureException
{
String result;
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
result = new String(Base64.encodeBase64(rawHmac));
} catch (Exception e) {
throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
}
NvpComparator - to sort parameters you need in the header.
public class NvpComparator implements Comparator<NameValuePair> {
#Override
public int compare(NameValuePair arg0, NameValuePair arg1) {
String name0 = arg0.getName();
String name1 = arg1.getName();
return name0.compareTo(name1);
}
}
OAuth - for URL encode.
class OAuth{
...
public static String percentEncode(String s) {
return URLEncoder.encode(s, "UTF-8")
.replace("+", "%20").replace("*", "%2A")
.replace("%7E", "~");
}
...
}
HeaderCreator - to create all needed parameters and generate an OAuth header param.
public class HeaderCreator {
private String authorization = "OAuth ";
private String oAuthSignature;
private String oAuthNonce;
private String oAuthTimestamp;
private String oAuthConsumerSecret;
private String oAuthTokenSecret;
public String getAuthorization() {
return authorization;
}
public String getoAuthSignature() {
return oAuthSignature;
}
public String getoAuthNonce() {
return oAuthNonce;
}
public String getoAuthTimestamp() {
return oAuthTimestamp;
}
public HeaderCreator(){}
public HeaderCreator(String oAuthConsumerSecret){
this.oAuthConsumerSecret = oAuthConsumerSecret;
}
public HeaderCreator(String oAuthConsumerSecret, String oAuthTokenSecret){
this(oAuthConsumerSecret);
this.oAuthTokenSecret = oAuthTokenSecret;
}
public String getTwitterServerTime() throws IOException, ParseException {
HttpsURLConnection con = (HttpsURLConnection)
new URL("https://api.twitter.com/oauth/request_token").openConnection();
con.setRequestMethod("HEAD");
con.getResponseCode();
String twitterDate= con.getHeaderField("Date");
DateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
Date date = formatter.parse(twitterDate);
return String.valueOf(date.getTime() / 1000L);
}
public String generatedSignature(String url, String method, List<NameValuePair> allParams,
boolean withToken) throws SignatureException {
oAuthNonce = String.valueOf(System.currentTimeMillis());
allParams.add(new BasicNameValuePair("oauth_nonce", oAuthNonce));
try {
oAuthTimestamp = getTwitterServerTime();
allParams.add(new BasicNameValuePair("oauth_timestamp", oAuthTimestamp));
}catch (Exception ex){
//TODO: Log!!
}
Collections.sort(allParams, new NvpComparator());
StringBuffer params = new StringBuffer();
for(int i=0;i<allParams.size();i++)
{
NameValuePair nvp = allParams.get(i);
if (i>0) {
params.append("&");
}
params.append(nvp.getName() + "=" + OAuth.percentEncode(nvp.getValue()));
}
String signatureBaseStringTemplate = "%s&%s&%s";
String signatureBaseString = String.format(signatureBaseStringTemplate,
OAuth.percentEncode(method),
OAuth.percentEncode(url),
OAuth.percentEncode(params.toString()));
String compositeKey = OAuth.percentEncode(oAuthConsumerSecret)+"&";
if(withToken) compositeKey+=OAuth.percentEncode(oAuthTokenSecret);
oAuthSignature = Signature.calculateRFC2104HMAC(signatureBaseString, compositeKey);
return oAuthSignature;
}
public String generatedAuthorization(List<NameValuePair> allParams){
authorization = "OAuth ";
Collections.sort(allParams, new NvpComparator());
for(NameValuePair nvm : allParams){
authorization+=nvm.getName()+"="+OAuth.percentEncode(nvm.getValue())+", ";
}
authorization=authorization.substring(0,authorization.length()-2);
return authorization;
}
}
Explain:
1. getTwitterServerTime
In oAuthTimestamp you need not your time of server but the time of a Twitter server. You can optimize it saving this param if you always send requests in the certain Twitter server.
2. HeaderCreator.generatedSignature(...)
url - logically url to twitter API
method - GET or POST. You must use always "POST"
allParams - Parameters which you know to generate signature ("param_name", "param_value");
withToken - if you know oAuthTokenSecret put true. Otherwise false.
3. HeaderCreator.generatedAuthorization(...)
Use this method after generatedSignature(...) to generate an OAuth header string.
allParams - it is parameters which you have used in generatedSignature(...) plus: nonce, signature, timestamp. Always use:
allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce()));
allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature()));
allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp()));
Now you can use it to fill UnpackedRequest.OAuth1Request in your library. Also here an example to authenticate user in SpringMVC without the library:
Requests - to send post requests.
public class Requests {
public static String sendPost(String url, String urlParameters, Map<String, String> prop) throws Exception {
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
if(prop!=null) {
for (Map.Entry<String, String> entry : prop.entrySet()) {
con.setRequestProperty(entry.getKey(), entry.getValue());
}
}
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = con.getResponseCode();
BufferedReader in;
if(responseCode==200) {
in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
}else{
in = new BufferedReader(
new InputStreamReader(con.getErrorStream()));
}
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
}
twAuth(...) - put it in your controller. Execute it when an user want to authenticate in your site via Twitter.
#RequestMapping(value = "/twauth", method = RequestMethod.GET)
#ResponseBody
public String twAuth(HttpServletResponse response) throws Exception{
try {
String url = "https://api.twitter.com/oauth/request_token";
List<NameValuePair> allParams = new ArrayList<NameValuePair>();
allParams.add(new BasicNameValuePair("oauth_callback", "http://127.0.0.1:8080/twlogin"));
allParams.add(new BasicNameValuePair("oauth_consumer_key", "2YhNLyum1VY10UrWBMqBnatiT"));
allParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
allParams.add(new BasicNameValuePair("oauth_version", "1.0"));
HeaderCreator headerCreator = new HeaderCreator("RUesRE56vVWzN9VFcfA0jCBz9VkvkAmidXj8d1h2tS5EZDipSL");
headerCreator.generatedSignature(url,"POST",allParams,false);
allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce()));
allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature()));
allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp()));
Map<String, String> props = new HashMap<String, String>();
props.put("Authorization", headerCreator.generatedAuthorization(allParams));
String twitterResponse = Requests.sendPost(url,"",props);
Integer indOAuthToken = twitterResponse.indexOf("oauth_token");
String oAuthToken = twitterResponse.substring(indOAuthToken, twitterResponse.indexOf("&",indOAuthToken));
response.sendRedirect("https://api.twitter.com/oauth/authenticate?" + oAuthToken);
}catch (Exception ex){
//TODO: Log
throw new Exception();
}
return "main";
}
twLogin(...) - put it in your controller. It is callback from Twitter.
#RequestMapping(value = "/twlogin", method = RequestMethod.GET)
public String twLogin(#RequestParam("oauth_token") String oauthToken,
#RequestParam("oauth_verifier") String oauthVerifier,
Model model, HttpServletRequest request){
try {
if(oauthToken==null || oauthToken.equals("") ||
oauthVerifier==null || oauthVerifier.equals(""))
return "main";
String url = "https://api.twitter.com/oauth/access_token";
List<NameValuePair> allParams = new ArrayList<NameValuePair>();
allParams.add(new BasicNameValuePair("oauth_consumer_key", "2YhNLyum1VY10UrWBMqBnatiT"));
allParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
allParams.add(new BasicNameValuePair("oauth_token", oauthToken));
allParams.add(new BasicNameValuePair("oauth_version", "1.0"));
NameValuePair oAuthVerifier = new BasicNameValuePair("oauth_verifier", oauthVerifier);
allParams.add(oAuthVerifier);
HeaderCreator headerCreator = new HeaderCreator("RUesRE56vVWzN9VFcfA0jCBz9VkvkAmidXj8d1h2tS5EZDipSL");
headerCreator.generatedSignature(url,"POST",allParams,false);
allParams.add(new BasicNameValuePair("oauth_nonce", headerCreator.getoAuthNonce()));
allParams.add(new BasicNameValuePair("oauth_signature", headerCreator.getoAuthSignature()));
allParams.add(new BasicNameValuePair("oauth_timestamp", headerCreator.getoAuthTimestamp()));
allParams.remove(oAuthVerifier);
Map<String, String> props = new HashMap<String, String>();
props.put("Authorization", headerCreator.generatedAuthorization(allParams));
String twitterResponse = Requests.sendPost(url,"oauth_verifier="+oauthVerifier,props);
//Get user id
Integer startIndexTmp = twitterResponse.indexOf("user_id")+8;
Integer endIndexTmp = twitterResponse.indexOf("&",startIndexTmp);
if(endIndexTmp<=0) endIndexTmp = twitterResponse.length()-1;
Long userId = Long.parseLong(twitterResponse.substring(startIndexTmp, endIndexTmp));
//Do what do you want...
}catch (Exception ex){
//TODO: Log
throw new Exception();
}
}
I am trying to cache images in an android app with this solution Android image caching. I have already implemented a specific ResponseCache and overriden the get and put methods.
Despite that, images are not properly cached. When I debug I can see that the put method of my ResponseCache implementation is never called. My get method is properly called each time a request is made but never is the put method. Nothing is never cached so it can't retrieve any file...
My request use HTTPS so i was wondering if caching the response was allowed or if i'll have to deal with requesting the server every time I want to display my images.
Here is the code :
public class ImageResponseCache extends ResponseCache {
File cacheDir;
public ImageResponseCache(File cacheDir) {
super();
this.cacheDir = cacheDir;
}
#Override
public CacheResponse get(URI uri, String s,
Map<String, List<String>> headers) throws IOException {
final File file = new File(cacheDir, escape(uri.getPath()));
if (file.exists()) {
return new CacheResponse() {
#Override
public Map<String, List<String>> getHeaders()
throws IOException {
return null;
}
#Override
public InputStream getBody() throws IOException {
return new FileInputStream(file);
}
};
} else {
return null;
}
}
#Override
public CacheRequest put(URI uri, URLConnection urlConnection)
throws IOException {
Log.i("Image Response", "PUT");
final File file = new File(cacheDir, escape(urlConnection.getURL()
.getPath()));
return new CacheRequest() {
#Override
public OutputStream getBody() throws IOException {
return new FileOutputStream(file);
}
#Override
public void abort() {
file.delete();
}
};
}
private String escape(String url) {
return url.replace("/", "-").replace(".", "-");
}
}
Here is the function that request my images in an adapter:
private Bitmap requestImage(String file) {
Bitmap bm = null;
Log.d(TAG, "path: " + file);
URL url = null;
HttpURLConnection http = null;
try {
url = new URL(file);
if (url.getProtocol().toLowerCase().equals("https")) {
NetworkUtils.trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url
.openConnection();
https.setHostnameVerifier(NetworkUtils.DO_NOT_VERIFY);
http = https;
} else {
http = (HttpURLConnection) url.openConnection();
}
http.setUseCaches(true);
ResponseCache.setDefault(new ImageResponseCache(
ImageAdapter.this.mContext.getCacheDir()));
http.setRequestProperty(
"Authorization",
"Basic "
+ Base64.encodeToString(
(Constants.USER + ":" + Constants.PASSWORD)
.getBytes(), Base64.NO_WRAP));
http.setConnectTimeout(Constants.TIME_OUT);
bm = BitmapFactory.decodeStream((InputStream) http.getContent());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
return bm;
}
Does anyone know what could be wrong?
I'm using the sandbox "Notification Serial Number" node. I'm acknowledging fine and getting the serial-number. When I go to get the message as described in "Requesting Notifications", I get a response body of two letters: OK .. Instead I need the the notification message.
https://developers.google.com/checkout/developer/Google_Checkout_HTML_API_Notification_API#responding_to_notifications
Here is my code:
public static enum Account {
sandbox, production;
String merchantId, merchantKey, url;
static {
sandbox.merchantId = "9999999999999999";
sandbox.merchantKey = "Ran0mUPPERlowerCaArAcTeRs";
sandbox.url = "https://sandbox.google.com/checkout/";
production.merchantId = "...";
production.merchantKey = "...";
production.url = "https://checkout.google.com/";
}
}
// https://developers.google.com/checkout/developer/Google_Checkout_HTML_API_Notification_API
//notification api entry-point
public static void sandbox() throws IOException, GoogleCheckoutException,
ParseException {
String sn = request.params.get("serial-number");
if (sn == null)
error();//playframework sends an error response code
//GoogleCheckoutApi.Account.sandbox is an enum with my information
//try to process first (or exception), then confirm below
processNotification(GoogleCheckoutApi.Account.sandbox, sn);
// confirm request
// https://developers.google.com/checkout/developer/Google_Checkout_HTML_API_Notification_API#responding_to_notifications
String s = "<notification-acknowledgment xmlns=\"http://checkout.google.com/schema/2\" serial-number=\""
+ sn + "\" />";
response.out.write(s.getBytes());
}
static final String URL_REPORTS_FORM = "api/checkout/v2/reportsForm/Merchant/";
public static void processNotification(Account account, final String sn)
throws IOException, GoogleCheckoutException, ParseException {
System.out.println(new Date() + "\tGoogle Notification IN\t" + sn);
Map<String, String> parms = new HashMap<String, String>() {
{
put("_type", "notification-history-request");
put("serial-number", sn);
}
};
HttpURLConnection c = requestPost(account.url + URL_REPORTS_FORM
+ account.merchantId, authProperties(account), parms, null);
try {
if (c.getResponseCode() != 200)
throw new GoogleCheckoutException("Error Response Code "
+ c.getResponseCode() + " from google API\n"
+ c.getResponseMessage());
handleResponse(account, c.getResponseMessage());
} finally {
c.disconnect();
}
}
private static void handleResponse(Account account, String responseMessage)
throws IOException, ParseException {
if (responseMessage.equals("OK")) {
System.out.println(new Date() + "\tGoogle Notification IN\tOK");
return;
}
//...other code to log and save the message...
}
//...requestPost seems to work fine. I know I'm authenticating correctly.
public static HttpURLConnection requestPost(String requestURL,
Map<String, String> properties, Map<String, String> params,
String content) throws IOException {
// derived from
// http://www.codejava.net/java-se/networking/an-http-utility-class-to-send-getpost-request
URL url = new URL(requestURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("POST");
if (properties != null)
for (String key : properties.keySet()) {
String value = properties.get(key);
connection.setRequestProperty(key, value);
}
connection.setUseCaches(false);
if (params != null && params.size() > 0) {
StringBuffer requestParams = new StringBuffer();
for (String key : params.keySet()) {
String value = params.get(key);
requestParams.append(URLEncoder.encode(key, "UTF-8"));
if (value != null)
requestParams.append("=").append(
URLEncoder.encode(value, "UTF-8"));
requestParams.append("&");
}
// remove &
requestParams.setLength(requestParams.length() - 1);//
connection.setDoOutput(true);
connection.setRequestProperty("Content-Length", String
.valueOf(requestParams.length()));
OutputStreamWriter writer = new OutputStreamWriter(connection
.getOutputStream());
writer.write(requestParams.toString());
writer.close();
} else if (content != null) {
connection.setDoOutput(true);
// sends POST data
connection.setRequestProperty("Content-Length", String
.valueOf(content.length()));
OutputStreamWriter writer = new OutputStreamWriter(connection
.getOutputStream());
writer.write(content);
writer.close();
}
return connection;
}
static final BASE64Encoder encoder = new BASE64Encoder();
private static Map<String, String> authProperties(final Account account) {
return new HashMap<String, String>() {
{
put("Content-Type", "application/xml; charset=UTF-8");
put("Accept", "application/xml; charset=UTF-8");
put(
"Authorization",
"Basic "
+ encoder
.encode((account.merchantId + ":" + account.merchantKey)
.getBytes()));
}
};
}
This code outputs:
Wed Mar 20 17:57:39 UTC 2013 Google Notification IN 748115991100000-00005-6
Wed Mar 20 17:57:39 UTC 2013 Google Notification IN OK
Instead of "OK" I was expecting to see the entire message.
I am getting the following error:
<error_code>104</error_code>
<error_msg>Incorrect signature</error_msg>
What should I be setting contentType type as? Should I set as:
String contentType = "application/x-www-form-urlencoded";
or
String contentType = "multipart/form-data; boundary=" + kStringBoundary;
This is how I am writing the stream:
HttpURLConnection conn = null;
OutputStream out = null;
InputStream in = null;
try {
conn = (HttpURLConnection) _loadingURL.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
if (method != null) {
conn.setRequestMethod(method);
if ("POST".equals(method)) {
//"application/x-www-form-urlencoded";
String contentType = "multipart/form-data; boundary=" + kStringBoundary;
//String contentType = "application/x-www-form-urlencoded";
conn.setRequestProperty("Content-Type", contentType);
}
// Cookies are used in FBPermissionDialog and FBFeedDialog to
// retrieve logged user
conn.connect();
out = conn.getOutputStream();
if ("POST".equals(method)) {
String body = generatePostBody(postParams);
if (body != null) {
out.write(body.getBytes("UTF-8"));
}
}
in = conn.getInputStream();
Here's the method I am using to publish the stream:
private void publishFeed(String themessage) {
//Intent intent = new Intent(this, FBFeedActivity.class);
// intent.putExtra("userMessagePrompt", themessage);
// intent.putExtra("attachment",
Map<String, String> getParams = new HashMap<String, String>();
// getParams.put("display", "touch");
// getParams.put("callback", "fbconnect://success");
// getParams.put("cancel", "fbconnect://cancel");
Map<String, String> postParams = new HashMap<String, String>();
postParams.put("api_key", _session.getApiKey());
postParams.put("method", "stream.publish");
postParams.put("session_key", _session.getSessionKey());
postParams.put("user_message", "TESTING 123");
// postParams.put("preview", "1");
postParams.put("attachment", "{\"name\":\"Facebook Connect for Android\",\"href\":\"http://code.google.com/p/fbconnect-android/\",\"caption\":\"Caption\",\"description\":\"Description\",\"media\":[{\"type\":\"image\",\"src\":\"http://img40.yfrog.com/img40/5914/iphoneconnectbtn.jpg\",\"href\":\"http://developers.facebook.com/connect.php?tab=iphone/\"}],\"properties\":{\"another link\":{\"text\":\"Facebook home page\",\"href\":\"http://www.facebook.com\"}}}");
// postParams.put("user_message_prompt", "22222");
try {
loadURL("http://api.facebook.com/restserver.php", "POST", getParams, postParams);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
Here is generatePostBody() :
private String generatePostBody(Map<String, String> params) {
StringBuilder body = new StringBuilder();
StringBuilder endLine = new StringBuilder("\r\n--").append(kStringBoundary).append("\r\n");
body.append("--").append(kStringBoundary).append("\r\n");
for (Entry<String, String> entry : params.entrySet()) {
body.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"\r\n\r\n");
String value = entry.getValue();
if ("user_message_prompt".equals(entry.getKey())) {
body.append(value);
}
else {
body.append(CcUtil.encode(value));
}
body.append(endLine);
}
return body.toString();
}
Thanks.
This is from Facebook Developers Wiki: http://wiki.developers.facebook.com/index.php/API
Note: If you manually form your HTTP
POST requests to Facebook, you must
include the request data in the POST
body. In addition, you should include
a Content-Type: header of
application/x-www-form-urlencoded.
Use multipart/form-data when uploading files only (e.g. Photos.upload on Facebook API)
Also, Based on this API reference, http://wiki.developers.facebook.com/index.php/How_Facebook_Authenticates_Your_Application, this is what you're doing wrong.
1) Don't use HashMap to store Facebook parameters. Parameters must be sorted by their key. Rather use SortedMap interface and use TreeMap to store Facebook parameters.
2) Always include the sig parameter in the map before sending the call to Facebook. You're getting a "104 Incorrect signature" because Facebook doesn't find the sig parameter in your request.
The reference (http://wiki.developers.facebook.com/index.php/How_Facebook_Authenticates_Your_Application) shows exactly how to create a MD5 signature which facebook uses.
Here's how to quickly create a sig value for Facebook.
String hashString = "";
Map<String, String> sortedMap = null;
if (parameters instanceof TreeMap) {
sortedMap = (TreeMap<String, String>) parameters;
} else {
sortedMap = new TreeMap<String, String>(parameters);
}
try {
Iterator<String> iter = sortedMap.keySet().iterator();
StringBuilder sb = new StringBuilder();
synchronized (iter) {
while (iter.hasNext()) {
String key = iter.next();
sb.append(key);
sb.append("=");
String value = sortedMap.get(key);
sb.append(value == null ? "" : value);
}
}
sb.append(secret);
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] digested = digest.digest(sb.toString().getBytes());
BigInteger bigInt = new BigInteger(1, digested);
hashString = bigInt.toString(16);
while (hashString.length() < 32) {
hashString = "0" + hashString;
}
} catch (NoSuchAlgorithmException nsae) {
// TODO: handle exception
logger.error(e.getLocalizedMessage(), e);
}
return hashString;