# HG changeset patch # User Brian Neal # Date 1379197393 18000 # Node ID 094492e66eb929a1a40e21d728978701c57d634f # Parent e888d627928ff81a96f08c46b6cb4bec6da044bb Implement rate limiting on user photo uploads. diff -r e888d627928f -r 094492e66eb9 sg101/settings/base.py --- a/sg101/settings/base.py Wed Sep 11 20:31:23 2013 -0500 +++ b/sg101/settings/base.py Sat Sep 14 17:23:13 2013 -0500 @@ -299,6 +299,7 @@ USER_PHOTOS_BASE_URL = 'https://s3-us-west-1.amazonaws.com' USER_PHOTOS_MAX_SIZE = (660, 720) USER_PHOTOS_THUMB_SIZE = (120, 120) +USER_PHOTOS_RATE_LIMIT = (50, 86400) # number / seconds ####################################################################### # Asynchronous settings (queues, queued_search, redis, celery, etc) diff -r e888d627928f -r 094492e66eb9 user_photos/forms.py --- a/user_photos/forms.py Wed Sep 11 20:31:23 2013 -0500 +++ b/user_photos/forms.py Sat Sep 14 17:23:13 2013 -0500 @@ -6,9 +6,26 @@ from core.s3 import S3Bucket from core.image_uploader import upload +from core.services import get_redis_connection from user_photos.models import Photo +def rate_limit(key, limit, seconds): + """Use Redis to do a rate limit check. Returns True if the limit is violated + and False otherwise. + + key - the key to check in Redis + limit - the rate limit maximum value + seconds - the rate limit period in seconds + + """ + conn = get_redis_connection() + val = conn.incr(key) + if val == 1: + conn.expire(key, seconds) + return val > limit + + class UploadForm(forms.Form): image_file = forms.ImageField() @@ -16,6 +33,18 @@ self.user = kwargs.pop('user') super(UploadForm, self).__init__(*args, **kwargs) + def clean(self): + cleaned_data = super(UploadForm, self).clean() + + # rate limit uploads + key = 'user_photos:counts:' + self.user.username + limit = settings.USER_PHOTOS_RATE_LIMIT + if rate_limit(key, *limit): + raise forms.ValidationError("You've exceeded your upload quota. " + "Please try again later.") + + return cleaned_data + def save(self): """Processes the image and creates a new Photo object, which is saved to the database. The new Photo instance is returned.