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 %5HdqA A iprestrict/__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 %5H iprestrict/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@+W W iprestrict/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 %5H2 iprestrict/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 %5HN iprestrict/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 %5H7Zm iprestrict/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'ܞh h 1 django_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 [5He e / 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 [5Hndn n ' 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 [5Hr r ( 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 %5HdqA A iprestrict/__init__.pyPK %5H[ [
iprestrict/models.pyPK %5H iprestrict/views.pyPK %5H@+W W ( iprestrict/restrictor.pyPK %5H2 + iprestrict/urls.pyPK %5H"h, - iprestrict/decorators.pyPK %5HN #3 iprestrict/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 - B iprestrict/management/commands/reloadrules.pyPK [5H'ܞh h 1 D django_iprestrict-0.4.1.dist-info/DESCRIPTION.rstPK [5He e / E django_iprestrict-0.4.1.dist-info/metadata.jsonPK [5Hw / 4I django_iprestrict-0.4.1.dist-info/top_level.txtPK [5Hndn n ' I django_iprestrict-0.4.1.dist-info/WHEELPK [5H+hK( ( * ?J django_iprestrict-0.4.1.dist-info/METADATAPK [5Hr r ( M django_iprestrict-0.4.1.dist-info/RECORDPK gT