class MessageTestCase(unittest.TestCase): def setUp(self): twisted.internet.base.DelayedCall.debug = True conf = AutopushConfig( hostname="localhost", statsd_host=None, crypto_key='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', ) db = test_db() self.message_mock = db._message = Mock(spec=Message) self.fernet_mock = conf.fernet = Mock(spec=Fernet) app = EndpointHTTPFactory.for_handler(MessageHandler, conf, db=db) self.client = Client(app) def url(self, **kwargs): return '/m/{message_id}'.format(**kwargs) @inlineCallbacks def test_delete_token_invalid(self): self.fernet_mock.configure_mock(**{ "decrypt.side_effect": InvalidToken}) resp = yield self.client.delete(self.url(message_id='%20')) assert resp.get_status() == 400 @inlineCallbacks def test_delete_token_wrong_components(self): self.fernet_mock.decrypt.return_value = "123:456" resp = yield self.client.delete(self.url(message_id="ignored")) assert resp.get_status() == 400 @inlineCallbacks def test_delete_token_wrong_kind(self): tok = ":".join(["r", dummy_uaid.hex, str(dummy_chid)]) self.fernet_mock.decrypt.return_value = tok resp = yield self.client.delete(self.url(message_id='ignored')) assert resp.get_status() == 400 @inlineCallbacks def test_delete_invalid_timestamp_token(self): tok = ":".join(["02", str(dummy_chid)]) self.fernet_mock.decrypt.return_value = tok resp = yield self.client.delete(self.url(message_id='ignored')) assert resp.get_status() == 400 @inlineCallbacks def test_delete_success(self): tok = ":".join(["m", dummy_uaid.hex, str(dummy_chid)]) self.fernet_mock.decrypt.return_value = tok self.message_mock.configure_mock(**{ "delete_message.return_value": True}) resp = yield self.client.delete(self.url(message_id="123-456")) self.message_mock.delete_message.assert_called() assert resp.get_status() == 204 @inlineCallbacks def test_delete_topic_success(self): tok = ":".join(["01", dummy_uaid.hex, str(dummy_chid), "Inbox"]) self.fernet_mock.decrypt.return_value = tok self.message_mock.configure_mock(**{ "delete_message.return_value": True}) resp = yield self.client.delete(self.url(message_id="123-456")) self.message_mock.delete_message.assert_called() assert resp.get_status() == 204 @inlineCallbacks def test_delete_topic_error_parts(self): tok = ":".join(["01", dummy_uaid.hex, str(dummy_chid)]) self.fernet_mock.decrypt.return_value = tok self.message_mock.configure_mock(**{ "delete_message.return_value": True}) resp = yield self.client.delete(self.url(message_id="123-456")) assert resp.get_status() == 400 @inlineCallbacks def test_delete_db_error(self): tok = ":".join(["m", dummy_uaid.hex, str(dummy_chid)]) self.fernet_mock.decrypt.return_value = tok self.message_mock.configure_mock(**{"delete_message.return_value": False}) resp = yield self.client.delete(self.url(message_id="ignored")) assert 204 == resp.get_status()
class TestClient(unittest.TestCase): def setUp(self): self.app = mock_app_builder() self.client = Client(self.app) def test_create_client(self): app = mock_app_builder() client = Client(app) self.assertTrue(client.app) @inlineCallbacks def test_get_request(self): response = yield self.client.get("/testing/") self.assertEqual(response.content, "Something") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_get_request_with_params(self): response = yield self.client.get("/testing/", {"q": "query"}) self.assertEqual(response.content, "Something") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_post_request(self): response = yield self.client.post("/testing/") self.assertEqual(response.content, "Something posted") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_put_request(self): response = yield self.client.put("/testing/") self.assertEqual(response.content, "Something put") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_head_request(self): response = yield self.client.head("/testing/") self.assertEqual(response.content, "") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_delete_request(self): response = yield self.client.delete("/testing/") self.assertEqual(response.content, "") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_get_deferred_request(self): response = yield self.client.get("/deferred_testing/") self.assertEqual(response.content, "Something...done!") self.assertTrue(len(response.headers) > 3) @inlineCallbacks def test_cookies(self): response = yield self.client.get("/cookie_testing/") self.assertEqual( self.client.cookies.get_secure_cookie("test_cookie"), "test_value" ) response = yield self.client.post("/cookie_testing/") self.assertEqual(response.content, "test_value")
class RegistrationTestCase(unittest.TestCase): CORS_HEAD = "POST" def setUp(self): twisted.internet.base.DelayedCall.debug = True conf = AutopushConfig( hostname="localhost", statsd_host=None, bear_hash_key='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB=', ) self.fernet_mock = conf.fernet = Mock(spec=Fernet) self.db = db = test_db() db.router = Mock(spec=Router) db.router.register_user.return_value = (True, {}, {}) db.router.get_uaid.return_value = { "router_type": "test", "router_data": dict() } db.create_initial_message_tables() self.routers = routers = routers_from_config(conf, db, Mock()) routers["test"] = Mock(spec=IRouter) app = EndpointHTTPFactory(conf, db=db, routers=routers) self.client = Client(app) self.request_mock = Mock(body=b'', arguments={}, headers={}) self.reg = NewRegistrationHandler(app, self.request_mock) self.auth = ("WebPush %s" % generate_hash(conf.bear_hash_key[0], dummy_uaid.hex)) self.conf = conf def url(self, router_token='test', **kwargs): urlfmt = '/v1/{router_type}/{router_token}/registration' result = urlfmt.format(router_token=router_token, **kwargs) if kwargs.get('uaid'): result += '/' + kwargs.get('uaid') if kwargs.get('chid'): result += '/subscription/' + kwargs.get('chid') return result def patch(self, *args, **kwargs): """Patch an object only for the duration of a test""" patch_obj = patch(*args, **kwargs) patch_obj.__enter__() self.addCleanup(patch_obj.__exit__) def test_base_tags(self): self.reg._base_tags = [] self.reg.request = Mock(headers={'user-agent': 'test'}, host='example.com:8080') tags = self.reg.base_tags() assert tags == ['user_agent:test', 'host:example.com:8080'] # previously failed tags = self.reg.base_tags() assert tags == ['user_agent:test', 'host:example.com:8080'] def _check_error(self, resp, code, errno, error, message=None): d = json.loads(resp.content) assert d.get("code") == code assert d.get("errno") == errno assert d.get("error") == error def test_init_info(self): h = self.request_mock.headers h["user-agent"] = "myself" d = self.reg._init_info() assert d["user_agent"] == "myself" self.request_mock.remote_ip = "local1" d = self.reg._init_info() assert d["remote_ip"] == "local1" self.request_mock.headers["x-forwarded-for"] = "local2" d = self.reg._init_info() assert d["remote_ip"] == "local2" def test_conf_crypto_key(self): fake = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' conf = AutopushConfig(crypto_key=fake) assert conf.fernet._fernets[0]._encryption_key == ( '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') fake2 = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=' conf = AutopushConfig(crypto_key=[fake, fake2]) assert conf.fernet._fernets[0]._encryption_key == ( '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert conf.fernet._fernets[1]._encryption_key == ( '\x10A\x04\x10A\x04\x10A\x04\x10A\x04\x10A\x04\x10') def test_cors(self): ch1 = "Access-Control-Allow-Origin" ch2 = "Access-Control-Allow-Methods" reg = self.reg reg.conf.cors = False assert reg._headers.get(ch1) != "*" assert reg._headers.get(ch2) != self.CORS_HEAD reg.clear_header(ch1) reg.clear_header(ch2) reg.conf.cors = True reg.prepare() assert reg._headers[ch1] == "*" assert reg._headers[ch2] == self.CORS_HEAD def test_cors_head(self): ch1 = "Access-Control-Allow-Origin" ch2 = "Access-Control-Allow-Methods" reg = self.reg reg.conf.cors = True reg.prepare() reg.head(None) assert reg._headers[ch1] == "*" assert reg._headers[ch2] == self.CORS_HEAD def test_cors_options(self): ch1 = "Access-Control-Allow-Origin" ch2 = "Access-Control-Allow-Methods" reg = self.reg reg.conf.cors = True reg.prepare() reg.options(None) assert reg._headers[ch1] == "*" assert reg._headers[ch2] == self.CORS_HEAD @inlineCallbacks def test_post(self): self.patch('uuid.uuid4', return_value=dummy_uaid) self.fernet_mock.configure_mock(**{ 'encrypt.return_value': 'abcd123', }) resp = yield self.client.post( self.url(router_type='webpush', router_token='yyy'), headers={"Authorization": self.auth}, body=json.dumps(dict( type="webpush", channelID=str(dummy_chid), data={}, )) ) assert resp.get_status() == 200 payload = json.loads(resp.content) assert payload["uaid"] == dummy_uaid.hex assert payload["channelID"] == dummy_chid.hex assert payload["endpoint"] == "http://localhost/wpush/v1/abcd123" assert "secret" in payload @inlineCallbacks def test_post_gcm(self): self.patch('uuid.uuid4', side_effect=(uuid.uuid4(), dummy_uaid, dummy_chid)) from autopush.router.gcm import GCMRouter sids = {"182931248179192": {"auth": "aailsjfilajdflijdsilfjsliaj"}} gcm = GCMRouter( self.conf, {"dryrun": True, "senderIDs": sids, "endpoint": "gcm-http.googleapis.com/gcm/send"}, SinkMetrics() ) self.routers["gcm"] = gcm self.fernet_mock.configure_mock(**{ 'encrypt.return_value': 'abcd123', }) resp = yield self.client.post( self.url(router_type="gcm", router_token="182931248179192"), headers={"Authorization": self.auth}, body=json.dumps(dict( channelID=str(dummy_chid), token="182931248179192", )) ) assert resp.get_status() == 200 payload = json.loads(resp.content) assert payload["uaid"] == dummy_uaid.hex assert payload["channelID"] == dummy_chid.hex assert payload["endpoint"] == "http://localhost/wpush/v1/abcd123" calls = self.db.router.register_user.call_args call_args = calls[0][0] assert has_connected_this_month(call_args) is True assert "secret" in payload @inlineCallbacks def test_post_invalid_args(self, *args): resp = yield self.client.post( self.url(router_type="foo"), headers={"Authorization": self.auth}, body=json.dumps(dict( type="invalid", data={}, )) ) self._check_error(resp, 400, 108, "Bad Request") @inlineCallbacks def test_post_bad_router_type(self): self.patch('uuid.uuid4', return_value=dummy_uaid) resp = yield self.client.post( self.url(router_type="simplepush"), headers={"Authorization": self.auth}, body=json.dumps(dict( type="invalid", channelID=str(dummy_chid), data={}, )) ) self._check_error(resp, 400, 108, "Bad Request") @inlineCallbacks def test_post_bad_router_register(self, *args): router = self.routers["webpush"] rexc = RouterException("invalid", status_code=402, errno=107) router.register = Mock(side_effect=rexc) resp = yield self.client.post( self.url(router_type="webpush"), headers={"Authorization": self.auth}, body=json.dumps(dict( type="webpush", channelID=str(dummy_chid), data={}, )) ) self._check_error(resp, rexc.status_code, rexc.errno, "") @inlineCallbacks def test_post_existing_uaid(self): self.patch('uuid.uuid4', return_value=dummy_chid) self.fernet_mock.configure_mock(**{ 'encrypt.return_value': 'abcd123', }) resp = yield self.client.post( self.url(router_type="test", uaid=dummy_uaid.hex) + "/subscription", headers={"Authorization": self.auth}, body=json.dumps(dict( channelID=str(dummy_chid), )) ) payload = json.loads(resp.content) assert payload["channelID"] == dummy_chid.hex assert payload["endpoint"] == "http://localhost/wpush/v1/abcd123" @inlineCallbacks def test_no_uaid(self): self.db.router.get_uaid.side_effect = ItemNotFound resp = yield self.client.delete( self.url(router_type="webpush", uaid=dummy_uaid.hex, chid=str(dummy_chid)) ) self._check_error(resp, 410, 103, "") @inlineCallbacks def test_no_auth(self): resp = yield self.client.delete( self.url(router_type="webpush", uaid=dummy_uaid.hex, chid=str(dummy_chid)), ) self._check_error(resp, 401, 109, "Unauthorized") @inlineCallbacks def test_bad_body(self): url = self.url(router_type="webpush", uaid=dummy_uaid.hex) resp = yield self.client.put(url, body="{invalid") self._check_error(resp, 400, 108, "Bad Request") @inlineCallbacks def test_post_bad_params(self): self.patch('uuid.uuid4', return_value=dummy_uaid) resp = yield self.client.delete( self.url(router_type="webpush", uaid=dummy_uaid.hex, chid=str(dummy_chid)), headers={"Authorization": "WebPush Invalid"}, body=json.dumps(dict( channelID=str(dummy_chid), )) ) self._check_error(resp, 401, 109, 'Unauthorized') @inlineCallbacks def test_post_nochid(self, *args): self.patch('uuid.uuid4', return_value=dummy_chid) self.fernet_mock.configure_mock(**{ 'encrypt.return_value': 'abcd123', }) resp = yield self.client.post( self.url(router_type="webpush", uaid=dummy_uaid.hex) + "/subscription", headers={"Authorization": self.auth}, body=json.dumps(dict( type="webpush", data={}, )) ) payload = json.loads(resp.content) assert payload["channelID"] == dummy_chid.hex assert payload["endpoint"] == "http://localhost/wpush/v1/abcd123" @inlineCallbacks def test_post_with_app_server_key(self, *args): self.patch('uuid.uuid4', return_value=dummy_chid) dummy_key = "RandomKeyString" def mock_encrypt(cleartext): assert len(cleartext) == 64 # dummy_uaid assert cleartext[0:16] == ( 'abad1dea00000000aabbccdd00000000'.decode('hex')) # dummy_chid assert cleartext[16:32] == ( 'deadbeef00000000decafbad00000000'.decode('hex')) # sha256(dummy_key).digest() assert cleartext[32:] == ( '47aedd050b9e19171f0fa7b8b65ca670' '28f0bc92cd3f2cd3682b1200ec759007').decode('hex') return 'abcd123' self.fernet_mock.configure_mock(**{ 'encrypt.side_effect': mock_encrypt, }) resp = yield self.client.post( self.url(router_type="webpush", uaid=dummy_uaid.hex) + "/subscription", headers={"Authorization": self.auth}, body=json.dumps(dict( type="webpush", key=utils.base64url_encode(dummy_key), data={}, )) ) payload = json.loads(resp.content) assert payload["channelID"] == dummy_chid.hex assert payload["endpoint"] == "http://localhost/wpush/v2/abcd123" @inlineCallbacks def test_put(self, *args): self.patch('uuid.uuid4', return_value=dummy_uaid) data = dict(token="some_token") frouter = self.routers["test"] frouter.register = Mock() frouter.register.return_value = data uri = self.url(router_type='test', uaid=dummy_uaid.hex) resp = yield self.client.put( uri, headers={"Authorization": self.auth}, body=json.dumps(data), ) payload = json.loads(resp.content) assert payload == {} frouter.register.assert_called_with( uaid="", router_data=data, app_id='test', ) user_data = self.db.router.register_user.call_args[0][0] assert user_data['uaid'] == dummy_uaid.hex assert user_data['router_type'] == 'test' assert user_data['router_data']['token'] == 'some_token' @inlineCallbacks def test_put_bad_auth(self, *args): self.patch('uuid.uuid4', return_value=dummy_uaid) resp = yield self.client.put( self.url(router_type="test", uaid=dummy_uaid.hex), headers={"Authorization": "Fred Smith"}, body=json.dumps(dict(token="blah")) ) self._check_error(resp, 401, 109, "Unauthorized") @inlineCallbacks def test_put_bad_uaid_path(self, *args): self.patch('uuid.uuid4', return_value=dummy_uaid) resp = yield self.client.put( self.url(router_type="test", uaid="invalid"), headers={"Authorization": "Fred Smith"}, body=json.dumps(dict(token="blah")) ) assert resp.get_status() == 404 @inlineCallbacks def test_put_bad_arguments(self, *args): self.patch('uuid.uuid4', return_value=dummy_chid) resp = yield self.client.put( self.url(router_type='foo', uaid=dummy_uaid.hex), headers={"Authorization": self.auth}, body=json.dumps(dict( type="test", data=dict(token="some_token"), )) ) self._check_error(resp, 400, 108, "Bad Request") @inlineCallbacks def test_put_bad_router_register(self): frouter = self.routers["webpush"] rexc = RouterException("invalid", status_code=400, errno=108) frouter.register = Mock(side_effect=rexc) resp = yield self.client.put( self.url(router_type='simplepush', uaid=dummy_uaid.hex), headers={"Authorization": self.auth}, body=json.dumps(dict(token="blah")) ) self._check_error(resp, rexc.status_code, rexc.errno, "Bad Request") @inlineCallbacks def test_delete_success(self): notif = make_webpush_notification(dummy_uaid.hex, str(dummy_chid)) messages = self.db.message messages.register_channel(dummy_uaid.hex, str(dummy_chid)) messages.store_message(notif) yield self.client.delete( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex, chid=str(dummy_chid)), headers={"Authorization": self.auth}, ) @inlineCallbacks def test_delete_bad_chid_value(self): notif = make_webpush_notification(dummy_uaid.hex, str(dummy_chid)) messages = self.db.message messages.register_channel(dummy_uaid.hex, str(dummy_chid)) messages.store_message(notif) resp = yield self.client.delete( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex, chid=uuid.uuid4().hex), headers={"Authorization": self.auth}, ) self._check_error(resp, 410, 106, "") @inlineCallbacks def test_delete_no_such_chid(self): notif = make_webpush_notification(dummy_uaid.hex, str(dummy_chid)) messages = self.db.message messages.register_channel(dummy_uaid.hex, str(dummy_chid)) messages.store_message(notif) # Moto can't handle set operations of this nature so we have # to mock the reply self.patch('autopush.db.Message.unregister_channel', return_value=False) resp = yield self.client.delete( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex, chid=str(uuid.uuid4())), headers={"Authorization": self.auth} ) self._check_error(resp, 410, 106, "") @inlineCallbacks def test_delete_uaid(self): notif = make_webpush_notification(dummy_uaid.hex, str(dummy_chid)) notif2 = make_webpush_notification(dummy_uaid.hex, str(dummy_chid)) messages = self.db.message messages.store_message(notif) messages.store_message(notif2) self.db.router.drop_user.return_value = True yield self.client.delete( self.url(router_type="webpush", router_token="test", uaid=dummy_uaid.hex), headers={"Authorization": self.auth}, ) # Note: Router is mocked, so the UAID is never actually # dropped. assert self.db.router.drop_user.called assert self.db.router.drop_user.call_args_list[0][0] == ( dummy_uaid.hex,) @inlineCallbacks def test_delete_bad_uaid(self): # Return a 401 as the random UUID was never registered resp = yield self.client.delete( self.url(router_type="test", router_token="test", uaid=uuid.uuid4().hex), headers={"Authorization": self.auth}, ) assert resp.get_status() == 401 @inlineCallbacks def test_delete_orphans(self): self.db.router.drop_user.return_value = False resp = yield self.client.delete( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex), headers={"Authorization": self.auth}, ) assert resp.get_status() == 410 @inlineCallbacks def test_delete_bad_auth(self, *args): resp = yield self.client.delete( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex), headers={"Authorization": "Invalid"}, ) assert resp.get_status() == 401 @inlineCallbacks def test_delete_bad_router(self): resp = yield self.client.delete( self.url(router_type="invalid", router_token="test", uaid=dummy_uaid.hex), headers={"Authorization": self.auth}, ) assert resp.get_status() == 400 @inlineCallbacks def test_get(self): chids = [str(dummy_chid), str(dummy_uaid)] self.db.message.all_channels = Mock() self.db.message.all_channels.return_value = (True, chids) resp = yield self.client.get( self.url(router_type="test", router_token="test", uaid=dummy_uaid.hex), headers={"Authorization": self.auth} ) self.db.message.all_channels.assert_called_with(str(dummy_uaid)) payload = json.loads(resp.content) assert chids == payload['channelIDs'] assert dummy_uaid.hex == payload['uaid']