def test_authentication_with_busted_signature_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) signature = parse_authz_header(req)["mac"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(signature, "XXX" + signature) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401)
def test_authentication_with_busted_signature_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) signature = parse_authz_header(req)["mac"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(signature, "XXX" + signature) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401)
def test_authentication_with_busted_token_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) token = parse_authz_header(req)["oauth_consumer_key"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(token, "XXX" + token) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401)
def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = Request.blank("/public") resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with valid credentials is allowed access. session = self._start_session() req = Request.blank("/public") sign_request(req, **session) resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with invalid credentials gets a 401. req = Request.blank("/public") sign_request(req, **session) signature = parse_authz_header(req)["mac"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(signature, "XXX" + signature) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req, status=401)
def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = Request.blank("/public") resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with valid credentials is allowed access. session = self._start_session() req = Request.blank("/public") sign_request(req, **session) resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with invalid credentials gets a 401. req = Request.blank("/public") sign_request(req, **session) signature = parse_authz_header(req)["mac"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(signature, "XXX" + signature) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req, status=401)
def _identify_oauth(self, request): """Parse, validate and return the request's OAuth parameters. This method grabs the OAuth credentials from the Authorization header and performs some sanity-checks. If the credentials are missing or malformed then it returns None; if they're ok then they are returned in a dict. Note that this method does *not* validate the OAuth signature. """ params = parse_authz_header(request, None) if params is None: return None if params.get("scheme") != "OAuth": return None # Check that various parameters are as expected. if params.get("oauth_signature_method") != "HMAC-SHA1": msg = "unsupported OAuth signature method" return self._respond_unauthorized(request, msg) if "oauth_consumer_key" not in params: msg = "missing oauth_consumer_key" return self._respond_unauthorized(request, msg) # Check the timestamp, reject if too far from current time. try: timestamp = int(params["oauth_timestamp"]) except (KeyError, ValueError): msg = "missing or malformed oauth_timestamp" return self._respond_unauthorized(request, msg) if abs(timestamp - time.time()) >= self.nonce_timeout: msg = "oauth_timestamp is not within accepted range" return self._respond_unauthorized(request, msg) # Check that the nonce is not being re-used. nonce = params.get("oauth_nonce") if nonce is None: msg = "missing oauth_nonce" return self._respond_unauthorized(request, msg) if nonce in self.nonce_cache: msg = "oauth_nonce has already been used" return self._respond_unauthorized(request, msg) # OK, they seem like sensible OAuth paramters. return params
def _identify_mac(self, request): """Parse, validate and return the request's MAC parameters. This method grabs the MAC credentials from the Authorization header and performs some sanity-checks. If the credentials are missing or malformed then it returns None; if they're ok then they are returned in a dict. Note that this method does *not* validate the MAC signature. """ params = parse_authz_header(request, None) if params is None: return None if params.get("scheme") != "MAC": return None # Check that various parameters are as expected. token = params.get("id") if token is None: msg = "missing MAC id" return self._respond_unauthorized(request, msg) # Check the timestamp and nonce for freshness or reuse. # TODO: the spec requires us to adjust for per-client clock skew. try: timestamp = int(params["ts"]) except (KeyError, ValueError): msg = "missing or malformed MAC timestamp" return self._respond_unauthorized(request, msg) nonce = params.get("nonce") if nonce is None: msg = "missing MAC nonce" return self._respond_unauthorized(request, msg) if not self.nonce_manager.is_fresh(token, timestamp, nonce): msg = "MAC has stale token or nonce" return self._respond_unauthorized(request, msg) # OK, they seem like sensible MAC paramters. return params
def _identify_mac(self, request): """Parse, validate and return the request's MAC parameters. This method grabs the MAC credentials from the Authorization header and performs some sanity-checks. If the credentials are missing or malformed then it returns None; if they're ok then they are returned in a dict. Note that this method does *not* validate the MAC signature. """ params = parse_authz_header(request, None) if params is None: return None if params.get("scheme") != "MAC": return None # Check that various parameters are as expected. token = params.get("id") if token is None: msg = "missing MAC id" return self._respond_unauthorized(request, msg) # Check the timestamp and nonce for freshness or reuse. # TODO: the spec requires us to adjust for per-client clock skew. try: timestamp = int(params["ts"]) except (KeyError, ValueError): msg = "missing or malformed MAC timestamp" return self._respond_unauthorized(request, msg) nonce = params.get("nonce") if nonce is None: msg = "missing MAC nonce" return self._respond_unauthorized(request, msg) if not self.nonce_manager.is_fresh(token, timestamp, nonce): msg = "MAC has stale token or nonce" return self._respond_unauthorized(request, msg) # OK, they seem like sensible MAC paramters. return params
def test_parse_authz_header(self): def req(authz): """Make a fake request with the given authz header.""" class request: environ = {"HTTP_AUTHORIZATION": authz} return request # Test parsing of a single unquoted parameter. params = parse_authz_header(req('Digest realm=hello')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['realm'], 'hello') # Test parsing of multiple parameters with mixed quotes. params = parse_authz_header(req('Digest test=one, again="two"')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['test'], 'one') self.assertEquals(params['again'], 'two') # Test parsing of an escaped quote and empty string. params = parse_authz_header(req('Digest test="\\"",again=""')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['test'], '"') self.assertEquals(params['again'], '') # Test parsing of embedded commas, escaped and non-escaped. params = parse_authz_header(req('Digest one="1\\,2", two="3,4"')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['one'], '1,2') self.assertEquals(params['two'], '3,4') # Test parsing on various malformed inputs self.assertRaises(ValueError, parse_authz_header, req(None)) self.assertRaises(ValueError, parse_authz_header, req("")) self.assertRaises(ValueError, parse_authz_header, req(" ")) self.assertRaises(ValueError, parse_authz_header, req('Broken raw-token')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="unclosed-quote')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm=unopened-quote"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="unescaped"quote"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="escaped-end-quote\\"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="duplicated",,what=comma')) # Test all those again, but returning a default value self.assertEquals(None, parse_authz_header(req(None), None)) self.assertEquals(None, parse_authz_header(req(""), None)) self.assertEquals(None, parse_authz_header(req(" "), None)) self.assertEquals(None, parse_authz_header(req('Broken raw-token'), None)) self.assertEquals( None, parse_authz_header(req('Broken realm="unclosed-quote'), None)) self.assertEquals( None, parse_authz_header(req('Broken realm=unopened-quote"'), None)) self.assertEquals( None, parse_authz_header(req('Broken realm="unescaped"quote"'), None)) self.assertEquals( None, parse_authz_header(req('Broken realm="escaped-end-quote\\"'), None)) self.assertEquals( None, parse_authz_header(req('Broken realm="duplicated",,what=comma'), None))
def test_parse_authz_header(self): def req(authz): """Make a fake request with the given authz header.""" class request: environ = {"HTTP_AUTHORIZATION": authz} return request # Test parsing of a single unquoted parameter. params = parse_authz_header(req('Digest realm=hello')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['realm'], 'hello') # Test parsing of multiple parameters with mixed quotes. params = parse_authz_header(req('Digest test=one, again="two"')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['test'], 'one') self.assertEquals(params['again'], 'two') # Test parsing of an escaped quote and empty string. params = parse_authz_header(req('Digest test="\\"",again=""')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['test'], '"') self.assertEquals(params['again'], '') # Test parsing of embedded commas, escaped and non-escaped. params = parse_authz_header(req('Digest one="1\\,2", two="3,4"')) self.assertEquals(params['scheme'], 'Digest') self.assertEquals(params['one'], '1,2') self.assertEquals(params['two'], '3,4') # Test parsing on various malformed inputs self.assertRaises(ValueError, parse_authz_header, req(None)) self.assertRaises(ValueError, parse_authz_header, req("")) self.assertRaises(ValueError, parse_authz_header, req(" ")) self.assertRaises(ValueError, parse_authz_header, req('Broken raw-token')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="unclosed-quote')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm=unopened-quote"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="unescaped"quote"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="escaped-end-quote\\"')) self.assertRaises(ValueError, parse_authz_header, req('Broken realm="duplicated",,what=comma')) # Test all those again, but returning a default value self.assertEquals(None, parse_authz_header(req(None), None)) self.assertEquals(None, parse_authz_header(req(""), None)) self.assertEquals(None, parse_authz_header(req(" "), None)) self.assertEquals(None, parse_authz_header(req('Broken raw-token'), None)) self.assertEquals(None, parse_authz_header( req('Broken realm="unclosed-quote'), None)) self.assertEquals(None, parse_authz_header( req('Broken realm=unopened-quote"'), None)) self.assertEquals(None, parse_authz_header( req('Broken realm="unescaped"quote"'), None)) self.assertEquals(None, parse_authz_header( req('Broken realm="escaped-end-quote\\"'), None)) self.assertEquals(None, parse_authz_header( req('Broken realm="duplicated",,what=comma'), None))