photon: validate geojson content received (#68414)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Nicolas Roche 2023-05-22 19:42:51 +02:00 committed by Nicolas Roche
parent 62ed945d62
commit 2cac256517
2 changed files with 74 additions and 0 deletions

View File

@ -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'):

View File

@ -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