From 6c13257ff96709760757fbb332fcab92e75fe8aa Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Thu, 26 Sep 2019 18:56:14 +0200
Subject: [PATCH] logging: add LOGGED_REQUEST_MAX_SIZE settings

---
 passerelle/settings.py           |  3 +++
 passerelle/utils/__init__.py     |  2 +-
 passerelle/utils/jsonresponse.py |  2 +-
 passerelle/views.py              |  2 +-
 tests/test_api_access.py         | 28 +++++++++++++++++++++++++++-
 tests/test_requests.py           | 17 +++++++++++++++++
 6 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/passerelle/settings.py b/passerelle/settings.py
index cc7ebd59..16845a27 100644
--- a/passerelle/settings.py
+++ b/passerelle/settings.py
@@ -211,6 +211,9 @@ LOGGED_CONTENT_TYPES_MESSAGES = (
 # Max size of the response to log
 LOGGED_RESPONSES_MAX_SIZE = 4096
 
+# Max size of the request to log
+LOGGED_REQUEST_MAX_SIZE = 5000
+
 # Number of days to keep logs
 LOG_RETENTION_DAYS = 7
 
diff --git a/passerelle/utils/__init__.py b/passerelle/utils/__init__.py
index 1a7a12ea..9bf2d5e9 100644
--- a/passerelle/utils/__init__.py
+++ b/passerelle/utils/__init__.py
@@ -158,7 +158,7 @@ def log_http_request(logger, request, response=None, exception=None, error_log=T
     if logger.level == 10:  # DEBUG
         extra['request_headers'] = dict(request.headers.items())
         if request.body:
-            extra['request_payload'] = repr(request.body[:5000])
+            extra['request_payload'] = repr(request.body[:settings.LOGGED_REQUEST_MAX_SIZE])
     if response is not None:
         message = message + ' (=> %s)' % response.status_code
         extra['response_status'] = response.status_code
diff --git a/passerelle/utils/jsonresponse.py b/passerelle/utils/jsonresponse.py
index d7ec5bef..b9b23dce 100644
--- a/passerelle/utils/jsonresponse.py
+++ b/passerelle/utils/jsonresponse.py
@@ -132,7 +132,7 @@ class to_json(object):
         except Exception as e:
             extras = {'method': req.method, 'exception': exception_to_text(e), 'request': req}
             if req.method == 'POST':
-                extras.update({'body': repr(req.body[:5000])})
+                extras.update({'body': repr(req.body[:settings.LOGGED_REQUEST_MAX_SIZE])})
             if (not isinstance(e, (Http404, PermissionDenied, ObjectDoesNotExist, RequestException))
                     and getattr(e, 'log_error', True)):
                 logger.exception("Error occurred while processing request", extra=extras)
diff --git a/passerelle/views.py b/passerelle/views.py
index e47f6410..822fe2e1 100644
--- a/passerelle/views.py
+++ b/passerelle/views.py
@@ -394,7 +394,7 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View):
         connector_name, endpoint_name = kwargs['connector'], kwargs['endpoint']
         connector = self.get_object()
         url = request.get_full_path()
-        payload = request.body[:5000]
+        payload = request.body[:settings.LOGGED_REQUEST_MAX_SIZE]
         try:
             payload.decode('utf-8')
         except UnicodeDecodeError:
diff --git a/tests/test_api_access.py b/tests/test_api_access.py
index c3133a75..add30f25 100644
--- a/tests/test_api_access.py
+++ b/tests/test_api_access.py
@@ -7,8 +7,10 @@ from django.core.urlresolvers import reverse
 
 import pytest
 
+from django.test import override_settings
+
 from passerelle.base import signature
-from passerelle.base.models import ApiUser, AccessRight
+from passerelle.base.models import ApiUser, AccessRight, ResourceLog
 from passerelle.apps.oxyd.models import OxydSMSGateway
 
 pytestmark = pytest.mark.django_db
@@ -181,3 +183,27 @@ def test_access_apiuser_with_ip_restriction(app, oxyd):
                          extra_environ={'REMOTE_ADDR': authorized_ip})
     assert resp.json['err'] == 1
     assert resp.json['err_desc'] == 'Payload error: missing "message" in JSON payload'
+
+def test_logged_request_max_size(app, oxyd):
+    endpoint_url = reverse('generic-endpoint',
+            kwargs={'connector': 'oxyd', 'slug': oxyd.slug, 'endpoint': 'send'})
+    api = ApiUser.objects.create(username='public',
+                    fullname='public',
+                    description='access for all',
+                    keytype='', key='')
+    obj_type = ContentType.objects.get_for_model(OxydSMSGateway)
+    AccessRight.objects.create(codename='can_send_messages',
+                    apiuser=api,
+                    resource_type=obj_type,
+                    resource_pk=oxyd.pk,
+    )
+    resp = app.post_json(endpoint_url, params={'foo': 'bar'})
+    assert ResourceLog.objects.all()[0].message[-9:] == '"bar"}\') '
+
+    # for empty payload the connector returns an APIError with
+    assert 'Error occurred' in ResourceLog.objects.all()[1].message
+
+    # troncate logs
+    with override_settings(LOGGED_REQUEST_MAX_SIZE=6):
+        resp = app.post_json(endpoint_url, params={'foo': 'bar'})
+        assert ResourceLog.objects.all()[2].message[-8:] == '"foo"\') '
diff --git a/tests/test_requests.py b/tests/test_requests.py
index c46f7f75..e4ec3f7b 100644
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -105,6 +105,23 @@ def test_log_error(caplog, log_level):
             assert not hasattr(record, 'response_content')
             assert not hasattr(record, 'response_headers')
 
+def test_log_error_request_max_size(caplog, log_level):
+    url = 'https://httperror.org/plop'
+
+    logger = logging.getLogger('requests')
+    logger.setLevel(log_level)
+
+    with override_settings(LOGGED_REQUEST_MAX_SIZE=8):
+        with HTTMock(http400_mock):
+            requests = Request(logger=logger)
+            response = requests.post(url, json={'name':'josh'})
+
+    print logger.level
+    if logger.level == 10:  # DEBUG
+        records = [record for record in caplog.records if record.name == 'requests']
+        assert records[0].request_payload == '\'{"name":\''
+
+
 @pytest.fixture(params=['xml', 'whatever', 'jpeg', 'pdf'])
 def endpoint_response(request):
     response_request = mock.Mock(
-- 
2.23.0

