Compare commits

...

2 Commits

Author SHA1 Message Date
sakisv
238dcae23c Add before_request check for request size
The test checks various routes to ensure that request above our
threshold fail before any other processing happens
2021-01-28 14:33:25 +02:00
sakisv
d35ab04a4e Set a request size limit of 5MB
This is to replicate the nginx behaviour. If the entire request is
larger than MAX_CONTENT_LENGTH [1] an entity too large error is raised
[2].

Enable for all environments except Staging and Production (i.e.
Development, Preview, Test)

1: https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/#improving-uploads
2: https://werkzeug.palletsprojects.com/en/1.0.x/exceptions/#werkzeug.exceptions.RequestEntityTooLarge
2021-01-28 14:33:21 +02:00
3 changed files with 51 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ from notifications_utils.clients.encryption.encryption_client import Encryption
from notifications_utils import logging, request_helper from notifications_utils import logging, request_helper
from sqlalchemy import event from sqlalchemy import event
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
from werkzeug.exceptions import RequestEntityTooLarge as WerkzeugRequestEntityTooLarge
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from app.celery.celery import NotifyCelery from app.celery.celery import NotifyCelery
@@ -281,6 +282,15 @@ def init_app(app):
g.start = monotonic() g.start = monotonic()
g.endpoint = request.endpoint g.endpoint = request.endpoint
@app.before_request
def check_content_length():
if (
request.content_length is not None
and current_app.config['MAX_CONTENT_LENGTH'] is not None
and request.content_length > current_app.config['MAX_CONTENT_LENGTH']
):
raise WerkzeugRequestEntityTooLarge()
@app.after_request @app.after_request
def after_request(response): def after_request(response):
CONCURRENT_REQUESTS.dec() CONCURRENT_REQUESTS.dec()

View File

@@ -381,6 +381,7 @@ class Config(object):
CBC_PROXY_ENABLED = bool(CBC_PROXY_AWS_ACCESS_KEY_ID) CBC_PROXY_ENABLED = bool(CBC_PROXY_AWS_ACCESS_KEY_ID)
ENABLED_CBCS = {BroadcastProvider.EE, BroadcastProvider.THREE, BroadcastProvider.O2, BroadcastProvider.VODAFONE} ENABLED_CBCS = {BroadcastProvider.EE, BroadcastProvider.THREE, BroadcastProvider.O2, BroadcastProvider.VODAFONE}
MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB
###################### ######################
@@ -508,6 +509,8 @@ class Staging(Config):
API_RATE_LIMIT_ENABLED = True API_RATE_LIMIT_ENABLED = True
CHECK_PROXY_HEADER = True CHECK_PROXY_HEADER = True
MAX_CONTENT_LENGTH = None
class Live(Config): class Live(Config):
NOTIFY_EMAIL_DOMAIN = 'notifications.service.gov.uk' NOTIFY_EMAIL_DOMAIN = 'notifications.service.gov.uk'
@@ -529,6 +532,8 @@ class Live(Config):
CRONITOR_ENABLED = True CRONITOR_ENABLED = True
MAX_CONTENT_LENGTH = None
class CloudFoundryConfig(Config): class CloudFoundryConfig(Config):
pass pass

View File

@@ -0,0 +1,36 @@
import pytest
import json
@pytest.mark.parametrize('endpoint, max_content_length, expected_status_code', [
("/_status", 5*1024*1024, 413),
("/provider-details", 5*1024*1024, 413),
("/v2/notifications/email", 5*1024*1024, 413),
("/_status", None, 200),
("/provider-details", None, 405),
("/v2/notifications/email", None, 401),
])
def test_request_status_when_content_length_is_set(
notify_api,
sample_email_template_with_placeholders,
mocker,
endpoint,
max_content_length,
expected_status_code):
notify_api.config['MAX_CONTENT_LENGTH'] = max_content_length
large_name = "J" * (max_content_length or 1 + 1)
data = {
'email_address': 'ok@ok.com',
'template_id': str(sample_email_template_with_placeholders.id),
'personalisation': {
'name': large_name
}
}
with notify_api.test_client() as client:
response = client.post(
path=endpoint,
data=json.dumps(data),
headers=[('Content-Type', 'application/json')])
assert response.status_code == expected_status_code