Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
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
Beispiel #4
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
Beispiel #5
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=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
Beispiel #6
0
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
Beispiel #7
0
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)
Beispiel #8
0
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"*****@*****.**")
Beispiel #10
0
    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')
Beispiel #11
0
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)
Beispiel #13
0
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')
Beispiel #17
0
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.')
Beispiel #18
0
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), "*****@*****.**")
Beispiel #19
0
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)
Beispiel #20
0
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), "*****@*****.**")
Beispiel #21
0
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')
Beispiel #22
0
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), "*****@*****.**")
Beispiel #24
0
    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')
Beispiel #25
0
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)