0001-misc-add-rate-limiting-to-tracking-code-URLs-35395.patch
combo/apps/wcs/views.py | ||
---|---|---|
17 | 17 |
import re |
18 | 18 | |
19 | 19 |
from django.contrib import messages |
20 |
from django.conf import settings |
|
21 |
from django.core.exceptions import PermissionDenied |
|
20 | 22 |
from django.core.urlresolvers import reverse |
21 | 23 |
from django.http import JsonResponse, HttpResponseRedirect, HttpResponseBadRequest |
22 | 24 |
from django.utils.http import urlquote |
... | ... | |
25 | 27 |
from django.views.decorators.csrf import csrf_exempt |
26 | 28 |
from django.views.generic import View |
27 | 29 | |
30 |
import ratelimit.utils |
|
31 | ||
28 | 32 |
from .models import TrackingCodeInputCell |
29 | 33 |
from .utils import get_wcs_services |
30 | 34 | |
... | ... | |
41 | 45 |
return super(TrackingCodeView, self).dispatch(*args, **kwargs) |
42 | 46 | |
43 | 47 |
@classmethod |
44 |
def search(self, code, wcs_site=None): |
|
48 |
def search(self, code, request, wcs_site=None):
|
|
45 | 49 |
code = code.strip().upper() |
46 | 50 |
if wcs_site: |
47 | 51 |
wcs_sites = [get_wcs_services().get(wcs_site)] |
48 | 52 |
else: |
49 | 53 |
wcs_sites = get_wcs_services().values() |
50 | 54 | |
55 |
rate_limit_option = settings.WCS_TRACKING_CODE_RATE_LIMIT |
|
56 |
if rate_limit_option and rate_limit_option != 'none': |
|
57 |
for rate_limit in rate_limit_option.split(): |
|
58 |
ratelimited = ratelimit.utils.is_ratelimited( |
|
59 |
request=request, |
|
60 |
group='trackingcode', |
|
61 |
key='ip', |
|
62 |
rate=rate_limit, |
|
63 |
increment=True) |
|
64 |
if ratelimited: |
|
65 |
raise PermissionDenied('rate limit reached (%s)' % rate_limit) |
|
66 | ||
51 | 67 |
for wcs_site in wcs_sites: |
52 | 68 |
response = requests.get('/api/code/' + urlquote(code), |
53 | 69 |
remote_service=wcs_site, log_errors=False) |
... | ... | |
65 | 81 |
return HttpResponseBadRequest('Missing code') |
66 | 82 |
code = request.POST['code'] |
67 | 83 | |
68 |
url = self.search(code, wcs_site=cell.wcs_site) |
|
84 |
url = self.search(code, request, wcs_site=cell.wcs_site)
|
|
69 | 85 |
if url: |
70 | 86 |
return HttpResponseRedirect(url) |
71 | 87 | |
... | ... | |
89 | 105 |
query = request.GET.get('q') or '' |
90 | 106 |
query = query.strip().upper() |
91 | 107 |
if re.match(r'^[BCDFGHJKLMNPQRSTVWXZ]{8}$', query): |
92 |
url = TrackingCodeView.search(query) |
|
108 |
url = TrackingCodeView.search(query, request)
|
|
93 | 109 |
if url: |
94 | 110 |
hits.append({ |
95 | 111 |
'text': _('Use tracking code %s') % query, |
combo/settings.py | ||
---|---|---|
310 | 310 |
# default duration of notifications (in days) |
311 | 311 |
COMBO_DEFAULT_NOTIFICATION_DURATION = 3 |
312 | 312 | |
313 |
# tracking code thorttling |
|
314 |
WCS_TRACKING_CODE_RATE_LIMIT = '3/s 1500/d' |
|
315 | ||
313 | 316 |
# predefined slots for assets |
314 | 317 |
# example: {'banner': {'label': 'Banner image'}} |
315 | 318 |
COMBO_ASSET_SLOTS = {} |
debian/control | ||
---|---|---|
22 | 22 |
python-xstatic-roboto-fontface (<< 0.5.0.0), |
23 | 23 |
python-eopayment (>= 1.35), |
24 | 24 |
python-django-haystack (>= 2.4.0), |
25 |
python-django-ratelimit, |
|
25 | 26 |
python-sorl-thumbnail, |
26 | 27 |
python-pil, |
27 | 28 |
python-pywebpush, |
setup.py | ||
---|---|---|
161 | 161 |
'python-dateutil', |
162 | 162 |
'djangorestframework>=3.3, <3.7', |
163 | 163 |
'django-haystack', |
164 |
'django-ratelimit<3', |
|
164 | 165 |
'whoosh', |
165 | 166 |
'sorl-thumbnail', |
166 | 167 |
'Pillow', |
tests/conftest.py | ||
---|---|---|
50 | 50 |
except User.DoesNotExist: |
51 | 51 |
user = User.objects.create_superuser('admin', email=None, password='admin') |
52 | 52 |
return user |
53 | ||
54 | ||
55 |
@pytest.fixture |
|
56 |
def nocache(settings): |
|
57 |
settings.CACHES = { |
|
58 |
'default': { |
|
59 |
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', |
|
60 |
} |
|
61 |
} |
tests/test_wcs.py | ||
---|---|---|
611 | 611 | |
612 | 612 | |
613 | 613 |
@wcs_present |
614 |
def test_tracking_code_cell(app): |
|
614 |
def test_tracking_code_cell(app, nocache):
|
|
615 | 615 |
Page.objects.all().delete() |
616 | 616 |
page = Page(title='One', slug='index', template_name='standard') |
617 | 617 |
page.save() |
... | ... | |
713 | 713 |
assert u'>Picture — form title (test)<' in resp.text |
714 | 714 | |
715 | 715 |
@wcs_present |
716 |
def test_tracking_code_search(app): |
|
716 |
def test_tracking_code_search(app, nocache):
|
|
717 | 717 |
assert len(app.get('/api/search/tracking-code/').json.get('data')) == 0 |
718 | 718 |
assert len(app.get('/api/search/tracking-code/?q=123').json.get('data')) == 0 |
719 | 719 |
assert len(app.get('/api/search/tracking-code/?q=BBCCDFF').json.get('data')) == 0 |
... | ... | |
722 | 722 |
assert len(app.get('/api/search/tracking-code/?q=BBCCDDFFG').json.get('data')) == 0 |
723 | 723 |
assert len(app.get('/api/search/tracking-code/?q= cnphntfb').json.get('data')) == 1 |
724 | 724 | |
725 |
@wcs_present |
|
726 |
def test_tracking_code_search_rate_limit(app): |
|
727 |
app.get('/api/search/tracking-code/?q=BBCCDDFF') |
|
728 |
app.get('/api/search/tracking-code/?q=BBCCDDFF') |
|
729 |
app.get('/api/search/tracking-code/?q=BBCCDDFF') |
|
730 |
app.get('/api/search/tracking-code/?q=BBCCDDFF', status=403) |
|
731 |
app.get('/api/search/tracking-code/?q=BBCCDDFF', status=403) |
|
732 | ||
725 | 733 |
@wcs_present |
726 | 734 |
def test_wcs_search_engines(app): |
727 | 735 |
search_engines = engines.get_engines() |
728 |
- |