Forráskód Böngészése

ZF-7033 - Fix Zend_Service_Amazon authentication violation

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@17144 44c647ce-9c0f-0410-b52a-842ac1e357ba
beberlei 16 éve
szülő
commit
5e410a5f1c

+ 14 - 12
documentation/manual/en/module_specs/Zend_Service_Amazon.xml

@@ -41,15 +41,17 @@
             </itemizedlist>
             </itemizedlist>
         </para>
         </para>
         <para>
         <para>
-            In order to use <classname>Zend_Service_Amazon</classname>, you should already have an Amazon developer API key. To
-            get a key and for more information, please visit the
-            <ulink url="http://www.amazon.com/gp/aws/landing.html">Amazon Web Services</ulink> web site.
+            In order to use <classname>Zend_Service_Amazon</classname>, you should already have an Amazon developer API key
+            aswell as a secret key. To get a key and for more information, please visit the
+            <ulink url="http://aws.amazon.com/">Amazon Web Services</ulink> web site. As of August 15th, 2009 you can
+            only use the Amazon Product Advertising API through <classname>Zend_Service_Amazon</classname>, when specifying
+            the additional secret key.
         </para>
         </para>
         <note>
         <note>
             <title>Attention</title>
             <title>Attention</title>
             <para>
             <para>
-                Your Amazon developer API key is linked to your Amazon identity,
-                so take appropriate measures to keep your API key private.
+                Your Amazon developer API and secret keys are linked to your Amazon identity,
+                so take appropriate measures to keep them private.
             </para>
             </para>
         </note>
         </note>
         <example id="zend.service.amazon.introduction.example.itemsearch">
         <example id="zend.service.amazon.introduction.example.itemsearch">
@@ -58,7 +60,7 @@
                 In this example, we search for PHP books at Amazon and loop through the results, printing them.
                 In this example, we search for PHP books at Amazon and loop through the results, printing them.
             </para>
             </para>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
-$amazon = new Zend_Service_Amazon('AMAZON_API_KEY');
+$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'US', 'AMAZON_SECRET_KEY');
 $results = $amazon->itemSearch(array('SearchIndex' => 'Books',
 $results = $amazon->itemSearch(array('SearchIndex' => 'Books',
                                      'Keywords' => 'php'));
                                      'Keywords' => 'php'));
 foreach ($results as $result) {
 foreach ($results as $result) {
@@ -73,7 +75,7 @@ foreach ($results as $result) {
                 resembles the Fluent Interface design pattern.
                 resembles the Fluent Interface design pattern.
             </para>
             </para>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
-$query = new Zend_Service_Amazon_Query('AMAZON_API_KEY');
+$query = new Zend_Service_Amazon_Query('AMAZON_API_KEY', 'US, 'AMAZON_SECRET_KEY');
 $query->category('Books')->Keywords('PHP');
 $query->category('Books')->Keywords('PHP');
 $results = $query->search();
 $results = $query->search();
 foreach ($results as $result) {
 foreach ($results as $result) {
@@ -93,7 +95,7 @@ foreach ($results as $result) {
             <title>Choosing an Amazon Web Service Country</title>
             <title>Choosing an Amazon Web Service Country</title>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
 // Connect to Amazon in Japan
 // Connect to Amazon in Japan
-$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'JP');
+$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'JP', 'AMAZON_SECRET_KEY');
 ]]></programlisting>
 ]]></programlisting>
         </example>
         </example>
         <note>
         <note>
@@ -113,7 +115,7 @@ $amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'JP');
         <example id="zend.service.amazon.itemlookup.example.asin">
         <example id="zend.service.amazon.itemlookup.example.asin">
             <title>Looking up a Specific Amazon Item by ASIN</title>
             <title>Looking up a Specific Amazon Item by ASIN</title>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
-$amazon = new Zend_Service_Amazon('AMAZON_API_KEY');
+$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'US', 'AMAZON_SECRET_KEY');
 $item = $amazon->itemLookup('B0000A432X');
 $item = $amazon->itemLookup('B0000A432X');
 ]]></programlisting>
 ]]></programlisting>
         </example>
         </example>
@@ -140,7 +142,7 @@ $item = $amazon->itemLookup('B0000A432X');
         <example id="zend.service.amazon.itemsearch.example.basic">
         <example id="zend.service.amazon.itemsearch.example.basic">
             <title>Performing Amazon Item Searches</title>
             <title>Performing Amazon Item Searches</title>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
-$amazon = new Zend_Service_Amazon('AMAZON_API_KEY');
+$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'US', 'AMAZON_SECRET_KEY');
 $results = $amazon->itemSearch(array('SearchIndex' => 'Books',
 $results = $amazon->itemSearch(array('SearchIndex' => 'Books',
                                      'Keywords' => 'php'));
                                      'Keywords' => 'php'));
 foreach ($results as $result) {
 foreach ($results as $result) {
@@ -155,7 +157,7 @@ foreach ($results as $result) {
                 in the response.
                 in the response.
             </para>
             </para>
             <programlisting language="php"><![CDATA[
             <programlisting language="php"><![CDATA[
-$amazon = new Zend_Service_Amazon('AMAZON_API_KEY');
+$amazon = new Zend_Service_Amazon('AMAZON_API_KEY', 'US', 'AMAZON_SECRET_KEY');
 $results = $amazon->itemSearch(array(
 $results = $amazon->itemSearch(array(
     'SearchIndex'   => 'Books',
     'SearchIndex'   => 'Books',
     'Keywords'      => 'php',
     'Keywords'      => 'php',
@@ -201,7 +203,7 @@ foreach ($results as $result) {
                     respective values:
                     respective values:
                 </para>
                 </para>
                 <programlisting language="php"><![CDATA[
                 <programlisting language="php"><![CDATA[
-$query = new Zend_Service_Amazon_Query('MY_API_KEY');
+$query = new Zend_Service_Amazon_Query('MY_API_KEY', 'US', 'AMAZON_SECRET_KEY');
 $query->Category('Books')->Keywords('PHP');
 $query->Category('Books')->Keywords('PHP');
 $results = $query->search();
 $results = $query->search();
 foreach ($results as $result) {
 foreach ($results as $result) {

+ 96 - 16
library/Zend/Service/Amazon.php

@@ -21,6 +21,10 @@
  * @version    $Id$
  * @version    $Id$
  */
  */
 
 
+/**
+ * @see Zend_Rest_Client
+ */
+require_once 'Zend/Rest/Client.php';
 
 
 /**
 /**
  * @category   Zend
  * @category   Zend
@@ -39,6 +43,16 @@ class Zend_Service_Amazon
     public $appId;
     public $appId;
 
 
     /**
     /**
+     * @var string
+     */ 
+    protected $_secretKey = null;
+
+    /**
+     * @var string
+     */
+    protected $_baseUri = null;
+
+    /**
      * List of Amazon Web Service base URLs, indexed by country code
      * List of Amazon Web Service base URLs, indexed by country code
      *
      *
      * @var array
      * @var array
@@ -55,7 +69,7 @@ class Zend_Service_Amazon
      *
      *
      * @var Zend_Rest_Client
      * @var Zend_Rest_Client
      */
      */
-    protected $_rest;
+    protected $_rest = null;
 
 
 
 
     /**
     /**
@@ -66,11 +80,12 @@ class Zend_Service_Amazon
      * @throws Zend_Service_Exception
      * @throws Zend_Service_Exception
      * @return Zend_Service_Amazon
      * @return Zend_Service_Amazon
      */
      */
-    public function __construct($appId, $countryCode = 'US')
+    public function __construct($appId, $countryCode = 'US', $secretKey = null)
     {
     {
         $this->appId = (string) $appId;
         $this->appId = (string) $appId;
-        $countryCode = (string) $countryCode;
+        $this->_secretKey = $secretKey;
 
 
+        $countryCode = (string) $countryCode;
         if (!isset($this->_baseUriList[$countryCode])) {
         if (!isset($this->_baseUriList[$countryCode])) {
             /**
             /**
              * @see Zend_Service_Exception
              * @see Zend_Service_Exception
@@ -79,11 +94,7 @@ class Zend_Service_Amazon
             throw new Zend_Service_Exception("Unknown country code: $countryCode");
             throw new Zend_Service_Exception("Unknown country code: $countryCode");
         }
         }
 
 
-        /**
-         * @see Zend_Rest_Client
-         */
-        require_once 'Zend/Rest/Client.php';
-        $this->_rest = new Zend_Rest_Client($this->_baseUriList[$countryCode]);
+        $this->_baseUri = $this->_baseUriList[$countryCode];
     }
     }
 
 
 
 
@@ -97,10 +108,13 @@ class Zend_Service_Amazon
      */
      */
     public function itemSearch(array $options)
     public function itemSearch(array $options)
     {
     {
+        $client = $this->getRestClient();
+        $client->setUri($this->_baseUri);
+
         $defaultOptions = array('ResponseGroup' => 'Small');
         $defaultOptions = array('ResponseGroup' => 'Small');
         $options = $this->_prepareOptions('ItemSearch', $options, $defaultOptions);
         $options = $this->_prepareOptions('ItemSearch', $options, $defaultOptions);
-        $this->_rest->getHttpClient()->resetParameters();
-        $response = $this->_rest->restGet('/onca/xml', $options);
+        $client->getHttpClient()->resetParameters();
+        $response = $client->restGet('/onca/xml', $options);
 
 
         if ($response->isError()) {
         if ($response->isError()) {
             /**
             /**
@@ -134,19 +148,23 @@ class Zend_Service_Amazon
      */
      */
     public function itemLookup($asin, array $options = array())
     public function itemLookup($asin, array $options = array())
     {
     {
-        $defaultOptions = array('IdType' => 'ASIN', 'ResponseGroup' => 'Small');
+        $client = $this->getRestClient();
+        $client->setUri($this->_baseUri);
+        $client->getHttpClient()->resetParameters();
+
+        $defaultOptions = array('ResponseGroup' => 'Small');
         $options['ItemId'] = (string) $asin;
         $options['ItemId'] = (string) $asin;
         $options = $this->_prepareOptions('ItemLookup', $options, $defaultOptions);
         $options = $this->_prepareOptions('ItemLookup', $options, $defaultOptions);
-        $this->_rest->getHttpClient()->resetParameters();
-        $response = $this->_rest->restGet('/onca/xml', $options);
+        $response = $client->restGet('/onca/xml', $options);
 
 
         if ($response->isError()) {
         if ($response->isError()) {
             /**
             /**
              * @see Zend_Service_Exception
              * @see Zend_Service_Exception
              */
              */
             require_once 'Zend/Service/Exception.php';
             require_once 'Zend/Service/Exception.php';
-            throw new Zend_Service_Exception('An error occurred sending request. Status code: '
-                                           . $response->getStatus());
+            throw new Zend_Service_Exception(
+                'An error occurred sending request. Status code: ' . $response->getStatus()
+            );
         }
         }
 
 
         $dom = new DOMDocument();
         $dom = new DOMDocument();
@@ -179,9 +197,24 @@ class Zend_Service_Amazon
      */
      */
     public function getRestClient()
     public function getRestClient()
     {
     {
+        if($this->_rest === null) {
+            $this->_rest = new Zend_Rest_Client();
+        }
         return $this->_rest;
         return $this->_rest;
     }
     }
 
 
+    /**
+     * Set REST client
+     *
+     * @param Zend_Rest_Client
+     * @return Zend_Service_Amazon
+     */
+    public function setRestClient(Zend_Rest_Client $client)
+    {
+        $this->_rest = $client;
+        return $this;
+    }
+
 
 
     /**
     /**
      * Prepare options for request
      * Prepare options for request
@@ -193,9 +226,10 @@ class Zend_Service_Amazon
      */
      */
     protected function _prepareOptions($query, array $options, array $defaultOptions)
     protected function _prepareOptions($query, array $options, array $defaultOptions)
     {
     {
-        $options['SubscriptionId'] = $this->appId;
+        $options['AWSAccessKeyId'] = $this->appId;
         $options['Service']        = 'AWSECommerceService';
         $options['Service']        = 'AWSECommerceService';
         $options['Operation']      = (string) $query;
         $options['Operation']      = (string) $query;
+        $options['Version']        = '2005-10-05';
 
 
         // de-canonicalize out sort key
         // de-canonicalize out sort key
         if (isset($options['ResponseGroup'])) {
         if (isset($options['ResponseGroup'])) {
@@ -208,9 +242,55 @@ class Zend_Service_Amazon
         }
         }
 
 
         $options = array_merge($defaultOptions, $options);
         $options = array_merge($defaultOptions, $options);
+
+        if($this->_secretKey !== null) {
+            $options['Timestamp'] = gmdate("Y-m-d\TH:i:s\Z");;
+            ksort($options);
+            $options['Signature'] = self::computeSignature($this->_baseUri, $this->_secretKey, $options);
+        }
+
         return $options;
         return $options;
     }
     }
 
 
+    /**
+     * Compute Signature for Authentication with Amazon Product Advertising Webservices
+     *
+     * @param  string $baseUri
+     * @param  string $secretKey
+     * @param  array $options
+     * @return string
+     */
+    static public function computeSignature($baseUri, $secretKey, array $options)
+    {
+        require_once "Zend/Crypt/Hmac.php";
+
+        $signature = self::buildRawSignature($baseUri, $options);
+        return base64_encode(
+            Zend_Crypt_Hmac::compute($secretKey, 'sha256', $signature, Zend_Crypt_Hmac::BINARY)
+        );
+    }
+
+    /**
+     * Build the Raw Signature Text
+     *
+     * @param  string $baseUri
+     * @param  array $options
+     * @return string
+     */
+    static public function buildRawSignature($baseUri, $options)
+    {
+        ksort($options);
+        $params = array();
+        foreach($options AS $k => $v) {
+            $params[] = $k."=".rawurlencode($v);
+        }
+
+        return sprintf("GET\n%s\n/onca/xml\n%s",
+            str_replace('http://', '', $baseUri),
+            implode("&", $params)
+        );
+    }
+
 
 
     /**
     /**
      * Check result for errors
      * Check result for errors

+ 71 - 0
tests/Zend/Service/Amazon/OfflineTest.php

@@ -145,4 +145,75 @@ class Zend_Service_Amazon_OfflineTest extends PHPUnit_Framework_TestCase
 
 
         $similarproduct = new Zend_Service_Amazon_SimilarProduct($product);
         $similarproduct = new Zend_Service_Amazon_SimilarProduct($product);
     }
     }
+
+    public function dataSignatureEncryption()
+    {
+        return array(
+            array(
+                'http://webservices.amazon.com',
+                array(
+                    'Service' => 'AWSECommerceService',
+                    'AWSAccessKeyId' => '00000000000000000000',
+                    'Operation' => 'ItemLookup',
+                    'ItemId' => '0679722769',
+                    'ResponseGroup' => 'ItemAttributes,Offers,Images,Reviews',
+                    'Version' => '2009-01-06',
+                    'Timestamp' => '2009-01-01T12:00:00Z',
+                ),
+                "GET\n".
+                "webservices.amazon.com\n".
+                "/onca/xml\n".
+                "AWSAccessKeyId=00000000000000000000&ItemId=0679722769&Operation=I".
+                "temLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReview".
+                "s&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&".
+                "Version=2009-01-06",
+                'Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D'
+            ),
+            array(
+                'http://ecs.amazonaws.co.uk',
+                array(
+                    'Service' => 'AWSECommerceService',
+                    'AWSAccessKeyId' => '00000000000000000000',
+                    'Operation' => 'ItemSearch',
+                    'Actor' => 'Johnny Depp',
+                    'ResponseGroup' => 'ItemAttributes,Offers,Images,Reviews,Variations',
+                    'Version' => '2009-01-01',
+                    'SearchIndex' => 'DVD',
+                    'Sort' => 'salesrank',
+                    'AssociateTag' => 'mytag-20',
+                    'Timestamp' => '2009-01-01T12:00:00Z',
+                ),
+                "GET\n".
+                "ecs.amazonaws.co.uk\n".
+                "/onca/xml\n".
+                "AWSAccessKeyId=00000000000000000000&Actor=Johnny%20Depp&Associate".
+                "Tag=mytag-20&Operation=ItemSearch&ResponseGroup=ItemAttributes%2C".
+                "Offers%2CImages%2CReviews%2CVariations&SearchIndex=DVD&Service=AW".
+                "SECommerceService&Sort=salesrank&Timestamp=2009-01-01T12%3A00%3A0".
+                "0Z&Version=2009-01-01",
+                'TuM6E5L9u%2FuNqOX09ET03BXVmHLVFfJIna5cxXuHxiU%3D',
+            ),
+        );
+    }
+
+    /**
+     * Checking if signature Encryption due on August 15th for Amazon Webservice API is working correctly.
+     *
+     * @dataProvider dataSignatureEncryption
+     * @group ZF-7033
+     */
+    public function testSignatureEncryption($baseUri, $params, $expectedStringToSign, $expectedSignature)
+    {
+        $this->assertEquals(
+            $expectedStringToSign,
+            Zend_Service_Amazon::buildRawSignature($baseUri, $params)
+        );
+
+        $this->assertEquals(
+            $expectedSignature,
+            rawurlencode(Zend_Service_Amazon::computeSignature(
+                $baseUri, '1234567890', $params
+            ))
+        );
+    }
 }
 }

+ 15 - 3
tests/Zend/Service/Amazon/OnlineTest.php

@@ -80,9 +80,21 @@ class Zend_Service_Amazon_OnlineTest extends PHPUnit_Framework_TestCase
      */
      */
     public function setUp()
     public function setUp()
     {
     {
-        $this->_amazon = new Zend_Service_Amazon(constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID'));
+        if(!defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID') || !defined('TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY')) {
+            $this->markTestSkipped('Constants AccessKeyId and SecretKey have to be set.');
+        }
+
+        $this->_amazon = new Zend_Service_Amazon(
+            TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID,
+            'US',
+            TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY
+        );
 
 
-        $this->_query = new Zend_Service_Amazon_Query(constant('TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID'));
+        $this->_query = new Zend_Service_Amazon_Query(
+            TESTS_ZEND_SERVICE_AMAZON_ONLINE_ACCESSKEYID,
+            'US',
+            TESTS_ZEND_SERVICE_AMAZON_ONLINE_SECRETKEY
+        );
 
 
         $this->_httpClientAdapterSocket = new Zend_Http_Client_Adapter_Socket();
         $this->_httpClientAdapterSocket = new Zend_Http_Client_Adapter_Socket();
 
 
@@ -96,7 +108,7 @@ class Zend_Service_Amazon_OnlineTest extends PHPUnit_Framework_TestCase
 
 
     /**
     /**
      * Ensures that itemSearch() works as expected when searching for PHP books
      * Ensures that itemSearch() works as expected when searching for PHP books
-     *
+     * @group ItemSearchPhp
      * @return void
      * @return void
      */
      */
     public function testItemSearchBooksPhp()
     public function testItemSearchBooksPhp()