class ApplicationTestMixin(object): def setUp(self): super(ApplicationTestMixin, self).setUp() self.config = {"db": {"url": "sqlite://"}} self.great = create_app(config=self.config) self.app = TestApp(wsgi.create_app(self.great)) METADATA.create_all(self.great.bin.provide("engine").connect()) def create(self, **params): return self.app.post_json(self.url.to_text(), params=params).json def delete(self, **params): return self.app.delete_json(self.url.to_text(), params=params) def list(self, **params): return self.app.get(self.url.to_text(), params=params).json def tracked(self, **params): return self.app.get( self.url.child(u"tracked").to_text(), params=params, ).json def test_invalid_json(self): self.app.post(self.url.to_text(), params="", status=400) def test_unknown_method(self): self.app.request(self.url.to_text(), method=b"TRACE", status=405)
class TestDecorator(unittest.TestCase): """ Ensure the decorators are applied and route correctly """ def setUp(self): """ Start each test with a new Router instance """ self.app = TestApp(router) def test_root_route(self): """ WebTest a basic route """ resp = self.app.get('/foo') self.assertEqual(resp.status_int, 200) self.assertEqual(resp.content_type, 'text/plain') self.assertIn('wor', resp) self.assertEqual(1, len(router.mapping)) def test_route_not_found(self): """ WebTest to make sure a route doesn't show up """ resp = self.app.get('/bar', status=404) self.assertEqual(resp.status_int, 404) def test_method_not_allowed(self): """ WebTest to check that method is not accepted """ for method in ['HEAD', 'GET']: resp = self.app.request('/foo', method=method) self.assertEqual(resp.status_int, 200) for method in ['POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'TRACE']: resp = self.app.request('/foo', method=method, status=405) self.assertEqual(resp.status_int, 405) @unittest.skip("WebTest does not support HTTP 501 NOT IMPLEMENTED yet") def test_not_implemented(self): """ WebTest to check response returns 'NOT IMPLEMENTED' on unrecognized methods """ resp = self.app.request('/foo', method='HERPS', status=501) self.assertEqual(resp.status_int, 501)
def test_request(testapp: TestApp, url: str, method: str, expected_status: HTTPStatus): response = testapp.request(url, method=method, expect_errors=True) # type: TestResponse assert response.status_code == expected_status assert 'json' in response.content_type assert isinstance(response.json, Dict) assert len(response.json) > 0
def test_bad_rest(self): class ThingsController(RestController): pass class RootController(object): things = ThingsController() # create the app app = TestApp(make_app(RootController())) # test get_all r = app.get('/things', status=404) assert r.status_int == 404 # test get_one r = app.get('/things/1', status=404) assert r.status_int == 404 # test post r = app.post('/things', {'value': 'one'}, status=404) assert r.status_int == 404 # test edit r = app.get('/things/1/edit', status=404) assert r.status_int == 404 # test put r = app.put('/things/1', {'value': 'ONE'}, status=404) # test put with _method parameter and GET r = app.get('/things/1?_method=put', {'value': 'ONE!'}, status=405) assert r.status_int == 405 # test put with _method parameter and POST r = app.post('/things/1?_method=put', {'value': 'ONE!'}, status=404) assert r.status_int == 404 # test get delete r = app.get('/things/1/delete', status=404) assert r.status_int == 404 # test delete r = app.delete('/things/1', status=404) assert r.status_int == 404 # test delete with _method parameter and GET r = app.get('/things/1?_method=DELETE', status=405) assert r.status_int == 405 # test delete with _method parameter and POST r = app.post('/things/1?_method=DELETE', status=404) assert r.status_int == 404 # test "RESET" custom action with warnings.catch_warnings(): warnings.simplefilter("ignore") r = app.request('/things', method='RESET', status=404) assert r.status_int == 404
def test_bad_rest(self): class ThingsController(RestController): pass class RootController(object): things = ThingsController() # create the app app = TestApp(make_app(RootController())) # test get_all r = app.get('/things', status=405) assert r.status_int == 405 # test get_one r = app.get('/things/1', status=405) assert r.status_int == 405 # test post r = app.post('/things', {'value': 'one'}, status=405) assert r.status_int == 405 # test edit r = app.get('/things/1/edit', status=405) assert r.status_int == 405 # test put r = app.put('/things/1', {'value': 'ONE'}, status=405) # test put with _method parameter and GET r = app.get('/things/1?_method=put', {'value': 'ONE!'}, status=405) assert r.status_int == 405 # test put with _method parameter and POST r = app.post('/things/1?_method=put', {'value': 'ONE!'}, status=405) assert r.status_int == 405 # test get delete r = app.get('/things/1/delete', status=405) assert r.status_int == 405 # test delete r = app.delete('/things/1', status=405) assert r.status_int == 405 # test delete with _method parameter and GET r = app.get('/things/1?_method=DELETE', status=405) assert r.status_int == 405 # test delete with _method parameter and POST r = app.post('/things/1?_method=DELETE', status=405) assert r.status_int == 405 # test "RESET" custom action with warnings.catch_warnings(): warnings.simplefilter("ignore") r = app.request('/things', method='RESET', status=405) assert r.status_int == 405
class TestTASRAppClient(TASRTestCase): ''' This is a wrapper class to encapsulate the route_to_testapp method, allowing us to use both httmock (in the test) and requests (in the client). ''' def setUp(self): self.app = APP self.tasr = TestApp(APP) @httmock.urlmatch(netloc=HOST_PORT) def route_to_testapp(self, url, requests_req): '''This is some tricky stuff. To test the client methods, we need the responses package calls to route to our webtest TestApp WSGI wrapper. We use httmock to intercept the requests call, then we handle processing in this function instead -- calling the TestApp wrapper. ''' if url.geturl() != requests_req.url: logging.warn('%s != %s', url.geturl(), requests_req.url) # create a webtest TestRequest from the requests PreparedRequest webtest_req = TestRequest.blank(requests_req.url, method=requests_req.method, body=requests_req.body, headers=requests_req.headers) # have the TestApp wrapper process the TestRequest webtest_resp = self.tasr.request(webtest_req) '''webtest responses support multiple headers with the same key, while the requests package holds them in a case-insensitive dict of lists of (key,value) tuples. We need to translate by hand here to keep cases with multiple headers with the same key ''' headers = HTTPHeaderDict() for key, value in webtest_resp.headers.iteritems(): headers.add(key, value) # use the webtest TestResponse to build a new requests HTTPResponse requests_http_resp = HTTPResponse(body=webtest_resp.body, headers=headers, status=webtest_resp.status_code) # get an HTTPAdaptor, then use it to build the requests Response object adap = requests.adapters.HTTPAdapter() requests_resp = adap.build_response(requests_req, requests_http_resp) '''For some reason, we need to explicitly set the _content attribute after the response object is built -- it is already in there as raw.data, but it doesn't make it to _content, so it never hits content() without this intervention. ''' requests_resp._content = webtest_resp.body return requests_resp
class TestLoginController(TestController): def setUp(self): self.app = TestApp("config:test.ini", relative_to="/Users/tonky/projects/Croner") def tearDown(self): pass def test_login_index(self): response = self.app.get(url(controller='login')) ok_('Login' in response.body) def test_bad_password(self): response = self.app.request("/login/save", POST={'login': '******', 'password': '******'}) ok_('wrong' in response.body) def test_do_login(self): response = self.app.request("/login/save", POST={'login': '******', 'password': '******'}) eq_(response.status, "302 Found") home = response.follow() ok_('Welcome, Joe!' in home.body)
class TestJWTAuthenticationPolicy_with_RSA(unittest.TestCase): """Testcases for the JWTAuthenticationPolicy class.""" def setUp(self): self.config = Configurator( settings={ "jwtauth.private_key_file": 'pyramid_jwtauth/tests/testkey', "jwtauth.public_key_file": 'pyramid_jwtauth/tests/testkey.pub', "jwtauth.algorithm": "RS256", }) self.config.include("pyramid_jwtauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) def test_from_settings_with_RSA_public_private_key(self): self.assertEqual(self.policy.algorithm, 'RS256') self.assertEqual(self.policy.master_secret, None) # from Crypto.PublicKey import RSA with open('pyramid_jwtauth/tests/testkey', 'r') as rsa_priv_file: private_key = rsa_priv_file.read() self.assertEqual(self.policy.private_key, private_key) with open('pyramid_jwtauth/tests/testkey.pub', 'r') as rsa_pub_file: public_key = rsa_pub_file.read() self.assertEqual(self.policy.public_key, public_key) self.assertNotEqual(private_key, public_key) req = self._make_request("/auth") claims = make_claims(userid="*****@*****.**") jwt_authenticate_request(req, claims, private_key, 'RS256') token = pyramid_jwtauth.utils.parse_authz_header(req)["token"] userid = self.policy.authenticated_userid(req) self.assertEqual(userid, "*****@*****.**") import jwt payload = jwt.decode(token, key=public_key, verify=True) self.assertIn('sub', payload) self.assertEqual(payload['sub'], "*****@*****.**") r = self.app.request(req) self.assertEqual(r.body, b"*****@*****.**")
class TestJWTAuthenticationPolicy_with_RSA(unittest.TestCase): """Testcases for the JWTAuthenticationPolicy class.""" def setUp(self): self.config = Configurator(settings={ "jwtauth.private_key_file": 'pyramid_jwtauth/tests/testkey', "jwtauth.public_key_file": 'pyramid_jwtauth/tests/testkey.pub', "jwtauth.algorithm": "RS256", }) self.config.include("pyramid_jwtauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) def test_from_settings_with_RSA_public_private_key(self): self.assertEqual(self.policy.algorithm, 'RS256') self.assertEqual(self.policy.master_secret, None) # from Crypto.PublicKey import RSA with open('pyramid_jwtauth/tests/testkey', 'r') as rsa_priv_file: private_key = rsa_priv_file.read() self.assertEqual(self.policy.private_key, private_key) with open('pyramid_jwtauth/tests/testkey.pub', 'r') as rsa_pub_file: public_key = rsa_pub_file.read() self. assertEqual(self.policy.public_key, public_key) self.assertNotEqual(private_key, public_key) req = self._make_request("/auth") claims = make_claims(userid="*****@*****.**") jwt_authenticate_request(req, claims, private_key, 'RS256') token = pyramid_jwtauth.utils.parse_authz_header(req)["token"] userid = self.policy.authenticated_userid(req) self.assertEqual(userid, "*****@*****.**") import jwt payload = jwt.decode(token, key=public_key, verify=True) self.assertIn('sub', payload) self.assertEqual(payload['sub'], "*****@*****.**") r = self.app.request(req) self.assertEqual(r.body, b"*****@*****.**")
def test_basic_rest(self): class OthersController(object): @expose() def index(self): return 'OTHERS' @expose() def echo(self, value): return str(value) class ThingsController(RestController): data = ['zero', 'one', 'two', 'three'] _custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']} others = OthersController() @expose() def get_one(self, id): return self.data[int(id)] @expose('json') def get_all(self): return dict(items=self.data) @expose() def length(self, id, value=None): length = len(self.data[int(id)]) if value: length += len(value) return str(length) @expose() def get_count(self): return str(len(self.data)) @expose() def new(self): return 'NEW' @expose() def post(self, value): self.data.append(value) response.status = 302 return 'CREATED' @expose() def edit(self, id): return 'EDIT %s' % self.data[int(id)] @expose() def put(self, id, value): self.data[int(id)] = value return 'UPDATED' @expose() def get_delete(self, id): return 'DELETE %s' % self.data[int(id)] @expose() def delete(self, id): del self.data[int(id)] return 'DELETED' @expose() def reset(self): return 'RESET' @expose() def post_options(self): return 'OPTIONS' @expose() def options(self): abort(500) @expose() def other(self): abort(500) class RootController(object): things = ThingsController() # create the app app = TestApp(make_app(RootController())) # test get_all r = app.get('/things') assert r.status_int == 200 assert r.body == b_(dumps(dict(items=ThingsController.data))) # test get_one for i, value in enumerate(ThingsController.data): r = app.get('/things/%d' % i) assert r.status_int == 200 assert r.body == b_(value) # test post r = app.post('/things', {'value': 'four'}) assert r.status_int == 302 assert r.body == b_('CREATED') # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == b_('four') # test edit r = app.get('/things/3/edit') assert r.status_int == 200 assert r.body == b_('EDIT three') # test put r = app.put('/things/4', {'value': 'FOUR'}) assert r.status_int == 200 assert r.body == b_('UPDATED') # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == b_('FOUR') # test put with _method parameter and GET r = app.get('/things/4?_method=put', {'value': 'FOUR!'}, status=405) assert r.status_int == 405 # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == b_('FOUR') # test put with _method parameter and POST r = app.post('/things/4?_method=put', {'value': 'FOUR!'}) assert r.status_int == 200 assert r.body == b_('UPDATED') # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == b_('FOUR!') # test get delete r = app.get('/things/4/delete') assert r.status_int == 200 assert r.body == b_('DELETE FOUR!') # test delete r = app.delete('/things/4') assert r.status_int == 200 assert r.body == b_('DELETED') # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body.decode())['items']) == 4 # test delete with _method parameter and GET r = app.get('/things/3?_method=DELETE', status=405) assert r.status_int == 405 # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body.decode())['items']) == 4 # test delete with _method parameter and POST r = app.post('/things/3?_method=DELETE') assert r.status_int == 200 assert r.body == b_('DELETED') # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body.decode())['items']) == 3 # test "RESET" custom action r = app.request('/things', method='RESET') assert r.status_int == 200 assert r.body == b_('RESET') # test "RESET" custom action with _method parameter r = app.get('/things?_method=RESET') assert r.status_int == 200 assert r.body == b_('RESET') # test the "OPTIONS" custom action r = app.request('/things', method='OPTIONS') assert r.status_int == 200 assert r.body == b_('OPTIONS') # test the "OPTIONS" custom action with the _method parameter r = app.post('/things', {'_method': 'OPTIONS'}) assert r.status_int == 200 assert r.body == b_('OPTIONS') # test the "other" custom action with warnings.catch_warnings(): warnings.simplefilter("ignore") r = app.request('/things/other', method='MISC', status=405) assert r.status_int == 405 # test the "other" custom action with the _method parameter r = app.post('/things/other', {'_method': 'MISC'}, status=405) assert r.status_int == 405 # test the "others" custom action with warnings.catch_warnings(): warnings.simplefilter("ignore") r = app.request('/things/others/', method='MISC') assert r.status_int == 200 assert r.body == b_('OTHERS') # test the "others" custom action missing trailing slash with warnings.catch_warnings(): warnings.simplefilter("ignore") r = app.request('/things/others', method='MISC', status=302) assert r.status_int == 302 # test the "others" custom action with the _method parameter r = app.get('/things/others/?_method=MISC') assert r.status_int == 200 assert r.body == b_('OTHERS') # test an invalid custom action r = app.get('/things?_method=BAD', status=404) assert r.status_int == 404 # test custom "GET" request "count" r = app.get('/things/count') assert r.status_int == 200 assert r.body == b_('3') # test custom "GET" request "length" r = app.get('/things/1/length') assert r.status_int == 200 assert r.body == b_(str(len('one'))) # test custom "GET" request through subcontroller r = app.get('/things/others/echo?value=test') assert r.status_int == 200 assert r.body == b_('test') # test custom "POST" request "length" r = app.post('/things/1/length', {'value': 'test'}) assert r.status_int == 200 assert r.body == b_(str(len('onetest'))) # test custom "POST" request through subcontroller r = app.post('/things/others/echo', {'value': 'test'}) assert r.status_int == 200 assert r.body == b_('test')
class TestController(unittest.TestCase): def setUp(self): self.app = TestApp(server(get_config())) def tearDown(self): pass # location str parser def test_01_LOCATION_load_location(self): """ location str parse and construct server info from server.txt""" loc_str = ':test/server0.txt, local:test/server1.txt, both:(hoge)test/server2.txt (gere)test/server3.txt, remote:test/server4.txt' loc = Location(loc_str) self.assertTrue(loc.has_location('')) self.assertTrue(loc.has_location('local')) self.assertTrue(loc.has_location('remote')) self.assertTrue(loc.has_location('both')) self.assertFalse(loc.has_location('nothing')) self.assertEqual({'webcache': {'http://127.0.0.1:8080': 'http://127.0.0.1:8888'}, 'container_prefix': {'http://127.0.0.1:8080': None}, 'swift': [['http://127.0.0.1:8080']]}, loc.servers_of('')) self.assertEqual({'webcache': {'http://127.0.0.1:8080': None}, 'container_prefix': {'http://127.0.0.1:8080': None}, 'swift': [['http://127.0.0.1:8080']]}, loc.servers_of('local')) self.assertEqual({'webcache': {'http://127.0.0.1:18080': None, 'http://127.0.0.1:8080': None}, 'container_prefix': {'http://127.0.0.1:18080': 'gere', 'http://127.0.0.1:8080': 'hoge'}, 'swift': [['http://127.0.0.1:8080'], ['http://127.0.0.1:18080']]}, loc.servers_of('both')) self.assertEqual({'webcache': {'http://127.0.0.1:18080': None}, 'container_prefix': {'http://127.0.0.1:18080': None}, 'swift': [['http://127.0.0.1:18080']]}, loc.servers_of('remote')) self.assertEqual([['http://127.0.0.1:8080']], loc.swift_of('')) self.assertEqual([['http://127.0.0.1:8080']], loc.swift_of('local')) self.assertEqual([['http://127.0.0.1:8080'], ['http://127.0.0.1:18080']], loc.swift_of('both')) self.assertEqual([['http://127.0.0.1:18080']], loc.swift_of('remote')) self.assertFalse(loc.is_merged('')) self.assertFalse(loc.is_merged('local')) self.assertTrue(loc.is_merged('both')) self.assertFalse(loc.is_merged('remote')) self.assertEqual(loc.is_merged('nothing') ,None) self.assertEqual(None, loc.container_prefix_of('', 'http://127.0.0.1:8080')) self.assertEqual('hoge', loc.container_prefix_of('both', 'http://127.0.0.1:8080')) self.assertEqual('gere', loc.container_prefix_of('both', 'http://127.0.0.1:18080')) self.assertEqual({'http://127.0.0.1:8080': 'http://127.0.0.1:8888'}, loc.webcache_of('')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.webcache_of('local')) self.assertEqual({'http://127.0.0.1:18080': None, 'http://127.0.0.1:8080': None}, loc.webcache_of('both')) self.assertEqual({'http://127.0.0.1:18080': None}, loc.webcache_of('remote')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.container_prefixes_of('')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.container_prefixes_of('local')) self.assertEqual({'http://127.0.0.1:18080': 'gere', 'http://127.0.0.1:8080': 'hoge'}, loc.container_prefixes_of('both')) self.assertEqual({'http://127.0.0.1:18080': None}, loc.container_prefixes_of('remote')) self.assertEqual( ['http://127.0.0.1:8080'], loc.servers_by_container_prefix_of('both', 'hoge')) self.assertEqual( ['http://127.0.0.1:18080'], loc.servers_by_container_prefix_of('both', 'gere')) self.assertEqual('http://127.0.0.1:9999', loc._sock_connect_faster(['http://127.0.0.1:8080', 'http://127.0.0.1:18080', 'http://127.0.0.1:9999'])[2]) loc_str = ':test/server0.txt test/server1.txt' loc = Location(loc_str) self.assertEqual(True, loc.is_merged('')) def test_02_LOCATION_update_location(self): """ server.txt reload if update.""" loc_str = ':test/server0.txt, local:test/server1.txt, both:(hoge)test/server2.txt (gere)test/server3.txt, remote:test/server4.txt' loc = Location(loc_str) old_server2_swifts = loc.swift_of('remote') with open('test/server4.txt', 'r') as f: olddata = f.read() with open('test/server4.txt', 'w') as f: f.write('http://192.168.2.1:8080') loc.reload() with open('test/server4.txt', 'w') as f: f.write(olddata) self.assertEqual([['http://192.168.2.1:8080']], loc.swift_of('remote')) def test_LOCATION_invalid_location_file(self): loc_str = ':test/server00.txt' self.assertRaises(ValueError, Location, loc_str) def test_LOCATION_invalid_location_str(self): loc_str = 'test/server0.txt' self.assertRaises(ValueError, Location, loc_str) loc_str = None self.assertRaises(ValueError, Location, loc_str) # rewrite url def test_03_REWRITE_PATH_blank(self): """ rewrite path when location prefix is blank.""" res = self.app.get('/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) def test_04_REWRITE_PATH_local(self): """ rewrite path when location prefix is 'local'.""" res = self.app.get('/local/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) def test_05_REWRITE_PATH_both(self): """ rewrite path when location prefix is 'both' (merge mode).""" res = self.app.get('/both/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) self.assertEqual('/v1.0/AUTH_test', proxy1_srv.env['PATH_INFO']) def test_06_REWRITE_PATH_remote(self): """ rewrite path when location prefix is 'remote'.""" res = self.app.get('/remote/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy1_srv.env['PATH_INFO']) # auth request in normal def test_07_REQUEST_normal_GET_auth(self): """ rewrite auth info from swift in normal mode. """ res = self.app.get('/local/auth/v1.0', headers={'X-Auth-User': '******', 'X-Auth-Key': 'testing'}) print res.body print res.headers body = {'storage': {'default': 'locals', 'locals': 'http://127.0.0.1:10000/local/v1.0/AUTH_test'}} self.assertEqual(res.headers['x-storage-url'], 'http://127.0.0.1:10000/local/v1.0/AUTH_test') self.assertEqual(res.headers['x-auth-token'], 'dummy') self.assertEqual(res.headers['x-storage-token'], 'dummy') self.assertEqual(body, json.loads(res.body)) # request relay def test_08_REQUEST_normal_GET_account(self): """ relay to get account (container listing) in normal mode. """ res = self.app.get('/local//v1.0/AUTH_test', headers=dict(X_Auth_Token='t')) body='TEST0\nTEST1\nTEST2\nTEST3\nTEST4' self.assertEqual(body, res.body) def test_09_REQUEST_normal_GET_container(self): """ relay to get container (object listing) in normal mode. """ res = self.app.get('/local/v1.0/AUTH_test/TEST0', headers=dict(X_Auth_Token='t')) body = 'test0.txt\ntest1.txt\ntest2.txt\ntest2.txt\ntest4.txt' print proxy0_srv.env self.assertEqual(body, res.body) def test_10_REQUEST_normal_GET_object(self): """ relay to get object in normal mode. """ res = self.app.get('/local/v1.0/AUTH_test/TEST0/test0.txt', headers=dict(X_Auth_Token='t')) body = 'This is a test0.\nOK?' self.assertEqual(body, res.body) # request via webcache def test_11_REQUEST_normal_GET_object_via_webcache(self): """ relay to get object in normal mode via WebCache. """ res = self.app.get('/v1.0/AUTH_test/TEST0/test0.txt', headers=dict(X_Auth_Token='t')) self.assertEqual('http://127.0.0.1:8080/v1.0/AUTH_test/TEST0/test0.txt', webcache0_srv.env['PATH_INFO']) # auth request in merge #@unittest.skip def test_12_REQUEST_merge_GET_auth(self): """ rewrite auth info from swift in merge mode. """ res = self.app.get('/both/auth/v1.0', headers={'X-Auth-User': '******', 'X-Auth-Key': 'testing'}) print res.body print res.headers body = {'storage': {'default': 'locals', 'locals': 'http://127.0.0.1:10000/both/v1.0/AUTH_test'}} self.assertEqual(res.headers['x-storage-url'], 'http://127.0.0.1:10000/both/v1.0/AUTH_test') self.assertEqual(res.headers['x-auth-token'], 'dummy__@@__dummy') self.assertEqual(res.headers['x-storage-token'], 'dummy__@@__dummy') self.assertEqual(body, json.loads(res.body)) res = self.app.get('/both/auth/v1.0', headers={'X-Auth-User': '******', 'X-Auth-Key': 'dummy'}, expect_errors=True) self.assertEqual(res.status, '401 Unauthorized') # request relay in merge mode #@unittest.skip def test_13_REQUEST_merge_GET_account(self): """ relay to get account (container listing) in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v')) body = 'gere:TEST0\ngere:TEST1\ngere:TEST2\ngere:TEST3\ngere:TEST4\n' + \ 'hoge:TEST0\nhoge:TEST1\nhoge:TEST2\nhoge:TEST3\nhoge:TEST4' print res.body print res.headers self.assertEqual(res.headers['x-account-bytes-used'], '40') self.assertEqual(res.headers['x-account-container-count'], '10') self.assertEqual(res.headers['x-account-object-count'], '2') self.assertEqual(body, res.body) def test_14_REQUEST_merge_GET_account_with_marker(self): """ relay to get account (container listing) in merge mode with marker param. """ res = self.app.get('/both/v1.0/AUTH_test?marker=hoge:TEST2', headers=dict(X_Auth_Token='t__@@__v')) body = 'hoge:TEST3\nhoge:TEST4' print res.body print res.headers self.assertEqual(body, res.body) def test_15_REQUEST_merge_GET_container(self): """ relay to get container (object listing) in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test/hoge:TEST0', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'test0.txt\ntest1.txt\ntest2.txt\ntest2.txt\ntest4.txt' #print self.app.app.debug print res.status print res.body print res.headers print proxy0_srv.env print proxy1_srv.env self.assertEqual(body, res.body) def test_16_REQUEST_merge_GET_object(self): """ relay to get object in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test/hoge:TEST0/test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' self.assertEqual(body, res.body) def test_17_REQUEST_merge_COPY_object_in_same_account(self): """ relay to copy object in the same account. """ res = self.app.request('/both/v1.0/AUTH_test/hoge:TEST1/copied_test0.txt', method='PUT', body='', headers={'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt'}, expect_errors=True) print res.status print res.body self.assertEqual(proxy0_srv.env['CONTENT_LENGTH'], '0') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') self.assertEqual(proxy0_srv.env['HTTP_X_COPY_FROM'], '/TEST0/test0.txt') def test_18_REQUEST_merge_COPY_object_across_accounts(self): """ relay to copy object across accounts. """ res = self.app.request('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', method='PUT', body='', headers={'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt'}, expect_errors=True) print proxy0_srv.env print proxy0_srv.env['REQUEST_METHOD'] print proxy0_srv.env['PATH_INFO'] print proxy1_srv.env print proxy1_srv.env['REQUEST_METHOD'] print proxy1_srv.env['PATH_INFO'] self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'GET') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0/test0.txt') self.assertEqual(proxy1_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy1_srv.env['SERVER_PORT'], '18080') self.assertEqual(proxy1_srv.env['REQUEST_METHOD'], 'PUT') self.assertEqual(proxy1_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') res = self.app.get('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' print res.body self.assertEqual(body, res.body) #@unittest.skip def test_19_REQUEST_merge_COPY_object_across_accounts_with_split_upload(self): """ relay to copy object across accounts with split uploading. """ swift_store_large_chunk_size = self.app.app.swift_store_large_chunk_size self.app.app.no_split_copy_max_size = 15 res = self.app.request('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', method='PUT', body='', headers={'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt'}, expect_errors=True) self.app.app.swift_store_large_chunk_size = swift_store_large_chunk_size print proxy0_srv.env print proxy0_srv.env['REQUEST_METHOD'] print proxy0_srv.env['PATH_INFO'] print proxy1_srv.env print proxy1_srv.env['REQUEST_METHOD'] print proxy1_srv.env['PATH_INFO'] self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'GET') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0/test0.txt') self.assertEqual(proxy1_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy1_srv.env['SERVER_PORT'], '18080') self.assertEqual(proxy1_srv.env['REQUEST_METHOD'], 'PUT') self.assertEqual(proxy1_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') res = self.app.get('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' print res.body self.assertEqual(body, res.body) def test_get_merged_auth_resp(self): req = Request.blank('/auth/v1.0') req.headers['x-auth-user'] = '******' req.headers['x-auth-key'] = 'testing' location = 'both' self.assertEqual(self.app.app.get_merged_auth_resp(req, location).status, '200 OK') req = Request.blank('/auth/v1.0') req.headers['x-auth-user'] = '******' req.headers['x-auth-key'] = 'dummy' location = 'both' self.assertEqual(self.app.app.get_merged_auth_resp(req, location).status, '401 Unauthorized') def test_no_exist_location(self): res = self.app.get('/nothing/v1.0/auth', headers={'X-Auth-User': '******', 'X-Auth-Key': 'testing'}, expect_errors=True) self.assertEqual(res.status, '404 Not Found') res = self.app.get('/nothing/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) self.assertEqual(res.status, '404 Not Found') def test_put_container(self): req = Request.blank('/') location = 'both' cont_prefix = 'hoge' each_tokens = ['t', 'v'] account = 'AUTH_test' container = 'TEST0' res = self.app.app._create_container(req, location, cont_prefix, each_tokens, account, container) self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0') self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'PUT')
class TestMACAuthPlugin(unittest.TestCase): """Testcases for the main MACAuthPlugin class.""" def setUp(self): self.plugin = MACAuthPlugin() application = PluggableAuthenticationMiddleware( stub_application, [["mac", self.plugin]], [["mac", self.plugin]], [["mac", self.plugin]], [], stub_request_classifier, stub_challenge_decider) self.app = TestApp(application) def _get_credentials(self, **data): id = tokenlib.make_token(data) key = tokenlib.get_token_secret(id) return {"id": id, "key": key} def test_implements(self): verifyClass(IIdentifier, MACAuthPlugin) verifyClass(IAuthenticator, MACAuthPlugin) verifyClass(IChallenger, MACAuthPlugin) def test_make_plugin_can_explicitly_set_all_properties(self): plugin = make_plugin(decode_mac_id=dotted_name("stub_decode_mac_id"), nonce_cache="macauthlib:NonceCache") self.assertEquals(plugin.decode_mac_id, stub_decode_mac_id) self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache)) def test_make_plugin_passes_on_args_to_nonce_cache(self): plugin = make_plugin(nonce_cache="macauthlib:NonceCache", nonce_cache_nonce_timeout=42) self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache)) self.assertEquals(plugin.nonce_cache.nonce_timeout, 42) self.assertRaises(TypeError, make_plugin, nonce_cache="macauthlib:NonceCache", nonce_cache_invalid_arg="WHAWHAWHAWHA") def test_make_plugin_errors_out_on_unexpected_keyword_args(self): self.assertRaises(TypeError, make_plugin, unexpected="spanish-inquisition") def test_make_plugin_errors_out_on_args_to_a_non_callable(self): self.assertRaises(ValueError, make_plugin, nonce_cache=dotted_name("unittest"), nonce_cache_arg="invalidarg") def test_make_plugin_errors_out_if_decode_mac_id_is_not_callable(self): self.assertRaises(ValueError, make_plugin, decode_mac_id=dotted_name("unittest")) def test_make_plugin_produces_sensible_defaults(self): plugin = make_plugin() self.assertEquals(plugin.decode_mac_id.im_func, MACAuthPlugin.decode_mac_id.im_func) self.assertTrue(isinstance(plugin.nonce_cache, macauthlib.NonceCache)) def test_make_plugin_curries_args_to_decode_mac_id(self): plugin = make_plugin(decode_mac_id=dotted_name("stub_decode_mac_id"), decode_mac_id_hello="hi") self.assertEquals(plugin.decode_mac_id(None, "id")[0], "id") self.assertEquals(plugin.decode_mac_id(None, "id")[1]["hello"], "hi") def test_remember_does_nothing(self): self.assertEquals(self.plugin.remember(make_environ(), {}), []) def test_forget_gives_a_challenge_header(self): headers = self.plugin.forget(make_environ(), {}) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "MAC") def test_unauthenticated_requests_get_a_challenge(self): # Requests to most URLs generate a 401, which is passed through # with the appropriate challenge. r = self.app.get("/", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC")) # Requests to URLs with "forbidden" generate a 403 in the downstream # app, which should be converted into a 401 by the plugin. r = self.app.get("/forbidden", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC")) def test_authenticated_request_works(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") def test_authentication_fails_when_macid_has_no_userid(self): creds = self._get_credentials(hello="world") req = Request.blank("/") macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_non_mac_scheme_fails(self): req = Request.blank("/") req.authorization = "OpenID hello=world" self.app.request(req, status=401) def test_authentication_without_macid_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a really old timestamp. ts = str(int(time.time() - 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a far future timestamp. ts = str(int(time.time() + 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): creds = self._get_credentials(username="******") # First request with that nonce should succeed. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") # Second request with that nonce should fail. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_busted_macid_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) id = macauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(id, "XXX" + id) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_busted_signature_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") macauthlib.sign_request(req, **creds) signature = macauthlib.utils.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_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. creds = self._get_credentials(username="******") req = Request.blank("/public") macauthlib.sign_request(req, **creds) resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with invalid credentials gets a 401. req = Request.blank("/public") macauthlib.sign_request(req, **creds) signature = macauthlib.utils.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_authenticate_only_accepts_mac_credentials(self): # Yes, this is a rather pointless test that boosts line coverage... self.assertEquals(self.plugin.authenticate(make_environ(), {}), None)
class TestTASRTopicApp(TASRTestCase): '''These tests check that the TASR native REST API (including the get by ID calls) are working as expected. This does not check the S+V API calls. ''' def setUp(self): self.event_type = "gold" fix_rel_path = "schemas/%s.avsc" % (self.event_type) self.avsc_file = TASRTestCase.get_fixture_file(fix_rel_path, "r") self.schema_str = self.avsc_file.read() self.tasr_app = TestApp(APP) self.url_prefix = 'http://%s:%s/tasr' % (APP.config.host, APP.config.port) self.topic_url = '%s/topic/%s' % (self.url_prefix, self.event_type) self.content_type = 'application/json; charset=utf8' # clear out all the keys before beginning -- careful! APP.ASR.redis.flushdb() def tearDown(self): # this clears out redis after each test -- careful! APP.ASR.redis.flushdb() def abort_diff_status(self, resp, code): self.assertEqual(code, resp.status_code, u'Non-%s status code: %s' % (code, resp.status_code)) def register_schema(self, schema_str, expect_errors=False): return self.tasr_app.request(self.topic_url, method='PUT', expect_errors=expect_errors, content_type=self.content_type, body=schema_str) def test_get_all_topics(self): '''GET /tasr/topic - as expected''' # reg two vers for target topic and one for an alt topic self.register_schema(self.schema_str) schema_str_2 = self.schema_str.replace('tagged.events', 'tagged.events.alt', 1) self.register_schema(schema_str_2) alt_topic = 'bob' alt_url = '%s/topic/%s' % (self.url_prefix, alt_topic) self.tasr_app.request(alt_url, method='PUT', content_type=self.content_type, body=self.schema_str) # now get all with versions and check the headers url = "%s/topic" % (self.url_prefix) resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) # we expect a list of SubjectMetadata objects here meta_dict = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(2, meta_dict[self.event_type].current_version, 'bad ver') self.assertEqual(1, meta_dict[alt_topic].current_version, 'bad ver') # lastly check the body buff = StringIO.StringIO(resp.body) group_names = [] for topic_line in buff: group_names.append(topic_line.strip()) buff.close() self.assertListEqual(sorted(group_names), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.') def test_register_schema(self): '''PUT /tasr/topic/<topic name> - as expected''' resp = self.register_schema(self.schema_str) self.abort_diff_status(resp, 201) smeta = SchemaHeaderBot.extract_metadata(resp) self.assertIn(self.event_type, smeta.group_names, 'event_type missing') self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') self.assertTrue(smeta.group_timestamp(self.event_type), 'missing ts') def test_reg_fail_on_empty_schema(self): '''PUT /tasr/topic/<topic name> - empty schema''' resp = self.register_schema(None, expect_errors=True) self.abort_diff_status(resp, 400) def test_reg_fail_on_invalid_schema(self): '''PUT /tasr/topic/<topic name> - bad schema''' bad_schema_str = "%s }" % self.schema_str resp = self.register_schema(bad_schema_str, expect_errors=True) self.abort_diff_status(resp, 400) def test_reg_fail_on_bad_content_type(self): '''PUT /tasr/topic/<topic name> - bad Content-Type''' resp = self.tasr_app.request(self.topic_url, method='PUT', content_type='text/plain; charset=utf8', expect_errors=True, body=self.schema_str) self.abort_diff_status(resp, 406) def test_reg_and_rereg(self): '''PUT /tasr/topic/<topic name> - multiple calls, same schema''' resp = self.register_schema(self.schema_str) self.abort_diff_status(resp, 201) smeta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') # on the re-registration, we should get the same version back resp2 = self.register_schema(self.schema_str) self.abort_diff_status(resp2, 200) smeta2 = SchemaHeaderBot.extract_metadata(resp2) self.assertEqual(1, smeta2.group_version(self.event_type), 'Re-reg produced a different group version.') def test_multi_topic_reg(self): '''PUT /tasr/topic/<topic name> - multiple group_names, same schema''' put_resp = self.register_schema(self.schema_str) self.abort_diff_status(put_resp, 201) smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') alt_topic = 'bob' alt_url = '%s/topic/%s' % (self.url_prefix, alt_topic) put_resp2 = self.tasr_app.request(alt_url, method='PUT', content_type=self.content_type, body=self.schema_str) self.abort_diff_status(put_resp2, 201) smeta2 = SchemaHeaderBot.extract_metadata(put_resp2) self.assertEqual(1, smeta2.group_version(alt_topic), 'bad ver') # getting by ID gives us all topic associations in headers id_url = "%s/id/%s" % (self.url_prefix, smeta.sha256_id) get_resp = self.tasr_app.request(id_url, method='GET') smeta3 = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(1, smeta3.group_version(self.event_type), 'bad ver') self.assertEqual(1, smeta3.group_version(alt_topic), 'bad ver') def test_get_latest_1(self): '''GET /tasr/topic/<topic name> - as expected''' put_resp = self.register_schema(self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body # now pull it back with a GET get_resp = self.tasr_app.request(self.topic_url, method='GET') self.abort_diff_status(get_resp, 200) smeta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_get_latest_2(self): '''GET /tasr/topic/<topic name>/latest - as expected''' put_resp = self.register_schema(self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body # now pull it back with a GET get_url = '%s/latest' % self.topic_url get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) smeta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_reg_50_and_get_by_version(self): '''GET /tasr/topic/<topic name>/version/<version> - as expected''' schemas = [] # add a bunch of versions for our topic for v in range(1, 50): ver_schema_str = self.schema_str.replace('tagged.events', 'tagged.events.%s' % v, 1) put_resp = self.register_schema(ver_schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body schemas.append(canonicalized_schema_str) self.abort_diff_status(put_resp, 201) # step through and request each version by version number for v in range(1, 50): query = "%s/version/%s" % (self.topic_url, v) get_resp = self.tasr_app.request(query, method='GET') self.abort_diff_status(get_resp, 200) self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_get_for_topic_and_version_fail_on_bad_version(self): '''GET /tasr/topic/<topic name>/version/<version> - fail on bad ver''' put_resp = self.register_schema(self.schema_str) smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') bad_ver = smeta.group_version(self.event_type) + 1 url = "%s/version/%s" % (self.topic_url, bad_ver) get_resp = self.tasr_app.request(url, method='GET', expect_errors=True) self.abort_diff_status(get_resp, 404) def test_get_for_stale_version(self): '''GET /tasr/topic/<topic name>/version/<version> - 1 schema, 2 vers''' put_resp = self.register_schema(self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body self.abort_diff_status(put_resp, 201) schema_str_2 = self.schema_str.replace('tagged.events', 'tagged.events.alt', 1) put_resp2 = self.register_schema(schema_str_2) self.abort_diff_status(put_resp2, 201) put_resp3 = self.register_schema(self.schema_str) smeta = SchemaHeaderBot.extract_metadata(put_resp3) self.assertEqual(3, smeta.group_version(self.event_type), 'bad ver') # now get version 1 -- should be same schema, but diff ver in headers url = "%s/version/%s" % (self.topic_url, 1) get_resp = self.tasr_app.request(url, method='GET', expect_errors=True) self.abort_diff_status(get_resp, 200) self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) smeta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver')
class TestVEPAuthPlugin(unittest2.TestCase): """Testcases for the main VEPAuthPlugin class.""" def setUp(self): self.plugin = VEPAuthPlugin( audiences=["localhost"], verifier=vep.DummyVerifier(), token_manager=StubTokenManager() ) application = PluggableAuthenticationMiddleware( stub_application, [["vep", self.plugin]], [["vep", self.plugin]], [["vep", self.plugin]], [], stub_request_classifier, stub_challenge_decider, ) self.app = TestApp(application) def _make_assertion(self, address, audience="http://localhost", **kwds): return vep.DummyVerifier.make_assertion(address, audience, **kwds) def _start_session(self, email="*****@*****.**", *args, **kwds): assertion = self._make_assertion(email, *args, **kwds) headers = {"Authorization": "Browser-ID " + assertion} session = self.app.get(self.plugin.token_url, headers=headers).json return {"token": session["id"], "secret": session["key"]} def test_implements(self): verifyClass(IIdentifier, VEPAuthPlugin) verifyClass(IAuthenticator, VEPAuthPlugin) verifyClass(IChallenger, VEPAuthPlugin) def test_make_plugin_can_explicitly_set_all_properties(self): plugin = make_plugin( audiences="example.com", token_url="/test_token_url", verifier="vep:DummyVerifier", token_manager="vep:LocalVerifier", nonce_timeout=42, ) self.assertEquals(plugin.audiences, ["example.com"]) self.assertEquals(plugin.token_url, "/test_token_url") self.assertTrue(isinstance(plugin.verifier, vep.DummyVerifier)) self.assertTrue(isinstance(plugin.token_manager, vep.LocalVerifier)) self.assertEquals(plugin.nonce_timeout, 42) def test_make_plugin_produces_sensible_defaults(self): # The "audiences" parameter must be set explicitly self.assertRaises(ValueError, make_plugin) plugin = make_plugin("one two") self.assertEquals(plugin.audiences, ["one", "two"]) self.assertEquals(plugin.token_url, "/request_token") self.assertTrue(isinstance(plugin.verifier, vep.RemoteVerifier)) self.assertTrue(isinstance(plugin.token_manager, SignedTokenManager)) self.assertEquals(plugin.nonce_timeout, 60) def test_make_plugin_loads_urlopen_from_dotted_name(self): plugin = make_plugin("one two", verifier="vep:LocalVerifier", verifier_urlopen="urllib2:urlopen") self.assertEquals(plugin.audiences, ["one", "two"]) self.assertTrue(isinstance(plugin.verifier, vep.LocalVerifier)) self.assertEquals(plugin.verifier.urlopen, urllib2.urlopen) def test_make_plugin_treats_empty_audiences_string_as_none(self): plugin = make_plugin("") self.assertEquals(plugin.audiences, None) plugin = make_plugin(" ") self.assertEquals(plugin.audiences, []) def test_make_plugin_errors_out_on_unexpected_keyword_args(self): self.assertRaises(TypeError, make_plugin, "", unexpected="spanish-inquisition") def test_make_plugin_errors_out_on_args_to_a_non_callable(self): self.assertRaises(ValueError, make_plugin, "", verifier="vep:__doc__", verifier_urlopen="urllib2:urlopen") def test_checking_for_silly_argument_errors(self): self.assertRaises(ValueError, VEPAuthPlugin, audiences="notalist") def test_remember_does_nothing(self): self.assertEquals(self.plugin.remember(make_environ(), {}), []) def test_forget_gives_a_challenge_header(self): headers = self.plugin.forget(make_environ(), {}) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1].startswith("MAC+BrowserID ")) self.assertTrue(self.plugin.token_url in headers[0][1]) def test_unauthenticated_requests_get_a_challenge(self): # Requests to most URLs generate a 401, which is passed through # with the appropriate challenge. r = self.app.get("/", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC+BrowserID")) self.assertTrue(self.plugin.token_url in challenge) # Requests to URLs with "forbidden" generate a 403 in the downstream # app, which should be converted into a 401 by the plugin. r = self.app.get("/forbidden", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC+BrowserID")) self.assertTrue(self.plugin.token_url in challenge) def test_sending_an_assertion_creates_a_token(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} # This fails since we're not at the token-provisioning URL. r = self.app.get("/", headers=headers, status=401) self.assertTrue("id" not in r.body) # This works since we're at the postback url. r = self.app.get(self.plugin.token_url, headers=headers) self.assertTrue("id" in r.body) def test_that_an_empty_token_url_disables_provisioning(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} self.plugin.token_url = "" r = self.app.get("/", headers=headers, status=401) self.assertTrue("id" not in r.body) r = self.app.get("/request_token", headers=headers, status=401) self.assertTrue("id" not in r.body) def test_non_get_requests_give_405(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} self.app.post(self.plugin.token_url, headers=headers, status=405) def test_provisioning_with_malformed_assertion(self): authz = "Browser-ID I AINT NO ASSERTION, FOOL!" headers = {"Authorization": authz} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("assertion" in r.body) def test_provisioning_with_no_credentials_gives_401(self): headers = {} self.app.get(self.plugin.token_url, headers=headers, status=401) def test_provisioning_with_basic_credentials_gives_400(self): headers = {"Authorization": "Basic dTpw"} self.app.get(self.plugin.token_url, headers=headers, status=400) def test_provisioning_with_untrusted_assertion(self): assertion = self._make_assertion("test@moz", assertion_sig="X") headers = {"Authorization": "Browser-ID " + assertion} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("assertion" in r.body) def test_provisioning_with_invalid_audience(self): assertion = self._make_assertion("*****@*****.**", "http://evil.com") headers = {"Authorization": "Browser-ID " + assertion} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("audience" in r.body) # Setting audiences to None will allow it to pass # if it matches the HTTP_HOST header. self.plugin.audiences = None r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("audience" in r.body) r = self.app.get(self.plugin.token_url, headers=headers, extra_environ={"HTTP_HOST": "evil.com"}) self.assertTrue("id" in r.body) def test_provisioning_with_unaccepted_email_address(self): assertion = self._make_assertion("*****@*****.**") headers = {"Authorization": "Browser-ID " + assertion} self.app.get(self.plugin.token_url, headers=headers, status=401) def test_authenticated_request_works(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") def test_authentication_with_non_mac_scheme_fails(self): req = Request.blank("/") req.authorization = "OpenID hello=world" self.app.request(req, status=401) def test_authentication_without_consumer_key_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): session = self._start_session() req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=200) # Now do one with a really old timestamp. ts = str(int(time.time() - 1000)) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): session = self._start_session() req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=200) # Now do one with a far future timestamp. ts = str(int(time.time() + 1000)) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): session = self._start_session() # First request with that nonce should succeed. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) sign_request(req, **session) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") # Second request with that nonce should fail. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) sign_request(req, **session) 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)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(token, "XXX" + token) 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_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_authenticate_only_accepts_mac_credentials(self): # Yes, this is a rather pointless test that boosts line coverage... self.assertEquals(self.plugin.authenticate(make_environ(), {}), None) def test_token_url_can_contain_placeholders(self): self.plugin.token_url = "/{application}/{version}/{test}/foo" authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} # this doesn't match the pattern and should return a 401 r = self.app.get("/foo/bar/bar", headers=headers, status=401) self.assertTrue("id" not in r.body) # valid pattern should return a consumer key r = self.app.get("/foo/1.0/bar/foo", headers=headers) self.assertTrue("id" in r.body) self.plugin.token_manager.applications = {"foo": ["1.0"], "bar": ["2.1"], "baz": ["1.2"]} # defining manually a set of applications an making a request for one # of them should work r = self.app.get("/foo/1.0/bar/foo", headers=headers) self.assertTrue("id" in r.body) # this doesn't match any of the defined applications and should return # a 404 self.assertRaises(HTTPNotFound, self.app.get, "/not_an_app/1.0/bar/foo", headers=headers) # bad version self.assertRaises(HTTPNotFound, self.app.get, "/foo/1.4/bar/foo", headers=headers) def test_extra_data_gets_added(self): self.plugin.token_manager = ExtraTokenManager() authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} resp = self.app.get(self.plugin.token_url, headers=headers, status=200) self.assertTrue("foo" in resp.json) self.assertEqual(resp.json["foo"], "bar")
class TestHawkAuthPlugin(unittest.TestCase): """Testcases for the main HawkAuthPlugin class.""" def setUp(self): self.plugin = HawkAuthPlugin() application = PluggableAuthenticationMiddleware(stub_application, [["hawk", self.plugin]], [["hawk", self.plugin]], [["hawk", self.plugin]], [], stub_request_classifier, stub_challenge_decider) self.app = TestApp(application) def _get_credentials(self, **data): id, key = self.plugin.encode_hawk_id(Request.blank("/"), data) return {"id": id, "key": key} def test_implements(self): verifyClass(IIdentifier, HawkAuthPlugin) verifyClass(IAuthenticator, HawkAuthPlugin) verifyClass(IChallenger, HawkAuthPlugin) def test_make_plugin_can_explicitly_set_all_properties(self): plugin = make_plugin( master_secret="elvislives", nonce_cache="hawkauthlib:NonceCache", decode_hawk_id=dotted_name("stub_decode_hawk_id"), encode_hawk_id=dotted_name("stub_encode_hawk_id")) self.assertEquals(plugin.master_secret, "elvislives") self.assertTrue(isinstance(plugin.nonce_cache, hawkauthlib.NonceCache)) self.assertEquals(plugin.decode_hawk_id, stub_decode_hawk_id) self.assertEquals(plugin.encode_hawk_id, stub_encode_hawk_id) def test_make_plugin_passes_on_args_to_nonce_cache(self): plugin = make_plugin( nonce_cache="hawkauthlib:NonceCache", nonce_cache_window=42) self.assertTrue(isinstance(plugin.nonce_cache, hawkauthlib.NonceCache)) self.assertEquals(plugin.nonce_cache.window, 42) self.assertRaises(TypeError, make_plugin, nonce_cache="hawkauthlib:NonceCache", nonce_cache_invalid_arg="WHAWHAWHAWHA") def test_make_plugin_errors_out_on_unexpected_keyword_args(self): self.assertRaises(TypeError, make_plugin, unexpected="spanish-inquisition") def test_make_plugin_errors_out_on_args_to_a_non_callable(self): self.assertRaises(ValueError, make_plugin, nonce_cache=dotted_name("unittest"), nonce_cache_arg="invalidarg") def test_make_plugin_errors_out_if_decode_hawk_id_is_not_callable(self): self.assertRaises(ValueError, make_plugin, decode_hawk_id=dotted_name("unittest")) def test_make_plugin_produces_sensible_defaults(self): plugin = make_plugin() self.assertEquals(plugin.decode_hawk_id.im_func, HawkAuthPlugin.decode_hawk_id.im_func) self.assertTrue(isinstance(plugin.nonce_cache, hawkauthlib.NonceCache)) def test_make_plugin_curries_args_to_decode_hawk_id(self): plugin = make_plugin( decode_hawk_id=dotted_name("stub_decode_hawk_id"), decode_hawk_id_hello="hi") self.assertEquals(plugin.decode_hawk_id(None, "id")[0], "id") self.assertEquals(plugin.decode_hawk_id(None, "id")[1]["hello"], "hi") def test_remember_does_nothing(self): self.assertEquals(self.plugin.remember(make_environ(), {}), []) def test_forget_gives_a_challenge_header(self): headers = self.plugin.forget(make_environ(), {}) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "Hawk") def test_unauthenticated_requests_get_a_challenge(self): # Requests to most URLs generate a 401, which is passed through # with the appropriate challenge. r = self.app.get("/", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("Hawk")) # Requests to URLs with "forbidden" generate a 403 in the downstream # app, which should be converted into a 401 by the plugin. r = self.app.get("/forbidden", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("Hawk")) def test_authenticated_request_works(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") def test_authentication_fails_when_hawkid_has_no_userid(self): creds = self._get_credentials(hello="world") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_non_hawk_scheme_fails(self): req = Request.blank("/") req.authorization = "OpenID hello=world" self.app.request(req, status=401) def test_authentication_without_hawkid_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") ts = str(int(time.time() - 1000)) req.authorization = ("Hawk", {"ts": ts}) hawkauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") ts = str(int(time.time() + 1000)) req.authorization = ("Hawk", {"ts": ts}) hawkauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): creds = self._get_credentials(username="******") # First request with that nonce should succeed. req = Request.blank("/") req.authorization = ("Hawk", {"nonce": "PEPPER"}) hawkauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") # Second request with that nonce should fail. req = Request.blank("/") req.authorization = ("Hawk", {"nonce": "PEPPER"}) hawkauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_busted_hawkid_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) id = hawkauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(id, "XXX" + id) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_busted_signature_fails(self): creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) signature = hawkauthlib.utils.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_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. creds = self._get_credentials(username="******") req = Request.blank("/public") hawkauthlib.sign_request(req, **creds) resp = self.app.request(req) self.assertEquals(resp.body, "public") # Request with invalid credentials gets a 401. req = Request.blank("/public") hawkauthlib.sign_request(req, **creds) signature = hawkauthlib.utils.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_authenticate_only_accepts_hawk_credentials(self): # Yes, this is a rather pointless test that boosts line coverage... self.assertEquals(self.plugin.authenticate(make_environ(), {}), None) def test_authentication_with_custom_master_secret(self): self.plugin.master_secret = "elvislives" try: creds = self._get_credentials(username="******") req = Request.blank("/") hawkauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") finally: self.plugin.master_secret = None
class TestVEPAuthPlugin(unittest2.TestCase): """Testcases for the main VEPAuthPlugin class.""" def setUp(self): self.plugin = VEPAuthPlugin(audiences=["localhost"], verifier=vep.DummyVerifier(), token_manager=StubTokenManager()) application = PluggableAuthenticationMiddleware(stub_application, [["vep", self.plugin]], [["vep", self.plugin]], [["vep", self.plugin]], [], stub_request_classifier, stub_challenge_decider) self.app = TestApp(application) def _make_assertion(self, address, audience="http://localhost", **kwds): return vep.DummyVerifier.make_assertion(address, audience, **kwds) def _start_session(self, email="*****@*****.**", *args, **kwds): assertion = self._make_assertion(email, *args, **kwds) headers = {"Authorization": "Browser-ID " + assertion} session = self.app.get(self.plugin.token_url, headers=headers).json return { "token": session["id"], "secret": session["key"], } def test_implements(self): verifyClass(IIdentifier, VEPAuthPlugin) verifyClass(IAuthenticator, VEPAuthPlugin) verifyClass(IChallenger, VEPAuthPlugin) def test_make_plugin_can_explicitly_set_all_properties(self): plugin = make_plugin( audiences="example.com", token_url="/test_token_url", verifier="vep:DummyVerifier", token_manager="vep:LocalVerifier", nonce_timeout=42) self.assertEquals(plugin.audiences, ["example.com"]) self.assertEquals(plugin.token_url, "/test_token_url") self.assertTrue(isinstance(plugin.verifier, vep.DummyVerifier)) self.assertTrue(isinstance(plugin.token_manager, vep.LocalVerifier)) self.assertEquals(plugin.nonce_timeout, 42) def test_make_plugin_produces_sensible_defaults(self): # The "audiences" parameter must be set explicitly self.assertRaises(ValueError, make_plugin) plugin = make_plugin("one two") self.assertEquals(plugin.audiences, ["one", "two"]) self.assertEquals(plugin.token_url, "/request_token") self.assertTrue(isinstance(plugin.verifier, vep.RemoteVerifier)) self.assertTrue(isinstance(plugin.token_manager, SignedTokenManager)) self.assertEquals(plugin.nonce_timeout, 60) def test_make_plugin_loads_urlopen_from_dotted_name(self): plugin = make_plugin("one two", verifier="vep:LocalVerifier", verifier_urlopen="urllib2:urlopen") self.assertEquals(plugin.audiences, ["one", "two"]) self.assertTrue(isinstance(plugin.verifier, vep.LocalVerifier)) self.assertEquals(plugin.verifier.urlopen, urllib2.urlopen) def test_make_plugin_treats_empty_audiences_string_as_none(self): plugin = make_plugin("") self.assertEquals(plugin.audiences, None) plugin = make_plugin(" ") self.assertEquals(plugin.audiences, []) def test_make_plugin_errors_out_on_unexpected_keyword_args(self): self.assertRaises(TypeError, make_plugin, "", unexpected="spanish-inquisition") def test_make_plugin_errors_out_on_args_to_a_non_callable(self): self.assertRaises(ValueError, make_plugin, "", verifier="vep:__doc__", verifier_urlopen="urllib2:urlopen") def test_checking_for_silly_argument_errors(self): self.assertRaises(ValueError, VEPAuthPlugin, audiences="notalist") def test_remember_does_nothing(self): self.assertEquals(self.plugin.remember(make_environ(), {}), []) def test_forget_gives_a_challenge_header(self): headers = self.plugin.forget(make_environ(), {}) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1].startswith("MAC+BrowserID ")) self.assertTrue(self.plugin.token_url in headers[0][1]) def test_unauthenticated_requests_get_a_challenge(self): # Requests to most URLs generate a 401, which is passed through # with the appropriate challenge. r = self.app.get("/", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC+BrowserID")) self.assertTrue(self.plugin.token_url in challenge) # Requests to URLs with "forbidden" generate a 403 in the downstream # app, which should be converted into a 401 by the plugin. r = self.app.get("/forbidden", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC+BrowserID")) self.assertTrue(self.plugin.token_url in challenge) def test_sending_an_assertion_creates_a_token(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} # This fails since we're not at the token-provisioning URL. r = self.app.get("/", headers=headers, status=401) self.assertTrue("id" not in r.body) # This works since we're at the postback url. r = self.app.get(self.plugin.token_url, headers=headers) self.assertTrue("id" in r.body) def test_that_an_empty_token_url_disables_provisioning(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} self.plugin.token_url = "" r = self.app.get("/", headers=headers, status=401) self.assertTrue("id" not in r.body) r = self.app.get("/request_token", headers=headers, status=401) self.assertTrue("id" not in r.body) def test_non_get_requests_give_405(self): authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} self.app.post(self.plugin.token_url, headers=headers, status=405) def test_provisioning_with_malformed_assertion(self): authz = "Browser-ID I AINT NO ASSERTION, FOOL!" headers = {"Authorization": authz} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("assertion" in r.body) def test_provisioning_with_no_credentials_gives_401(self): headers = {} self.app.get(self.plugin.token_url, headers=headers, status=401) def test_provisioning_with_basic_credentials_gives_400(self): headers = {"Authorization": "Basic dTpw"} self.app.get(self.plugin.token_url, headers=headers, status=400) def test_provisioning_with_untrusted_assertion(self): assertion = self._make_assertion("test@moz", assertion_sig="X") headers = {"Authorization": "Browser-ID " + assertion} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("assertion" in r.body) def test_provisioning_with_invalid_audience(self): assertion = self._make_assertion("*****@*****.**", "http://evil.com") headers = {"Authorization": "Browser-ID " + assertion} r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("audience" in r.body) # Setting audiences to None will allow it to pass # if it matches the HTTP_HOST header. self.plugin.audiences = None r = self.app.get(self.plugin.token_url, headers=headers, status=400) self.assertTrue("audience" in r.body) r = self.app.get(self.plugin.token_url, headers=headers, extra_environ={"HTTP_HOST": "evil.com"}) self.assertTrue("id" in r.body) def test_provisioning_with_unaccepted_email_address(self): assertion = self._make_assertion("*****@*****.**") headers = {"Authorization": "Browser-ID " + assertion} self.app.get(self.plugin.token_url, headers=headers, status=401) def test_authenticated_request_works(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") def test_authentication_with_non_mac_scheme_fails(self): req = Request.blank("/") req.authorization = "OpenID hello=world" self.app.request(req, status=401) def test_authentication_without_consumer_key_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): session = self._start_session() req = Request.blank("/") sign_request(req, **session) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): session = self._start_session() req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=200) # Now do one with a really old timestamp. ts = str(int(time.time() - 1000)) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): session = self._start_session() req = Request.blank("/") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=200) # Now do one with a far future timestamp. ts = str(int(time.time() + 1000)) req.authorization = ("MAC", {"ts": ts}) sign_request(req, **session) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): session = self._start_session() # First request with that nonce should succeed. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) sign_request(req, **session) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") # Second request with that nonce should fail. req = Request.blank("/") req.authorization = ("MAC", {"nonce": "PEPPER"}) sign_request(req, **session) 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)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(token, "XXX" + token) 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_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_authenticate_only_accepts_mac_credentials(self): # Yes, this is a rather pointless test that boosts line coverage... self.assertEquals(self.plugin.authenticate(make_environ(), {}), None) def test_token_url_can_contain_placeholders(self): self.plugin.token_url = "/{application}/{version}/{test}/foo" authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} # this doesn't match the pattern and should return a 401 r = self.app.get("/foo/bar/bar", headers=headers, status=401) self.assertTrue("id" not in r.body) # valid pattern should return a consumer key r = self.app.get("/foo/1.0/bar/foo", headers=headers) self.assertTrue("id" in r.body) self.plugin.token_manager.applications = {"foo": ["1.0"], "bar": ["2.1"], "baz": ["1.2"]} # defining manually a set of applications an making a request for one # of them should work r = self.app.get("/foo/1.0/bar/foo", headers=headers) self.assertTrue("id" in r.body) # this doesn't match any of the defined applications and should return # a 404 self.assertRaises(HTTPNotFound, self.app.get, "/not_an_app/1.0/bar/foo", headers=headers) # bad version self.assertRaises(HTTPNotFound, self.app.get, "/foo/1.4/bar/foo", headers=headers) def test_extra_data_gets_added(self): self.plugin.token_manager = ExtraTokenManager() authz = "Browser-ID " + self._make_assertion("*****@*****.**") headers = {"Authorization": authz} resp = self.app.get(self.plugin.token_url, headers=headers, status=200) self.assertTrue('foo' in resp.json) self.assertEqual(resp.json['foo'], 'bar')
class TestTASRCoreApp(TASRTestCase): '''These tests check that the TASR core app endpoints work.''' def setUp(self): self.event_type = "gold" fix_rel_path = "schemas/%s.avsc" % (self.event_type) self.avsc_file = TASRTestCase.get_fixture_file(fix_rel_path, "r") self.schema_str = self.avsc_file.read() self.tasr_app = TestApp(APP) self.url_prefix = 'http://%s:%s/tasr' % (APP.config.host, APP.config.port) self.subject_url = '%s/subject/%s' % (self.url_prefix, self.event_type) self.content_type = 'application/json; charset=utf8' # clear out all the keys before beginning -- careful! APP.ASR.redis.flushdb() def tearDown(self): # this clears out redis after each test -- careful! APP.ASR.redis.flushdb() def abort_diff_status(self, resp, code): self.assertEqual(code, resp.status_code, u'Non-%s status code: %s' % (code, resp.status_code)) def register_subject(self, subject_name): url = '%s/subject/%s' % (self.url_prefix, subject_name) return self.tasr_app.put(url, {'subject_name': subject_name}) def register_schema(self, subject_name, schema_str, expect_errors=False): reg_url = '%s/subject/%s/register' % (self.url_prefix, subject_name) return self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=expect_errors, body=schema_str) ########################################################################### # /id app ########################################################################### def test_lookup_by_md5_id(self): '''GET /tasr/id/<MD5 ID> - as expected''' put_resp = self.register_schema(self.event_type, self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') url = "%s/id/%s" % (self.url_prefix, smeta.md5_id) get_resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(get_resp, 200) self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_md5_id__accept_json(self): '''GET /tasr/id/<MD5 ID> - "Accept: text/json" as expected''' put_resp = self.register_schema(self.event_type, self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') url = "%s/id/%s" % (self.url_prefix, smeta.md5_id) get_resp = self.tasr_app.request(url, method='GET', accept='text/json') self.abort_diff_status(get_resp, 200) self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_sha256_id(self): '''GET /tasr/id/<SHA256 ID> - as expected''' put_resp = self.register_schema(self.event_type, self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') url = "%s/id/%s" % (self.url_prefix, smeta.sha256_id) get_resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(get_resp, 200) self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_sha256_id__accept_json(self): '''GET /tasr/id/<SHA256 ID> - "Accept: text/json" as expected''' put_resp = self.register_schema(self.event_type, self.schema_str) # the canonicalized form returned has normalized whitespace canonicalized_schema_str = put_resp.body smeta = SchemaHeaderBot.extract_metadata(put_resp) self.assertEqual(1, smeta.group_version(self.event_type), 'bad ver') url = "%s/id/%s" % (self.url_prefix, smeta.sha256_id) get_resp = self.tasr_app.request(url, method='GET', accept='text/json') self.abort_diff_status(get_resp, 200) self.assertEqual(canonicalized_schema_str, get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_sha256_id_str__bad_id(self): '''GET /tasr/id/<id str> - fail on bad ID''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) sha256_id = ver_meta.sha256_id # get a "bad" ID from a different schema string rs = tasr.registered_schema.RegisteredSchema() rs.schema_str = self.schema_str.replace('tagged.events', 'bob') bad_sha256_id = rs.sha256_id self.assertNotEqual(sha256_id, bad_sha256_id, 'IDs should differ') # try getting the schema for the "bad" ID get_url = '%s/id/%s' % (self.url_prefix, bad_sha256_id) get_resp = self.tasr_app.request(get_url, method='GET', expect_errors=True) self.abort_diff_status(get_resp, 404) def test_lookup_by_sha256_id_str__accept_json__bad_id(self): '''GET /tasr/id/<id str> - "Accept: text/json" fail on bad ID''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) sha256_id = ver_meta.sha256_id # get a "bad" ID from a different schema string rs = tasr.registered_schema.RegisteredSchema() rs.schema_str = self.schema_str.replace('tagged.events', 'bob') bad_sha256_id = rs.sha256_id self.assertNotEqual(sha256_id, bad_sha256_id, 'IDs should differ') # try getting the schema for the "bad" ID get_url = '%s/id/%s' % (self.url_prefix, bad_sha256_id) get_resp = self.tasr_app.request(get_url, method='GET', accept='text/json', expect_errors=True) self.abort_diff_status(get_resp, 404) # we expect a JSON error back, so check that we got it json_error = json.loads(get_resp.body) # body is parseable JSON self.assertEqual(404, json_error["status_code"], "expected a 404") def test_lookup_by_sha256_id_str(self): '''GET /tasr/id/<id_str> - as expected''' sha256_ids = [] schemas = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) sha256_ids.append(ver_meta.sha256_id) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # step through and request each version by version number for v in range(1, 50): get_url = '%s/id/%s' % (self.url_prefix, sha256_ids[v - 1]) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(sha256_ids[v - 1], meta.sha256_id, 'bad ID') self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_md5_id_str(self): '''GET /tasr/id/<id_str> - as expected''' md5_ids = [] schemas = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) md5_ids.append(ver_meta.md5_id) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # step through and request each version by version number for v in range(1, 50): get_url = '%s/id/%s' % (self.url_prefix, md5_ids[v - 1]) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(md5_ids[v - 1], meta.md5_id, 'bad ID') self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) ########################################################################### # /schema app ########################################################################### def test_lookup_by_schema_str(self): '''POST /tasr/schema - as expected''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # canonicalized schema string is passed back on registration canonicalized_schema_str = resp.body meta_1 = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta_1.group_version(self.event_type), 'bad ver') schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) # get by POSTed schema post_url = "%s/schema" % self.url_prefix post_resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, body=self.schema_str) meta_2 = SchemaHeaderBot.extract_metadata(post_resp) self.assertEqual(1, meta_2.group_version(self.event_type), 'bad ver') self.assertEqual(meta_1.sha256_id, meta_2.sha256_id, 'SHA mismatch') self.assertEqual(meta_1.md5_id, meta_2.md5_id, 'MD5 mismatch') self.assertEqual(canonicalized_schema_str, post_resp.body, u'Unexpected body: %s' % post_resp.body) def test_lookup_by_schema_str__accept_json(self): '''POST /tasr/schema - "Accept: text/json" as expected''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # canonicalized schema string is passed back on registration canonicalized_schema_str = resp.body meta_1 = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta_1.group_version(self.event_type), 'bad ver') schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) # get by POSTed schema post_url = "%s/schema" % self.url_prefix post_resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, accept='text/json', body=self.schema_str) meta_2 = SchemaHeaderBot.extract_metadata(post_resp) self.assertEqual(1, meta_2.group_version(self.event_type), 'bad ver') self.assertEqual(meta_1.sha256_id, meta_2.sha256_id, 'SHA mismatch') self.assertEqual(meta_1.md5_id, meta_2.md5_id, 'MD5 mismatch') self.assertEqual(canonicalized_schema_str, post_resp.body, u'Unexpected body: %s' % post_resp.body) def test_fail_lookup_by_schema_str_on_empty_schema_str(self): '''POST /tasr/schema - fail on empty schema string''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body=None) self.abort_diff_status(resp, 400) def test_fail_lookup_by_schema_str_on_empty_schema_str__accept_json(self): '''POST /tasr/schema - "Accept: text/json" fail on empty schema str''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, accept='text/json', expect_errors=True, body=None) self.abort_diff_status(resp, 400) # we expect a JSON error back, so check that we got it json_error = json.loads(resp.body) # body is parseable JSON self.assertEqual(400, json_error["status_code"], "expected a 404") def test_fail_lookup_by_schema_str_on_invalid_schema_str(self): '''POST /tasr/schema - fail on bad schema string''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body="%s }" % self.schema_str) self.abort_diff_status(resp, 400) def test_fail_lookup_by_schema_str_on_invalid_schema_str__accept_json(self): '''POST /tasr/schema - "Accept: text/json" fail on bad schema string''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, accept='text/json', expect_errors=True, body="%s }" % self.schema_str) self.abort_diff_status(resp, 400) # we expect a JSON error back, so check that we got it json_error = json.loads(resp.body) # body is parseable JSON self.assertEqual(400, json_error["status_code"], "expected a 404") def test_fail_lookup_by_schema_str_on_unreg_schema_str(self): '''POST /tasr/schema - fail on new schema string''' post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body=self.schema_str) self.assertEqual(404, resp.status_int, u'Unexpected status: %s' % resp.status_int) meta = SchemaHeaderBot.extract_metadata(resp) self.assertTrue(meta.sha256_id, 'SHA missing') self.assertTrue(meta.md5_id, 'MD5 missing') def test_fail_lookup_by_schema_str_on_unreg_schema_str__accept_json(self): '''POST /tasr/schema - fail on new schema string''' post_url = "%s/schema" % self.url_prefix resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, accept='text/json', expect_errors=True, body=self.schema_str) self.assertEqual(404, resp.status_int, u'Unexpected status: %s' % resp.status_int) meta = SchemaHeaderBot.extract_metadata(resp) self.assertTrue(meta.sha256_id, 'SHA missing') self.assertTrue(meta.md5_id, 'MD5 missing') # we expect a JSON error back, so check that we got it json_error = json.loads(resp.body) # body is parseable JSON self.assertEqual(404, json_error["status_code"], "expected a 404") ########################################################################### # /collection app ########################################################################### def test_all_subject_names(self): '''GET /tasr/collection/subjects/all - get _all_ registered subjects''' # reg two vers for target subject and one for an alt subject self.register_subject(self.event_type) alt_subject_name = 'bob' self.register_subject(alt_subject_name) # now get all and check the headers get_url = '%s/collection/subjects/all' % self.url_prefix resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) self.assertIn(self.event_type, meta_dict.keys(), 'missing subject') subj = meta_dict[self.event_type] self.assertEqual(self.event_type, subj.name, 'bad subject name') self.assertIn(alt_subject_name, meta_dict.keys(), 'missing subject') alt_subj = meta_dict[alt_subject_name] self.assertEqual(alt_subject_name, alt_subj.name, 'bad subject name') # lastly check the body buff = StringIO.StringIO(resp.body) group_names = [] for topic_line in buff: group_names.append(topic_line.strip()) buff.close() self.assertListEqual(sorted(group_names), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.') def test_all_subject_names__accept_json(self): '''GET /tasr/collection/subjects/all - get _all_ registered subjects''' # reg two vers for target subject and one for an alt subject self.register_subject(self.event_type) alt_subject_name = 'bob' self.register_subject(alt_subject_name) # now get all and check the headers get_url = '%s/collection/subjects/all' % self.url_prefix resp = self.tasr_app.request(get_url, method='GET', accept='text/json') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) self.assertIn(self.event_type, meta_dict.keys(), 'missing subject') subj = meta_dict[self.event_type] self.assertEqual(self.event_type, subj.name, 'bad subject name') self.assertIn(alt_subject_name, meta_dict.keys(), 'missing subject') alt_subj = meta_dict[alt_subject_name] self.assertEqual(alt_subject_name, alt_subj.name, 'bad subject name') # the body should be a JSON dict of subject objects keyed by name sub_dict = json.loads(resp.body) self.assertListEqual(sorted(sub_dict.keys()), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.') def test_active_subjects(self): '''GET /tasr/collection/subjects/active - gets _active_ subjects (that is, ones with at least one schema), as expected''' # reg two vers for target subject and one for an alt subject self.register_subject(self.event_type) alt_subject_name = 'bob' self.register_subject(alt_subject_name) # now get all and check the headers all_url = "%s/collection/subjects/all" % self.url_prefix resp = self.tasr_app.request(all_url, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have a GroupMetadata object for each group in the headers for sub_name in [self.event_type, alt_subject_name]: self.assertIn(sub_name, meta_dict.keys(), 'missing subject') subj = meta_dict[sub_name] self.assertEqual(sub_name, subj.name, 'bad subject name') # now get the ACTIVE subjects, which should be empty so far active_url = "%s/collection/subjects/active" % self.url_prefix resp = self.tasr_app.request(active_url, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have no GroupMetadata objects for sub_name in [self.event_type, alt_subject_name]: self.assertNotIn(sub_name, meta_dict.keys(), 'unexpected subject') # now register a schema for the base subject and recheck resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # the get_all should be unchanged, the get_active should have one resp = self.tasr_app.request(all_url, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have a GroupMetadata object for each group in the headers for sub_name in [self.event_type, alt_subject_name]: self.assertIn(sub_name, meta_dict.keys(), 'missing subject') subj = meta_dict[sub_name] self.assertEqual(sub_name, subj.name, 'bad subject name') # now get the ACTIVE subjects, which should be empty so far resp = self.tasr_app.request(active_url, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have a GroupMetadata object for one group in the headers self.assertNotIn(alt_subject_name, meta_dict.keys(), 'unexpected obj') # the event_type should be there self.assertIn(self.event_type, meta_dict.keys(), 'missing subject') subj = meta_dict[self.event_type] self.assertEqual(self.event_type, subj.name, 'bad subject name') # lastly check the body buff = StringIO.StringIO(resp.body) group_names = [] for topic_line in buff: group_names.append(topic_line.strip()) buff.close() self.assertListEqual(sorted(group_names), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.') def test_active_subjects__accept_json(self): '''GET /tasr/collection/subjects/active - gets _active_ subjects (that is, ones with at least one schema), as expected''' # reg two vers for target subject and one for an alt subject self.register_subject(self.event_type) alt_subject_name = 'bob' self.register_subject(alt_subject_name) # now get the ACTIVE subjects, which should be empty so far active_url = "%s/collection/subjects/active" % self.url_prefix resp = self.tasr_app.request(active_url, method='GET', accept='text/json') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have no GroupMetadata objects for sub_name in [self.event_type, alt_subject_name]: self.assertNotIn(sub_name, meta_dict.keys(), 'unexpected subject') # we should have received an empty dict as the content body active_dict = json.loads(resp.body) self.assertDictEqual({}, active_dict, 'expected empty dict') # now register a schema for the base subject and recheck resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # now get the ACTIVE subjects, which should be empty so far resp = self.tasr_app.request(active_url, method='GET', accept='text/json') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) # we should have a GroupMetadata object for one group in the headers self.assertNotIn(alt_subject_name, meta_dict.keys(), 'unexpected obj') # the event_type should be there self.assertIn(self.event_type, meta_dict.keys(), 'missing subject') subj = meta_dict[self.event_type] self.assertEqual(self.event_type, subj.name, 'bad subject name') # and check the expected content body JSON active_dict = json.loads(resp.body) self.assertListEqual(sorted(active_dict.keys()), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.')
class TestMACAuthenticationPolicy(unittest.TestCase): """Testcases for the MACAuthenticationPolicy class.""" def setUp(self): self.config = Configurator(settings={ "macauth.find_groups": "pyramid_macauth.tests:stub_find_groups", }) self.config.include("pyramid_macauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) def _make_signed_request(self, userid, *args, **kwds): req = self._make_request(*args, **kwds) creds = self._get_credentials(req, userid=userid) macauthlib.sign_request(req, **creds) return req def _get_credentials(self, req, **data): id, key = self.policy.encode_mac_id(req, **data) return {"id": id, "key": key} def test_the_class_implements_auth_policy_interface(self): verifyClass(IAuthenticationPolicy, MACAuthenticationPolicy) def test_from_settings_can_explicitly_set_all_properties(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.find_groups": "pyramid_macauth.tests:stub_find_groups", "macauth.master_secret": "V8 JUICE IS 1/8TH GASOLINE", "macauth.nonce_cache": "macauthlib:NonceCache", "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", "macauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", }) self.assertEquals(policy.find_groups, stub_find_groups) self.assertEquals(policy.master_secret, "V8 JUICE IS 1/8TH GASOLINE") self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) self.assertEquals(policy.decode_mac_id, stub_decode_mac_id) self.assertEquals(policy.encode_mac_id, stub_encode_mac_id) def test_from_settings_passes_on_args_to_nonce_cache(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.nonce_cache": "macauthlib:NonceCache", "macauth.nonce_cache_nonce_ttl": 42, }) self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) self.assertEquals(policy.nonce_cache.nonce_ttl, 42) self.assertRaises( TypeError, MACAuthenticationPolicy.from_settings, { "macauth.nonce_cache": "macauthlib:NonceCache", "macauth.nonce_cache_invalid_arg": "WHAWHAWHAWHA", }) def test_from_settings_errors_out_on_unexpected_keyword_args(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.unexpected": "spanish-inquisition", }) def test_from_settings_errors_out_on_args_to_a_non_callable(self): self.assertRaises( ValueError, MACAuthenticationPolicy.from_settings, { "macauth.nonce_cache": "pyramid_macauth.tests:stub_non_callable", "macauth.nonce_cache_arg": "invalidarg", }) def test_from_settings_errors_out_if_decode_mac_id_is_not_callable(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.decode_mac_id": "pyramid_macauth.tests:stub_non_callable", }) def test_from_settings_errors_out_if_encode_mac_id_is_not_callable(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.encode_mac_id": "pyramid_macauth.tests:stub_non_callable", }) def test_from_settings_produces_sensible_defaults(self): policy = MACAuthenticationPolicy.from_settings({}) # Using __code__ here is a Py2/Py3 compatible way of checking # that a bound and unbound method point to the same function object. self.assertEquals(policy.find_groups.__code__, MACAuthenticationPolicy.find_groups.__code__) self.assertEquals(policy.decode_mac_id.__code__, MACAuthenticationPolicy.decode_mac_id.__code__) self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) def test_from_settings_curries_args_to_decode_mac_id(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", "macauth.decode_mac_id_suffix": "-TEST", }) self.assertEquals(policy.decode_mac_id(None, "id"), ("id", "id-TEST")) def test_from_settings_curries_args_to_encode_mac_id(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", "macauth.encode_mac_id_suffix": "-TEST", }) self.assertEquals(policy.encode_mac_id(None, "id"), ("id", "id-TEST")) def test_remember_does_nothing(self): policy = MACAuthenticationPolicy() req = self._make_signed_request("*****@*****.**", "/") self.assertEquals(policy.remember(req, "*****@*****.**"), []) def test_forget_gives_a_challenge_header(self): policy = MACAuthenticationPolicy() req = self._make_signed_request("*****@*****.**", "/") headers = policy.forget(req) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "MAC") def test_unauthenticated_requests_get_a_challenge(self): r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC")) def test_authenticated_request_works(self): req = self._make_signed_request("*****@*****.**", "/auth") r = self.app.request(req) self.assertEquals(r.body, b"*****@*****.**") def test_authentication_fails_when_macid_has_no_userid(self): req = self._make_request("/auth") creds = self._get_credentials(req, hello="world") macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_non_mac_scheme_fails(self): req = self._make_request("/auth") req.authorization = "OpenID hello=world" self.app.request(req, status=401) req = self._make_request("/public") req.authorization = "OpenID hello=world" self.app.request(req, status=200) def test_authentication_without_macid_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): req = self._make_request("/auth") creds = self._get_credentials(req, username="******") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a really old timestamp. ts = str(int(time.time() - 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): req = self._make_request("/auth") creds = self._get_credentials(req, username="******") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a far future timestamp. ts = str(int(time.time() + 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): req = self._make_request("/auth") creds = self._get_credentials(req, username="******") # First request with that nonce should succeed. req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, b"*****@*****.**") # Second request with that nonce should fail. req = self._make_request("/auth") req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_busted_macid_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") id = macauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(id, "XXX" + id) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_busted_signature_fails(self): req = self._make_request("/auth") creds = self._get_credentials(req, username="******") macauthlib.sign_request(req, **creds) signature = macauthlib.utils.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_groupfinder_can_block_authentication(self): req = self._make_signed_request("baduser", "/auth") r = self.app.request(req, status=401) req = self._make_signed_request("baduser", "/public") r = self.app.request(req, status=200) self.assertEquals(r.body, b"baduser") def test_groupfinder_gruops_are_correctly_reported(self): req = self._make_request("/groups") r = self.app.request(req) self.assertEquals(r.json, [str(Everyone)]) req = self._make_signed_request("gooduser", "/groups") r = self.app.request(req) self.assertEquals( r.json, ["gooduser", str(Everyone), str(Authenticated)]) req = self._make_signed_request("test", "/groups") r = self.app.request(req) self.assertEquals( r.json, ["test", str(Everyone), str(Authenticated), "group"]) req = self._make_signed_request("baduser", "/groups") r = self.app.request(req) self.assertEquals(r.json, [str(Everyone)]) def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = self._make_request("/public") resp = self.app.request(req) self.assertEquals(resp.body, b"None") # Request with valid credentials is allowed access. req = self._make_signed_request("*****@*****.**", "/public") resp = self.app.request(req) self.assertEquals(resp.body, b"*****@*****.**") # Request with invalid credentials still reports a userid. req = self._make_signed_request("*****@*****.**", "/public") signature = macauthlib.utils.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) self.assertEquals(resp.body, b"*****@*****.**") # Request with malformed credentials gets a 401 req = self._make_signed_request("*****@*****.**", "/public") tokenid = macauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(tokenid, "XXX" + tokenid) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req, status=401) def test_check_signature_fails_if_no_params_present(self): req = self._make_request("/auth") self.assertRaises(HTTPUnauthorized, self.policy._check_signature, req, "XXX") def test_default_groupfinder_returns_empty_list(self): policy = MACAuthenticationPolicy() req = self._make_request("/auth") self.assertEquals(policy.find_groups("test", req), []) def test_auth_can_be_checked_several_times_on_same_request(self): req = self._make_signed_request("*****@*****.**", "/public") self.assertEquals(authenticated_userid(req), "*****@*****.**") self.assertEquals(authenticated_userid(req), "*****@*****.**")
def request_response(request, testapp: TestApp) -> RequestResponse: req = request.param # type: Request test_request = TestRequest.blank(req.url, **req.kwargs) test_response = testapp.request(test_request, expect_errors=True) return RequestResponse(test_request, test_response, req.expected_status, None)
class TestJWTAuthenticationPolicy(unittest.TestCase): """Testcases for the JWTAuthenticationPolicy class.""" def setUp(self): self.config = Configurator( settings={ "jwtauth.find_groups": "pyramid_jwtauth.tests.test_jwtauth:stub_find_groups", "jwtauth.master_secret": MASTER_SECRET, }) self.config.include("pyramid_jwtauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) # create an authenticated request that has a JWT with the userid set # in our case, this is the 'sub' request. # # we need to have the following claims as a minimum: # 'sub': the userid that we want to authenticate - it can be anything. # 'iat': the issued at time stamp # 'nbf': the not before time stamp # 'exp': the expiry time for the JWT def _make_authenticated_request(self, userid, *args, **kwds): claims = None if 'claims' in kwds: claims = kwds['claims'] del kwds['claims'] req = self._make_request(*args, **kwds) # creds = self._get_credentials(req, userid=userid) claims = make_claims(userid=userid, claims=claims) # jwt_authenticate_request(req, **creds) # note jwt_authenticate_request() returns headers if wanted jwt_authenticate_request(req, claims, MASTER_SECRET) return req def test_the_class_implements_auth_policy_interface(self): verifyClass(IAuthenticationPolicy, JWTAuthenticationPolicy) def test_from_settings_can_explicitly_set_all_properties(self): policy = JWTAuthenticationPolicy.from_settings({ "jwtauth.find_groups": "pyramid_jwtauth.tests.test_jwtauth:stub_find_groups", "jwtauth.master_secret": MASTER_SECRET, # "jwtauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", # "jwtauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", }) self.assertEqual(policy.find_groups, stub_find_groups) self.assertEqual(policy.master_secret, MASTER_SECRET) # self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) # self.assertEqual(policy.decode_mac_id, stub_decode_mac_id) # self.assertEqual(policy.encode_mac_id, stub_encode_mac_id) # def test_from_settings_passes_on_args_to_nonce_cache(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.nonce_cache": "macauthlib:NonceCache", # "macauth.nonce_cache_nonce_ttl": 42, # }) # self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) # self.assertEqual(policy.nonce_cache.nonce_ttl, 42) # self.assertRaises(TypeError, MACAuthenticationPolicy.from_settings, { # "macauth.nonce_cache": "macauthlib:NonceCache", # "macauth.nonce_cache_invalid_arg": "WHAWHAWHAWHA", # }) def test_from_settings_errors_out_on_unexpected_keyword_args(self): self.assertRaises(ValueError, JWTAuthenticationPolicy.from_settings, { "jwtauth.unexpected": "spanish-inquisition", }) # def test_from_settings_errors_out_on_args_to_a_non_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.nonce_cache": "pyramid_macauth.tests:stub_non_callable", # "macauth.nonce_cache_arg": "invalidarg", # }) # def test_from_settings_errors_out_if_decode_mac_id_is_not_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.decode_mac_id": "pyramid_macauth.tests:stub_non_callable", # }) # def test_from_settings_errors_out_if_encode_mac_id_is_not_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.encode_mac_id": "pyramid_macauth.tests:stub_non_callable", # }) def test_from_settings_produces_sensible_defaults(self): policy = JWTAuthenticationPolicy.from_settings({}) # Using __code__ here is a Py2/Py3 compatible way of checking # that a bound and unbound method point to the same function object. self.assertEqual(policy.find_groups.__code__, JWTAuthenticationPolicy.find_groups.__code__) # def test_from_settings_curries_args_to_decode_mac_id(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", # "macauth.decode_mac_id_suffix": "-TEST", # }) # self.assertEqual(policy.decode_mac_id(None, "id"), ("id", "id-TEST")) # def test_from_settings_curries_args_to_encode_mac_id(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", # "macauth.encode_mac_id_suffix": "-TEST", # }) # self.assertEqual(policy.encode_mac_id(None, "id"), ("id", "id-TEST")) # not sure if this test is particularly useful as JWTAuthenticationPolicy # doesn't work on 'remembering' requests. def test_remember_does_nothing(self): policy = JWTAuthenticationPolicy() req = self._make_authenticated_request("*****@*****.**", "/") self.assertEqual(policy.remember(req, "*****@*****.**"), []) def test_forget_gives_a_challenge_header(self): policy = JWTAuthenticationPolicy() req = self._make_authenticated_request("*****@*****.**", "/") headers = policy.forget(req) self.assertEqual(len(headers), 1) self.assertEqual(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "JWT") def test_forget_gives_a_challenge_header_with_custom_scheme(self): policy = JWTAuthenticationPolicy(scheme='Bearer') req = self._make_authenticated_request("*****@*****.**", "/") headers = policy.forget(req) self.assertTrue(headers[0][1] == "Bearer") def test_unauthenticated_requests_get_a_challenge(self): r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("JWT")) def test_unauthenticated_requests_get_a_challenge_custom_scheme(self): self.policy.scheme = 'Bearer' r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith('Bearer')) def test_authenticated_request_works(self): req = self._make_authenticated_request("*****@*****.**", "/auth") r = self.app.request(req) self.assertEqual(r.body, b"*****@*****.**") # def test_authentication_fails_when_macid_has_no_userid(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, hello="world") # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) def test_authentication_with_non_jwt_scheme_fails(self): req = self._make_request("/auth") req.authorization = "OpenID hello=world" self.app.request(req, status=401) req = self._make_request("/public") req.authorization = "OpenID hello=world" self.app.request(req, status=200) # def test_authentication_without_macid_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("id", "idd") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_without_timestamp_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("ts", "typostamp") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_without_nonce_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("nonce", "typonce") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): req = self._make_authenticated_request("*****@*****.**", "/auth") self.app.request(req, status=200) # now do an out of date one. ts = datetime.datetime.utcnow() - datetime.timedelta(days=1) claims = {'exp': ts} req = self._make_authenticated_request("*****@*****.**", "/auth", claims=claims) self.app.request(req, status=401) def test_can_get_claims_from_token(self): claims = {'urn:websandhq.co.uk/auth:jti': 'hello'} req = self._make_authenticated_request("*****@*****.**", "/auth", claims=claims) policy = JWTAuthenticationPolicy( master_secret="V8 JUICE IS 1/8TH GASOLINE") encoded_claims = policy.get_claims(req) self.assertTrue('urn:websandhq.co.uk/auth:jti' in encoded_claims) self.assertEqual(encoded_claims['urn:websandhq.co.uk/auth:jti'], 'hello') # def test_authentication_with_far_future_timestamp_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # # Do an initial request so that the server can # # calculate and cache our clock skew. # ts = str(int(time.time())) # req.authorization = ("MAC", {"ts": ts}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=200) # # Now do one with a far future timestamp. # ts = str(int(time.time() + 1000)) # req.authorization = ("MAC", {"ts": ts}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) # def test_authentication_with_reused_nonce_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # # First request with that nonce should succeed. # req.authorization = ("MAC", {"nonce": "PEPPER"}) # macauthlib.sign_request(req, **creds) # r = self.app.request(req) # self.assertEqual(r.body, b"*****@*****.**") # # Second request with that nonce should fail. # req = self._make_request("/auth") # req.authorization = ("MAC", {"nonce": "PEPPER"}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) # def test_authentication_with_busted_macid_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # id = macauthlib.utils.parse_authz_header(req)["id"] # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace(id, "XXX" + id) # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_with_busted_signature_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # macauthlib.sign_request(req, **creds) # signature = macauthlib.utils.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_groupfinder_can_block_authentication(self): req = self._make_authenticated_request("baduser", "/auth") r = self.app.request(req, status=403) req = self._make_authenticated_request("baduser", "/public") r = self.app.request(req, status=200) self.assertEqual(r.body, b"baduser") def test_groupfinder_groups_are_correctly_reported(self): req = self._make_request("/groups") r = self.app.request(req) self.assertEqual(r.json, [str(Everyone)]) req = self._make_authenticated_request("gooduser", "/groups") r = self.app.request(req) self.assertEqual( r.json, ["gooduser", str(Everyone), str(Authenticated)]) req = self._make_authenticated_request("test", "/groups") r = self.app.request(req) self.assertEqual( r.json, ["test", str(Everyone), str(Authenticated), "group"]) req = self._make_authenticated_request("baduser", "/groups") r = self.app.request(req) self.assertEqual(r.json, [str(Everyone)]) def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = self._make_request("/public") resp = self.app.request(req) self.assertEqual(resp.body, b"None") # Request with valid credentials is allowed access. req = self._make_authenticated_request("*****@*****.**", "/public") resp = self.app.request(req) self.assertEqual(resp.body, b"*****@*****.**") # Request with invalid credentials still reports a userid. req = self._make_authenticated_request("*****@*****.**", "/public") token = pyramid_jwtauth.utils.parse_authz_header(req)["token"] bits = token.split('.') bits[2] = 'XXX' + bits[2] broken_token = '.'.join(bits) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(token, broken_token) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req) self.assertEqual(resp.body, b"*****@*****.**") # Request with malformed credentials gets a 401 # req = self._make_signed_request("*****@*****.**", "/public") # tokenid = macauthlib.utils.parse_authz_header(req)["id"] # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace(tokenid, "XXX" + tokenid) # req.environ["HTTP_AUTHORIZATION"] = authz # resp = self.app.request(req, status=401) # def test_check_signature_fails_if_no_params_present(self): # req = self._make_request("/auth") # self.assertRaises(HTTPUnauthorized, # self.policy._check_signature, req, "XXX") def test_default_groupfinder_returns_empty_list(self): policy = JWTAuthenticationPolicy() req = self._make_request("/auth") self.assertEqual(policy.find_groups("test", req), []) def test_auth_can_be_checked_several_times_on_same_request(self): req = self._make_authenticated_request("*****@*****.**", "/public") self.assertEqual(authenticated_userid(req), "*****@*****.**") self.assertEqual(authenticated_userid(req), "*****@*****.**")
class TestController(unittest.TestCase): def setUp(self): self.app = TestApp(server(get_config())) def tearDown(self): pass # location str parser def test_01_LOCATION_load_location(self): """ location str parse and construct server info from server.txt""" loc_str = ':test/server0.txt, local:test/server1.txt, both:(hoge)test/server2.txt (gere)test/server3.txt, remote:test/server4.txt' loc = Location(loc_str) self.assertTrue(loc.has_location('')) self.assertTrue(loc.has_location('local')) self.assertTrue(loc.has_location('remote')) self.assertTrue(loc.has_location('both')) self.assertFalse(loc.has_location('nothing')) self.assertEqual( { 'webcache': { 'http://127.0.0.1:8080': 'http://127.0.0.1:8888' }, 'container_prefix': { 'http://127.0.0.1:8080': None }, 'swift': [['http://127.0.0.1:8080']] }, loc.servers_of('')) self.assertEqual( { 'webcache': { 'http://127.0.0.1:8080': None }, 'container_prefix': { 'http://127.0.0.1:8080': None }, 'swift': [['http://127.0.0.1:8080']] }, loc.servers_of('local')) self.assertEqual( { 'webcache': { 'http://127.0.0.1:18080': None, 'http://127.0.0.1:8080': None }, 'container_prefix': { 'http://127.0.0.1:18080': 'gere', 'http://127.0.0.1:8080': 'hoge' }, 'swift': [['http://127.0.0.1:8080'], ['http://127.0.0.1:18080']] }, loc.servers_of('both')) self.assertEqual( { 'webcache': { 'http://127.0.0.1:18080': None }, 'container_prefix': { 'http://127.0.0.1:18080': None }, 'swift': [['http://127.0.0.1:18080']] }, loc.servers_of('remote')) self.assertEqual([['http://127.0.0.1:8080']], loc.swift_of('')) self.assertEqual([['http://127.0.0.1:8080']], loc.swift_of('local')) self.assertEqual( [['http://127.0.0.1:8080'], ['http://127.0.0.1:18080']], loc.swift_of('both')) self.assertEqual([['http://127.0.0.1:18080']], loc.swift_of('remote')) self.assertFalse(loc.is_merged('')) self.assertFalse(loc.is_merged('local')) self.assertTrue(loc.is_merged('both')) self.assertFalse(loc.is_merged('remote')) self.assertEqual(loc.is_merged('nothing'), None) self.assertEqual(None, loc.container_prefix_of('', 'http://127.0.0.1:8080')) self.assertEqual( 'hoge', loc.container_prefix_of('both', 'http://127.0.0.1:8080')) self.assertEqual( 'gere', loc.container_prefix_of('both', 'http://127.0.0.1:18080')) self.assertEqual({'http://127.0.0.1:8080': 'http://127.0.0.1:8888'}, loc.webcache_of('')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.webcache_of('local')) self.assertEqual( { 'http://127.0.0.1:18080': None, 'http://127.0.0.1:8080': None }, loc.webcache_of('both')) self.assertEqual({'http://127.0.0.1:18080': None}, loc.webcache_of('remote')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.container_prefixes_of('')) self.assertEqual({'http://127.0.0.1:8080': None}, loc.container_prefixes_of('local')) self.assertEqual( { 'http://127.0.0.1:18080': 'gere', 'http://127.0.0.1:8080': 'hoge' }, loc.container_prefixes_of('both')) self.assertEqual({'http://127.0.0.1:18080': None}, loc.container_prefixes_of('remote')) self.assertEqual(['http://127.0.0.1:8080'], loc.servers_by_container_prefix_of('both', 'hoge')) self.assertEqual(['http://127.0.0.1:18080'], loc.servers_by_container_prefix_of('both', 'gere')) self.assertEqual( 'http://127.0.0.1:9999', loc._sock_connect_faster([ 'http://127.0.0.1:8080', 'http://127.0.0.1:18080', 'http://127.0.0.1:9999' ])[2]) loc_str = ':test/server0.txt test/server1.txt' loc = Location(loc_str) self.assertEqual(True, loc.is_merged('')) def test_02_LOCATION_update_location(self): """ server.txt reload if update.""" loc_str = ':test/server0.txt, local:test/server1.txt, both:(hoge)test/server2.txt (gere)test/server3.txt, remote:test/server4.txt' loc = Location(loc_str) old_server2_swifts = loc.swift_of('remote') with open('test/server4.txt', 'r') as f: olddata = f.read() with open('test/server4.txt', 'w') as f: f.write('http://192.168.2.1:8080') loc.reload() with open('test/server4.txt', 'w') as f: f.write(olddata) self.assertEqual([['http://192.168.2.1:8080']], loc.swift_of('remote')) def test_LOCATION_invalid_location_file(self): loc_str = ':test/server00.txt' self.assertRaises(ValueError, Location, loc_str) def test_LOCATION_invalid_location_str(self): loc_str = 'test/server0.txt' self.assertRaises(ValueError, Location, loc_str) loc_str = None self.assertRaises(ValueError, Location, loc_str) # rewrite url def test_03_REWRITE_PATH_blank(self): """ rewrite path when location prefix is blank.""" res = self.app.get('/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) def test_04_REWRITE_PATH_local(self): """ rewrite path when location prefix is 'local'.""" res = self.app.get('/local/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) def test_05_REWRITE_PATH_both(self): """ rewrite path when location prefix is 'both' (merge mode).""" res = self.app.get('/both/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy0_srv.env['PATH_INFO']) self.assertEqual('/v1.0/AUTH_test', proxy1_srv.env['PATH_INFO']) def test_06_REWRITE_PATH_remote(self): """ rewrite path when location prefix is 'remote'.""" res = self.app.get('/remote/v1.0/AUTH_test', headers=dict(X_Auth_Token='t'), expect_errors=True) self.assertEqual('/v1.0/AUTH_test', proxy1_srv.env['PATH_INFO']) # auth request in normal def test_07_REQUEST_normal_GET_auth(self): """ rewrite auth info from swift in normal mode. """ res = self.app.get('/local/auth/v1.0', headers={ 'X-Auth-User': '******', 'X-Auth-Key': 'testing' }) print res.body print res.headers body = { 'storage': { 'default': 'locals', 'locals': 'http://127.0.0.1:10000/local/v1.0/AUTH_test' } } self.assertEqual(res.headers['x-storage-url'], 'http://127.0.0.1:10000/local/v1.0/AUTH_test') self.assertEqual(res.headers['x-auth-token'], 'dummy') self.assertEqual(res.headers['x-storage-token'], 'dummy') self.assertEqual(body, json.loads(res.body)) # request relay def test_08_REQUEST_normal_GET_account(self): """ relay to get account (container listing) in normal mode. """ res = self.app.get('/local//v1.0/AUTH_test', headers=dict(X_Auth_Token='t')) body = 'TEST0\nTEST1\nTEST2\nTEST3\nTEST4' self.assertEqual(body, res.body) def test_09_REQUEST_normal_GET_container(self): """ relay to get container (object listing) in normal mode. """ res = self.app.get('/local/v1.0/AUTH_test/TEST0', headers=dict(X_Auth_Token='t')) body = 'test0.txt\ntest1.txt\ntest2.txt\ntest2.txt\ntest4.txt' print proxy0_srv.env self.assertEqual(body, res.body) def test_10_REQUEST_normal_GET_object(self): """ relay to get object in normal mode. """ res = self.app.get('/local/v1.0/AUTH_test/TEST0/test0.txt', headers=dict(X_Auth_Token='t')) body = 'This is a test0.\nOK?' self.assertEqual(body, res.body) # request via webcache def test_11_REQUEST_normal_GET_object_via_webcache(self): """ relay to get object in normal mode via WebCache. """ res = self.app.get('/v1.0/AUTH_test/TEST0/test0.txt', headers=dict(X_Auth_Token='t')) self.assertEqual( 'http://127.0.0.1:8080/v1.0/AUTH_test/TEST0/test0.txt', webcache0_srv.env['PATH_INFO']) # auth request in merge #@unittest.skip def test_12_REQUEST_merge_GET_auth(self): """ rewrite auth info from swift in merge mode. """ res = self.app.get('/both/auth/v1.0', headers={ 'X-Auth-User': '******', 'X-Auth-Key': 'testing' }) print res.body print res.headers body = { 'storage': { 'default': 'locals', 'locals': 'http://127.0.0.1:10000/both/v1.0/AUTH_test' } } self.assertEqual(res.headers['x-storage-url'], 'http://127.0.0.1:10000/both/v1.0/AUTH_test') self.assertEqual(res.headers['x-auth-token'], 'dummy__@@__dummy') self.assertEqual(res.headers['x-storage-token'], 'dummy__@@__dummy') self.assertEqual(body, json.loads(res.body)) res = self.app.get('/both/auth/v1.0', headers={ 'X-Auth-User': '******', 'X-Auth-Key': 'dummy' }, expect_errors=True) self.assertEqual(res.status, '401 Unauthorized') # request relay in merge mode #@unittest.skip def test_13_REQUEST_merge_GET_account(self): """ relay to get account (container listing) in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v')) body = 'gere:TEST0\ngere:TEST1\ngere:TEST2\ngere:TEST3\ngere:TEST4\n' + \ 'hoge:TEST0\nhoge:TEST1\nhoge:TEST2\nhoge:TEST3\nhoge:TEST4' print res.body print res.headers self.assertEqual(res.headers['x-account-bytes-used'], '40') self.assertEqual(res.headers['x-account-container-count'], '10') self.assertEqual(res.headers['x-account-object-count'], '2') self.assertEqual(body, res.body) def test_14_REQUEST_merge_GET_account_with_marker(self): """ relay to get account (container listing) in merge mode with marker param. """ res = self.app.get('/both/v1.0/AUTH_test?marker=hoge:TEST2', headers=dict(X_Auth_Token='t__@@__v')) body = 'hoge:TEST3\nhoge:TEST4' print res.body print res.headers self.assertEqual(body, res.body) def test_15_REQUEST_merge_GET_container(self): """ relay to get container (object listing) in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test/hoge:TEST0', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'test0.txt\ntest1.txt\ntest2.txt\ntest2.txt\ntest4.txt' #print self.app.app.debug print res.status print res.body print res.headers print proxy0_srv.env print proxy1_srv.env self.assertEqual(body, res.body) def test_16_REQUEST_merge_GET_object(self): """ relay to get object in merge mode. """ res = self.app.get('/both/v1.0/AUTH_test/hoge:TEST0/test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' self.assertEqual(body, res.body) def test_17_REQUEST_merge_COPY_object_in_same_account(self): """ relay to copy object in the same account. """ res = self.app.request( '/both/v1.0/AUTH_test/hoge:TEST1/copied_test0.txt', method='PUT', body='', headers={ 'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt' }, expect_errors=True) print res.status print res.body self.assertEqual(proxy0_srv.env['CONTENT_LENGTH'], '0') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') self.assertEqual(proxy0_srv.env['HTTP_X_COPY_FROM'], '/TEST0/test0.txt') def test_18_REQUEST_merge_COPY_object_across_accounts(self): """ relay to copy object across accounts. """ res = self.app.request( '/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', method='PUT', body='', headers={ 'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt' }, expect_errors=True) print proxy0_srv.env print proxy0_srv.env['REQUEST_METHOD'] print proxy0_srv.env['PATH_INFO'] print proxy1_srv.env print proxy1_srv.env['REQUEST_METHOD'] print proxy1_srv.env['PATH_INFO'] self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'GET') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0/test0.txt') self.assertEqual(proxy1_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy1_srv.env['SERVER_PORT'], '18080') self.assertEqual(proxy1_srv.env['REQUEST_METHOD'], 'PUT') self.assertEqual(proxy1_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') res = self.app.get('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' print res.body self.assertEqual(body, res.body) #@unittest.skip def test_19_REQUEST_merge_COPY_object_across_accounts_with_split_upload( self): """ relay to copy object across accounts with split uploading. """ swift_store_large_chunk_size = self.app.app.swift_store_large_chunk_size self.app.app.no_split_copy_max_size = 15 res = self.app.request( '/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', method='PUT', body='', headers={ 'X_Auth_Token': 't__@@__v', 'X_Copy_From': '/hoge:TEST0/test0.txt' }, expect_errors=True) self.app.app.swift_store_large_chunk_size = swift_store_large_chunk_size print proxy0_srv.env print proxy0_srv.env['REQUEST_METHOD'] print proxy0_srv.env['PATH_INFO'] print proxy1_srv.env print proxy1_srv.env['REQUEST_METHOD'] print proxy1_srv.env['PATH_INFO'] self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'GET') self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0/test0.txt') self.assertEqual(proxy1_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy1_srv.env['SERVER_PORT'], '18080') self.assertEqual(proxy1_srv.env['REQUEST_METHOD'], 'PUT') self.assertEqual(proxy1_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST1/copied_test0.txt') res = self.app.get('/both/v1.0/AUTH_test/gere:TEST1/copied_test0.txt', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) body = 'This is a test0.\nOK?' print res.body self.assertEqual(body, res.body) def test_get_merged_auth_resp(self): req = Request.blank('/auth/v1.0') req.headers['x-auth-user'] = '******' req.headers['x-auth-key'] = 'testing' location = 'both' self.assertEqual( self.app.app.get_merged_auth_resp(req, location).status, '200 OK') req = Request.blank('/auth/v1.0') req.headers['x-auth-user'] = '******' req.headers['x-auth-key'] = 'dummy' location = 'both' self.assertEqual( self.app.app.get_merged_auth_resp(req, location).status, '401 Unauthorized') def test_no_exist_location(self): res = self.app.get('/nothing/v1.0/auth', headers={ 'X-Auth-User': '******', 'X-Auth-Key': 'testing' }, expect_errors=True) self.assertEqual(res.status, '404 Not Found') res = self.app.get('/nothing/v1.0/AUTH_test', headers=dict(X_Auth_Token='t__@@__v'), expect_errors=True) self.assertEqual(res.status, '404 Not Found') def test_put_container(self): req = Request.blank('/') location = 'both' cont_prefix = 'hoge' each_tokens = ['t', 'v'] account = 'AUTH_test' container = 'TEST0' res = self.app.app._create_container(req, location, cont_prefix, each_tokens, account, container) self.assertEqual(proxy0_srv.env['PATH_INFO'], '/v1.0/AUTH_test/TEST0') self.assertEqual(proxy0_srv.env['SERVER_NAME'], '127.0.0.1') self.assertEqual(proxy0_srv.env['SERVER_PORT'], '8080') self.assertEqual(proxy0_srv.env['REQUEST_METHOD'], 'PUT')
class TestMACAuthenticationPolicy(unittest.TestCase): """Testcases for the MACAuthenticationPolicy class.""" def setUp(self): self.config = Configurator(settings={ "macauth.find_groups": "pyramid_macauth.tests:stub_find_groups", }) self.config.include("pyramid_macauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) def _make_signed_request(self, userid, *args, **kwds): creds = self._get_credentials(userid=userid) req = self._make_request(*args, **kwds) macauthlib.sign_request(req, **creds) return req def _get_credentials(self, **data): id = tokenlib.make_token(data) key = tokenlib.get_token_secret(id) return {"id": id, "key": key} def test_the_class_implements_auth_policy_interface(self): verifyClass(IAuthenticationPolicy, MACAuthenticationPolicy) def test_from_settings_can_explicitly_set_all_properties(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.find_groups": "pyramid_macauth.tests:stub_find_groups", "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", "macauth.nonce_cache": "macauthlib:NonceCache", }) self.assertEquals(policy.find_groups, stub_find_groups) self.assertEquals(policy.decode_mac_id, stub_decode_mac_id) self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) def test_from_settings_passes_on_args_to_nonce_cache(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.nonce_cache": "macauthlib:NonceCache", "macauth.nonce_cache_nonce_ttl": 42, }) self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) self.assertEquals(policy.nonce_cache.nonce_ttl, 42) self.assertRaises(TypeError, MACAuthenticationPolicy.from_settings, { "macauth.nonce_cache": "macauthlib:NonceCache", "macauth.nonce_cache_invalid_arg": "WHAWHAWHAWHA", }) def test_from_settings_errors_out_on_unexpected_keyword_args(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.unexpected": "spanish-inquisition", }) def test_from_settings_errors_out_on_args_to_a_non_callable(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.nonce_cache": "pyramid_macauth.tests:stub_non_callable", "macauth.nonce_cache_arg": "invalidarg", }) def test_from_settings_errors_out_if_decode_mac_id_is_not_callable(self): self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { "macauth.decode_mac_id": "pyramid_macauth.tests:stub_non_callable", }) def test_from_settings_produces_sensible_defaults(self): policy = MACAuthenticationPolicy.from_settings({}) self.assertEquals(policy.find_groups.im_func, MACAuthenticationPolicy.find_groups.im_func) self.assertEquals(policy.decode_mac_id.im_func, MACAuthenticationPolicy.decode_mac_id.im_func) self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) def test_from_settings_curries_args_to_decode_mac_id(self): policy = MACAuthenticationPolicy.from_settings({ "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", "macauth.decode_mac_id_suffix": "-TEST", }) self.assertEquals(policy.decode_mac_id(None, "id"), ("id", "id-TEST")) def test_remember_does_nothing(self): policy = MACAuthenticationPolicy() req = self._make_signed_request("*****@*****.**", "/") self.assertEquals(policy.remember(req, "*****@*****.**"), []) def test_forget_gives_a_challenge_header(self): policy = MACAuthenticationPolicy() req = self._make_signed_request("*****@*****.**", "/") headers = policy.forget(req) self.assertEquals(len(headers), 1) self.assertEquals(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "MAC") def test_unauthenticated_requests_get_a_challenge(self): r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("MAC")) def test_authenticated_request_works(self): req = self._make_signed_request("*****@*****.**", "/auth") r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") def test_authentication_fails_when_macid_has_no_userid(self): creds = self._get_credentials(hello="world") req = self._make_request("/auth") macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_non_mac_scheme_fails(self): req = self._make_request("/auth") req.authorization = "OpenID hello=world" self.app.request(req, status=401) req = self._make_request("/public") req.authorization = "OpenID hello=world" self.app.request(req, status=200) def test_authentication_without_macid_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("id", "idd") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_timestamp_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("ts", "typostamp") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_without_nonce_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace("nonce", "typonce") req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): creds = self._get_credentials(username="******") req = self._make_request("/auth") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a really old timestamp. ts = str(int(time.time() - 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_far_future_timestamp_fails(self): creds = self._get_credentials(username="******") req = self._make_request("/auth") # Do an initial request so that the server can # calculate and cache our clock skew. ts = str(int(time.time())) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=200) # Now do one with a far future timestamp. ts = str(int(time.time() + 1000)) req.authorization = ("MAC", {"ts": ts}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_reused_nonce_fails(self): creds = self._get_credentials(username="******") # First request with that nonce should succeed. req = self._make_request("/auth") req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) r = self.app.request(req) self.assertEquals(r.body, "*****@*****.**") # Second request with that nonce should fail. req = self._make_request("/auth") req.authorization = ("MAC", {"nonce": "PEPPER"}) macauthlib.sign_request(req, **creds) self.app.request(req, status=401) def test_authentication_with_busted_macid_fails(self): req = self._make_signed_request("*****@*****.**", "/auth") id = macauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(id, "XXX" + id) req.environ["HTTP_AUTHORIZATION"] = authz self.app.request(req, status=401) def test_authentication_with_busted_signature_fails(self): creds = self._get_credentials(username="******") req = self._make_request("/auth") macauthlib.sign_request(req, **creds) signature = macauthlib.utils.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_groupfinder_can_block_authentication(self): req = self._make_signed_request("baduser", "/auth") r = self.app.request(req, status=401) req = self._make_signed_request("baduser", "/public") r = self.app.request(req, status=200) self.assertEquals(r.body, "baduser") def test_groupfinder_gruops_are_correctly_reported(self): req = self._make_request("/groups") r = self.app.request(req) self.assertEquals(r.json, [str(Everyone)]) req = self._make_signed_request("gooduser", "/groups") r = self.app.request(req) self.assertEquals(r.json, ["gooduser", str(Everyone), str(Authenticated)]) req = self._make_signed_request("test", "/groups") r = self.app.request(req) self.assertEquals(r.json, ["test", str(Everyone), str(Authenticated), "group"]) req = self._make_signed_request("baduser", "/groups") r = self.app.request(req) self.assertEquals(r.json, [str(Everyone)]) def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = self._make_request("/public") resp = self.app.request(req) self.assertEquals(resp.body, "None") # Request with valid credentials is allowed access. req = self._make_signed_request("*****@*****.**", "/public") resp = self.app.request(req) self.assertEquals(resp.body, "*****@*****.**") # Request with invalid credentials still reports a userid. req = self._make_signed_request("*****@*****.**", "/public") signature = macauthlib.utils.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) self.assertEquals(resp.body, "*****@*****.**") # Request with malformed credentials gets a 401 req = self._make_signed_request("*****@*****.**", "/public") tokenid = macauthlib.utils.parse_authz_header(req)["id"] authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(tokenid, "XXX" + tokenid) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req, status=401) def test_check_signature_fails_if_no_params_present(self): req = self._make_request("/auth") self.assertRaises(HTTPUnauthorized, self.policy._check_signature, req, "XXX") def test_default_groupfinder_returns_empty_list(self): policy = MACAuthenticationPolicy() req = self._make_request("/auth") self.assertEquals(policy.find_groups("test", req), []) def test_auth_can_be_checked_several_times_on_same_request(self): req = self._make_signed_request("*****@*****.**", "/public") self.assertEquals(authenticated_userid(req), "*****@*****.**") self.assertEquals(authenticated_userid(req), "*****@*****.**")
class TestJWTAuthenticationPolicy(unittest.TestCase): """Testcases for the JWTAuthenticationPolicy class.""" def setUp(self): self.config = Configurator(settings={ "jwtauth.find_groups": "pyramid_jwtauth.tests.test_jwtauth:stub_find_groups", "jwtauth.master_secret": MASTER_SECRET, }) self.config.include("pyramid_jwtauth") self.config.add_route("public", "/public") self.config.add_view(stub_view_public, route_name="public") self.config.add_route("auth", "/auth") self.config.add_view(stub_view_auth, route_name="auth") self.config.add_route("groups", "/groups") self.config.add_view(stub_view_groups, route_name="groups") self.app = TestApp(self.config.make_wsgi_app()) self.policy = self.config.registry.queryUtility(IAuthenticationPolicy) def _make_request(self, *args, **kwds): return make_request(self.config, *args, **kwds) # create an authenticated request that has a JWT with the userid set # in our case, this is the 'sub' request. # # we need to have the following claims as a minimum: # 'sub': the userid that we want to authenticate - it can be anything. # 'iat': the issued at time stamp # 'nbf': the not before time stamp # 'exp': the expiry time for the JWT def _make_authenticated_request(self, userid, *args, **kwds): claims = None if 'claims' in kwds: claims = kwds['claims'] del kwds['claims'] req = self._make_request(*args, **kwds) # creds = self._get_credentials(req, userid=userid) claims = make_claims(userid=userid, claims=claims) # jwt_authenticate_request(req, **creds) # note jwt_authenticate_request() returns headers if wanted jwt_authenticate_request(req, claims, MASTER_SECRET) return req def test_the_class_implements_auth_policy_interface(self): verifyClass(IAuthenticationPolicy, JWTAuthenticationPolicy) def test_from_settings_can_explicitly_set_all_properties(self): policy = JWTAuthenticationPolicy.from_settings({ "jwtauth.find_groups": "pyramid_jwtauth.tests.test_jwtauth:stub_find_groups", "jwtauth.master_secret": MASTER_SECRET, # "jwtauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", # "jwtauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", }) self.assertEqual(policy.find_groups, stub_find_groups) self.assertEqual(policy.master_secret, MASTER_SECRET) # self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) # self.assertEqual(policy.decode_mac_id, stub_decode_mac_id) # self.assertEqual(policy.encode_mac_id, stub_encode_mac_id) # def test_from_settings_passes_on_args_to_nonce_cache(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.nonce_cache": "macauthlib:NonceCache", # "macauth.nonce_cache_nonce_ttl": 42, # }) # self.assertTrue(isinstance(policy.nonce_cache, macauthlib.NonceCache)) # self.assertEqual(policy.nonce_cache.nonce_ttl, 42) # self.assertRaises(TypeError, MACAuthenticationPolicy.from_settings, { # "macauth.nonce_cache": "macauthlib:NonceCache", # "macauth.nonce_cache_invalid_arg": "WHAWHAWHAWHA", # }) def test_from_settings_errors_out_on_unexpected_keyword_args(self): self.assertRaises(ValueError, JWTAuthenticationPolicy.from_settings, { "jwtauth.unexpected": "spanish-inquisition", }) # def test_from_settings_errors_out_on_args_to_a_non_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.nonce_cache": "pyramid_macauth.tests:stub_non_callable", # "macauth.nonce_cache_arg": "invalidarg", # }) # def test_from_settings_errors_out_if_decode_mac_id_is_not_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.decode_mac_id": "pyramid_macauth.tests:stub_non_callable", # }) # def test_from_settings_errors_out_if_encode_mac_id_is_not_callable(self): # self.assertRaises(ValueError, MACAuthenticationPolicy.from_settings, { # "macauth.encode_mac_id": "pyramid_macauth.tests:stub_non_callable", # }) def test_from_settings_produces_sensible_defaults(self): policy = JWTAuthenticationPolicy.from_settings({}) # Using __code__ here is a Py2/Py3 compatible way of checking # that a bound and unbound method point to the same function object. self.assertEqual(policy.find_groups.__code__, JWTAuthenticationPolicy.find_groups.__code__) # def test_from_settings_curries_args_to_decode_mac_id(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.decode_mac_id": "pyramid_macauth.tests:stub_decode_mac_id", # "macauth.decode_mac_id_suffix": "-TEST", # }) # self.assertEqual(policy.decode_mac_id(None, "id"), ("id", "id-TEST")) # def test_from_settings_curries_args_to_encode_mac_id(self): # policy = MACAuthenticationPolicy.from_settings({ # "macauth.encode_mac_id": "pyramid_macauth.tests:stub_encode_mac_id", # "macauth.encode_mac_id_suffix": "-TEST", # }) # self.assertEqual(policy.encode_mac_id(None, "id"), ("id", "id-TEST")) # not sure if this test is particularly useful as JWTAuthenticationPolicy # doesn't work on 'remembering' requests. def test_remember_does_nothing(self): policy = JWTAuthenticationPolicy() req = self._make_authenticated_request("*****@*****.**", "/") self.assertEqual(policy.remember(req, "*****@*****.**"), []) def test_forget_gives_a_challenge_header(self): policy = JWTAuthenticationPolicy() req = self._make_authenticated_request("*****@*****.**", "/") headers = policy.forget(req) self.assertEqual(len(headers), 1) self.assertEqual(headers[0][0], "WWW-Authenticate") self.assertTrue(headers[0][1] == "Bearer") def test_forget_gives_a_challenge_header_with_custom_scheme(self): policy = JWTAuthenticationPolicy(scheme='Bearer') req = self._make_authenticated_request("*****@*****.**", "/") headers = policy.forget(req) self.assertTrue(headers[0][1] == "Bearer") def test_unauthenticated_requests_get_a_challenge(self): r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith("Bearer")) def test_unauthenticated_requests_get_a_challenge_custom_scheme(self): self.policy.scheme = 'Lolwut' r = self.app.get("/auth", status=401) challenge = r.headers["WWW-Authenticate"] self.assertTrue(challenge.startswith('Lolwut')) def test_authenticated_request_works(self): req = self._make_authenticated_request("*****@*****.**", "/auth") r = self.app.request(req) self.assertEqual(r.body, b"*****@*****.**") # def test_authentication_fails_when_macid_has_no_userid(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, hello="world") # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) def test_authentication_with_non_jwt_scheme_fails(self): req = self._make_request("/auth") req.authorization = "OpenID hello=world" self.app.request(req, status=401) req = self._make_request("/public") req.authorization = "OpenID hello=world" self.app.request(req, status=200) # def test_authentication_without_macid_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("id", "idd") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_without_timestamp_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("ts", "typostamp") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_without_nonce_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace("nonce", "typonce") # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) def test_authentication_with_expired_timestamp_fails(self): req = self._make_authenticated_request("*****@*****.**", "/auth") self.app.request(req, status=200) # now do an out of date one. ts = datetime.datetime.utcnow() - datetime.timedelta(days=1) claims = {'exp': ts} req = self._make_authenticated_request("*****@*****.**", "/auth", claims=claims) self.app.request(req, status=401) def test_can_get_claims_from_token(self): claims = { 'urn:websandhq.co.uk/auth:jti': 'hello' } req = self._make_authenticated_request("*****@*****.**", "/auth", claims=claims) policy = JWTAuthenticationPolicy( master_secret="V8 JUICE IS 1/8TH GASOLINE") encoded_claims = policy.get_claims(req) self.assertTrue('urn:websandhq.co.uk/auth:jti' in encoded_claims) self.assertEqual(encoded_claims['urn:websandhq.co.uk/auth:jti'], 'hello') # def test_authentication_with_far_future_timestamp_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # # Do an initial request so that the server can # # calculate and cache our clock skew. # ts = str(int(time.time())) # req.authorization = ("MAC", {"ts": ts}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=200) # # Now do one with a far future timestamp. # ts = str(int(time.time() + 1000)) # req.authorization = ("MAC", {"ts": ts}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) # def test_authentication_with_reused_nonce_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # # First request with that nonce should succeed. # req.authorization = ("MAC", {"nonce": "PEPPER"}) # macauthlib.sign_request(req, **creds) # r = self.app.request(req) # self.assertEqual(r.body, b"*****@*****.**") # # Second request with that nonce should fail. # req = self._make_request("/auth") # req.authorization = ("MAC", {"nonce": "PEPPER"}) # macauthlib.sign_request(req, **creds) # self.app.request(req, status=401) # def test_authentication_with_busted_macid_fails(self): # req = self._make_signed_request("*****@*****.**", "/auth") # id = macauthlib.utils.parse_authz_header(req)["id"] # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace(id, "XXX" + id) # req.environ["HTTP_AUTHORIZATION"] = authz # self.app.request(req, status=401) # def test_authentication_with_busted_signature_fails(self): # req = self._make_request("/auth") # creds = self._get_credentials(req, username="******") # macauthlib.sign_request(req, **creds) # signature = macauthlib.utils.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_groupfinder_can_block_authentication(self): req = self._make_authenticated_request("baduser", "/auth") r = self.app.request(req, status=403) req = self._make_authenticated_request("baduser", "/public") r = self.app.request(req, status=200) self.assertEqual(r.body, b"baduser") def test_groupfinder_groups_are_correctly_reported(self): req = self._make_request("/groups") r = self.app.request(req) self.assertEqual(r.json, [str(Everyone)]) req = self._make_authenticated_request("gooduser", "/groups") r = self.app.request(req) self.assertEqual(r.json, ["gooduser", str(Everyone), str(Authenticated)]) req = self._make_authenticated_request("test", "/groups") r = self.app.request(req) self.assertEqual(r.json, ["test", str(Everyone), "group", str(Authenticated)]) req = self._make_authenticated_request("baduser", "/groups") r = self.app.request(req) self.assertEqual(r.json, [str(Everyone)]) def test_access_to_public_urls(self): # Request with no credentials is allowed access. req = self._make_request("/public") resp = self.app.request(req) self.assertEqual(resp.body, b"None") # Request with valid credentials is allowed access. req = self._make_authenticated_request("*****@*****.**", "/public") resp = self.app.request(req) self.assertEqual(resp.body, b"*****@*****.**") # Request with invalid credentials still reports a userid. req = self._make_authenticated_request("*****@*****.**", "/public") token = pyramid_jwtauth.utils.parse_authz_header(req)["token"] bits = token.split('.') bits[2] = 'XXX' + bits[2] broken_token = '.'.join(bits) authz = req.environ["HTTP_AUTHORIZATION"] authz = authz.replace(token, broken_token) req.environ["HTTP_AUTHORIZATION"] = authz resp = self.app.request(req) self.assertEqual(resp.body, b"*****@*****.**") # Request with malformed credentials gets a 401 # req = self._make_signed_request("*****@*****.**", "/public") # tokenid = macauthlib.utils.parse_authz_header(req)["id"] # authz = req.environ["HTTP_AUTHORIZATION"] # authz = authz.replace(tokenid, "XXX" + tokenid) # req.environ["HTTP_AUTHORIZATION"] = authz # resp = self.app.request(req, status=401) # def test_check_signature_fails_if_no_params_present(self): # req = self._make_request("/auth") # self.assertRaises(HTTPUnauthorized, # self.policy._check_signature, req, "XXX") def test_default_groupfinder_returns_empty_list(self): policy = JWTAuthenticationPolicy() req = self._make_request("/auth") self.assertEqual(policy.find_groups("test", req), []) def test_auth_can_be_checked_several_times_on_same_request(self): req = self._make_authenticated_request("*****@*****.**", "/public") self.assertEqual(authenticated_userid(req), "*****@*****.**") self.assertEqual(authenticated_userid(req), "*****@*****.**")
class TestTASRSubjectApp(TASRTestCase): '''These tests check that the TASR S+V REST API, expected by the Avro-1124 repo code. This does not check the TASR native API calls. ''' def setUp(self): self.event_type = "gold" fix_rel_path = "schemas/%s.avsc" % (self.event_type) self.avsc_file = TASRTestCase.get_fixture_file(fix_rel_path, "r") self.schema_str = self.avsc_file.read() self.tasr_app = TestApp(APP) self.url_prefix = 'http://%s:%s/tasr' % (APP.config.host, APP.config.port) self.subject_url = '%s/subject/%s' % (self.url_prefix, self.event_type) self.content_type = 'application/json; charset=utf8' # clear out all the keys before beginning -- careful! APP.ASR.redis.flushdb() def tearDown(self): # this clears out redis after each test -- careful! APP.ASR.redis.flushdb() def abort_diff_status(self, resp, code): self.assertEqual(code, resp.status_code, u'Non-%s status code: %s' % (code, resp.status_code)) def register_subject(self, subject_name): url = '%s/subject/%s' % (self.url_prefix, subject_name) return self.tasr_app.put(url, {'subject_name': subject_name}) def register_schema(self, subject_name, schema_str, expect_errors=False): reg_url = '%s/subject/%s/register' % (self.url_prefix, subject_name) return self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=expect_errors, body=schema_str) ########################################################################### # subject tests ########################################################################### def test_all_subject_names(self): '''GET /tasr/subject - gets _all_ current subjects, as expected''' # reg two vers for target subject and one for an alt subject self.register_subject(self.event_type) alt_subject_name = 'bob' self.register_subject(alt_subject_name) # now get all and check the headers resp = self.tasr_app.request('%s/subject' % self.url_prefix, method='GET') self.abort_diff_status(resp, 200) meta_dict = SubjectHeaderBot.extract_metadata(resp) self.assertIn(self.event_type, meta_dict.keys(), 'missing subject') subj = meta_dict[self.event_type] self.assertEqual(self.event_type, subj.name, 'bad subject name') self.assertIn(alt_subject_name, meta_dict.keys(), 'missing subject') alt_subj = meta_dict[alt_subject_name] self.assertEqual(alt_subject_name, alt_subj.name, 'bad subject name') # lastly check the body buff = StringIO.StringIO(resp.body) group_names = [] for topic_line in buff: group_names.append(topic_line.strip()) buff.close() self.assertListEqual(sorted(group_names), sorted(meta_dict.keys()), 'Expected group_names in body to match headers.') def test_lookup_subject(self): '''GET /tasr/subject/<subject> - lookup the subject by name''' self.register_subject(self.event_type) resp = self.tasr_app.request(self.subject_url, method='GET') self.abort_diff_status(resp, 200) metas = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(self.event_type, metas[self.event_type].name, 'unexpected subject name') def test_lookup_subject__accept_json(self): '''GET /tasr/subject/<subject> - lookup the subject by name''' self.register_subject(self.event_type) resp = self.tasr_app.request(self.subject_url, method='GET', accept='text/json') self.abort_diff_status(resp, 200) metas = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(self.event_type, metas[self.event_type].name, 'unexpected subject name') def test_lookup_missing_subject(self): '''GET /tasr/subject/<subject> - lookup the subject by name''' missing_subject_name = 'bob' url = '%s/%s' % (self.url_prefix, missing_subject_name) resp = self.tasr_app.request(url, method='GET', expect_errors=True) self.abort_diff_status(resp, 404) def test_lookup_missing_subject__accept_json(self): '''GET /tasr/subject/<subject> - lookup the subject by name''' missing_subject_name = 'bob' url = '%s/%s' % (self.url_prefix, missing_subject_name) resp = self.tasr_app.request(url, method='GET', accept='text/json', expect_errors=True) self.abort_diff_status(resp, 404) # we expect a JSON error back, so check that we got it json_error = json.loads(resp.body) # body is parseable JSON self.assertEqual(404, json_error["status_code"], "expected a 404") def test_register_subject(self): '''PUT /tasr/subject - registers the subject (not the schema)''' resp = self.register_subject(self.event_type) self.abort_diff_status(resp, 201) metas = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(self.event_type, metas[self.event_type].name, 'unexpected subject name') def test_delete_subject(self): '''DELETE /tasr/subject/<subject> - delete the subject''' # ensure expose_delete is True mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_delete') APP.config.config.set(mode, 'expose_delete', 'True') try: self.register_subject(self.event_type) resp = self.tasr_app.request(self.subject_url, method='GET') self.abort_diff_status(resp, 200) resp = self.tasr_app.request(self.subject_url, method='DELETE', expect_errors=False) self.abort_diff_status(resp, 200) finally: # reset expose_delete to its original value APP.config.config.set(mode, 'expose_delete', orig_val) def test_delete_missing_subject(self): '''DELETE /tasr/subject/<subject> - delete a missing subject''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_delete') APP.config.config.set(mode, 'expose_delete', 'True') try: resp = self.tasr_app.request(self.subject_url, method='DELETE', expect_errors=True) self.abort_diff_status(resp, 404) finally: # reset expose_delete to its original value APP.config.config.set(mode, 'expose_delete', orig_val) def test_unexposed_delete_subject(self): '''DELETE /tasr/subject/<subject> - delete when not allowed''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_delete') APP.config.config.set(mode, 'expose_delete', 'False') try: resp = self.tasr_app.request(self.subject_url, method='DELETE', expect_errors=True) self.abort_diff_status(resp, 403) finally: # reset expose_delete to its original value APP.config.config.set(mode, 'expose_delete', orig_val) def test_register_subject__accept_json(self): '''PUT /tasr/subject - registers the subject (not the schema)''' url = '%s/subject/%s' % (self.url_prefix, self.event_type) dummy_config = {'dummy_config_key': 'dummy_config_val'} resp = self.tasr_app.put(url, dummy_config, {'Accept': 'text/json'}) self.abort_diff_status(resp, 201) metas = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(self.event_type, metas[self.event_type].name, 'unexpected subject name') # check the returned JSON to ensure it worked json_sub = json.loads(resp.body) self.assertEqual(self.event_type, json_sub["subject_name"], "bad subject name") self.assertEqual(dummy_config, json_sub["config"], "bad config") def test_register_subject_with_no_config(self): '''PUT /tasr/subject/<subject> - missing body should be OK''' resp = self.tasr_app.request(self.subject_url, method='PUT', body=None, expect_errors=False) self.abort_diff_status(resp, 201) def test_register_subject_with_config_with_empty_field_val(self): '''PUT /tasr/subject - empty subject name should be OK''' resp = self.tasr_app.put(self.subject_url, {'subject_name': ''}, expect_errors=False) self.abort_diff_status(resp, 201) def test_register_subject_with_config_with_colliding_fields(self): '''PUT /tasr/subject - empty subject name should return a 400''' resp = self.tasr_app.put(self.subject_url, {'subject_name': ['alice', 'bob']}, expect_errors=True) self.abort_diff_status(resp, 400) def test_rereg_subject_with_non_conflicting_config(self): '''PUT /tasr/subject - configs match, so no problem''' resp = self.tasr_app.put(self.subject_url, {'subject_name': 'bob'}, expect_errors=False) self.abort_diff_status(resp, 201) resp = self.tasr_app.put(self.subject_url, {'subject_name': 'bob'}, expect_errors=False) self.abort_diff_status(resp, 200) def test_rereg_subject_with_conflicting_config(self): '''PUT /tasr/subject - conflict with preexisting config should 409''' resp = self.tasr_app.put(self.subject_url, {'subject_name': 'bob'}, expect_errors=False) self.abort_diff_status(resp, 201) resp = self.tasr_app.put(self.subject_url, {'subject_name': 'alice'}, expect_errors=True) self.abort_diff_status(resp, 409) def test_reg_and_rereg_subject(self): '''PUT /tasr/subject - registers the subject (not the schema), then re-registers the same subject. The second reg should return a 200.''' resp = self.register_subject(self.event_type) self.abort_diff_status(resp, 201) resp = self.register_subject(self.event_type) self.abort_diff_status(resp, 200) metas = SubjectHeaderBot.extract_metadata(resp) self.assertEqual(self.event_type, metas[self.event_type].name, 'unexpected subject name') def test_get_subject_config(self): '''GET /tasr/subject/<subject>/config - get the config map''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) url = '%s/config' % self.subject_url resp = self.tasr_app.request(url, method='GET') self.assertEqual('subject_name=%s' % self.event_type, resp.body.strip(), 'Bad response body.') def test_update_subject_config(self): '''GET /tasr/subject/<subject>/config - get the config map''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}) self.abort_diff_status(resp, 201) url = '%s/config' % self.subject_url resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual('subject_name=%s' % self.event_type, resp.body.strip(), 'Bad response body.') resp = self.tasr_app.post(url, {'subject_name': 'alice'}) self.abort_diff_status(resp, 200) resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual('subject_name=alice', resp.body.strip(), 'Bad response body.') def test_get_subject_config_entry(self): '''GET /tasr/subject/<subject>/config/<key> - get a config value''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), self.event_type, 'value mismatch') def test_get_subject_config_entry_as_json(self): '''GET /tasr/subject/<subject>/config/<key> - get as JSON''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, headers={'Accept': 'text/json'}, method='GET') self.abort_diff_status(resp, 200) expected_dict = {'subject_name': self.event_type} json_dict = json.loads(resp.body.strip()) self.assertDictEqual(json_dict, expected_dict, 'mismatch') def test_set_subject_config_entry_overwrite(self): '''POST /tasr/subject/<subject>/config/<key> - get a config value''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) # check that the original value is right url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), self.event_type, 'value mismatch') # set a new entry -- key is part of the URL, the POST body is the value val = 'ALICE_VAL' resp = self.tasr_app.post(url, val) self.abort_diff_status(resp, 200) # now grab the value for the new entry and check it is what we sent resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) rval = resp.body.strip() self.assertEqual(rval, val, 'value mismatch (%s != %s)' % (rval, val)) def test_set_subject_config_entry_new(self): '''POST /tasr/subject/<subject>/config/<key> - get a config value''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) # check that the original value is right url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), self.event_type, 'value mismatch') # set a new entry -- key is part of the URL, the POST body is the value key = 'alice_key' val = 'ALICE_VAL' url = '%s/config/%s' % (self.subject_url, key) resp = self.tasr_app.post(url, val) self.abort_diff_status(resp, 200) # now grab the value for the new entry and check it is what we sent resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) rval = resp.body.strip() self.assertEqual(rval, val, 'value mismatch (%s != %s)' % (rval, val)) def test_delete_subject_config_entry(self): '''POST /tasr/subject/<subject>/config/<key> - get a config value''' resp = self.tasr_app.put(self.subject_url, {'subject_name': self.event_type}, expect_errors=False) self.abort_diff_status(resp, 201) # add a new entry # set a new entry -- key is part of the URL, the POST body is the value key = 'alice_key' val = 'ALICE_VAL' url = '%s/config/%s' % (self.subject_url, key) resp = self.tasr_app.post(url, val) self.abort_diff_status(resp, 200) # check that both entries are present and correct url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), self.event_type, 'value mismatch') url = '%s/config/%s' % (self.subject_url, key) resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), val, 'value mismatch') # delete the new entry resp = self.tasr_app.delete(url) self.abort_diff_status(resp, 200) # now check that the new entry is gone... resp = self.tasr_app.request(url, method='GET', expect_errors=True) self.abort_diff_status(resp, 404) # ...and that the original entry is unaffected url = '%s/config/%s' % (self.subject_url, 'subject_name') resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(resp.body.strip(), self.event_type, 'value mismatch') def test_subject_integral(self): '''GET /tasr/subject/<subject_name>/integral - should always return False as our IDs are not integers (though our version numbers are). ''' self.register_subject(self.event_type) url = '%s/integral' % self.subject_url resp = self.tasr_app.request(url, method='GET') self.abort_diff_status(resp, 200) self.assertEqual(u'False', resp.body.strip(), 'Bad response body.') def test_missing_subject_integral(self): '''GET /tasr/subject/<subject_name>/integral - for a bad subject''' url = '%s/integral' % self.subject_url resp = self.tasr_app.request(url, method='GET', expect_errors=True) self.abort_diff_status(resp, 404) def test_all_subject_ids(self): '''GET /tasr/subject/<subject>/all_ids - gets schema IDs for all versions of the subject, in order, one per line in the response body. ''' sha256_ids = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) sha256_ids.append(meta.sha256_id) url = '%s/all_ids' % self.subject_url resp = self.tasr_app.get(url) buff = StringIO.StringIO(resp.body) all_ids = [] for topic_line in buff: all_ids.append(topic_line.strip()) buff.close() self.assertListEqual(sha256_ids, all_ids, 'Bad ID list.') def test_all_subject_schemas(self): '''GET /tasr/subject/<subject>/all_schemas - gets schemas for all versions of the subject, in order, one per line in the response body. ''' versions = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) versions.append(resp.body.strip()) url = '%s/all_schemas' % self.subject_url resp = self.tasr_app.get(url) buff = StringIO.StringIO(resp.body) all_vers = [] for topic_line in buff: all_vers.append(topic_line.strip()) buff.close() self.assertListEqual(versions, all_vers, 'Bad versions list.') ########################################################################### # schema tests ########################################################################### def test_register_schema(self): '''PUT /tasr/subject/<subject>/register - as expected''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) def test_reg_fail_on_empty_schema(self): '''PUT /tasr/subject/<subject>/register - empty schema''' resp = self.register_schema(self.event_type, None, True) self.abort_diff_status(resp, 400) def test_reg_fail_on_invalid_schema(self): '''PUT /tasr/subject/<subject>/register - bad schema''' bad_schema_str = "%s }" % self.schema_str resp = self.register_schema(self.event_type, bad_schema_str, True) self.abort_diff_status(resp, 400) def test_reg_fail_on_bad_content_type(self): '''PUT /tasr/subject/<subject>/register - bad content type''' reg_url = '%s/register' % self.subject_url resp = self.tasr_app.request(reg_url, method='PUT', content_type='text/plain; charset=utf8', expect_errors=True, body=self.schema_str) self.abort_diff_status(resp, 406) def test_reg_and_rereg(self): '''PUT /tasr/subject/<subject>/register - multiple calls, one schema''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta.group_version(self.event_type), 'bad ver') resp1 = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp1, 200) meta1 = SchemaHeaderBot.extract_metadata(resp1) self.assertEqual(1, meta1.group_version(self.event_type), 'bad ver') def test_fail_rereg_with_incompatible_schema(self): '''PUT /tasr/subject/<subject>/register - incompatible schemas''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta.group_version(self.event_type), 'bad ver') # swap long for int, an incompatible change targ = '{"name": "source__timestamp", "type": "long"}' replacement = '{"name": "source__timestamp", "type": "int"}' incompat_schema_str = self.schema_str.replace(targ, replacement, 1) # now registering the schema should return a 409 reg_url = '%s/register' % self.subject_url resp1 = self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=True, body=incompat_schema_str) self.abort_diff_status(resp1, 409) def test_force_rereg_with_incompatible_schema(self): '''PUT /tasr/subject/<subject>/force_register - incompatible schemas''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_force_register') APP.config.config.set(mode, 'expose_force_register', 'True') try: resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta.group_version(self.event_type), 'bad ver') # swap long for int, an incompatible change targ = '{"name": "source__timestamp", "type": "long"}' replacement = '{"name": "source__timestamp", "type": "int"}' incompat_schema_str = self.schema_str.replace(targ, replacement, 1) # now forcibly registering the schema should return a 201 reg_url = '%s/force_register' % self.subject_url resp1 = self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=False, body=incompat_schema_str) self.abort_diff_status(resp1, 201) meta = SchemaHeaderBot.extract_metadata(resp1) self.assertEqual(2, meta.group_version(self.event_type), 'bad ver') finally: # reset expose_force_register to its original value APP.config.config.set(mode, 'expose_force_register', orig_val) def test_unexposed_force_rereg_with_incompatible_schema(self): '''PUT /tasr/subject/<subject>/force_register - incompatible schemas''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_force_register') APP.config.config.set(mode, 'expose_force_register', 'False') try: resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta.group_version(self.event_type), 'bad ver') # swap long for int, an incompatible change targ = '{"name": "source__timestamp", "type": "long"}' replacement = '{"name": "source__timestamp", "type": "int"}' incompat_schema_str = self.schema_str.replace(targ, replacement, 1) # now forcibly registering the schema should return a 201 reg_url = '%s/force_register' % self.subject_url resp1 = self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=True, body=incompat_schema_str) self.abort_diff_status(resp1, 403) finally: # reset expose_force_register to its original value APP.config.config.set(mode, 'expose_force_register', orig_val) def test_multi_subject_reg(self): '''PUT /tasr/subject/<subject>/register - multi subjects, one schema''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta.group_version(self.event_type), 'bad ver') alt_subject = 'bob' resp2 = self.register_schema(alt_subject, self.schema_str) self.abort_diff_status(resp2, 201) meta2 = SchemaHeaderBot.extract_metadata(resp2) self.assertEqual(1, meta2.group_version(alt_subject), 'bad ver') # check that first association still holds resp3 = self.tasr_app.get('%s/latest' % self.subject_url) meta3 = SchemaHeaderBot.extract_metadata(resp3) self.assertEqual(1, meta3.group_version(self.event_type), 'lost reg') def test_reg_if_latest(self): '''PUT /tasr/subject/<subject name>/register_if_latest/<version> As expected, we reference the version number of the latest version. ''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) cur_ver = meta.group_version(self.event_type) schema_str_2 = self.get_schema_permutation(self.schema_str) url = '%s/register_if_latest/%s' % (self.subject_url, cur_ver) resp = self.tasr_app.request(url, method='PUT', content_type=self.content_type, body=schema_str_2) self.abort_diff_status(resp, 201) def test_fail_reg_if_latest_bad_ver(self): '''PUT /tasr/subject/<subject name>/register_if_latest/<version> Should fail as version number is non-existent. ''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) bad_ver = meta.group_version(self.event_type) + 1 schema_str_2 = self.get_schema_permutation(self.schema_str) url = '%s/register_if_latest/%s' % (self.subject_url, bad_ver) resp = self.tasr_app.request(url, method='PUT', content_type=self.content_type, expect_errors=True, body=schema_str_2) self.abort_diff_status(resp, 409) def test_fail_reg_if_latest_old_ver(self): '''PUT /tasr/subject/<subject name>/register_if_latest/<version> Should fail as version number exists but is not the latest. ''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) old_ver = meta.group_version(self.event_type) - 1 url = '%s/register_if_latest/%s' % (self.subject_url, old_ver) resp = self.tasr_app.request(url, method='PUT', content_type=self.content_type, expect_errors=True, body=self.schema_str) self.abort_diff_status(resp, 409) # retrieval def test_lookup_by_schema_str(self): '''POST /tasr/subject/<subject>/schema - as expected''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # canonicalized schema string is passed back on registration canonicalized_schema_str = resp.body meta_1 = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(1, meta_1.group_version(self.event_type), 'bad ver') schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) # get by POSTed schema post_url = "%s/schema" % self.subject_url post_resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, body=self.schema_str) meta_2 = SchemaHeaderBot.extract_metadata(post_resp) self.assertEqual(1, meta_2.group_version(self.event_type), 'bad ver') self.assertEqual(meta_1.sha256_id, meta_2.sha256_id, 'SHA mismatch') self.assertEqual(meta_1.md5_id, meta_2.md5_id, 'MD5 mismatch') self.assertEqual(canonicalized_schema_str, post_resp.body, u'Unexpected body: %s' % post_resp.body) def test_fail_lookup_by_schema_str_on_empty_schema_str(self): '''POST /tasr/subject/<subject>/schema - fail on empty schema string''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.subject_url resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body=None) self.abort_diff_status(resp, 400) def test_fail_lookup_by_schema_str_on_invalid_schema_str(self): '''POST /tasr/subject/<subject>/schema - fail on bad schema string''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) post_url = "%s/schema" % self.subject_url resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body="%s }" % self.schema_str) self.abort_diff_status(resp, 400) def test_fail_lookup_by_schema_str_on_unregistered_schema_str(self): '''POST /tasr/subject/<subject>/schema - fail on new schema string''' post_url = "%s/schema" % self.subject_url resp = self.tasr_app.request(post_url, method='POST', content_type=self.content_type, expect_errors=True, body=self.schema_str) self.assertEqual(404, resp.status_int, u'Unexpected status: %s' % resp.status_int) meta = SchemaHeaderBot.extract_metadata(resp) self.assertTrue(meta.sha256_id, 'SHA missing') self.assertTrue(meta.md5_id, 'MD5 missing') def test_lookup_by_subject_and_version(self): '''GET /tasr/subject/<subject>/version/<version> - as expected''' schemas = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # step through and request each version by version number for v in range(1, 50): get_url = '%s/version/%s' % (self.subject_url, v) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(v, meta.group_version(self.event_type), 'bad ver') self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_fail_lookup_for_subject_and_version_on_bad_version(self): '''GET /tasr/subject/<subject>/version/<version> - fail on bad ver''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) meta = SchemaHeaderBot.extract_metadata(resp) bad_version = meta.group_version(self.event_type) + 1 get_url = '%s/version/%s' % (self.subject_url, bad_version) get_resp = self.tasr_app.request(get_url, method='GET', expect_errors=True) self.abort_diff_status(get_resp, 404) def test_lookup_for_subject_and_version_on_stale_version(self): '''GET /tasr/subject/<subject>/version/<version> - 1 schema, 2 vers''' resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # get the latest version, which should be 3 resp = self.tasr_app.get('%s/latest' % self.subject_url) self.abort_diff_status(resp, 200) meta_v3 = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(3, meta_v3.group_version(self.event_type), 'bad ver') # now get ver 1, which should have the same body as ver 3 get_url = '%s/version/%s' % (self.subject_url, 1) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta_v1 = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(1, meta_v1.group_version(self.event_type), 'bad ver') self.assertEqual(resp.body, get_resp.body, 'schema body mismatch') def test_lookup_by_subject_and_sha256_id_str(self): '''GET /tasr/subject/<subject>/id/<id_str> - as expected''' sha256_ids = [] schemas = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) sha256_ids.append(ver_meta.sha256_id) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # step through and request each version by version number for v in range(1, 50): get_url = '%s/id/%s' % (self.subject_url, sha256_ids[v - 1]) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(sha256_ids[v - 1], meta.sha256_id, 'bad ID') self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_by_subject_and_md5_id_str(self): '''GET /tasr/subject/<subject>/id/<id_str> - as expected''' md5_ids = [] schemas = [] # add a bunch of versions for our subject for v in range(1, 50): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) ver_meta = SchemaHeaderBot.extract_metadata(resp) md5_ids.append(ver_meta.md5_id) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # step through and request each version by version number for v in range(1, 50): get_url = '%s/id/%s' % (self.subject_url, md5_ids[v - 1]) get_resp = self.tasr_app.request(get_url, method='GET') self.abort_diff_status(get_resp, 200) meta = SchemaHeaderBot.extract_metadata(get_resp) self.assertEqual(md5_ids[v - 1], meta.md5_id, 'bad ID') self.assertEqual(schemas[v - 1], get_resp.body, u'Unexpected body: %s' % get_resp.body) def test_lookup_latest(self): '''GET /tasr/subject/<subject name>/latest''' # should be nothing there to start with resp = self.tasr_app.request('%s/latest' % self.subject_url, method='GET', expect_errors=True) self.abort_diff_status(resp, 404) # reg a schema so we'll have something to lookup resp = self.register_schema(self.event_type, self.schema_str) self.abort_diff_status(resp, 201) # reg a second schema so we could get a stale version schema_str_2 = self.get_schema_permutation(self.schema_str) resp = self.register_schema(self.event_type, schema_str_2) self.abort_diff_status(resp, 201) # check that lookup gets the _latest_ schema resp = self.tasr_app.get('%s/latest' % self.subject_url) self.abort_diff_status(resp, 200) meta = SchemaHeaderBot.extract_metadata(resp) self.assertEqual(2, meta.group_version(self.event_type), 'bad ver') def test_master_schema_for_subject(self): '''GET /tasr/subject/<subject>/master - as expected''' schemas = [] # add a bunch of versions for our subject for v in range(1, 4): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # grab the master and check that all the expected fields are there resp = self.tasr_app.get('%s/master' % self.subject_url) self.abort_diff_status(resp, 200) master_fnames = [] for mfield in json.loads(resp.body)['fields']: master_fnames.append(mfield['name']) # check the original fields for ofield in json.loads(self.schema_str)['fields']: if not ofield['name'] in master_fnames: self.fail('missing original field %s' % ofield['name']) # now check all of the extra fields from the version permutations for v in range(1, 4): fname = "fn_%s" % v if not fname in master_fnames: self.fail('missing field %s' % fname) def test_incompat_master_schema_for_subject(self): '''GET /tasr/subject/<subject>/master - as expected''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'expose_force_register') APP.config.config.set(mode, 'expose_force_register', 'True') try: # first add a version that will not be compatible targ = '{"name": "source__timestamp", "type": "long"}' replacement = '{"name": "source__timestamp", "type": "int"}' incompat_schema_str = self.schema_str.replace(targ, replacement, 1) resp = self.register_schema(self.event_type, incompat_schema_str) self.abort_diff_status(resp, 201) # now force register a set of compatible schemas schemas = [] for v in range(1, 4): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) reg_url = '%s/force_register' % self.subject_url resp1 = self.tasr_app.request(reg_url, method='PUT', content_type=self.content_type, expect_errors=False, body=ver_schema_str) self.abort_diff_status(resp1, 201) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp1.body schemas.append(canonicalized_schema_str) # grab the master and check that all the expected fields are there reg_url = '%s/master' % self.subject_url resp2 = self.tasr_app.request(reg_url, method='GET', content_type=self.content_type, expect_errors=True, body=None) self.abort_diff_status(resp2, 409) master_fnames = [] for mfield in json.loads(resp2.body)['fields']: master_fnames.append(mfield['name']) # check the original fields for ofield in json.loads(self.schema_str)['fields']: if not ofield['name'] in master_fnames: self.fail('missing original field %s' % ofield['name']) # now check all of the extra fields from the version permutations for v in range(1, 4): fname = "fn_%s" % v if not fname in master_fnames: self.fail('missing field %s' % fname) finally: # reset expose_force_register to its original value APP.config.config.set(mode, 'expose_force_register', orig_val) def test_hdfs_master_schema_for_subject(self): '''GET /tasr/subject/<subject>/master - as expected, check hdfs''' mode = APP.config.mode orig_val = APP.config.config.get(mode, 'push_masters_to_hdfs') APP.config.config.set(mode, 'push_masters_to_hdfs', 'True') try: resp = None schemas = [] # add a bunch of versions for our subject for v in range(1, 4): ver_schema_str = self.get_schema_permutation(self.schema_str, "fn_%s" % v) resp = self.register_schema(self.event_type, ver_schema_str) self.abort_diff_status(resp, 201) # schema str with canonicalized whitespace returned canonicalized_schema_str = resp.body schemas.append(canonicalized_schema_str) # grab the master FROM HDFS, check that all the expected fields hdfs_path = resp.headers['X-Tasr-Hdfs-Master-Path'] hdfs_user = APP.config.config.get(mode, 'webhdfs_user') # first get the current master from HDFS resp = requests.get('%s?user.name=%s&op=OPEN' % (hdfs_path, hdfs_user)) self.abort_diff_status(resp, 200) master_fnames = [] for mfield in json.loads(resp.content)['fields']: master_fnames.append(mfield['name']) # check the original fields for ofield in json.loads(self.schema_str)['fields']: if not ofield['name'] in master_fnames: self.fail('missing original field %s' % ofield['name']) # now check all of the extra fields from the version permutations for v in range(1, 4): fname = "fn_%s" % v if not fname in master_fnames: self.fail('missing field %s' % fname) finally: # reset expose_force_register to its original value APP.config.config.set(mode, 'push_masters_to_hdfs', orig_val)