Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
bug: trap JWS/JWT errors from being reported as Sentry Errors
Browse files Browse the repository at this point in the history
Silence Jose errors from handling the Auth header.

Closes #610
  • Loading branch information
jrconlin committed Aug 19, 2016
1 parent 8391f8a commit 3e0dd71
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
11 changes: 11 additions & 0 deletions autopush/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
)
from cryptography.fernet import InvalidToken
from cryptography.hazmat.primitives import constant_time
from jose import JOSEError
from twisted.internet.defer import Deferred
from twisted.internet.threads import deferToThread

Expand Down Expand Up @@ -245,6 +246,14 @@ def _overload_err(self, fail):
self._write_response(503, errno=201,
message="Please slow message send rate")

def _jws_err(self, fail):
"""errBack for JWS/JWT exceptions"""
fail.trap(JOSEError)
self.log.info(format="Authorization Failure: %s" % fail.value,
status_code=401, errno=109, **self._client_info)
self._write_response(401, errno=109,
message="Invalid Authorization")

def _router_response(self, response):
for name, val in response.headers.items():
self.set_header(name, val)
Expand Down Expand Up @@ -374,6 +383,7 @@ def _process_auth(self, result):
if auth_type.lower() in AUTH_SCHEMES and '.' in token:
d = deferToThread(extract_jwt, token, public_key)
d.addCallback(self._store_auth, public_key, token, result)
d.addErrback(self._jws_err)
d.addErrback(self._invalid_auth)
return d
# otherwise, it's not, so ignore the VAPID data.
Expand Down Expand Up @@ -471,6 +481,7 @@ def put(self, api_ver="v0", token=None):
crypto_key_header)
d.addCallback(self._process_auth)
d.addCallback(self._token_valid)
d.addErrback(self._jws_err)
d.addErrback(self._auth_err)
d.addErrback(self._token_err)
d.addErrback(self._response_err)
Expand Down
17 changes: 17 additions & 0 deletions autopush/tests/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from hashlib import sha256

import ecdsa
import jose
import twisted.internet.base
from cryptography.fernet import Fernet, InvalidToken
from cyclone.web import Application
Expand Down Expand Up @@ -1677,6 +1678,22 @@ def handle_finish(value):
uaid=dummy_uaid, chid=dummy_chid)
return self.finish_deferred

@patch('uuid.uuid4', return_value=uuid.UUID(dummy_uaid))
@patch('jose.jws.verify', side_effect=jose.exceptions.JWTError)
def test_post_bad_jwt(self, *args):
self.reg.request.body = json.dumps(dict(
channelID=dummy_chid,
))

def handle_finish(value):
self._check_error(401, 109, 'Unauthorized')

self.finish_deferred.addCallback(handle_finish)
self.reg.request.headers["Authorization"] = "WebPush Dummy"
self.reg.post(router_type="webpush",
uaid=dummy_uaid, chid=dummy_chid)
return self.finish_deferred

@patch('uuid.uuid4', return_value=uuid.UUID(dummy_uaid))
def test_post_uaid_chid(self, *args):
self.reg.request.body = json.dumps(dict(
Expand Down
36 changes: 36 additions & 0 deletions autopush/tests/test_web_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from cryptography.fernet import InvalidToken
from jose import jws
from jose.exceptions import JWTClaimsError
from marshmallow import Schema, fields
from mock import Mock, patch
from moto import mock_dynamodb2
Expand Down Expand Up @@ -689,6 +690,41 @@ def test_invalid_encryption_header(self, mock_jwt):
eq_(cm.exception.status_code, 401)
eq_(cm.exception.errno, 110)

@patch("autopush.web.validation.extract_jwt")
def test_invalid_encryption_jwt(self, mock_jwt):
schema = self._makeFUT()
self.fernet_mock.decrypt.return_value = dummy_token
# use a deeply superclassed error to make sure that it gets picked up.
mock_jwt.side_effect = JWTClaimsError("invalid claim")

header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://push.example.com",
"exp": int(time.time()) + 86400,
"sub": "mailto:[email protected]"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "Bearer %s" % token
ckey = 'keyid="a1"; dh="foo";p256ecdsa="%s"' % crypto_key
info = self._make_test_data(
body="asdfasdfasdfasdf",
path_kwargs=dict(
api_ver="v0",
token="asdfasdf",
),
headers={
"content-encoding": "aes128",
"encryption": "salt=stuff",
"authorization": auth,
"crypto-key": ckey
}
)

with assert_raises(InvalidRequest) as cm:
schema.load(info)

eq_(cm.exception.status_code, 401)
eq_(cm.exception.errno, 109)

@patch("autopush.web.validation.extract_jwt")
def test_invalid_crypto_key_header_content(self, mock_jwt):
schema = self._makeFUT()
Expand Down
5 changes: 3 additions & 2 deletions autopush/web/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ItemNotFound,
)
from cryptography.fernet import InvalidToken
from jose import JOSEError
from marshmallow import (
Schema,
fields,
Expand Down Expand Up @@ -320,8 +321,8 @@ def validate_auth(self, d):

try:
jwt = extract_jwt(token, public_key)
except ValueError:
raise InvalidRequest("Invalid Authorization Header",
except (ValueError, JOSEError) as ex:
raise InvalidRequest("Invalid Authorization Header: %s" % str(ex),
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})
if jwt.get('exp', 0) < time.time():
Expand Down

0 comments on commit 3e0dd71

Please sign in to comment.