misc: add rate limiting to tracking code URL (#35386)

This commit is contained in:
Frédéric Péters 2019-08-13 13:58:55 +02:00
parent b7cbff134c
commit 32f304fd51
6 changed files with 41 additions and 11 deletions

1
debian/control vendored
View File

@ -15,6 +15,7 @@ Depends: ${misc:Depends}, ${python:Depends},
python-hobo,
graphviz,
python-django-ckeditor,
python-django-ratelimit,
python-feedparser,
python-imaging,
python-pyproj,

View File

@ -27,7 +27,7 @@ $PIP_BIN install --upgrade setuptools
$PIP_BIN install --upgrade 'pytest<4.1' WebTest mock pytest-cov pyquery pytest-django
$PIP_BIN install --upgrade 'pylint<1.8' # 1.8 broken (cf build #3023)
$PIP_BIN install git+https://git.entrouvert.org/debian/django-ckeditor.git
$PIP_BIN install --upgrade 'Django<1.12' 'gadjo' 'pyproj'
$PIP_BIN install --upgrade 'Django<1.12' 'gadjo' 'pyproj' 'django-ratelimit<3'
DJANGO_SETTINGS_MODULE=wcs.settings \
WCS_SETTINGS_FILE=tests/settings.py \

View File

@ -109,6 +109,7 @@ setup(
install_requires=[
'gadjo>=0.53',
'django-ckeditor<=4.5.3',
'django-ratelimit<3',
'XStatic-Leaflet',
'pyproj',
],

View File

@ -68,3 +68,12 @@ def sms_mocking():
def http_requests():
with HttpRequestsMocking() as http_requests:
yield http_requests
@pytest.fixture
def nocache(settings):
settings.CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}

View File

@ -55,7 +55,6 @@ def assert_equal_zip(stream1, stream2):
t1, t2 = z1.read(name), z2.read(name)
assert t1 == t2, 'file "%s" differs' % name
def pytest_generate_tests(metafunc):
if 'pub' in metafunc.fixturenames:
metafunc.parametrize('pub', ['pickle', 'sql', 'pickle-templates', 'pickle-lazy'], indirect=True)
@ -1311,7 +1310,7 @@ def get_displayed_tracking_code(resp):
break
return tracking_code
def test_form_tracking_code(pub):
def test_form_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
@ -1412,8 +1411,16 @@ def test_form_tracking_code(pub):
assert resp.location == 'http://example.net/test/%s' % formdata_id
resp = resp.follow()
def test_form_tracking_code_rate_limit(pub):
# three errors
get_app(pub).get('/code/ABC/load', status=404)
get_app(pub).get('/code/ABC/load', status=404)
get_app(pub).get('/code/ABC/load', status=404)
# and out
get_app(pub).get('/code/ABC/load', status=403)
get_app(pub).get('/code/ABC/load', status=403)
def test_form_tracking_code_as_user(pub):
def test_form_tracking_code_as_user(pub, nocache):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
@ -1493,7 +1500,7 @@ def test_form_tracking_code_as_user(pub):
resp = app.get('/code/%s/load' % tracking_code,
headers={'User-agent': 'Googlebot'}, status=403)
def test_form_empty_tracking_code(pub):
def test_form_empty_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
@ -1512,7 +1519,7 @@ def test_form_empty_tracking_code(pub):
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow(status=404)
def test_form_tracking_code_email(pub, emails):
def test_form_tracking_code_email(pub, emails, nocache):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string'),
@ -1541,7 +1548,7 @@ def test_form_tracking_code_email(pub, emails):
resp = resp.follow()
assert resp.forms[1]['f0'].value == 'barfoo'
def test_form_tracking_code_remove_draft(pub):
def test_form_tracking_code_remove_draft(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
@ -1587,7 +1594,7 @@ def test_form_tracking_code_remove_draft(pub):
assert resp.location == 'http://example.net/'
assert formdef.data_class().count() == 0
def test_form_tracking_code_remove_empty_draft(pub):
def test_form_tracking_code_remove_empty_draft(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
@ -1635,7 +1642,7 @@ def test_form_tracking_code_remove_empty_draft(pub):
assert resp.location == 'http://example.net/'
assert formdef.data_class().count() == 0
def test_form_discard_draft(pub):
def test_form_discard_draft(pub, nocache):
user = create_user(pub)
formdef = create_formdef()
@ -1733,7 +1740,7 @@ def test_form_discard_draft(pub):
resp = resp.forms[1].submit('cancel')
assert [x.status for x in formdef.data_class().select()] == ['draft']
def test_form_invalid_tracking_code(pub):
def test_form_invalid_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
@ -1785,7 +1792,7 @@ def test_form_invalid_tracking_code(pub):
assert resp.location == 'http://example.net/code/%s/load' % code.id
resp = resp.follow(status=404)
def test_form_tracking_code_as_variable(pub):
def test_form_tracking_code_as_variable(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),

View File

@ -29,6 +29,8 @@ from django.utils.http import quote
from django.utils.six import StringIO
from django.utils.safestring import mark_safe
import ratelimit.utils
from quixote import (get_publisher, get_request, get_response, get_session,
get_session_manager, redirect)
from quixote.directory import Directory, AccessControlled
@ -148,6 +150,16 @@ class TrackingCodeDirectory(Directory):
return r.getvalue()
def load(self):
rate_limit = get_publisher().get_site_option('rate-limit') or '3/s'
if rate_limit != 'none':
ratelimited = ratelimit.utils.is_ratelimited(
request=get_request().django_request,
group='trackingcode',
key='ip',
rate=rate_limit,
increment=True)
if ratelimited:
raise errors.AccessForbiddenError('rate limit reached')
try:
tracking_code = get_publisher().tracking_code_class.get(self.code)
if tracking_code.formdata_id is None: