photon: validate geojson content received (#68414) #258
|
@ -23,12 +23,47 @@ from django.db.models import JSONField
|
|||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from jsonschema import ValidationError, validate, validators
|
||||
from requests import RequestException
|
||||
|
||||
from passerelle.base.models import BaseResource
|
||||
from passerelle.utils.api import endpoint
|
||||
from passerelle.utils.jsonresponse import APIError
|
||||
|
||||
GEOJSON_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'features': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
},
|
||||
'geometry': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {
|
||||
'type': 'string',
|
||||
},
|
||||
'coordinates': {
|
||||
'type': 'array',
|
||||
'minItems': 2,
|
||||
'items': {
|
||||
'oneOf': [{'type': 'number'}, {'type': 'string'}],
|
||||
},
|
||||
},
|
||||
},
|
||||
'required': ['type', 'coordinates'],
|
||||
},
|
||||
},
|
||||
'required': ['properties', 'geometry'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AddressCacheModel(models.Model):
|
||||
api_id = models.CharField(max_length=32, unique=True)
|
||||
|
@ -100,6 +135,18 @@ class Photon(BaseResource):
|
|||
result['id'] = hashlib.md5(force_bytes(dict_dump)).hexdigest()
|
||||
return result
|
||||
|
||||
def validate_geojson(self, response_json):
|
||||
validator = validators.validator_for(GEOJSON_SCHEMA)
|
||||
validator.META_SCHEMA['properties'].pop('description', None)
|
||||
validator.META_SCHEMA['properties'].pop('title', None)
|
||||
try:
|
||||
validate(response_json, GEOJSON_SCHEMA)
|
||||
except ValidationError as e:
|
||||
error_msg = e.message
|
||||
if e.path:
|
||||
error_msg = '%s: %s' % ('/'.join(map(str, e.path)), error_msg)
|
||||
raise APIError(error_msg)
|
||||
|
||||
@endpoint(
|
||||
pattern='(?P<q>.+)?$',
|
||||
description=_('Addresses list'),
|
||||
|
@ -154,6 +201,7 @@ class Photon(BaseResource):
|
|||
response_json = result_response.json()
|
||||
except ValueError:
|
||||
raise APIError('invalid photon response (%r)' % result_response.content[:1024])
|
||||
self.validate_geojson(response_json)
|
||||
|
||||
result = []
|
||||
for feature in response_json.get('features'):
|
||||
|
@ -219,6 +267,7 @@ class Photon(BaseResource):
|
|||
response_json = result_response.json()
|
||||
except ValueError:
|
||||
raise APIError('invalid photon response (%r)' % result_response.content[:1024])
|
||||
self.validate_geojson(response_json)
|
||||
|
||||
result = None
|
||||
for feature in response_json.get('features'):
|
||||
|
|
|
@ -333,3 +333,28 @@ def test_photon_non_json(mocked_get, app, photon):
|
|||
resp = app.get('/photon/%s/reverse' % photon.slug, params={'lat': '0', 'lon': '0'}, status=200)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == "invalid photon response (b'xxx')"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'endpoint', ['/photon/test/addresses', '/photon/test/search', '/photon/test/reverse']
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
'content',
|
||||
[
|
||||
'',
|
||||
'{"features": ""}',
|
||||
'{"features": null}',
|
||||
'{"features": [null]}',
|
||||
'{"features": [{}]}',
|
||||
'{"features": [{"properties": null, "geometry": null}]}',
|
||||
'{"features": [{"properties": {}, "geometry": {}}]}',
|
||||
'{"features": [{"properties": {}, "geometry": {"type": ""}}]}',
|
||||
'{"features": [{"properties": {}, "geometry": {"type": "", "coordinates": null}}]}',
|
||||
'{"features": [{"properties": {}, "geometry": {"type": "", "coordinates": [42]}}]}',
|
||||
],
|
||||
)
|
||||
@mock.patch('passerelle.utils.Request.get')
|
||||
def test_photon_bad_geojson_response(mocked_get, content, endpoint, app, photon):
|
||||
mocked_get.return_value = tests.utils.FakedResponse(content=content, status_code=200)
|
||||
resp = app.get(endpoint, params={'q': 'plop', 'lat': 48, 'lon': 2})
|
||||
assert resp.json['err'] == 1
|
||||
|
|
Loading…
Reference in New Issue