async def test_invalid_signature_in_pn_registration(self): body = { "registration_id": TEST_APN_ID, } timestamp = int(time.time()) signature = sign_request(FAUCET_PRIVATE_KEY, "POST", "/v1/apn/register", timestamp, json_encode(body).encode('utf-8')) resp = await self.fetch_signed("/apn/register", method="POST", body=body, signature=signature, address=TEST_ADDRESS, timestamp=timestamp) self.assertEqual(resp.code, 400, resp.body) async with self.pool.acquire() as con: rows1 = await con.fetch( "SELECT * FROM notification_registrations WHERE toshi_id = $1", TEST_ADDRESS) rows2 = await con.fetch( "SELECT * FROM notification_registrations WHERE toshi_id = $1", FAUCET_ADDRESS) self.assertEqual(len(rows1), 0) self.assertEqual(len(rows2), 0)
async def test_transactions_with_known_sender_toshi_id_but_invalid_signature( self): body = {"from": FAUCET_ADDRESS, "to": TEST_ADDRESS, "value": 10**10} resp = await self.fetch("/tx/skel", method="POST", body=body) self.assertEqual(resp.code, 200) tx = sign_transaction(json_decode(resp.body)['tx'], FAUCET_PRIVATE_KEY) body = {"tx": tx} timestamp = int(time.time()) signature = sign_request(FAUCET_PRIVATE_KEY, "POST", "/v1/tx", timestamp, json_encode(body).encode('utf-8')) resp = await self.fetch_signed("/tx", method="POST", body=body, address=TEST_ADDRESS_2, signature=signature, timestamp=timestamp) self.assertEqual(resp.code, 400, resp.body) self.assertIsNotNone(resp.body) error = json_decode(resp.body) self.assertIn('errors', error) self.assertEqual(len(error['errors']), 1)
async def test_invalid_signature_in_deregistration(self): async with self.pool.acquire() as con: await con.fetchrow( "INSERT INTO notification_registrations VALUES ($2, 'apn', $1, $2)", TEST_APN_ID, TEST_ADDRESS) body = {"registration_id": TEST_APN_ID} timestamp = int(time.time()) signature = sign_request(FAUCET_PRIVATE_KEY, "POST", "/v1/apn/deregister", timestamp, json_encode(body).encode('utf-8')) resp = await self.fetch_signed("/apn/deregister", method="POST", body=body, timestamp=timestamp, signature=signature, address=TEST_ADDRESS) self.assertEqual(resp.code, 400, resp.body) async with self.pool.acquire() as con: rows = await con.fetch( "SELECT * FROM notification_registrations WHERE toshi_id = $1", TEST_ADDRESS) self.assertEqual(len(rows), 1)
def fetch_signed(self, path, *, signing_key=None, signature=None, timestamp=None, address=None, **kwargs): if not isinstance(path, str) or not path.startswith("/"): # for simplicity's sake, don't accept HTTPRequest objects or external urls in the tests raise Exception("first argument must be path string starting with a / (e.g. /v1/tx)") # find out if there's a path prefix added by get_url prefix = "/{}".format(self.get_url(path).split('/', 3)[-1]).split(path)[0] headers = kwargs.setdefault('headers', tornado.httputil.HTTPHeaders()) if 'body' in kwargs: body = kwargs.pop('body') if isinstance(body, dict): headers['Content-Type'] = "application/json" body = tornado.escape.json_encode(body).encode('utf-8') elif isinstance(body, str): # try and find the charset to use to encode this if 'Content-Type' in headers: idx = headers['Content-Type'].find('charset=') if idx >= 0: charset = headers['Content-Type'][idx + 8:] idx = charset.find(';') if idx >= 0: charset = charset[:idx] else: charset = 'utf-8' else: charset = 'utf-8' # encode to a byte string body = body.encode(charset) elif not isinstance(body, bytes): raise Exception("Unable to handle bodys of type '{}'".format(type(body))) else: body = None method = kwargs.setdefault('method', 'GET').upper() if signing_key is None and (address is None or signature is None): raise Exception("signing_key is required unless address and signature is given") if timestamp is None and signature is not None: raise Exception("timestamp is required if signature is given explicitly") if address is None: address = private_key_to_address(signing_key) if timestamp is None: timestamp = int(time.time()) if signature is None: signature = sign_request(signing_key, method, "{}{}".format(prefix, path), timestamp, body) headers[TOSHI_ID_ADDRESS_HEADER] = address headers[TOSHI_SIGNATURE_HEADER] = signature headers[TOSHI_TIMESTAMP_HEADER] = str(timestamp) # because tornado doesn't like POSTs with body set to None if body is None and method == "POST": body = b"" return self.fetch(path, body=body, **kwargs)
async def test_wrong_address(self): timestamp = int(time.time()) body = {"payment_address": TEST_PAYMENT_ADDRESS} signature = sign_request(TEST_PRIVATE_KEY, "POST", "/v1/user", timestamp, body) address = "{}00000".format(TEST_ADDRESS[:-5]) resp = await self.fetch_signed("/user", method="POST", timestamp=timestamp, address=address, signature=signature, body=body) self.assertResponseCodeEqual(resp, 400, resp.body)
def __call__(self, r): method = r.method path = "/{}".format(r.url.split('/', 3)[-1]) body = r.body timestamp = int(time.time()) signature = sign_request(self.signing_key, method, path, timestamp, body) r.headers[TOSHI_ID_ADDRESS_HEADER] = self.address r.headers[TOSHI_SIGNATURE_HEADER] = signature r.headers[TOSHI_TIMESTAMP_HEADER] = str(timestamp) return r
async def connect(self): # find out if there's a path prefix added by get_url path = "/{}".format(self.url.split('/', 3)[-1]) address = private_key_to_address(self.signing_key) timestamp = int(time.time()) signature = sign_request(self.signing_key, "GET", path, timestamp, None) request = tornado.httpclient.HTTPRequest( self.url, headers={ 'User-Agent': 'Toshi-Test-Websocket-Client', TOSHI_ID_ADDRESS_HEADER: address, TOSHI_SIGNATURE_HEADER: signature, TOSHI_TIMESTAMP_HEADER: str(timestamp) }) self.con = await tornado.websocket.websocket_connect(request) return self.con