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

Commit

Permalink
feat: Allow both "Bearer" and "WebPush" as Auth tokens
Browse files Browse the repository at this point in the history
VAPID spec recently changed to specify "WebPush" as valid Authorization
token ID. Code allows either Bearer or WebPush while external code
transitions.

Closes #592
  • Loading branch information
jrconlin committed Aug 10, 2016
1 parent 8415600 commit 1891f91
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 26 deletions.
12 changes: 7 additions & 5 deletions autopush/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
# Our max TTL is 60 days realistically with table rotation, so we hard-code it
MAX_TTL = 60 * 60 * 24 * 60
VALID_TTL = re.compile(r'^\d+$')
AUTH_SCHEME = "bearer"
AUTH_SCHEMES = ["bearer", "webpush"]
PREF_SCHEME = "webpush"

status_codes = {
200: "OK",
201: "Created",
Expand Down Expand Up @@ -217,7 +219,7 @@ def _write_response(self, status_code, errno, message=None, headers=None,

def _write_unauthorized_response(self, message="Invalid authentication",
**kwargs):
headers = {"www-authenticate": AUTH_SCHEME}
headers = {"www-authenticate": PREF_SCHEME}
self._write_response(401, errno=109, message=message, headers=headers,
**kwargs)

Expand Down Expand Up @@ -352,7 +354,7 @@ def _process_auth(self, result):
"""Process the optional VAPID auth token.
VAPID requires two headers to be present;
`Authorization: Bearer ...` and `Crypto-Key: p256ecdsa=..`.
`Authorization: WebPush ...` and `Crypto-Key: p256ecdsa=..`.
The problem is that VAPID is optional and Crypto-Key can carry
content for other functions.
Expand All @@ -368,7 +370,7 @@ def _process_auth(self, result):
except ValueError:
raise VapidAuthException("Invalid Authorization header")
# if it's a bearer token containing what may be a JWT
if auth_type.lower() == AUTH_SCHEME and '.' in token:
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._invalid_auth)
Expand Down Expand Up @@ -852,7 +854,7 @@ def _validate_auth(self, uaid):
header.strip()).split(" ", 2)
except ValueError:
return False
if AUTH_SCHEME != token_type.lower():
if token_type.lower() not in AUTH_SCHEMES:
return False
if self.ap_settings.bear_hash_key:
for key in self.ap_settings.bear_hash_key:
Expand Down
12 changes: 6 additions & 6 deletions autopush/tests/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ def test_post_webpush_with_vapid_auth(self):
"sub": "mailto:admin@example.com"}

(token, crypto_key) = self._gen_jwt(header, payload)
auth = "Bearer %s" % token
auth = "WebPush %s" % token
""" # to verify that the object is encoded correctly
kd2 = utils.base64url_decode(crypto_key)
Expand Down Expand Up @@ -857,7 +857,7 @@ def test_post_webpush_with_other_than_vapid_auth(self):
"sub": "mailto:admin@example.com"}

(token, crypto_key) = self._gen_jwt(header, payload)
auth = "Bearer other_token"
auth = "WebPush other_token"
self.request_mock.headers["crypto-key"] = "p256ecdsa=%s" % crypto_key
self.request_mock.headers["authorization"] = auth
self.router_mock.get_uaid.return_value = dict(
Expand Down Expand Up @@ -969,7 +969,7 @@ def test_post_webpush_bad_sig(self):

(sig, crypto_key) = self._gen_jwt(header, payload)
sigs = sig.split('.')
auth = "Bearer %s.%s.%s" % (sigs[0], sigs[1], "invalid")
auth = "WebPush %s.%s.%s" % (sigs[0], sigs[1], "invalid")
self.request_mock.headers["crypto-key"] = "p256ecdsa=%s" % crypto_key
self.request_mock.headers["authorization"] = auth
self.router_mock.get_uaid.return_value = dict(
Expand Down Expand Up @@ -1003,7 +1003,7 @@ def test_post_webpush_bad_exp(self):

(token, crypto_key) = self._gen_jwt(header, payload)
self.request_mock.headers["crypto-key"] = "p256ecdsa=%s" % crypto_key
self.request_mock.headers["authorization"] = "Bearer %s" % token
self.request_mock.headers["authorization"] = "WebPush %s" % token
self.router_mock.get_uaid.return_value = dict(
router_type="webpush",
router_data=dict(),
Expand Down Expand Up @@ -1391,7 +1391,7 @@ def setUp(self):

self.status_mock = self.reg.set_status = Mock()
self.write_mock = self.reg.write = Mock()
self.auth = ("Bearer %s" %
self.auth = ("WebPush %s" %
generate_hash(self.reg.ap_settings.bear_hash_key[0],
dummy_uaid))

Expand Down Expand Up @@ -1636,7 +1636,7 @@ def handle_finish(value):
self._check_error(401, 109, 'Unauthorized')

self.finish_deferred.addCallback(handle_finish)
self.reg.request.headers["Authorization"] = "Bearer Invalid"
self.reg.request.headers["Authorization"] = "WebPush Invalid"
self.reg.post(router_type="simplepush",
uaid=dummy_uaid, chid=dummy_chid)
return self.finish_deferred
Expand Down
2 changes: 2 additions & 0 deletions autopush/tests/test_web_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ def test_init_info(self):
eq_(d["message_ttl"], "0")
eq_(d["authorization"], "bearer token fred")
self.request_mock.headers["x-forwarded-for"] = "local2"
self.request_mock.headers["authorization"] = "webpush token barney"
d = self.base._init_info()
eq_(d["remote_ip"], "local2")
eq_(d["authorization"], "webpush token barney")

def test_properties(self):
eq_(self.base.uaid, "")
Expand Down
34 changes: 32 additions & 2 deletions autopush/tests/test_web_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,36 @@ def test_valid_vapid_crypto_header(self):
eq_(errors, {})
ok_("jwt" in result)

def test_valid_vapid_crypto_header_webpush(self):
schema = self._makeFUT()
self.fernet_mock.decrypt.return_value = dummy_token

header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
"exp": int(time.time()) + 86400,
"sub": "mailto:admin@example.com"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "WebPush %s" % token
ckey = 'keyid="a1"; key="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": "stuff",
"authorization": auth,
"crypto-key": ckey
}
)

result, errors = schema.load(info)
eq_(errors, {})
ok_("jwt" in result)

@patch("autopush.web.validation.extract_jwt")
def test_invalid_vapid_crypto_header(self, mock_jwt):
schema = self._makeFUT()
Expand All @@ -587,7 +617,7 @@ def test_invalid_vapid_crypto_header(self, mock_jwt):
"sub": "mailto:admin@example.com"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "Bearer %s" % token
auth = "WebPush %s" % token
ckey = 'keyid="a1"; key="foo";p256ecdsa="%s"' % crypto_key
info = self._make_test_data(
body="asdfasdfasdfasdf",
Expand Down Expand Up @@ -619,7 +649,7 @@ def test_expired_vapid_header(self):
"sub": "mailto:admin@example.com"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "Bearer %s" % token
auth = "WebPush %s" % token
ckey = 'keyid="a1"; key="foo";p256ecdsa="%s"' % crypto_key
info = self._make_test_data(
body="asdfasdfasdfasdf",
Expand Down
12 changes: 7 additions & 5 deletions autopush/web/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
)

MAX_TTL = 60 * 60 * 24 * 60
AUTH_SCHEME = "bearer"
# Older versions used "bearer", newer specification requires "webpush"
AUTH_SCHEMES = ["bearer", "webpush"]
PREF_SCHEME = "webpush"


class ThreadedValidate(object):
Expand Down Expand Up @@ -297,22 +299,22 @@ def validate_auth(self, d):
except ValueError:
raise InvalidRequest("Invalid Authorization Header",
status_code=401, errno=109,
headers={"www-authenticate": AUTH_SCHEME})
headers={"www-authenticate": PREF_SCHEME})

# If its not a bearer token containing what may be JWT, stop
if auth_type.lower() != AUTH_SCHEME or '.' not in token:
if auth_type.lower() not in AUTH_SCHEMES or '.' not in token:
return

try:
jwt = extract_jwt(token, public_key)
except ValueError:
raise InvalidRequest("Invalid Authorization Header",
status_code=401, errno=109,
headers={"www-authenticate": AUTH_SCHEME})
headers={"www-authenticate": PREF_SCHEME})
if jwt.get('exp', 0) < time.time():
raise InvalidRequest("Invalid bearer token: Auth expired",
status_code=401, errno=109,
headers={"www-authenticate": AUTH_SCHEME})
headers={"www-authenticate": PREF_SCHEME})
jwt_crypto_key = base64url_encode(public_key)
d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)

Expand Down
16 changes: 8 additions & 8 deletions docs/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,13 @@ agent.

Most calls to the HTTP interface require a Authorization header. The
Authorization header is a bearer token, which has been provided by the
**Registration** call and is preceded by the token type word "Bearer".
**Registration** call and is preceded by the token type word "WebPush".

An example of the Authorization header would be:

::

Authorization: Bearer 0123abcdef
Authorization: WebPush 0123abcdef

Calls
-----
Expand Down Expand Up @@ -347,7 +347,7 @@ Update the current bridge token value
::

Authorization: Bearer {auth_token}
Authorization: WebPush {auth_token}

**Parameters:**

Expand All @@ -372,7 +372,7 @@ example:
.. code-block:: http
> PUT /v1/fcm/a1b2c3/registration/abcdef012345
> Authorization: Bearer 0123abcdef
> Authorization: WebPush 0123abcdef
>
> {"token": "5e6g7h8i"}
Expand All @@ -397,7 +397,7 @@ Acquire a new ChannelID for a given UAID.
::

Authorization: Bearer {auth_token}
Authorization: WebPush {auth_token}

**Parameters:**

Expand All @@ -416,7 +416,7 @@ example:
.. code-block:: http
> POST /v1/fcm/a1b2c3/registration/abcdef012345/subscription
> Authorization: Bearer 0123abcdef
> Authorization: WebPush 0123abcdef
>
> {}
Expand All @@ -443,7 +443,7 @@ is no longer valid.
::

Authorization: Bearer {auth_token}
Authorization: WebPush {auth_token}

**Parameters:**

Expand Down Expand Up @@ -471,7 +471,7 @@ Remove a given ChannelID subscription from a UAID.
::

Authorization: Bearer {auth_token}
Authorization: WebPush {auth_token}

**Parameters:**

Expand Down

0 comments on commit 1891f91

Please sign in to comment.