PK =5JuɰA A iprestrict/admin.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django import forms
import re
from . import ip_utils as ipu
from . import models
from .geoip import is_valid_country_code
NOT_LETTER = re.compile(r'[^A-Z]+')
@admin.register(models.Rule)
class RuleAdmin(admin.ModelAdmin):
exclude = ('rank',)
list_display = ('url_pattern', 'ip_group', 'reverse_ip_group', 'is_allowed', 'move_up_url', 'move_down_url')
class IPRangeForm(forms.ModelForm):
class Meta:
model = models.IPRange
exclude = ()
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
version = ipu.get_version(first_ip)
last_ip = cleaned_data['last_ip']
cidr = cleaned_data.get('cidr_prefix_length')
if cidr is not None:
if version == ipu.IPv4 and not (1 <= cidr <= 31):
self.add_error('cidr_prefix_length', 'Must be a number between 1 and 31')
return cleaned_data
if version == ipu.IPv6 and not (1 <= cidr <= 127):
self.add_error('cidr_prefix_length', 'Must be a number between 1 and 127')
return cleaned_data
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 version != ipu.get_version(last_ip):
raise forms.ValidationError(
"Last IP should be the same type as First IP (%s)" % version)
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, version=version)
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 IPLocationForm(forms.ModelForm):
class Meta:
model = models.IPLocation
exclude = ()
def clean_country_codes(self):
codes = self.cleaned_data['country_codes']
codes = set(filter(lambda x: x != '',
NOT_LETTER.split(self.cleaned_data['country_codes'].upper())))
if not all(map(is_valid_country_code, codes)):
incorrect = [c for c in codes if not is_valid_country_code(c)]
msg = ('""%s" must be a valid country code' if len(incorrect) == 1 else
'""%s" must be valid country codes')
raise forms.ValidationError(msg % ', '.join(incorrect))
codes = list(codes)
codes.sort()
return ', '.join(codes)
class IPLocationInline(admin.TabularInline):
model = models.IPLocation
form = IPLocationForm
fields = ['country_codes']
extra = 2
@admin.register(models.RangeBasedIPGroup)
class RangeBasedIPGroupAdmin(admin.ModelAdmin):
exclude = ('type',)
inlines = [IPRangeInline]
@admin.register(models.LocationBasedIPGroup)
class LocationBasedIPGroupAdmin(admin.ModelAdmin):
exclude = ('type',)
inlines = [IPLocationInline]
PK CNJܙ iprestrict/__init__.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from .restrictor import IPRestrictor
__all__ = ["IPRestrictor"]
__version__ = "1.3.1"
PK NJ' ' iprestrict/models.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from . import ip_utils as ipu
from .geoip import get_geoip, NO_COUNTRY
TYPE_LOCATION = 'location'
TYPE_RANGE = 'range'
geoip = get_geoip()
class IPGroupManager(models.Manager):
def get_queryset(self):
qs = super(IPGroupManager, self).get_queryset()
if self.model.TYPE is not None:
return qs.filter(type=self.model.TYPE)
return qs
class IPGroup(models.Model):
TYPE_CHOICES = ((TYPE_LOCATION, 'Location based'),
(TYPE_RANGE, 'Range based'))
TYPE = None
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
type = models.CharField(max_length=10, default=TYPE_RANGE, choices=TYPE_CHOICES)
class Meta:
verbose_name = 'IP Group'
objects = IPGroupManager()
def __init__(self, *args, **kwargs):
super(IPGroup, self).__init__(*args, **kwargs)
self.load()
def load(self):
pass
def save(self, *args, **kwargs):
if self.TYPE is not None:
self.type = self.TYPE
super(IPGroup, self).save(*args, **kwargs)
def __str__(self):
return self.name
__unicode__ = __str__
def typed_ip_group(ip_group):
obj = None
if ip_group.type == TYPE_RANGE:
obj = RangeBasedIPGroup(pk=ip_group.pk)
elif ip_group.type == TYPE_LOCATION:
obj = LocationBasedIPGroup(pk=ip_group.pk)
else:
raise ValueError("Invalid type '%s'" % ip_group.type)
obj.__dict__.update(ip_group.__dict__)
return obj
class RangeBasedIPGroup(IPGroup):
TYPE = TYPE_RANGE
class Meta:
proxy = True
verbose_name = 'IP Group'
def load_ranges(self):
self._ranges = {ipu.IPv4: [], ipu.IPv6: []}
for r in self.iprange_set.all():
self._ranges[r.ip_type].append(r)
load = load_ranges
def ranges(self, ip_type=None):
if ip_type is None:
return self._ranges[ipu.IPv4] + self._ranges[ipu.IPv6]
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 details_str(self):
return ', '.join([str(r) for r in self.ranges()])
class LocationBasedIPGroup(IPGroup):
TYPE = TYPE_LOCATION
class Meta:
proxy = True
verbose_name = 'Location Based IP Group'
def load_locations(self):
countries = ", ".join(self.iplocation_set.values_list('country_codes', flat=True)).split(', ')
countries.sort()
self._countries = ', '.join(countries)
load = load_locations
def matches(self, ip):
country_code = geoip.country_code(ip) or NO_COUNTRY
return country_code in self._countries
def details_str(self):
return self._countries
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 IPLocation(models.Model):
class Meta:
verbose_name = "IP Location"
ip_group = models.ForeignKey(IPGroup)
country_codes = models.CharField(max_length=2000, help_text='Comma-separated list of 2 character country codes')
def __contains__(self, country_code):
return country_code in re.split(r'[^A-Z]+', self.country_codes)
def __str__(self):
return self.country_codes
__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)
reverse_ip_group = models.BooleanField(default=False)
action = models.CharField(max_length=1, choices=ACTION_CHOICES, default='D')
rank = models.IntegerField(blank=True)
def __init__(self, *args, **kwargs):
super(Rule, self).__init__(*args, **kwargs)
self.ip_group = typed_ip_group(self.ip_group)
@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):
match = typed_ip_group(self.ip_group).matches(ip)
if self.reverse_ip_group:
return not match
return match
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: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: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 = timezone.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 =5Jx
iprestrict/views.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import validate_ipv46_address
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):
request_dict = request.GET
if request.method == 'POST':
request_dict = request.POST
url = request_dict['url']
ip = request_dict['ip']
try:
validate_ipv46_address(ip)
except ValidationError:
return HttpResponse(_test_match_result('Error', msg='Invalid IP address'))
matching_rule_id, action = find_matching_rule(url, ip)
rules = list_rules(matching_rule_id, url, ip)
if matching_rule_id is None:
action = 'Allowed'
msg = 'No rules matched.'
else:
msg = 'URL matched Rule highlighted below.'
return HttpResponse(_test_match_result(action, msg, rules))
def _test_match_result(action, msg=None, rules=None):
return json.dumps({
'action': action,
'msg': msg or '',
'rules': rules or [],
})
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,
'reverse_ip_group': 'NOT' if r.reverse_ip_group else '',
'ranges': r.ip_group.details_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 =5JÜ iprestrict/restrictor.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils import timezone
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 = timezone.now()
reload_rules = load_rules
PK =5JJI I iprestrict/urls.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from 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, name='move_rule_up'),
url(r'^move_rule_down/(?P\d+)[/]?$', move_rule_down, name='move_rule_down'),
url(r'^reload_rules[/]?$', reload_rules, name='reload_rules'),
url(r'^test_match[/]?$', test_match, name='test_match'),
]
PK =5J? iprestrict/decorators.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from 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 =5JN
iprestrict/middleware.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core import exceptions
from django.conf import settings
import logging
import warnings
from .models import ReloadRulesRequest
from .restrictor import IPRestrictor
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
class MiddlewareMixin(object):
def __init__(self, *args, **kwargs):
pass
logger = logging.getLogger(__name__)
class IPRestrictMiddleware(MiddlewareMixin):
restrictor = None
trusted_proxies = None
allow_proxies = None
reload_rules = None
def __init__(self, *args, **kwargs):
super(IPRestrictMiddleware, self).__init__(*args, **kwargs)
self.restrictor = IPRestrictor()
self.trusted_proxies = tuple(get_setting('IPRESTRICT_TRUSTED_PROXIES', 'TRUSTED_PROXIES', []))
self.reload_rules = get_reload_rules_setting()
self.ignore_proxy_header = bool(get_setting('IPRESTRICT_IGNORE_PROXY_HEADER', 'IGNORE_PROXY_HEADER', False))
def process_request(self, request):
if self.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.warn("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.warn("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()
def get_setting(new_name, old_name, default=None):
setting_name = new_name
if hasattr(settings, old_name):
setting_name = old_name
warn_about_changed_setting(old_name, new_name)
return getattr(settings, setting_name, default)
def get_reload_rules_setting():
if hasattr(settings, 'DONT_RELOAD_RULES'):
warn_about_changed_setting('DONT_RELOAD_RULES', 'IPRESTRICT_RELOAD_RULES')
return not bool(getattr(settings, 'DONT_RELOAD_RULES'))
return bool(getattr(settings, 'IPRESTRICT_RELOAD_RULES', True))
def warn_about_changed_setting(old_name, new_name):
# DeprecationWarnings are ignored by default, so lets make sure that
# the warnings are shown by using the default UserWarning instead
warnings.warn("The setting name '%s' has been deprecated and it will be removed in a future version. "
"Please use '%s' instead." % (old_name, new_name))
PK =5J~P P iprestrict/ip_utils.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
IPv4 = 'ipv4'
IPv6 = 'ipv6'
def get_version(ip):
return IPv6 if ':' in ip else IPv4
def is_ipv4(ip):
return get_version(ip) == IPv4
def is_ipv6(ip):
return get_version(ip) == IPv6
def to_number(ip):
return ipv6_to_number(ip) if is_ipv6(ip) else ipv4_to_number(ip)
def ipv4_to_number(ip):
return _ip_to_number(ip)
def ipv6_to_number(ip):
if '.' in ip:
ip = convert_mixed(ip)
if '::' in ip:
ip = explode(ip)
return _ip_to_number(ip, separator=':', group_size=2 ** 16, base=16)
def explode(ip):
if ip.count('::') > 1:
raise ValueError('Invalid ip address "%s". "::" can appear only once.' % ip)
pre, post = [reject_empty(x.split(':')) for x in ip.split('::')]
return ':'.join(pre + ['0'] * (8 - len(pre) - len(post)) + post)
def convert_mixed(ip):
last_colon = ip.rfind(':')
ipv6, ipv4 = ip[:last_colon+1], ip[last_colon+1:]
if ipv4.count('.') != 3:
raise ValueError('Invalid IPv6 address "%s". Dotted ipv4 part should be at the end.' % ip)
a, b, c, d = ipv4.split('.')
pre_last = 256 * int(a) + int(b)
last = 256 * int(c) + int(d)
return '%s:%x:%x' % (ipv6, pre_last, last)
def to_ip(number, version=IPv4):
if version == IPv6:
separator = ':'
parts_count = 8
parts_length = 16
fmt = '%x'
else:
separator = '.'
parts_count = 4
parts_length = 8
fmt = '%d'
mask = int('1' * parts_length, 2)
parts = []
for i in range(parts_count):
shifted_number = number >> (parts_length * i)
parts.append(shifted_number & mask)
return separator.join(map(lambda i: fmt % i, reversed(parts)))
def cidr_to_range(ip, prefix_length):
ip_length = 128 if is_ipv6(ip) else 32
ip = to_number(ip)
start_mask = int('1' * prefix_length, 2) << (ip_length - prefix_length)
end_mask = int('1' * (ip_length - prefix_length), 2)
start = ip & start_mask
end = start | end_mask
return (start, end)
def _ip_to_number(ip, separator='.', group_size=2 ** 8, base=10):
parts = ip.split(separator)
parts = [int(p, base) for p in reversed(parts)]
nr = 0
for i, d in enumerate(parts):
nr += (group_size ** i) * d
return nr
def reject_empty(l):
return [x for x in l if x]
PK >J7w;.
.
iprestrict/geoip.py# -*- coding: utf-8 -*-
'''Client-side wrapper lib around GeoIP.
By using this module we allow:
- if GeoIP2 is unavailable (pre Django 1.9) fall back on GeoIP
- make the geoip module optional. If the user opts out we don't fail on importing required modules
'''
from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
available_geoip = 2
try:
from django.contrib.gis.geoip2 import GeoIP2
from geoip2.errors import AddressNotFoundError
except ImportError:
available_geoip = 1
try:
from django.contrib.gis.geoip import GeoIP
except ImportError:
available_geoip = None
try:
from pycountry import countries
except ImportError:
if getattr(settings, 'IPRESTRICT_GEOIP_ENABLED', True):
raise ImproperlyConfigured(
"You are using location based IP Groups, but the python package "
"pycountry isn't installed. Please install pycountry or set 'IPRESTRICT_GEOIP_ENABLED' "
"to False in settings.py")
# Special value for IP addresses that have no country like localhost.
# Using the 'XX' special value allows for rules being set up on the 'XX' country code
# and giving more control to end-users on what to do for special cases like this
NO_COUNTRY = 'XX'
class AdaptedGeoIP2(object):
'''Makes GeoIP2 behave like GeoIP'''
def __init__(self, *args, **kwargs):
self._geoip = GeoIP2()
def country_code(self, ip):
# if the IP isn't in the DB return None instead of throwing an Exception as GeoIP does
try:
return self._geoip.country_code(ip)
except AddressNotFoundError:
return None
class OurGeoIP(object):
def country_code(self, ip):
raise ImproperlyConfigured(
"You are using location based IP Groups, "
"but 'IPRESTRICT_GEOIP_ENABLED' isn't set to True in settings.py")
_geoip = OurGeoIP()
if getattr(settings, 'IPRESTRICT_GEOIP_ENABLED', True):
if available_geoip is None:
raise ImproperlyConfigured(
"'IPRESTRICT_GEOIP_ENABLED' is set to True, but neither geoip nor geoip2 is available "
" to import. Make sure the geoip libraries are installed as described in the Django "
"documentation")
_geoip = AdaptedGeoIP2() if available_geoip == 2 else GeoIP()
def get_geoip():
return _geoip
def is_valid_country_code(code):
if code == NO_COUNTRY:
return True
try:
countries.get(alpha_2=code)
return True
except KeyError:
return False
PK =5JѪO+ 0 iprestrict/migrations/0006_auto_20161013_1327.py# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-13 13:27
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('iprestrict', '0005_add_reverse_ipgroup'),
]
operations = [
migrations.AlterField(
model_name='ipgroup',
name='type',
field=models.CharField(choices=[('location', 'Location based'), ('range', 'Range based')], default='range', max_length=10),
),
migrations.AlterField(
model_name='iplocation',
name='country_codes',
field=models.CharField(help_text='Comma-separated list of 2 character country codes', max_length=2000),
),
migrations.AlterField(
model_name='rule',
name='action',
field=models.CharField(choices=[('A', 'ALLOW'), ('D', 'DENY')], default='D', max_length=1),
),
]
PK =5Jk5{v 1 iprestrict/migrations/0005_add_reverse_ipgroup.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('iprestrict', '0004_add_iplocation'),
]
operations = [
migrations.AlterModelOptions(
name='ipgroup',
options={'verbose_name': 'IP Group'},
),
migrations.AddField(
model_name='rule',
name='reverse_ip_group',
field=models.BooleanField(default=False),
),
]
PK =5J#t t ! iprestrict/migrations/__init__.py# -*- coding: utf-8 -*-
from __future__ import (
absolute_import, division, print_function, unicode_literals
)
PK %5HgsN N * iprestrict/migrations/0002_initial_data.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def create_data(apps, schema_editor):
IPGroup = apps.get_model('iprestrict', 'IPGroup')
IPRange = apps.get_model("iprestrict", "IPRange")
Rule = apps.get_model("iprestrict", "Rule")
# Default IP Groups: ALL and localhost
all_group = IPGroup.objects.create(
name='ALL',
description='Matches all IP Addresses')
localhost_group = IPGroup.objects.create(
name='localhost',
description='IP address of localhost')
# IP ranges defining ALL and localhost for ipv4 and ipv6
IPRange.objects.create(
ip_group=all_group,
first_ip='0.0.0.0',
last_ip='255.255.255.255')
IPRange.objects.create(
ip_group=all_group,
first_ip='0:0:0:0:0:0:0:0',
last_ip='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
IPRange.objects.create(
ip_group=localhost_group,
first_ip='127.0.0.1',
last_ip=None)
IPRange.objects.create(
ip_group=localhost_group,
first_ip='::1',
last_ip=None)
# Default rules: Allow all for localhost and Deny everything else
Rule.objects.create(
ip_group=localhost_group,
action='A',
url_pattern='ALL',
rank=65535)
Rule.objects.create(
ip_group=all_group,
action='D',
url_pattern='ALL',
rank=65536)
class Migration(migrations.Migration):
dependencies = [
('iprestrict', '0001_initial'),
]
operations = [
migrations.RunPython(create_data),
]
PK =5J,5> > , iprestrict/migrations/0004_add_iplocation.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('iprestrict', '0003_add_ipgroup_types'),
]
operations = [
migrations.CreateModel(
name='IPLocation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('country_codes', models.CharField(help_text=b'Comma-separated list of 2 character country codes', max_length=2000)),
],
options={
'verbose_name': 'IP Location',
},
),
migrations.AlterModelOptions(
name='ipgroup',
options={},
),
migrations.AlterModelOptions(
name='rangebasedipgroup',
options={'verbose_name': 'IP Group'},
),
migrations.AlterField(
model_name='ipgroup',
name='type',
field=models.CharField(default=b'range', max_length=10, choices=[(b'location', b'Location based'), (b'range', b'Range based')]),
),
migrations.AddField(
model_name='iplocation',
name='ip_group',
field=models.ForeignKey(to='iprestrict.IPGroup'),
),
]
PK =5J / iprestrict/migrations/0003_add_ipgroup_types.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('iprestrict', '0002_initial_data'),
]
operations = [
migrations.CreateModel(
name='LocationBasedIPGroup',
fields=[
],
options={
'verbose_name': 'Location Based IP Group',
'proxy': True,
},
bases=('iprestrict.ipgroup',),
),
migrations.CreateModel(
name='RangeBasedIPGroup',
fields=[
],
options={
'proxy': True,
},
bases=('iprestrict.ipgroup',),
),
migrations.AddField(
model_name='ipgroup',
name='type',
field=models.CharField(default=b'range', max_length=10, choices=[(b'location', b'location based'), (b'range', b'range based')]),
),
]
PK %5H=9 % iprestrict/migrations/0001_initial.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='IPGroup',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=100)),
('description', models.TextField(null=True, blank=True)),
],
options={
'verbose_name': 'IP Group',
},
),
migrations.CreateModel(
name='IPRange',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('first_ip', models.GenericIPAddressField()),
('cidr_prefix_length', models.PositiveSmallIntegerField(null=True, blank=True)),
('last_ip', models.GenericIPAddressField(null=True, blank=True)),
('ip_group', models.ForeignKey(to='iprestrict.IPGroup')),
],
options={
'verbose_name': 'IP Range',
},
),
migrations.CreateModel(
name='ReloadRulesRequest',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Rule',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('url_pattern', models.CharField(max_length=500)),
('action', models.CharField(default=b'D', max_length=1, choices=[(b'A', b'ALLOW'), (b'D', b'DENY')])),
('rank', models.IntegerField(blank=True)),
('ip_group', models.ForeignKey(default=1, to='iprestrict.IPGroup')),
],
options={
'ordering': ['rank', 'id'],
},
),
]
PK %5H ! iprestrict/management/__init__.pyPK =5J . iprestrict/management/commands/reload_rules.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.management.base import BaseCommand, CommandError
from ... import models
from ...middleware import get_reload_rules_setting
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'))
reload_rules = get_reload_rules_setting()
if not reload_rules:
raise CommandError("IPRESTRICT_RELOAD_RULES is set to False. "
"Your IPRestrict rules can't be changed dynamically.")
models.ReloadRulesRequest.request_reload()
if verbosity >= 1:
self.stdout.write('Successfully requested reload of rules')
PK %5H * iprestrict/management/commands/__init__.pyPK =5Jo-tS S - iprestrict/management/commands/importrules.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._utils import warn_about_renamed_command
from . import import_rules
class Command(import_rules.Command):
def handle(self, *args, **options):
warn_about_renamed_command('importrules', 'import_rules')
super(Command, self).handle(*args, **options)
PK =5JA'b@ @ . iprestrict/management/commands/import_rules.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.db import transaction
from ... import models
class Command(BaseCommand):
help = 'Replaces the current rules in the DB with the rules in the given fixture file(s).'
def add_arguments(self, parser):
parser.add_argument('fixture', nargs='+')
def handle(self, *args, **options):
fixtures = options.get('fixture', [])
with transaction.atomic():
self.delete_existing_rules()
call_command('loaddata', *fixtures, **options)
def delete_existing_rules(self):
models.Rule.objects.all().delete()
models.IPRange.objects.all().delete()
models.IPGroup.objects.all().delete()
PK =5JvS S - iprestrict/management/commands/reloadrules.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._utils import warn_about_renamed_command
from . import reload_rules
class Command(reload_rules.Command):
def handle(self, *args, **options):
warn_about_renamed_command('reloadrules', 'reload_rules')
super(Command, self).handle(*args, **options)
PK =5Jn ( iprestrict/management/commands/_utils.pyimport warnings
def warn_about_renamed_command(old_name, new_name):
# DeprecationWarnings are ignored by default, so lets make sure that
# the warnings are shown by using the default UserWarning instead
warnings.warn("The command '%s' has been deprecated and it will be removed in a future version. "
"Please use '%s' instead." % (old_name, new_name))
PK =5JD@Y Y 1 iprestrict/management/commands/add_ip_to_group.py# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand, CommandError
from django.core.validators import validate_ipv46_address
from ... import models
class Command(BaseCommand):
help = 'Adds an IP address to an IP Group.'
def add_arguments(self, parser):
parser.add_argument('group_name')
parser.add_argument('ip', nargs='+')
def handle(self, *args, **options):
group_name = options.get('group_name')
ips = options.get('ip')
try:
ip_group = models.IPGroup.objects.get(name__iexact=group_name, type=models.TYPE_RANGE)
except models.IPGroup.DoesNotExist:
try:
models.IPGroup.objects.get(name__iexact=group_name)
except models.IPGroup.DoesNotExist:
raise CommandError("IPGroup '%s' doesn't exist." % group_name)
else:
raise CommandError("Can add IP address only to a Range based IP Group.")
for ip in ips:
try:
validate_ipv46_address(ip)
except ValidationError:
raise CommandError("'%s' not a valid IPv4 or IPv6 address." % ip)
for ip in ips:
models.IPRange.objects.get_or_create(ip_group=ip_group, first_ip=ip)
PK NJ'ܞh h 1 django_iprestrict-1.3.1.dist-info/DESCRIPTION.rstDjango app + middleware to restrict access to all or sections of a Django project by client IP ranges
PK NJ@+XK K / django_iprestrict-1.3.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": ["dev", "geoip"], "generator": "bdist_wheel (0.26.0)", "metadata_version": "2.0", "name": "django-iprestrict", "run_requires": [{"requires": ["Django (>=1.8)", "django-templatetag-handlebars (==1.3.1)"]}, {"extra": "geoip", "requires": ["GeoIP (==1.3.2)", "geoip2 (==2.5.0)", "pycountry (==17.5.14)"]}, {"extra": "dev", "requires": ["Sphinx", "Werkzeug", "django-extensions", "flake8", "mock", "pep8", "tox"]}], "summary": "Django app + middleware to restrict access to all or sections of a Django project by client IP ranges", "version": "1.3.1"}PK NJw / django_iprestrict-1.3.1.dist-info/top_level.txtiprestrict
PK NJndn n ' django_iprestrict-1.3.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 NJB\ * django_iprestrict-1.3.1.dist-info/METADATAMetadata-Version: 2.0
Name: django-iprestrict
Version: 1.3.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.8)
Requires-Dist: django-templatetag-handlebars (==1.3.1)
Provides-Extra: dev
Requires-Dist: Sphinx; extra == 'dev'
Requires-Dist: Werkzeug; extra == 'dev'
Requires-Dist: django-extensions; extra == 'dev'
Requires-Dist: flake8; extra == 'dev'
Requires-Dist: mock; extra == 'dev'
Requires-Dist: pep8; extra == 'dev'
Requires-Dist: tox; extra == 'dev'
Provides-Extra: geoip
Requires-Dist: GeoIP (==1.3.2); extra == 'geoip'
Requires-Dist: geoip2 (==2.5.0); extra == 'geoip'
Requires-Dist: pycountry (==17.5.14); extra == 'geoip'
Django app + middleware to restrict access to all or sections of a Django project by client IP ranges
PK NJP ( django_iprestrict-1.3.1.dist-info/RECORDdjango_iprestrict-1.3.1.dist-info/DESCRIPTION.rst,sha256=MW1Mpxmn0Ho1mDJ2PJjKS20MmZ8tb4X3lAVDelDxU-Y,104
django_iprestrict-1.3.1.dist-info/METADATA,sha256=oo9QcC_idBwPvTEL-eY_qNXp59CSUHzljS4aqbn-VAg,1286
django_iprestrict-1.3.1.dist-info/RECORD,,
django_iprestrict-1.3.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110
django_iprestrict-1.3.1.dist-info/metadata.json,sha256=rpeRlbpfQkOnrpEaHDOrsLadGSsYuPUXaVlKeYnKSA0,1099
django_iprestrict-1.3.1.dist-info/top_level.txt,sha256=CeJEtZeoqyJUxGxI__2rkygfrHfNhrmZITWotbsleNA,11
iprestrict/__init__.py,sha256=a-LV_8aQSIcFfYv75cvz6xohxRh7ukpWjauN_9ra6os,153
iprestrict/admin.py,sha256=jOnDTqu-buFchc7AJa3S348DRj15YXt9hjUl9NFs2Lk,3649
iprestrict/decorators.py,sha256=iej7lbnz-XL7kOVOn5BpsyWklNAXbkFQfPxSwJVQ_ZA,1677
iprestrict/geoip.py,sha256=BiwWI_A-_bP3F7Kl4HL7qfAhUcRuX6h_j41V-ASZkts,2606
iprestrict/ip_utils.py,sha256=VVwm0O6T71FouWTzcen1xpWCzmiskIa-lfl_QeGznt4,2384
iprestrict/middleware.py,sha256=qoTHZ0ADNB8O1PXQTC7A6IdSO6zZIPWMV8G1HW_CUH0,3464
iprestrict/models.py,sha256=VniDnfWtTerUTYMbM8L8fAafuy-4rq5zhXPM7ZzYoWA,8231
iprestrict/restrictor.py,sha256=0Oq8mwpky3PiI6mVYQ1yRg2BUdt7pLv9iHlSMwXDZtE,668
iprestrict/urls.py,sha256=ZaTfMwxaZC-UoggqlGz-OunkSBEBnYyAWOXgqD-wR9M,585
iprestrict/views.py,sha256=oHFLn9MDi1F2LI1CaHC9_LqEBs5eQ5ys-dsecpL5Jxo,2810
iprestrict/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
iprestrict/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
iprestrict/management/commands/_utils.py,sha256=-JOQ1EG3rUBLjGHtES0w53dUUK0_0sZ_TSBxHofhH94,384
iprestrict/management/commands/add_ip_to_group.py,sha256=KMx8YKr-3zAjJv7fIBw7g_TdYfBsVI__M5nr5nblm5M,1369
iprestrict/management/commands/import_rules.py,sha256=TCg8aEj7KUyGxhEmSIc8AdoZL0FuKu1lUNQCBnGQZ3M,832
iprestrict/management/commands/importrules.py,sha256=q1aIDDxrFxQJah38V489nPeCo7VOl2qQ-tKsJb31AB0,339
iprestrict/management/commands/reload_rules.py,sha256=UzuhLjJWKYOaS89X5_g5i9vFytJ9-F05vVZdzKztkdc,797
iprestrict/management/commands/reloadrules.py,sha256=4GWgCUpgKtc6dVjxqtaQJQzSpRPfQC_jEXCamTBplJ0,339
iprestrict/migrations/0001_initial.py,sha256=vH3fmyGIbQ_J7US4FOLrpQlrOdyRofxJEDUnVtSa3uU,2184
iprestrict/migrations/0002_initial_data.py,sha256=9nukteVPM5N0_yeK6ejlPnyvpKYCx0mz-o1x7NxtxVc,1614
iprestrict/migrations/0003_add_ipgroup_types.py,sha256=vc4dBulZqkRC_HUhar5ipPA_wx8-OGAhrHmVnIm5eP8,1004
iprestrict/migrations/0004_add_iplocation.py,sha256=agS-56q0jdknhnciKpAFb53tLtp-mPjoVEm3qH8GI8Y,1342
iprestrict/migrations/0005_add_reverse_ipgroup.py,sha256=HxXhR73LoMp6Yj4hhYLFp9wLX8S8LjN6-6CLjvo7Yww,537
iprestrict/migrations/0006_auto_20161013_1327.py,sha256=lInm25e4KU8lCyp6hWQelwyaf0BqN6eTQlvKCQUQ9Xc,972
iprestrict/migrations/__init__.py,sha256=yOrDciBNF3Ei_d4aDoIbLdkLbsavYD3ks2RIwHvS9FU,116
PK =5JuɰA A iprestrict/admin.pyPK CNJܙ r iprestrict/__init__.pyPK NJ' ' ? iprestrict/models.pyPK =5Jx
/ iprestrict/views.pyPK =5JÜ : iprestrict/restrictor.pyPK =5JJI I = iprestrict/urls.pyPK =5J? @ iprestrict/decorators.pyPK =5JN
F iprestrict/middleware.pyPK =5J~P P T iprestrict/ip_utils.pyPK >J7w;.
.
^ iprestrict/geoip.pyPK =5JѪO+ 0 rh iprestrict/migrations/0006_auto_20161013_1327.pyPK =5Jk5{v 1 l iprestrict/migrations/0005_add_reverse_ipgroup.pyPK =5J#t t ! n iprestrict/migrations/__init__.pyPK %5HgsN N * o iprestrict/migrations/0002_initial_data.pyPK =5J,5> > , =v iprestrict/migrations/0004_add_iplocation.pyPK =5J / { iprestrict/migrations/0003_add_ipgroup_types.pyPK %5H=9 % iprestrict/migrations/0001_initial.pyPK %5H ! Ɉ iprestrict/management/__init__.pyPK =5J . iprestrict/management/commands/reload_rules.pyPK %5H * q iprestrict/management/commands/__init__.pyPK =5Jo-tS S - iprestrict/management/commands/importrules.pyPK =5JA'b@ @ . W iprestrict/management/commands/import_rules.pyPK =5JvS S - iprestrict/management/commands/reloadrules.pyPK =5Jn ( iprestrict/management/commands/_utils.pyPK =5JD@Y Y 1 G iprestrict/management/commands/add_ip_to_group.pyPK NJ'ܞh h 1 django_iprestrict-1.3.1.dist-info/DESCRIPTION.rstPK NJ@+XK K / django_iprestrict-1.3.1.dist-info/metadata.jsonPK NJw / > django_iprestrict-1.3.1.dist-info/top_level.txtPK NJndn n ' django_iprestrict-1.3.1.dist-info/WHEELPK NJB\ * I django_iprestrict-1.3.1.dist-info/METADATAPK NJP ( django_iprestrict-1.3.1.dist-info/RECORDPK