From 7e94c5e373232c2133699f7403ac09385a4fd01e Mon Sep 17 00:00:00 2001 From: "Augusto F. Hack" Date: Wed, 31 May 2017 09:08:28 -0300 Subject: [PATCH] proper validation errors --- raiden/api/v1/encoding.py | 46 ++++++++++++++--- raiden/tests/api/test_api.py | 98 ++++++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 12 deletions(-) diff --git a/raiden/api/v1/encoding.py b/raiden/api/v1/encoding.py index 169b992759..f7bbf0c7d9 100644 --- a/raiden/api/v1/encoding.py +++ b/raiden/api/v1/encoding.py @@ -9,9 +9,15 @@ SchemaOpts, ) from webargs import validate -from werkzeug.routing import BaseConverter - -from pyethapp.jsonrpc import address_encoder, address_decoder, data_encoder, data_decoder +from werkzeug.routing import ( + BaseConverter, + ValidationError, +) +from pyethapp.jsonrpc import ( + address_encoder, + data_encoder, + data_decoder, +) from raiden.api.objects import ( Channel, @@ -37,22 +43,46 @@ class HexAddressConverter(BaseConverter): def to_python(self, value): - if value[:2] == '0x': - return value[2:].decode('hex') + if value[:2] != '0x': + raise ValidationError() + + try: + value = value[2:].decode('hex') + except TypeError: + raise ValidationError() + + if len(value) != 20: + raise ValidationError() - raise Exception('invalid address') + return value def to_url(self, value): return address_encoder(value) class AddressField(fields.Field): + default_error_messages = { + 'missing_prefix': 'Not a valid hex encoded address, must be 0x prefixed.', + 'invalid_data': 'Not a valid hex encoded address, contains invalid characters.', + 'invalid_size': 'Not a valid hex encoded address, decoded address is not 20 bytes long.', + } def _serialize(self, value, attr, obj): return address_encoder(value) - def _deserialize(self, value, attr, obj): - return address_decoder(value) + def _deserialize(self, value, attr, data): + if value[:2] != '0x': + self.fail('missing_prefix') + + try: + value = value[2:].decode('hex') + except TypeError: + self.fail('invalid_data') + + if len(value) != 20: + self.fail('invalid_size') + + return value class DataField(fields.Field): diff --git a/raiden/tests/api/test_api.py b/raiden/tests/api/test_api.py index 2e3573759b..bfc377c3e3 100644 --- a/raiden/tests/api/test_api.py +++ b/raiden/tests/api/test_api.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- -import pytest -import grequests import httplib import json -from flask import url_for +import pytest +import grequests +from flask import url_for from pyethapp.jsonrpc import address_encoder, address_decoder -from raiden.api.v1.encoding import HexAddressConverter +from raiden.api.v1.encoding import ( + AddressField, + HexAddressConverter, +) from raiden.utils import channel_to_api_dict from raiden.tests.utils.transfer import channel from raiden.transfer.state import ( @@ -59,6 +62,28 @@ def test_hex_converter(): assert converter.to_python('0x414d72a6f6e28f4950117696081450d63d56c354') == address +def test_address_field(): + # pylint: disable=protected-access + field = AddressField() + attr = 'test' + data = object() + + # invalid hex data + with pytest.raises(Exception): + field._deserialize('-', attr, data) + + # invalid address, too short + with pytest.raises(Exception): + field._deserialize('1234', attr, data) + + # missing prefix 0x + with pytest.raises(Exception): + field._deserialize('414d72a6f6e28f4950117696081450d63d56c354', attr, data) + + address = b'AMr\xa6\xf6\xe2\x8fIP\x11v\x96\x08\x14P\xd6=V\xc3T' + assert field._deserialize('0x414d72a6f6e28f4950117696081450d63d56c354', attr, data) == address + + @pytest.mark.parametrize('blockchain_type', ['geth']) @pytest.mark.parametrize('number_of_nodes', [2]) def test_channel_to_api_dict(raiden_network, token_addresses, settle_timeout): @@ -80,6 +105,71 @@ def test_channel_to_api_dict(raiden_network, token_addresses, settle_timeout): assert result == expected_result +def test_url_with_invalid_address(rest_api_port_number, api_backend): + """ Addresses required the leading 0x in the urls. """ + + url_without_prefix = ( + 'http://localhost:{port}/api/1/' + 'channels/ea674fdde714fd979de3edf0f56aa9716b898ec8' + ).format(port=rest_api_port_number) + + request = grequests.patch( + url_without_prefix, + json={'state': CHANNEL_STATE_SETTLED} + ) + response = request.send().response + + assert response.status_code == httplib.NOT_FOUND + + +def test_payload_with_address_without_prefix(api_backend): + """ Addresses required leading 0x in the payload. """ + invalid_address = '61c808d82a3ac53231750dadc13c777b59310bd9' + channel_data_obj = { + 'partner_address': invalid_address, + 'token_address': '0xea674fdde714fd979de3edf0f56aa9716b898ec8', + 'settle_timeout': 10, + } + request = grequests.put( + api_url_for(api_backend, 'channelsresource'), + json=channel_data_obj + ) + response = request.send().response + assert response.status_code == httplib.BAD_REQUEST + + +def test_payload_with_address_invalid_chars(api_backend): + """ Addresses cannot have invalid characters in it. """ + invalid_address = '0x61c808d82a3ac53231750dadc13c777b59310bdg' # g at the end is invalid + channel_data_obj = { + 'partner_address': invalid_address, + 'token_address': '0xea674fdde714fd979de3edf0f56aa9716b898ec8', + 'settle_timeout': 10, + } + request = grequests.put( + api_url_for(api_backend, 'channelsresource'), + json=channel_data_obj + ) + response = request.send().response + assert response.status_code == httplib.BAD_REQUEST + + +def test_payload_with_address_invalid_length(api_backend): + """ Encoded addresses must have the right length. """ + invalid_address = '0x61c808d82a3ac53231750dadc13c777b59310b' # g at the end is invalid + channel_data_obj = { + 'partner_address': invalid_address, + 'token_address': '0xea674fdde714fd979de3edf0f56aa9716b898ec8', + 'settle_timeout': 10, + } + request = grequests.put( + api_url_for(api_backend, 'channelsresource'), + json=channel_data_obj + ) + response = request.send().response + assert response.status_code == httplib.BAD_REQUEST + + def test_api_query_channels( api_backend, api_test_context,