From 1d218ce802ae33119ba2e011cf20c3f26e7697ed Mon Sep 17 00:00:00 2001 From: jrconlin Date: Mon, 6 Jun 2016 14:01:05 -0700 Subject: [PATCH] feat: make python2/3 compatible Closes #3 --- .coveragerc | 1 + CHANGELOG.md | 3 +++ pywebpush/__init__.py | 31 ++++++++++++++++++++----------- pywebpush/tests/test_webpush.py | 19 ++++++++++--------- setup.py | 2 +- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/.coveragerc b/.coveragerc index de5541b..ce6de6e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,3 @@ [report] omit = *noseplugin* +show_missing = True diff --git a/CHANGELOG.md b/CHANGELOG.md index 7464fb4..cf1962e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.4.0 (2016-06-05) +feat: make python 2.7 / 3.5 polyglot + ## 0.3.4 (2016-05-17) bug: make header keys case insenstive diff --git a/pywebpush/__init__.py b/pywebpush/__init__.py index fc79a4d..8e9911f 100644 --- a/pywebpush/__init__.py +++ b/pywebpush/__init__.py @@ -86,6 +86,11 @@ class WebPusher: the client. """ + # Python 2 v. 3 hack + try: + self.basestr = basestring + except NameError: + self.basestr = str if 'endpoint' not in subscription_info: raise WebPushException("subscription_info missing endpoint URL") if 'keys' not in subscription_info: @@ -95,22 +100,22 @@ class WebPusher: for k in ['p256dh', 'auth']: if keys.get(k) is None: raise WebPushException("Missing keys value: %s", k) - receiver_raw = base64.urlsafe_b64decode( - self._repad(keys['p256dh'].encode('utf8'))) + if isinstance(keys[k], self.basestr): + keys[k] = bytes(keys[k].encode('utf8')) + receiver_raw = base64.urlsafe_b64decode(self._repad(keys['p256dh'])) if len(receiver_raw) != 65 and receiver_raw[0] != "\x04": raise WebPushException("Invalid p256dh key specified") self.receiver_key = receiver_raw - self.auth_key = base64.urlsafe_b64decode( - self._repad(keys['auth'].encode('utf8'))) + self.auth_key = base64.urlsafe_b64decode(self._repad(keys['auth'])) - def _repad(self, str): + def _repad(self, data): """Add base64 padding to the end of a string, if required""" - return str + "===="[:len(str) % 4] + return data + b"===="[:len(data) % 4] def encode(self, data): """Encrypt the data. - :param data: A serialized block of data (String, JSON, bit array, + :param data: A serialized block of byte data (String, JSON, bit array, etc.) Make sure that whatever you send, your client knows how to understand it. @@ -124,6 +129,9 @@ class WebPusher: # ID tag. server_key_id = base64.urlsafe_b64encode(server_key.get_pubkey()[1:]) + if isinstance(data, self.basestr): + data = bytes(data.encode('utf8')) + # http_ece requires that these both be set BEFORE encrypt or # decrypt is called if you specify the key as "dh". http_ece.keys[server_key_id] = server_key @@ -138,8 +146,8 @@ class WebPusher: return CaseInsensitiveDict({ 'crypto_key': base64.urlsafe_b64encode( - server_key.get_pubkey()).strip('='), - 'salt': base64.urlsafe_b64encode(salt).strip("="), + server_key.get_pubkey()).strip(b'='), + 'salt': base64.urlsafe_b64encode(salt).strip(b'='), 'body': encrypted, }) @@ -160,11 +168,12 @@ class WebPusher: crypto_key = headers.get("crypto-key", "") if crypto_key: crypto_key += ',' - crypto_key += "keyid=p256dh;dh=" + encoded["crypto_key"] + crypto_key += "keyid=p256dh;dh=" + encoded["crypto_key"].decode('utf8') headers.update({ 'crypto-key': crypto_key, 'content-encoding': 'aesgcm', - 'encryption': "keyid=p256dh;salt=" + encoded['salt'], + 'encryption': "keyid=p256dh;salt=" + + encoded['salt'].decode('utf8'), }) if 'ttl' not in headers or ttl: headers['ttl'] = ttl diff --git a/pywebpush/tests/test_webpush.py b/pywebpush/tests/test_webpush.py index 5a7d66c..93fc059 100644 --- a/pywebpush/tests/test_webpush.py +++ b/pywebpush/tests/test_webpush.py @@ -15,9 +15,9 @@ class WebpushTestCase(unittest.TestCase): return { "endpoint": "https://example.com/", "keys": { - 'auth': base64.urlsafe_b64encode(os.urandom(16)).strip('='), + 'auth': base64.urlsafe_b64encode(os.urandom(16)).strip(b'='), 'p256dh': base64.urlsafe_b64encode( - recv_key.get_pubkey()).strip('='), + recv_key.get_pubkey()).strip(b'='), } } @@ -31,6 +31,11 @@ class WebpushTestCase(unittest.TestCase): u"auth": u"k8JV6sjdbhAi1n3_LDBLvA" } } + rk_decode = (b'\x04\xea\xe7"\xc9W\xadJ0\xd9P3(%\x00\x13\x8b' + b'\x08l\xad4u\xa1\x19\n\xcc\x0eq\xff&\xdd1' + b'|W\xcd\x81\xf8\xeaN\x83\x92[\x99\x82\xe0\xe3' + b'\x89\x11r\xf4\x02\xd4M\xa00\x9b!\xb1F\x00' + b'\xfb\xfc\xcc=\x1f') self.assertRaises( WebPushException, WebPusher, @@ -55,12 +60,8 @@ class WebpushTestCase(unittest.TestCase): push = WebPusher(subscription_info) eq_(push.subscription_info, subscription_info) - eq_(push.receiver_key, ('\x04\xea\xe7"\xc9W\xadJ0\xd9P3(%\x00\x13\x8b' - '\x08l\xad4u\xa1\x19\n\xcc\x0eq\xff&\xdd1' - '|W\xcd\x81\xf8\xeaN\x83\x92[\x99\x82\xe0\xe3' - '\x89\x11r\xf4\x02\xd4M\xa00\x9b!\xb1F\x00' - '\xfb\xfc\xcc=\x1f')) - eq_(push.auth_key, '\x93\xc2U\xea\xc8\xddn\x10"\xd6}\xff,0K\xbc') + eq_(push.receiver_key, rk_decode) + eq_(push.auth_key, b'\x93\xc2U\xea\xc8\xddn\x10"\xd6}\xff,0K\xbc') def test_encode(self): recv_key = pyelliptic.ECC(curve="prime256v1") @@ -88,7 +89,7 @@ class WebpushTestCase(unittest.TestCase): authSecret=raw_auth ) - eq_(decoded, data) + eq_(decoded.decode('utf8'), data) @patch("requests.post") def test_send(self, mock_post): diff --git a/setup.py b/setup.py index bd00e72..a8e17c9 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import os from setuptools import find_packages, setup -__version__ = "0.3.4" +__version__ = "0.4.0" def read_from(file):