Permalink
Browse files

Switch to using requests for external connections.

  • Loading branch information...
Osmose committed Apr 3, 2012
1 parent 1712462 commit 8f5f3d9e44fb5044fc73189ecbb89afe83c52302
View
@@ -5,10 +5,11 @@
import collections
import threading
import time
-
from urlparse import urljoin
-from browserid.utils import secure_urlopen
+import requests
+from requests.exceptions import RequestException
+
from browserid.errors import (ConnectionError,
InvalidIssuerError)
@@ -141,71 +142,44 @@ def __len__(self):
return len(self.items_map)
-def fetch_public_key(hostname, well_known_url=None):
+def _get(url):
+ """Fetch resource with requests."""
+ try:
+ return requests.get(url)
+ except RequestException, e:
+ raise ConnectionError(str(e))
+
+
+def fetch_public_key(hostname, well_known_url=WELL_KNOWN_URL):
"""Fetch the BrowserID public key for the given hostname.
This function uses the well-known BrowserID meta-data file to extract
the public key for the given hostname.
"""
- if well_known_url is None:
- well_known_url = WELL_KNOWN_URL
+ hostname = 'https://%s' % hostname
- hostname = "https://" + hostname
# Try to find the public key. If it can't be found then we
# raise an InvalidIssuerError. Any other connection-related
# errors are passed back up to the caller.
- try:
- # Try to read the well-known browserid file to load the key.
+ response = _get(urljoin(hostname, well_known_url))
+ if response.status_code == 200:
try:
- browserid_url = urljoin(hostname, well_known_url)
- browserid_data = urlread(browserid_url)
- except ConnectionError, e:
- if "404" not in str(e):
- raise
- # The well-known file was not found, try falling back to
- # just "/pk". Not really a good idea, but that's currently
- # the only way to get browserid.org's public key.
- pubkey_url = urljoin(hostname, "/pk")
- key = urlread(urljoin(hostname, pubkey_url))
+ key = json.loads(response.text)['public-key']
+ except (ValueError, KeyError):
+ raise InvalidIssuerError('Host %r has malformed public key '
+ 'document' % hostname)
+ else:
+ # The well-known file was not found, try falling back to
+ # just "/pk".
+ response = _get(urljoin(hostname, '/pk'))
+ if response.status_code == 200:
try:
- key = json.loads(key)
+ key = json.loads(response.text)
except ValueError:
- msg = "Host %r has malformed public key document"
- raise InvalidIssuerError(msg % (hostname,))
+ raise InvalidIssuerError('Host %r has malformed BrowserID '
+ 'metadata document' % hostname)
else:
- # The well-known file was found, it must contain the key
- # data as part of its JSON response.
- try:
- key = json.loads(browserid_data)["public-key"]
- except (ValueError, KeyError):
- msg = "Host %r has malformed BrowserID metadata document"
- raise InvalidIssuerError(msg % (hostname,))
- return key
- except ConnectionError, e:
- if "404" not in str(e):
- raise
- msg = "Host %r does not declare support for BrowserID" % (hostname,)
- raise InvalidIssuerError(msg)
-
+ raise InvalidIssuerError('Host %r does not declare support for '
+ 'BrowserID' % hostname)
-def urlread(url, data=None):
- """Read the given URL, return response as a string."""
- # Anything that goes wrong inside this function will
- # be re-raised as an instance of ConnectionError.
- try:
- resp = secure_urlopen(url, data)
- try:
- info = resp.info()
- except AttributeError:
- info = {}
- content_length = info.get("Content-Length")
- if content_length is None:
- data = resp.read()
- else:
- try:
- data = resp.read(int(content_length))
- except ValueError:
- raise ConnectionError("server sent invalid content-length")
- except Exception, e:
- raise ConnectionError(str(e))
- return data
+ return key
@@ -0,0 +1,81 @@
+import json
+import unittest
+
+from mock import Mock, patch
+from requests.exceptions import RequestException
+
+from browserid.certificates import fetch_public_key
+from browserid.errors import ConnectionError, InvalidIssuerError
+
+
+# Retrieved from browserid.org on April 3rd 2012
+BROWSERID_PK = (
+ '{"public-key":{"algorithm":"RS","n":"175097498616944944948724600'
+ '5376783505040424585022287992333285107040747762043794156382037076'
+ '4990477086508806099113661589535215545809212436371869690217935480'
+ '2753014948839838403516326408223252892569346143048785710656475684'
+ '9535475273645907806528415369926171343773128172627893352378261396'
+ '0153494025694829910802495907763077221584500090734186210456302804'
+ '6884323084778492418923884368673543934239778647619964884232166051'
+ '7909653959911229288600229842193433562918970249484466937121698566'
+ '1583323059605724956419024024496484812121544425787678170853739436'
+ '5238417167558546493512404073066199364247440288962324288605736789'
+ '20055912798079","e":"65537"}}')
+BROWSERID_PK_PY = json.loads(BROWSERID_PK)
+
+
+class TestFetchPublicKey(unittest.TestCase):
+ @patch('browserid.certificates.requests')
+ def _fetch(self, hostname, requests, well_known_url=None,
+ side_effect=None, response_text='', status_code=200):
+ response = Mock()
+ response.text = response_text
+ response.status_code = status_code
+ requests.get.side_effect = side_effect
+ requests.get.return_value = response
+
+ kwargs = {}
+ if well_known_url is not None:
+ kwargs['well_known_url'] = well_known_url
+
+ return fetch_public_key(hostname, **kwargs)
+
+ def test_connection_error(self):
+ """If there is an error connecting, raise a ConnectionError."""
+ with self.assertRaises(ConnectionError):
+ self._fetch('test.com', side_effect=RequestException)
+
+ @patch('browserid.certificates.fetch_public_key')
+ def test_missing_well_known_document(self, fetch):
+ with self.assertRaises(InvalidIssuerError):
+ self._fetch('test.com', status_code=404)
+
+ def test_malformed_well_known_document(self):
+ response_text = 'I AINT NO JSON, FOOL!'
+ with self.assertRaises(InvalidIssuerError):
+ self._fetch('test.com', response_text=response_text)
+
+ def test_malformed_pub_key_document(self):
+ # We need the first request to raise a 404, so we replace
+ # post with a custom function here.
+ def post(url, data):
+ response = Mock()
+ if not post.called:
+ response.status_code = 404
+ post.called = True
+ response.text = 'I AINT NO JSON, FOOL!'
+ return response
+ post.called = False
+
+ with patch('browserid.certificates.requests') as requests:
+ requests.post = post
+ with self.assertRaises(InvalidIssuerError):
+ fetch_public_key('test.com')
+
+ def test_well_known_doc_with_no_public_key(self):
+ with self.assertRaises(InvalidIssuerError):
+ self._fetch('test.com', response_text='{}')
+
+ def test_successful_fetch(self):
+ key = self._fetch('test.com', response_text=BROWSERID_PK)
+ self.assertEquals(key, BROWSERID_PK_PY['public-key'])
@@ -76,38 +76,6 @@ def shutdown(self):
class TestUtils(unittest.TestCase):
- def test_secure_urlopen(self):
- server = TestingServer()
- server.start()
- try:
- kwds = {"timeout": 1}
- # We don't trust the server's certificate, so this fails
- # if we're doing strong validation.
- try:
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- secure_urlopen(server.base_url, **kwds)
- except ConnectionError:
- # This means we have a system ca_certs file.
- # The request is unverified and should therefore fail.
- pass
- else:
- # This means we have no system ca_certs file.
- # We issue a warning and forego verification.
- self.assertEquals(len(w), 1) # pragma: nocover
- # The certificate doesn't belong to localhost, so this fails.
- kwds["ca_certs"] = server.certfile
- self.assertRaises(ConnectionError,
- secure_urlopen, server.base_url, **kwds)
- # Set a valid cert for local host, trust it, we succeed.
- server.certfile = _filepath("certs/localhost.crt")
- server.keyfile = _filepath("certs/localhost.key")
- kwds["ca_certs"] = server.certfile
- self.assertEquals(secure_urlopen(server.base_url, **kwds).read(),
- "OK")
- finally:
- server.shutdown()
-
def test_encode_decode_bytes(self):
self.assertEquals("HELLO", decode_bytes(encode_bytes("HELLO")))
self.assertEquals("HELLO", decode_bytes(encode_bytes(u"HELLO")))
Oops, something went wrong.

0 comments on commit 8f5f3d9

Please sign in to comment.