Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3103 from groovecoder/django-ratelimit-1122658
Browse files Browse the repository at this point in the history
bug 1113260 - use django-ratelimit for ip bans
  • Loading branch information
jezdez committed Mar 18, 2015
2 parents 7de757d + 82bd76e commit b559bcf
Show file tree
Hide file tree
Showing 22 changed files with 249 additions and 96 deletions.
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,6 @@
[submodule "vendor/src/django-celery"]
path = vendor/src/django-celery
url = git://github.com/celery/django-celery.git
[submodule "vendor/src/django-banish"]
path = vendor/src/django-banish
url = git://github.com/yourabi/django-banish.git
[submodule "vendor/src/django-honeypot"]
path = vendor/src/django-honeypot
url = git://github.com/sunlightlabs/django-honeypot.git
Expand All @@ -148,3 +145,6 @@
[submodule "vendor/src/django-cacheback"]
path = vendor/src/django-cacheback
url = https://github.com/codeinthehole/django-cacheback.git
[submodule "vendor/src/django-ratelimit"]
path = vendor/src/django-ratelimit
url = git://github.com/jsocol/django-ratelimit.git
36 changes: 1 addition & 35 deletions kuma/actioncounters/utils.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
import re
import hashlib

# this is not intended to be an all-knowing IP address regex
IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')


def get_ip(request):
"""
Retrieves the remote IP address from the request data. If the user is
behind a proxy, they may have a comma-separated list of IP addresses, so
we need to account for that. In such a case, only the first IP in the
list will be retrieved. Also, some hosts that use a proxy will put the
REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
IP from the proper place.
**NOTE** This function was taken from django-tracking (MIT LICENSE)
http://code.google.com/p/django-tracking/
"""

# if neither header contain a value, just use local loopback
ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
request.META.get('REMOTE_ADDR', '127.0.0.1'))
if ip_address:
# make sure we have one and only one IP
try:
ip_address = IP_RE.match(ip_address)
if ip_address:
ip_address = ip_address.group(0)
else:
# no IP, probably from some dirty proxy or other device
# throw in some bogus IP
ip_address = '10.0.0.1'
except IndexError:
pass

return ip_address
from kuma.core.utils import get_ip


def get_unique(content_type, object_pk, name,
Expand Down
36 changes: 1 addition & 35 deletions kuma/contentflagging/utils.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,6 @@
import re
import hashlib

# this is not intended to be an all-knowing IP address regex
IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')


def get_ip(request):
"""
Retrieves the remote IP address from the request data. If the user is
behind a proxy, they may have a comma-separated list of IP addresses, so
we need to account for that. In such a case, only the first IP in the
list will be retrieved. Also, some hosts that use a proxy will put the
REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
IP from the proper place.
**NOTE** This function was taken from django-tracking (MIT LICENSE)
http://code.google.com/p/django-tracking/
"""

# if neither header contain a value, just use local loopback
ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
request.META.get('REMOTE_ADDR', '127.0.0.1'))
if ip_address:
# make sure we have one and only one IP
try:
ip_address = IP_RE.match(ip_address)
if ip_address:
ip_address = ip_address.group(0)
else:
# no IP, probably from some dirty proxy or other device
# throw in some bogus IP
ip_address = '10.0.0.1'
except IndexError:
pass

return ip_address
from kuma.core.utils import get_ip


def get_unique(content_type, object_pk, request=None, ip=None, user_agent=None, user=None):
Expand Down
10 changes: 10 additions & 0 deletions kuma/core/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

from kuma.core.models import IPBan


class IPBanAdmin(admin.ModelAdmin):
list_display = ('ip', 'created', 'deleted')


admin.site.register(IPBan, IPBanAdmin)
14 changes: 14 additions & 0 deletions kuma/core/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ def hash(self, value):
if isinstance(value, tuple):
value = tuple(to_bytestring(v) for v in value)
return hashlib.md5(six.b(':').join(value)).hexdigest()


class IPBanJob(KumaJob):
lifetime = 60 * 60 * 3
refresh_timeout = 60

def fetch(self, ip):
from .models import IPBan
if IPBan.objects.active(ip=ip).exists():
return "0/s"
return "60/m"

def empty(self):
return "60/m"
19 changes: 19 additions & 0 deletions kuma/core/management/commands/delete_old_ip_bans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Delete old revision IPs
"""
from optparse import make_option

from django.core.management.base import BaseCommand
from .tasks import delete_old_ip_bans


class Command(BaseCommand):
help = "Delete old IP Bans"
option_list = BaseCommand.option_list + (
make_option('--days', dest="days", default=30, type=int,
help="How many days 'old' (Default 30)"),
)

def handle(self, *args, **options):
self.options = options
delete_old_ip_bans(days=options['days'])
14 changes: 13 additions & 1 deletion kuma/core/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
- Permissions for tag namespaces (eg. system:* is superuser-only)
- Machine tag assists
"""
from datetime import date, timedelta
import operator
from django.db import router

from django.db import router
from django.db import models
from django.db.models.fields import BLANK_CHOICE_DASH
from django.contrib.auth.models import AnonymousUser

Expand Down Expand Up @@ -241,6 +243,16 @@ def resolve_allowed_tags(model_obj, tags_curr, tags_new,
return tags_out


class IPBanManager(models.Manager):
def active(self, ip):
return self.filter(ip=ip, deleted__isnull=True)

def delete_old(self, days=30):
cutoff_date = date.today() - timedelta(days=days)
old_ip_bans = self.filter(created__lte=cutoff_date)
old_ip_bans.delete()


# Tell South to ignore our fields, if present.
try:
import south.modelsinspector
Expand Down
36 changes: 36 additions & 0 deletions kuma/core/migrations/0002_auto__add_ipban.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Adding model 'IPBan'
db.create_table('core_ipban', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)),
('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
('deleted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
))
db.send_create_signal('core', ['IPBan'])


def backwards(self, orm):
# Deleting model 'IPBan'
db.delete_table('core_ipban')


models = {
'core.ipban': {
'Meta': {'object_name': 'IPBan'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
'deleted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
}
}

complete_apps = ['core']
20 changes: 20 additions & 0 deletions kuma/core/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from django.db import models
from django.utils import timezone

from south.modelsinspector import add_introspection_rules

from .managers import IPBanManager
from .jobs import IPBanJob


class ModelBase(models.Model):
"""Common base model for all MDN models: Implements caching."""
Expand Down Expand Up @@ -34,6 +38,22 @@ def update(self, **kw):
created=False)


class IPBan(models.Model):
ip = models.GenericIPAddressField()
created = models.DateTimeField(default=timezone.now, db_index=True)
deleted = models.DateTimeField(null=True, blank=True)

objects = IPBanManager()

def delete(self, *args, **kwargs):
self.deleted = timezone.now()
self.save()
IPBanJob().invalidate(self.ip)

def __unicode__(self):
return u'%s banned on %s' % (self.ip, self.created)


add_introspection_rules([], [
'^kuma\.core\.fields\.LocaleField',
'^kuma\.core\.fields\.JSONField',
Expand Down
8 changes: 7 additions & 1 deletion kuma/core/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import constance.config

from kuma.core.cache import memcache
from .cache import memcache
from .models import IPBan


LOCK_ID = 'clean-sessions-lock'
Expand Down Expand Up @@ -51,3 +52,8 @@ def clean_sessions():
else:
logger.error('The clean_sessions task is already running since %s' %
memcache.get(LOCK_ID))


@task
def delete_old_ip_bans(days=30):
IPBan.objects.delete_old(days=days)
36 changes: 36 additions & 0 deletions kuma/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from datetime import date, timedelta

from nose.tools import eq_

from django.test import TestCase

from ..models import IPBan


class RevisionIPTests(TestCase):
def test_delete_older_than_default_30_days(self):
old_date = date.today() - timedelta(days=31)
IPBan(ip='127.0.0.1', created=old_date).save()
eq_(1, IPBan.objects.count())
IPBan.objects.delete_old()
eq_(0, IPBan.objects.count())

def test_delete_older_than_days_argument(self):
ban_date = date.today() - timedelta(days=5)
IPBan(ip='127.0.0.1', created=ban_date).save()
eq_(1, IPBan.objects.count())
IPBan.objects.delete_old(days=4)
eq_(0, IPBan.objects.count())

def test_delete_older_than_only_deletes_older_than(self):
oldest_date = date.today() - timedelta(days=31)
IPBan(ip='127.0.0.1', created=oldest_date).save()

old_date = date.today() - timedelta(days=29)
IPBan(ip='127.0.0.2', created=old_date).save()

now_date = date.today()
IPBan(ip='127.0.0.3', created=now_date).save()
eq_(3, IPBan.objects.count())
IPBan.objects.delete_old()
eq_(2, IPBan.objects.count())
43 changes: 43 additions & 0 deletions kuma/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
import random
import re
import tempfile
import time
from itertools import islice
Expand All @@ -20,11 +21,48 @@
from taggit.utils import split_strip

from .cache import memcache
from .jobs import IPBanJob


# this is not intended to be an all-knowing IP address regex
IP_RE = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')


log = commonware.log.getLogger('kuma.core.utils')


def get_ip(request):
"""
Retrieves the remote IP address from the request data. If the user is
behind a proxy, they may have a comma-separated list of IP addresses, so
we need to account for that. In such a case, only the first IP in the
list will be retrieved. Also, some hosts that use a proxy will put the
REMOTE_ADDR into HTTP_X_FORWARDED_FOR. This will handle pulling back the
IP from the proper place.
**NOTE** This function was taken from django-tracking (MIT LICENSE)
http://code.google.com/p/django-tracking/
"""

# if neither header contain a value, just use local loopback
ip_address = request.META.get('HTTP_X_FORWARDED_FOR',
request.META.get('REMOTE_ADDR', '127.0.0.1'))
if ip_address:
# make sure we have one and only one IP
try:
ip_address = IP_RE.match(ip_address)
if ip_address:
ip_address = ip_address.group(0)
else:
# no IP, probably from some dirty proxy or other device
# throw in some bogus IP
ip_address = '10.0.0.1'
except IndexError:
pass

return ip_address


def paginate(request, queryset, per_page=20):
"""Get a Paginator, abstracting some common paging actions."""
paginator = Paginator(queryset, per_page)
Expand Down Expand Up @@ -284,3 +322,8 @@ def chord_flow(pre_task, tasks, post_task):
return chain(*tasks)
else:
return chain(pre_task, chord(header=tasks, body=post_task))


def limit_banned_ip_to_0(group, request):
ip = get_ip(request)
return IPBanJob().get(ip)
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
{% set revision_ip = revision.revisionip_set.all()[0].ip %}
<span class="revision_ip">
<br/>IP: {{ revision_ip }}
{% set ban_ip_url = '%s?%s' % (url('admin:banish_banishment_add'), 'condition=%s' % revision_ip) %}
<br/><a class="dashboard-ban-ip-link" href="{{ ban_ip_url }}" target="_blank">{{ _('Ban IP') }}</a>
{% set ban_ip_url = '%s?%s' % (url('admin:core_ipban_add'), 'ip=%s' % revision_ip) %}
<br/><a class="dashboard-ban-ip-link" href="{{ ban_ip_url }}" target="_blank">{{ _('Ban IP from Editing') }}</a>
</span>
{% endif %}
{% if request.user.is_superuser and revision.creator.get_profile().is_banned %}
Expand Down
3 changes: 1 addition & 2 deletions kuma/wiki/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,5 @@ def get_query_set(self):
class RevisionIPManager(models.Manager):
def delete_old(self, days=30):
cutoff_date = date.today() - timedelta(days=days)
old_rev_ips = self.get_query_set().filter(
revision__created__lte=cutoff_date)
old_rev_ips = self.filter(revision__created__lte=cutoff_date)
old_rev_ips.delete()
Loading

0 comments on commit b559bcf

Please sign in to comment.