Package net.datacrow.util.amazon

Source Code of net.datacrow.util.amazon.SignedRequestsHelper

/**********************************************************************************************
* Copyright 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
* except in compliance with the License. A copy of the License is located at
*
*       http://aws.amazon.com/apache2.0/
*
* or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under the License.
*
* ********************************************************************************************
*
*  Amazon Product Advertising API
*  Signed Requests Sample Code
*
*  API Version: 2009-03-31
*
*/

package net.datacrow.util.amazon;

import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
* This class contains all the logic for signing requests
* to the Amazon Product Advertising API.
*/
public class SignedRequestsHelper {
   
    private static final String UTF8_CHARSET = "UTF-8";
    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
    private static final String REQUEST_URI = "/onca/xml";
    private static final String REQUEST_METHOD = "GET";

    private final String awsAccessKeyId;
    private final String awsSecretKey;
    private final SecretKeySpec secretKeySpec;
    private final Mac mac;

    public SignedRequestsHelper(String awsAccessKeyId, String awsSecretKey) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        this.awsAccessKeyId = awsAccessKeyId;
        this.awsSecretKey = awsSecretKey;

        byte[] secretyKeyBytes = this.awsSecretKey.getBytes(UTF8_CHARSET);
        this.secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
        this.mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
        this.mac.init(secretKeySpec);
    }
   
   
    public synchronized String sign(URL url) {
        String server = url.getHost();
        String query = url.getQuery();
       
        Map<String, String> params = createParameterMap(query);
       
        // remove old signature information
        params.remove("AWSAccessKeyId");
        params.remove("Timestamp");
        params.remove("Signature");
       
        return sign(server, params);
    }
   
    /**
     * This method signs requests in hash map form. It returns a URL that should
     * be used to fetch the response. The URL returned should not be modified in
     * any way, doing so will invalidate the signature and Amazon will reject
     * the request.
     */
    public synchronized String sign(String server, Map<String, String> params) {
        params.put("AWSAccessKeyId", this.awsAccessKeyId);
        params.put("Timestamp", this.timestamp());

        SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(params);
        String canonicalQS = this.canonicalize(sortedParamMap);
       
        // create the string upon which the signature is calculated
        String toSign = REQUEST_METHOD + "\n" + server + "\n"+
                        REQUEST_URI + "\n" + canonicalQS;

        // sign
        String hmac = this.hmac(toSign);
        String sig = this.percentEncodeRfc3986(hmac);

        return "http://" + server + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig;
    }

    /**
     * This method signs requests in query-string form. It returns a URL that
     * should be used to fetch the response. The URL returned should not be
     * modified in any way, doing so will invalidate the signature and Amazon
     * will reject the request.
     */
    public String sign(String server, String queryString) {
        Map<String, String> params = this.createParameterMap(queryString);
        return this.sign(server, params);
    }

    /**
     * Compute the HMAC.
     * 
     * @param stringToSign  String to compute the HMAC over.
     * @return              base64-encoded HMAC value.
     */
    private String hmac(String stringToSign) {
        String signature = null;
        byte[] data;
        byte[] rawHmac;
        try {
            data = stringToSign.getBytes(UTF8_CHARSET);
            rawHmac = mac.doFinal(data);
            Base64 encoder = new Base64();
            signature = new String(encoder.encode(rawHmac));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
        }
        return signature;
    }

    /**
     * Generate a ISO-8601 format time stamp as required by Amazon.
     * @return  ISO-8601 format time stamp.
     */
    private String timestamp() {
        String timestamp = null;
        Calendar cal = Calendar.getInstance();
        DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
        timestamp = dfm.format(cal.getTime());
        return timestamp;
    }

    /**
     * Canonicalize the query string as required by Amazon.
     *
     * @param sortedParamMap    Parameter name-value pairs in lexicographical order.
     * @return                  Canonical form of query string.
     */
    private String canonicalize(SortedMap<String, String> sortedParamMap) {
        if (sortedParamMap.isEmpty()) {
            return "";
        }

        StringBuffer buffer = new StringBuffer();
        Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();

        while (iter.hasNext()) {
            Map.Entry<String, String> kvpair = iter.next();
            buffer.append(percentEncodeRfc3986(kvpair.getKey()));
            buffer.append("=");
            buffer.append(percentEncodeRfc3986(kvpair.getValue()));
            if (iter.hasNext()) {
                buffer.append("&");
            }
        }
        String cannoical = buffer.toString();
        return cannoical;
    }

    /**
     * Percent-encode values according the RFC 3986. The built-in Java
     * URLEncoder does not encode according to the RFC, so we make the
     * extra replacements.
     *
     * @param s decoded string
     * @return  encoded string per RFC 3986
     */
    private String percentEncodeRfc3986(String s) {
        String out;
        try {
            out = URLEncoder.encode(s, UTF8_CHARSET).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            out = s;
        }
        return out;
    }

    /**
     * Takes a query string, separates the constituent name-value pairs
     * and stores them in a hash map.
     *
     * @param queryString
     * @return
     */
    private Map<String, String> createParameterMap(String queryString) {
        Map<String, String> map = new HashMap<String, String>();
       
        queryString = queryString.indexOf("&Signature") > -1 ? queryString.substring(0, queryString.indexOf("&Signature")) : queryString;
        String[] pairs = queryString.split("&");

        for (String pair: pairs) {
           
            if (pair.length() < 1) continue;

            String[] tokens = pair.split("=", 2);
            for(int j = 0; j < tokens.length; j++) {
                try {
                    tokens[j] = URLDecoder.decode(tokens[j], UTF8_CHARSET);
                } catch (UnsupportedEncodingException ignore) {}
            }
           
            switch (tokens.length) {
                case 1:
                    if (pair.charAt(0) == '=')
                        map.put("", tokens[0]);
                    else
                        map.put(tokens[0], "");

                    break;
                case 2:
                    map.put(tokens[0], tokens[1]);
                    break;
            }
        }
        return map;
    }
}
TOP

Related Classes of net.datacrow.util.amazon.SignedRequestsHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.