├── block_ip ├── __init__.py ├── admin.py ├── models.py └── middleware.py ├── MANIFEST.in ├── README.md ├── LICENSE └── setup.py /block_ip/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /block_ip/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | 4 | from .models import BlockIP 5 | 6 | 7 | @admin.register(BlockIP) 8 | class BlockIPAdmin(admin.ModelAdmin): 9 | pass 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-block-ip 2 | --------------- 3 | 4 | This is a simple application to restrict access by IP address. There's a few other apps that do this out there, but they tend to have other features such as rate limiting. I think it's best to leave rate-limiting to rate-limiting specific apps. 5 | 6 | Usage 7 | ===== 8 | 9 | 1. `pip install django-block-ip` 10 | 1. Add `block_ip` to your `INSTALLED_APPS`. 11 | 1. Add `block_ip.middleware.BlockIPMiddleware` to your `MIDDLEWARE_CLASSES`. 12 | 1. Run `syncdb`. 13 | 1. Add one or more entries to the `BlockIP` list in the admin. 14 | You can just enter a single IP or use a network mask, like this: 213.67.43.0/24 15 | 16 | Acknowledgments 17 | =============== 18 | 19 | This is based on http://github.com/svetlyak40wt/django-ban, which was based on the Justquick's django snippet (http://www.djangosnippets.org/snippets/725/). 20 | -------------------------------------------------------------------------------- /block_ip/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import ipcalc 3 | 4 | from django.db import models 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django.core.cache import cache 7 | from django.db.models.signals import post_save, post_delete 8 | from django.utils.encoding import python_2_unicode_compatible 9 | 10 | 11 | @python_2_unicode_compatible 12 | class BlockIP(models.Model): 13 | network = models.CharField(_('IP address or mask'), max_length=18) 14 | reason_for_block = models.TextField(blank=True, null=True, help_text=_("Optional reason for block")) 15 | 16 | def __str__(self): 17 | return 'BlockIP: %s' % self.network 18 | 19 | def get_network(self): 20 | return ipcalc.Network(self.network) 21 | 22 | class Meta: 23 | verbose_name = _('IPs & masks to ban') 24 | verbose_name_plural = _('IPs & masks to ban') 25 | 26 | 27 | def _clear_cache(sender, instance, **kwargs): 28 | cache.set('blockip:list', BlockIP.objects.all()) 29 | 30 | 31 | post_save.connect(_clear_cache, sender=BlockIP) 32 | post_delete.connect(_clear_cache, sender=BlockIP) 33 | -------------------------------------------------------------------------------- /block_ip/middleware.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseForbidden 2 | from django.conf import settings 3 | from django.core.cache import cache 4 | 5 | from .models import BlockIP 6 | 7 | 8 | def get_ip(req): 9 | return req.META['REMOTE_ADDR'] 10 | 11 | 12 | def is_ip_in_nets(ip, nets): 13 | for net in nets: 14 | if ip in net: 15 | return True 16 | return False 17 | 18 | 19 | class BlockIPMiddleware(object): 20 | def process_request(self, request): 21 | is_banned = False 22 | 23 | ip = get_ip(request) 24 | # TODO: Look into something more optimized for large numbers 25 | # of blocks. https://github.com/jimfunk/django-postgresql-netfields 26 | block_ips = cache.get('blockip:list') 27 | if block_ips is None: 28 | block_ips = BlockIP.objects.all() 29 | cache.set('blockip:list', block_ips) 30 | deny_ips = [i.get_network() for i in block_ips] 31 | 32 | for net in deny_ips: 33 | if ip in net: 34 | is_banned = True 35 | break 36 | 37 | if is_banned: 38 | # delete sessions when denied 39 | for k in request.session.keys(): 40 | del request.session[k] 41 | return HttpResponseForbidden("") 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, 2015, Philip Neustrom 2 | Copyright (c) 2008, Alexander Artemenko 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the organization nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ALEXANDER ARTEMENKO ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL ALEXANDER ARTEMENKO BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | import re 6 | import os 7 | import sys 8 | 9 | 10 | def get_packages(package): 11 | """ 12 | Return root package and all sub-packages. 13 | """ 14 | return [dirpath 15 | for dirpath, dirnames, filenames in os.walk(package) 16 | if os.path.exists(os.path.join(dirpath, '__init__.py'))] 17 | 18 | 19 | def get_package_data(package): 20 | """ 21 | Return all files under the root package, that are not in a 22 | package themselves. 23 | """ 24 | walk = [(dirpath.replace(package + os.sep, '', 1), filenames) 25 | for dirpath, dirnames, filenames in os.walk(package) 26 | if not os.path.exists(os.path.join(dirpath, '__init__.py'))] 27 | 28 | filepaths = [] 29 | for base, filenames in walk: 30 | filepaths.extend([os.path.join(base, filename) 31 | for filename in filenames]) 32 | return {package: filepaths} 33 | 34 | 35 | setup( 36 | name='django-block-ip', 37 | version='0.2.0', 38 | url='http://github.com/philipn/django-block-ip', 39 | license='BSD', 40 | description='Simple IP and IP-range blocking for Django', 41 | long_description=open('README.md', 'r').read(), 42 | author='Philip Neustrom', 43 | author_email='philipn@gmail.com', 44 | packages=get_packages('block_ip'), 45 | package_data=get_package_data('.'), 46 | install_requires=[ 47 | 'ipcalc', 48 | ], 49 | classifiers=[ 50 | 'Development Status :: 5 - Production/Stable', 51 | 'Environment :: Web Environment', 52 | 'Framework :: Django', 53 | 'Intended Audience :: Developers', 54 | 'License :: OSI Approved :: BSD License', 55 | 'Operating System :: OS Independent', 56 | 'Programming Language :: Python', 57 | 'Topic :: Internet :: WWW/HTTP', 58 | ] 59 | ) 60 | --------------------------------------------------------------------------------