PK%5HAZ Z iprestrict/admin.pyfrom django.contrib import admin from django import forms from . import ip_utils as ipu from . import models class RuleAdmin(admin.ModelAdmin): model = models.Rule exclude = ('rank',) list_display = ('url_pattern', 'ip_group', 'is_allowed', 'move_up_url', 'move_down_url') class IPRangeForm(forms.ModelForm): class Meta: model = models.IPRange exclude = () def clean_cidr_prefix_length(self): cidr = self.cleaned_data['cidr_prefix_length'] if cidr: if not (1 <= cidr <= 31): raise forms.ValidationError("Must be a number between 1 and 31") return cidr def clean(self): cleaned_data = super(IPRangeForm, self).clean() first_ip = cleaned_data.get('first_ip') if first_ip is None: # first_ip is Mandatory, so just let the default validator catch this return cleaned_data last_ip = cleaned_data['last_ip'] cidr = cleaned_data['cidr_prefix_length'] if last_ip and cidr: raise forms.ValidationError("Don't specify the Last IP if you specified a CIDR prefix length") if last_ip: if ipu.get_version(first_ip) != ipu.get_version(last_ip): raise forms.ValidationError("Last IP should be the same type as First IP (%s)" % ipu.get_version(first_ip)) if ipu.get_version(last_ip) != 'ipv6': # Ignore rest of validation for ipv6, support isn't there yet if ipu.to_number(first_ip) > ipu.to_number(last_ip): raise forms.ValidationError("Last IP should be greater than First IP") if cidr: # With CIDR the starting address could be different than the one # the user specified. Making sure it is set to the first ip in the # subnet. start, end = ipu.cidr_to_range(first_ip, cidr) cleaned_data['first_ip'] = ipu.to_ip(start) return cleaned_data class IPRangeInline(admin.TabularInline): model = models.IPRange form = IPRangeForm fields = ['first_ip', 'cidr_prefix_length', 'last_ip', 'ip_type'] readonly_fields = ['ip_type'] extra = 2 class IPGroupAdmin(admin.ModelAdmin): model = models.IPGroup inlines = [IPRangeInline] admin.site.register(models.Rule, RuleAdmin) admin.site.register(models.IPGroup, IPGroupAdmin) PK%5HdqAAiprestrict/__init__.pyfrom .restrictor import IPRestrictor __all__ = ["IPRestrictor"] PK%5H[[iprestrict/models.pyimport re from django.core.urlresolvers import reverse from django.db import models from datetime import datetime from . import ip_utils as ipu class IPGroup(models.Model): class Meta: verbose_name = "IP Group" name = models.CharField(max_length=100) description = models.TextField(null=True, blank=True) def __init__(self, *args, **kwargs): models.Model.__init__(self, *args, **kwargs) self.load_ranges() def load_ranges(self): self._ranges = {'ipv4': [], 'ipv6': []} for r in self.iprange_set.all(): self._ranges[r.ip_type].append(r) def ranges(self, ip_type='ipv4'): return self._ranges[ip_type] def matches(self, ip): ip_type = ipu.get_version(ip) for r in self.ranges(ip_type): if ip in r: return True return False def ranges_str(self): return ', '.join([str(r) for r in self.ranges()]) def __str__(self): return self.name __unicode__ = __str__ class IPRange(models.Model): class Meta: verbose_name = "IP Range" ip_group = models.ForeignKey(IPGroup) first_ip = models.GenericIPAddressField() cidr_prefix_length = models.PositiveSmallIntegerField(null=True, blank=True) last_ip = models.GenericIPAddressField(null=True, blank=True) @property def start(self): if self.cidr_prefix_length is not None: start, end = ipu.cidr_to_range(self.first_ip, self.cidr_prefix_length) return start else: return ipu.to_number(self.first_ip) @property def end(self): if self.last_ip is not None: return ipu.to_number(self.last_ip) if self.cidr_prefix_length is not None: start, end = ipu.cidr_to_range(self.first_ip, self.cidr_prefix_length) return end return self.start @property def ip_type(self): if not self.first_ip: return '' return ipu.get_version(self.first_ip) def __contains__(self, ip): ip_nr = ipu.to_number(ip) return self.start <= ip_nr <= self.end def __str__(self): result = str(self.first_ip) if self.cidr_prefix_length is not None: result += '/' + str(self.cidr_prefix_length) elif self.last_ip is not None: result += '-' + str(self.last_ip) return result __unicode__ = __str__ class Rule(models.Model): class Meta: ordering = ['rank', 'id'] ACTION_CHOICES = ( ('A', 'ALLOW'), ('D', 'DENY') ) url_pattern = models.CharField(max_length=500) ip_group = models.ForeignKey(IPGroup, default=1) action = models.CharField(max_length=1, choices=ACTION_CHOICES, default='D') rank = models.IntegerField(blank=True) @property def regex(self): if not hasattr(self, '_regex'): self._regex = re.compile(self.url_pattern) return self._regex def matches_url(self, url): if self.url_pattern == 'ALL': return True else: return self.regex.match(url) is not None def matches_ip(self, ip): return self.ip_group.matches(ip) def is_restricted(self): return self.action != 'A' def is_allowed(self): return self.action == 'A' is_allowed.boolean = True is_allowed.short_description = 'Is allowed?' def action_str(self): return 'Allowed' if self.is_allowed() else 'Denied' def swap_with_rule(self, other): other.rank, self.rank = self.rank, other.rank other.save() self.save() def move_up(self): rules_above = Rule.objects.filter(rank__lt=self.rank).order_by('-rank') if len(rules_above) == 0: return self.swap_with_rule(rules_above[0]) def move_up_url(self): url = reverse('iprestrict.views.move_rule_up', args=[self.pk]) return 'Move Up' % url move_up_url.allow_tags = True move_up_url.short_description = 'Move Up' def move_down_url(self): url = reverse('iprestrict.views.move_rule_down', args=[self.pk]) return 'Move Down' % url move_down_url.allow_tags = True move_down_url.short_description = 'Move Down' def move_down(self): rules_below = Rule.objects.filter(rank__gt=self.rank) if len(rules_below) == 0: return self.swap_with_rule(rules_below[0]) def save(self, *args, **kwargs): if self.rank is None: max_aggr = Rule.objects.filter(rank__lt=65000).aggregate(models.Max('rank')) max_rank = max_aggr.get('rank__max') if max_rank is None: max_rank = 0 self.rank = max_rank + 1 super(Rule, self).save(*args, **kwargs) class ReloadRulesRequest(models.Model): at = models.DateTimeField(auto_now_add=True) @classmethod def request_reload(cls): rrs = ReloadRulesRequest.objects.all() if len(rrs) > 0: obj = rrs[0] obj.at = datetime.now() obj.save() else: cls.objects.create() @staticmethod def last_request(): result = None rrs = ReloadRulesRequest.objects.all() if len(rrs) > 0: result = rrs[0].at return result PK%5Hiprestrict/views.pyfrom django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render_to_response import json from . import models from .decorators import superuser_required @superuser_required def move_rule_up(request, rule_id): rule = models.Rule.objects.get(pk=rule_id) rule.move_up() return HttpResponseRedirect(reverse('admin:iprestrict_rule_changelist')) @superuser_required def move_rule_down(request, rule_id): rule = models.Rule.objects.get(pk=rule_id) rule.move_down() return HttpResponseRedirect(reverse('admin:iprestrict_rule_changelist')) @superuser_required def reload_rules(request): models.ReloadRulesRequest.request_reload() return HttpResponse('ok') @superuser_required def test_rules_page(request): return render_to_response('iprestrict/test_rules.html') @superuser_required def test_match(request): url = request.REQUEST['url'] ip = request.REQUEST['ip'] matching_rule_id, action = find_matching_rule(url, ip) rules = list_rules(matching_rule_id, url, ip) if matching_rule_id is None: result = { 'action': 'Allowed', 'msg': 'No rules matched.', } else: result = { 'action': action, 'msg': 'URL matched Rule highlighted below.' } result['rules'] = rules return HttpResponse(json.dumps(result)) def find_matching_rule(url, ip): for r in models.Rule.objects.all(): if r.matches_url(url) and r.matches_ip(ip): return r.pk, r.action_str() return None, None def list_rules(matching_rule_id, url, ip): return [map_rule(r, matching_rule_id, url, ip) for r in models.Rule.objects.all()] def map_rule(r, matching_rule_id, url, ip): rule = { 'url_pattern': { 'value': r.url_pattern, 'matchStatus': 'match' if r.matches_url(url) else 'noMatch' }, 'ip_group': { 'name': r.ip_group.name, 'ranges': r.ip_group.ranges_str(), 'matchStatus': 'match' if r.matches_ip(ip) else 'noMatch' }, 'action': r.action_str(), } if r.pk == matching_rule_id: rule['matched'] = True return rule PK%5H@+WWiprestrict/restrictor.pyfrom datetime import datetime class IPRestrictor(object): rules = None last_reload = None def __init__(self): self.load_rules() def is_restricted(self, url, ip): for rule in self.rules: if rule.matches_url(url) and rule.matches_ip(ip): return rule.is_restricted() return False def load_rules(self): # We are caching the rules, to avoid DB lookup on each request from .models import Rule self.rules = list(Rule.objects.all()) self.last_reload = datetime.now() reload_rules = load_rules PK%5H2iprestrict/urls.pyfrom django.conf.urls import url from .views import test_rules_page, test_match, reload_rules from .views import move_rule_up, move_rule_down app_name = "iprestrict" urlpatterns = [ url(r'^$', test_rules_page), url(r'^move_rule_up/(?P\d+)[/]?$', move_rule_up), url(r'^move_rule_down/(?P\d+)[/]?$', move_rule_down), url(r'^reload_rules[/]?$', reload_rules), url(r'^test_match[/]?$', test_match), ] PK%5H"h,iprestrict/decorators.pyfrom functools import wraps from django.utils.translation import ugettext as _ from django.contrib.admin.forms import AdminAuthenticationForm from django.contrib.auth.views import login from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseForbidden # Copied from django staff_member_required # Why isn't this provided by Django? def superuser_required(view_func): """ Decorator for views that checks that the user is logged in and is a staff member, displaying the login page if necessary. """ @wraps(view_func) def _checklogin(request, *args, **kwargs): if request.user.is_active and request.user.is_superuser: # The user is valid. Continue to the admin page. return view_func(request, *args, **kwargs) if request.user.is_authenticated(): return HttpResponseForbidden('Forbidden!') assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." defaults = { 'template_name': 'admin/login.html', 'authentication_form': AdminAuthenticationForm, 'extra_context': { 'title': _('Log in'), 'app_path': request.get_full_path(), REDIRECT_FIELD_NAME: request.get_full_path(), }, } return login(request, **defaults) return _checklogin PK%5HNiprestrict/middleware.pyfrom django.core import exceptions from django.conf import settings import logging from .models import ReloadRulesRequest from .restrictor import IPRestrictor logger = logging.getLogger(__name__) class IPRestrictMiddleware(object): restrictor = None trusted_proxies = None allow_proxies = None dont_reload_rules = None def __init__(self): self.restrictor = IPRestrictor() self.trusted_proxies = tuple(getattr(settings, 'TRUSTED_PROXIES', [])) self.dont_reload_rules = bool(getattr(settings, 'DONT_RELOAD_RULES', False)) self.ignore_proxy_header = bool(getattr(settings, 'IGNORE_PROXY_HEADER', False)) def process_request(self, request): if not self.dont_reload_rules: self.reload_rules_if_needed() url = request.path_info client_ip = self.extract_client_ip(request) if self.restrictor.is_restricted(url, client_ip): logger.info("Denying access of %s to %s" % (url, client_ip)) raise exceptions.PermissionDenied def extract_client_ip(self, request): client_ip = request.META['REMOTE_ADDR'] if not self.ignore_proxy_header: forwarded_for = self.get_forwarded_for(request) if forwarded_for: closest_proxy = client_ip client_ip = forwarded_for.pop(0) proxies = [closest_proxy] + forwarded_for for proxy in proxies: if proxy not in self.trusted_proxies: logger.info("Client IP %s forwarded by untrusted proxy %s" % (client_ip, proxy)) raise exceptions.PermissionDenied return client_ip def get_forwarded_for(self, request): hdr = request.META.get('HTTP_X_FORWARDED_FOR') if hdr is not None: return [ip.strip() for ip in hdr.split(',')] else: return [] def reload_rules_if_needed(self): last_reload_request = ReloadRulesRequest.last_request() if last_reload_request is not None: if self.restrictor.last_reload < last_reload_request: self.restrictor.reload_rules() PK%5H7Zmiprestrict/ip_utils.pydef get_version(ip): return 'ipv4' if '.' in ip else 'ipv6' def to_number(ip): parts = ip.split('.') parts = [int(p) for p in reversed(parts)] nr = 0 for i, d in enumerate(parts): nr += (256 ** i) * d return nr def to_ip(number): mask = int('1' * 8, 2) parts = [] for i in range(4): shifted_number = number >> (8 * i) parts.append(shifted_number & mask) return '.'.join(map(lambda i: str(i), reversed(parts))) def cidr_to_range(ip, prefix_length): ip = to_number(ip) start_mask = int('1' * prefix_length, 2) << (32 - prefix_length) end_mask = int('1' * (32 - prefix_length), 2) start = ip & start_mask end = start | end_mask return (start, end) PK%5H!iprestrict/management/__init__.pyPK%5H*iprestrict/management/commands/__init__.pyPK%5H.-iprestrict/management/commands/importrules.pyfrom django.core.management.base import BaseCommand from django.core.management import call_command from ... import models class Command(BaseCommand): help = 'Replaces the current rules in the DB with the rules in the given fixture file(s).' args = "fixture [fixture ...]" def handle(self, *args, **options): verbosity = int(options.get('verbosity', '1')) self.delete_existing_rules() if verbosity >= 1: self.stdout.write('Successfully deleted rules\n') call_command('loaddata', *args, verbosity=verbosity, interactive=False) def delete_existing_rules(self): models.Rule.objects.all().delete() models.IPRange.objects.all().delete() models.IPGroup.objects.all().delete() PK%5HwqD-iprestrict/management/commands/reloadrules.pyfrom django.core.management.base import BaseCommand from ... import models class Command(BaseCommand): help = 'Requests the reload of the ip restriction rules from the DB.' def handle(self, *args, **options): verbosity = int(options.get('verbosity', '1')) models.ReloadRulesRequest.request_reload() if verbosity >= 1: self.stdout.write('Successfully requested reload of rules\n') PK[5H'ܞhh1django_iprestrict-0.4.1.dist-info/DESCRIPTION.rstDjango app + middleware to restrict access to all or sections of a Django project by client IP ranges PK[5Hee/django_iprestrict-0.4.1.dist-info/metadata.json{"classifiers": ["Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development"], "download_url": "https://github.com/muccg/django-iprestrict/releases", "extensions": {"python.details": {"contacts": [{"email": "devops@ccg.murdoch.edu.au", "name": "Tamas Szabo, CCG, Murdoch University", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/muccg/django-iprestrict"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "metadata_version": "2.0", "name": "django-iprestrict", "run_requires": [{"requires": ["Django (>=1.7)", "django-templatetag-handlebars"]}], "summary": "Django app + middleware to restrict access to all or sections of a Django project by client IP ranges", "version": "0.4.1"}PK[5Hw /django_iprestrict-0.4.1.dist-info/top_level.txtiprestrict PK[5Hndnn'django_iprestrict-0.4.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK[5H+hK((*django_iprestrict-0.4.1.dist-info/METADATAMetadata-Version: 2.0 Name: django-iprestrict Version: 0.4.1 Summary: Django app + middleware to restrict access to all or sections of a Django project by client IP ranges Home-page: https://github.com/muccg/django-iprestrict Author: Tamas Szabo, CCG, Murdoch University Author-email: devops@ccg.murdoch.edu.au License: UNKNOWN Download-URL: https://github.com/muccg/django-iprestrict/releases Platform: UNKNOWN Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development Requires-Dist: Django (>=1.7) Requires-Dist: django-templatetag-handlebars Django app + middleware to restrict access to all or sections of a Django project by client IP ranges PK[5Hrr(django_iprestrict-0.4.1.dist-info/RECORDdjango_iprestrict-0.4.1.dist-info/DESCRIPTION.rst,sha256=MW1Mpxmn0Ho1mDJ2PJjKS20MmZ8tb4X3lAVDelDxU-Y,104 django_iprestrict-0.4.1.dist-info/METADATA,sha256=Mi8gGXuUpXRuVWHz3pXUPCykCYoprF0DHTUHQUK6HSM,808 django_iprestrict-0.4.1.dist-info/RECORD,, django_iprestrict-0.4.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 django_iprestrict-0.4.1.dist-info/metadata.json,sha256=wvhxg20vFQVO1M9eV1UqlZDa9n5kkD5736E6pPp-kFQ,869 django_iprestrict-0.4.1.dist-info/top_level.txt,sha256=CeJEtZeoqyJUxGxI__2rkygfrHfNhrmZITWotbsleNA,11 iprestrict/__init__.py,sha256=aKfDqk97lsr_hv7ZbM1ODZXd7NRXolaS-YzzQSewvmU,65 iprestrict/admin.py,sha256=LPHI4FxCwx4iGxplZuYZcq4o81asgPh5Ms613on6g9A,2394 iprestrict/decorators.py,sha256=kq1kix3moB0ltuV3dw4wK6m1toH38HEzH2uk-5qDSj4,1514 iprestrict/ip_utils.py,sha256=cbULhHBItZo2YxqL3SGJJ1ZtgBoaqDoBYALQwm0dbaQ,740 iprestrict/middleware.py,sha256=_0LJnn2XOGfbPCrhr5Myt3wzZwWL7gyoRNKtdBr3E4g,2176 iprestrict/models.py,sha256=dXxwaA8XbG96c3LyAVd1kvFWiT-0IQqDrZQODXhC4s4,5467 iprestrict/restrictor.py,sha256=4uQ9Lph6j0ecWVd23A_HCyc8gniI8MBNTpCb6-_qDvI,599 iprestrict/urls.py,sha256=3s8wwDk_keZnJS1OgmZyfkXy979zhSJ8ZsiZW4xV6bA,436 iprestrict/views.py,sha256=Kx587IInxKeaJb2y7c9YhrwSP8PeBT3kJ1_kYZ1U8Xs,2260 iprestrict/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 iprestrict/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 iprestrict/management/commands/importrules.py,sha256=nE360qy1UhvIwAF_GZHFKJKtvwruFs6tWYYEWSyEcKE,785 iprestrict/management/commands/reloadrules.py,sha256=xqWL1FYRAVXXpBVN-VHmVB55NU-Jda79e50xppVsa4M,428 PK%5HAZ Z iprestrict/admin.pyPK%5HdqAA iprestrict/__init__.pyPK%5H[[ iprestrict/models.pyPK%5Hiprestrict/views.pyPK%5H@+WW(iprestrict/restrictor.pyPK%5H2+iprestrict/urls.pyPK%5H"h,-iprestrict/decorators.pyPK%5HN#3iprestrict/middleware.pyPK%5H7Zm;iprestrict/ip_utils.pyPK%5H!>iprestrict/management/__init__.pyPK%5H*0?iprestrict/management/commands/__init__.pyPK%5H.-x?iprestrict/management/commands/importrules.pyPK%5HwqD-Biprestrict/management/commands/reloadrules.pyPK[5H'ܞhh1Ddjango_iprestrict-0.4.1.dist-info/DESCRIPTION.rstPK[5Hee/Edjango_iprestrict-0.4.1.dist-info/metadata.jsonPK[5Hw /4Idjango_iprestrict-0.4.1.dist-info/top_level.txtPK[5Hndnn'Idjango_iprestrict-0.4.1.dist-info/WHEELPK[5H+hK((*?Jdjango_iprestrict-0.4.1.dist-info/METADATAPK[5Hrr(Mdjango_iprestrict-0.4.1.dist-info/RECORDPKgT