Skip to content

Commit 0d2d9c8

Browse files
committed
Cleanup and add travis
Conflicts: python/py_vapid/__init__.py
1 parent d549680 commit 0d2d9c8

File tree

4 files changed

+88
-31
lines changed

4 files changed

+88
-31
lines changed

.travis.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
language: python
2+
python:
3+
- "2.7"
4+
install:
5+
- cd python
6+
- pip install -r requirements.txt
7+
- pip install -r test-requirements.txt
8+
script:
9+
- nosetests
10+
- flake8 pywebpush
11+
after_success:
12+
- coverage report --omit py_vapid/main.py

python/.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[report]
2+
omit = *noseplugin*
3+
show_missing = true

python/py_vapid/__init__.py

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@ class Vapid(object):
2525

2626
def __init__(self, private_key_file=None, private_key=None):
2727
"""Initialize VAPID using an optional file containing a private key
28-
in PEM format.
28+
in PEM format, or a string containing the PEM formatted private key.
29+
30+
:param private_key_file: Name of the file containing the private key
31+
:type private_key_file: str
32+
:param private_key: A private key in PEM format
33+
:type private_key: str
2934
30-
:param private_key_file: The name of the file containing the
31-
private key
3235
"""
3336
if private_key_file:
3437
if not os.path.isfile(private_key_file):
@@ -50,31 +53,46 @@ def __init__(self, private_key_file=None, private_key=None):
5053

5154
@property
5255
def private_key(self):
53-
"""Return the private key."""
56+
"""The VAPID private ECDSA key"""
5457
if not self._private_key:
5558
raise VapidException(
5659
"No private key defined. Please import or generate a key.")
5760
return self._private_key
5861

5962
@private_key.setter
6063
def private_key(self, value):
61-
"""Set the private key."""
64+
"""Set the VAPID private ECDSA key
65+
66+
:param value: the byte array containing the private ECDSA key data
67+
:type value: bytes
68+
69+
"""
6270
self._private_key = value
6371

6472
@property
6573
def public_key(self):
66-
"""Return the public key."""
74+
"""The VAPID public ECDSA key
75+
76+
The public key is currently read only. Set it via the `.private_key`
77+
method.
78+
79+
"""
6780
if not self._public_key:
6881
self._public_key = self.private_key.get_verifying_key()
6982
return self._public_key
7083

7184
def generate_keys(self):
7285
"""Generate a valid ECDSA Key Pair."""
7386
self.private_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
74-
self.public_key
87+
self._public_key = self.private_key.get_verifying_key()
7588

7689
def save_key(self, key_file):
77-
"""Save the private key to a PEM file."""
90+
"""Save the private key to a PEM file.
91+
92+
:param key_file: The file path to save the private key data
93+
:type key_file: str
94+
95+
"""
7896
file = open(key_file, "wb")
7997
if not self._private_key:
8098
self.generate_keys()
@@ -84,30 +102,52 @@ def save_key(self, key_file):
84102
def save_public_key(self, key_file):
85103
"""Save the public key to a PEM file.
86104
:param key_file: The name of the file to save the public key
105+
:type key_file: str
106+
87107
"""
88108
with open(key_file, "wb") as file:
89109
file.write(self.public_key.to_pem())
90110
file.close()
91111

92-
def validate(self, token):
93-
"""Sign a Valdiation token from the dashboard"""
94-
sig = self.private_key.sign(token, hashfunc=self._hasher)
95-
token = base64.urlsafe_b64encode(sig)
96-
return token
112+
def validate(self, validation_token):
113+
"""Sign a Valdiation token from the dashboard
114+
115+
:param validation_token: Short validation token from the dev dashboard
116+
:type validation_token: str
117+
:returns: corresponding token for key verification
118+
:rtype: str
119+
120+
"""
121+
sig = self.private_key.sign(validation_token, hashfunc=self._hasher)
122+
verification_token = base64.urlsafe_b64encode(sig)
123+
return verification_token
97124

98-
def verify_token(self, sig, token):
99-
"""Verify the signature against the token."""
100-
hsig = base64.urlsafe_b64decode(sig)
101-
return self.public_key.verify(hsig, token,
125+
def verify_token(self, validation_token, verification_token):
126+
"""Internally used to verify the verification token is correct.
127+
128+
:param validation_token: Provided validation token string
129+
:type validation_token: str
130+
:param verification_token: Generated verification token
131+
:type verification_token: str
132+
:returns: Boolean indicating if verifictation token is valid.
133+
:rtype: boolean
134+
135+
"""
136+
hsig = base64.urlsafe_b64decode(verification_token)
137+
return self.public_key.verify(hsig, validation_token,
102138
hashfunc=self._hasher)
103139

104140
def sign(self, claims, crypto_key=None):
105141
"""Sign a set of claims.
106142
:param claims: JSON object containing the JWT claims to use.
143+
:type claims: dict
107144
:param crypto_key: Optional existing crypto_key header content. The
108145
vapid public key will be appended to this data.
109-
:returns result: a hash containing the header fields to use in
146+
:type crypto_key: str
147+
:returns: a hash containing the header fields to use in
110148
the subscription update.
149+
:rtype: dict
150+
111151
"""
112152
if not claims.get('exp'):
113153
claims['exp'] = int(time.time()) + 86400

python/py_vapid/tests/test_vapid.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
from jose import jws
1010
from py_vapid import Vapid, VapidException
1111

12-
T_PRIVATE = """-----BEGIN EC PRIVATE KEY-----
12+
T_DER = """
1313
MHcCAQEEIPeN1iAipHbt8+/KZ2NIF8NeN24jqAmnMLFZEMocY8RboAoGCCqGSM49
1414
AwEHoUQDQgAEEJwJZq/GN8jJbo1GGpyU70hmP2hbWAUpQFKDByKB81yldJ9GTklB
1515
M5xqEwuPM7VuQcyiLDhvovthPIXx+gsQRQ==
16-
-----END EC PRIVATE KEY-----
1716
"""
17+
T_PRIVATE = ("-----BEGIN EC PRIVATE KEY-----{}"
18+
"-----END EC PRIVATE KEY-----\n").format(T_DER)
1819

1920
T_PUBLIC = """-----BEGIN PUBLIC KEY-----
2021
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEJwJZq/GN8jJbo1GGpyU70hmP2hb
@@ -48,6 +49,13 @@ def test_init(self):
4849
v2 = Vapid(private_key=T_PRIVATE)
4950
eq_(v2.private_key.to_pem(), T_PRIVATE)
5051
eq_(v2.public_key.to_pem(), T_PUBLIC)
52+
v3 = Vapid(private_key=T_DER)
53+
eq_(v3.private_key.to_pem(), T_PRIVATE)
54+
eq_(v3.public_key.to_pem(), T_PUBLIC)
55+
no_exist = '/tmp/not_exist'
56+
Vapid(private_key_file=no_exist)
57+
ok_(os.path.isfile(no_exist))
58+
os.unlink(no_exist)
5159

5260
@patch("ecdsa.SigningKey.from_pem", side_effect=Exception)
5361
def test_init_bad_priv(self, mm):
@@ -57,19 +65,12 @@ def test_init_bad_priv(self, mm):
5765

5866
def test_private(self):
5967
v = Vapid()
60-
61-
def getKey(v):
62-
v.private_key
63-
64-
self.assertRaises(VapidException, getKey, v)
68+
self.assertRaises(VapidException, lambda x=None: v.private_key)
6569

6670
def test_public(self):
6771
v = Vapid()
6872

69-
def getKey(v):
70-
v.public_key
71-
72-
self.assertRaises(VapidException, getKey, v)
73+
self.assertRaises(VapidException, lambda x=None: v.public_key)
7374

7475
def test_gen_key(self):
7576
v = Vapid()
@@ -95,6 +96,8 @@ def test_validate(self):
9596
ok_(v.public_key.verify(base64.urlsafe_b64decode(vtoken),
9697
msg,
9798
hashfunc=hashlib.sha256))
99+
# test verify
100+
ok_(v.verify_token(msg, vtoken))
98101

99102
def test_sign(self):
100103
v = Vapid("/tmp/private")
@@ -103,7 +106,7 @@ def test_sign(self):
103106
eq_(result['Crypto-Key'],
104107
'id=previous,'
105108
'p256ecdsa=' + T_PUBLIC_RAW)
106-
items = jws.verify(result['Authorization'][7:],
109+
items = jws.verify(result['Authorization'].split(' ')[1],
107110
v.public_key,
108111
algorithms=["ES256"])
109112
eq_(json.loads(items), claims)
@@ -116,4 +119,3 @@ def test_bad_sign(self):
116119
self.assertRaises(VapidException,
117120
v.sign,
118121
{'aud': "p.example.com"})
119-

0 commit comments

Comments
 (0)