Fixed Library: Added signing to AWS requests.
authorMichael Ellis <me@michaelellis.ca>
Tue, 8 Sep 2009 21:04:44 +0000 (17:04 -0400)
committerMichael Ellis <me@michaelellis.ca>
Tue, 8 Sep 2009 21:04:44 +0000 (17:04 -0400)
ceo/pymazon.py
ceo/urwid/library.py
etc/library.cf

index 47fe6df..d27cc0c 100644 (file)
 from xml.dom import minidom, Node
 import urllib
 import time
 from xml.dom import minidom, Node
 import urllib
 import time
+import datetime
+import hashlib
+import base64
+import hmac
 
 class PyMazonError(Exception):
 
 class PyMazonError(Exception):
-       """Holds information about an error that occured during a pymazon request"""
-       def __init__(self, messages):
-               self.__message = '\n'.join(messages)
+    """Holds information about an error that occured during a pymazon request"""
+    def __init__(self, messages):
+        self.__message = '\n'.join(messages)
 
 
-       def __get_message(self):
-               return self.__message
+    def __get_message(self):
+        return self.__message
 
 
-       def __str__(self):
-               return repr(self.__message)
+    def __str__(self):
+        return repr(self.__message)
 
 
-       message = property(fget=__get_message)
+    message = property(fget=__get_message)
 
 
 class PyMazonBook:
 
 
 class PyMazonBook:
-       """Stores information about a book retrieved via PyMazon."""
-       def __init__(self, title, authors, publisher, year, isbn10, isbn13, edition):
-               self.__title = title
-               self.__authors = authors
-               self.__publisher = publisher
-               self.__year = year 
-               self.__isbn10 = isbn10
-               self.__isbn13 = isbn13
-               self.__edition = edition
-
-       def __str__(self):
-               return 'Title:     ' + self.title + '\n' + \
-                      'Author(s): ' + ', '.join(self.authors) + '\n' \
-                                'Publisher: ' + self.publisher + '\n' + \
-                                'Year:      ' + self.year + '\n' + \
-                                'ISBN-10:   ' + self.isbn10 + '\n' + \
-                                'ISBN-13:   ' + self.isbn13 + '\n' + \
-                                'Edition:   ' + self.edition
-
-       def __get_title(self):
-               return self.__title
-
-       def __get_authors(self):
-               return self.__authors
-       
-       def __get_publisher(self):
-               return self.__publisher
-
-       def __get_year(self):
-               return self.__year
-
-       def __get_isbn10(self):
-               return self.__isbn10
-
-       def __get_isbn13(self):
-               return self.__isbn13
-
-       def __get_edition(self):
-               return self.__edition
-
-       title = property(fget=__get_title)
-       authors = property(fget=__get_authors)
-       publisher = property(fget=__get_publisher)
-       year = property(fget=__get_year)
-       isbn10 = property(fget=__get_isbn10)
-       isbn13 = property(fget=__get_isbn13)
-       edition = property(fget=__get_edition)
+    """Stores information about a book retrieved via PyMazon."""
+    def __init__(self, title, authors, publisher, year, isbn10, isbn13, edition):
+        self.__title = title
+        self.__authors = authors
+        self.__publisher = publisher
+        self.__year = year 
+        self.__isbn10 = isbn10
+        self.__isbn13 = isbn13
+        self.__edition = edition
+
+    def __str__(self):
+        return 'Title:     ' + self.title + '\n' + \
+               'Author(s): ' + ', '.join(self.authors) + '\n' \
+                 'Publisher: ' + self.publisher + '\n' + \
+                 'Year:      ' + self.year + '\n' + \
+                 'ISBN-10:   ' + self.isbn10 + '\n' + \
+                 'ISBN-13:   ' + self.isbn13 + '\n' + \
+                 'Edition:   ' + self.edition
+
+    def __get_title(self):
+        return self.__title
+
+    def __get_authors(self):
+        return self.__authors
+    
+    def __get_publisher(self):
+        return self.__publisher
+
+    def __get_year(self):
+        return self.__year
+
+    def __get_isbn10(self):
+        return self.__isbn10
+
+    def __get_isbn13(self):
+        return self.__isbn13
+
+    def __get_edition(self):
+        return self.__edition
+
+    title = property(fget=__get_title)
+    authors = property(fget=__get_authors)
+    publisher = property(fget=__get_publisher)
+    year = property(fget=__get_year)
+    isbn10 = property(fget=__get_isbn10)
+    isbn13 = property(fget=__get_isbn13)
+    edition = property(fget=__get_edition)
 
 
 class PyMazon:
 
 
 class PyMazon:
-       """A method of looking up book information on Amazon."""
-       def __init__(self, accesskey):
-               self.__key = accesskey
-               self.__last_query_time = 0
-
-       def __form_request(self, isbn):
-               return 'http://webservices.amazon.com/onca/xml?' + \
-                      'Service=AWSECommerceService' + \
-                                '&Version=2008-08-19' + \
-                      '&AWSAccessKeyId=' + self.__key + \
-                                '&Operation=ItemLookup' + \
-                                '&ResponseGroup=ItemAttributes' + \
-                                '&IdType=ISBN' + \
-                                '&SearchIndex=Books' + \
-                                '&ItemId=' + isbn
-
-       def __elements_text(self, element, name):
-               result = []
-               matching = element.getElementsByTagName(name)
-               for match in matching:
-                       if len(match.childNodes) != 1:
-                               continue
-                       child = match.firstChild
-                       if child.nodeType != Node.TEXT_NODE:
-                               continue
-                       result.append(child.nodeValue.strip())
-               return result
-
-       def __format_errors(self, errors):
-               error_list = []
-               for error in errors:
-                       error_list.extend(self.__elements_text(error, 'Message'))
-               return error_list
-
-       def __extract_single(self, element, name):
-               matches = self.__elements_text(element, name)
-               if len(matches) == 0:
-                       return ''
-               return matches[0]
-
-       def lookup(self, isbn):
-               file = urllib.urlretrieve(self.__form_request(isbn))[0]
-               xmldoc = minidom.parse(file)
-
-               cur_time = time.time()
-               while cur_time - self.__last_query_time < 1.0:
-                       sleep(cur_time - self.__last_query_time)
-                       cur_time = time.time()
-               self.__last_query_time = cur_time
-
-               errors = xmldoc.getElementsByTagName('Errors')
-               if len(errors) != 0:
-                       raise PyMazonError, self.__format_errors(errors)
-
-               title = self.__extract_single(xmldoc, 'Title')
-               authors = self.__elements_text(xmldoc, 'Author')
-               publisher = self.__extract_single(xmldoc, 'Publisher')
-               year = self.__extract_single(xmldoc, 'PublicationDate')[0:4]
-               isbn10 = self.__extract_single(xmldoc, 'ISBN')
-               isbn13 = self.__extract_single(xmldoc, 'EAN')
-               edition = self.__extract_single(xmldoc, 'Edition')
-
-               return PyMazonBook(title, authors, publisher, year, isbn10, isbn13, edition)
+    """A method of looking up book information on Amazon."""
+    def __init__(self, accesskey, secretkey):
+        self.__key = accesskey
+        self.__secret = secretkey
+        self.__last_query_time = 0
+
+    def __form_request(self, isbn):
+        content = {}
+        dstamp = datetime.datetime.utcfromtimestamp(time.time())
+        content['Timestamp'] = dstamp.strftime('%Y-%m-%dT%H:%M:%S.000Z')
+        content['Service'] = 'AWSECommerceService'
+        content['Version'] = '2008-08-19'
+        content['Operation'] = 'ItemLookup'
+        content['ResponseGroup'] = 'ItemAttributes'
+        content['IdType'] = 'ISBN'
+        content['SearchIndex'] = 'Books'
+        content['ItemId'] = isbn
+        content['AWSAccessKeyId'] = self.__key
+
+        URI_String = []
+
+        for key, value in sorted(content.items()):
+            URI_String.append('%s=%s' % (key, urllib.quote(value)))
+
+        req = '&'.join(URI_String)
+        to_sign_req = 'GET\necs.amazonaws.com\n/onca/xml\n' + req
+
+        h = hmac.new(self.__secret, to_sign_req, hashlib.sha256)
+        sig = base64.b64encode(h.digest())
+        req += '&Signature=%s' % urllib.quote(sig)
+
+        return 'http://ecs.amazonaws.com/onca/xml?' + req
+
+    def __elements_text(self, element, name):
+        result = []
+        matching = element.getElementsByTagName(name)
+        for match in matching:
+            if len(match.childNodes) != 1:
+                continue
+            child = match.firstChild
+            if child.nodeType != Node.TEXT_NODE:
+                continue
+            result.append(child.nodeValue.strip())
+        return result
+
+    def __format_errors(self, errors):
+        error_list = []
+        for error in errors:
+            error_list.extend(self.__elements_text(error, 'Message'))
+        return error_list
+
+    def __extract_single(self, element, name):
+        matches = self.__elements_text(element, name)
+        if len(matches) == 0:
+            return ''
+        return matches[0]
+
+    def lookup(self, isbn):
+        file = urllib.urlretrieve(self.__form_request(isbn))[0]
+        xmldoc = minidom.parse(file)
+
+        cur_time = time.time()
+        while cur_time - self.__last_query_time < 1.0:
+            sleep(cur_time - self.__last_query_time)
+            cur_time = time.time()
+        self.__last_query_time = cur_time
+
+        errors = xmldoc.getElementsByTagName('Errors')
+        if len(errors) != 0:
+            raise PyMazonError, self.__format_errors(errors)
+
+        title = self.__extract_single(xmldoc, 'Title')
+        authors = self.__elements_text(xmldoc, 'Author')
+        publisher = self.__extract_single(xmldoc, 'Publisher')
+        year = self.__extract_single(xmldoc, 'PublicationDate')[0:4]
+        isbn10 = self.__extract_single(xmldoc, 'ISBN')
+        isbn13 = self.__extract_single(xmldoc, 'EAN')
+        edition = self.__extract_single(xmldoc, 'Edition')
+        
+        return PyMazonBook(title, authors, publisher, year, isbn10, isbn13, edition)
index 4d6118f..64d09c7 100644 (file)
@@ -21,7 +21,7 @@ def configure():
     """
     Load configuration
     """
     """
     Load configuration
     """
-    cfg_fields = [ "aws_account_key" ]
+    cfg_fields = [ "aws_account_key", "aws_secret_key" ]
     temp_cfg = conf.read(CONFIG_FILE)
     conf.check_string_fields(CONFIG_FILE, cfg_fields, temp_cfg)
     cfg.update(temp_cfg)
     temp_cfg = conf.read(CONFIG_FILE)
     conf.check_string_fields(CONFIG_FILE, cfg_fields, temp_cfg)
     cfg.update(temp_cfg)
@@ -127,7 +127,7 @@ class BookAddPage(WizardPanel):
         isbn = self.isbn.get_edit_text()
 
         try:
         isbn = self.isbn.get_edit_text()
 
         try:
-            pymazon = PyMazon(cfg["aws_account_key"])
+            pymazon = PyMazon(cfg["aws_account_key"], cfg["aws_secret_key"])
             book = pymazon.lookup(isbn)
 
             currents = lib.Book.select(lib.Book.q.isbn==isbn)
             book = pymazon.lookup(isbn)
 
             currents = lib.Book.select(lib.Book.q.isbn==isbn)
index 41d11d8..afdeccb 100644 (file)
@@ -2,3 +2,4 @@
 
 library_connect_string = "postgres://librarian:PWPWPWPWPWPWPWPWPWPW@127.0.0.1/library"
 aws_account_key = "KEYKEYKEYKEYKEYKEYKY"
 
 library_connect_string = "postgres://librarian:PWPWPWPWPWPWPWPWPWPW@127.0.0.1/library"
 aws_account_key = "KEYKEYKEYKEYKEYKEYKY"
+aws_secret_key = "KEYKEYKEYKEYKEYKEYKY"