Dienstagscommit

This commit is contained in:
Sebastian Lohff 2017-03-22 04:06:24 +01:00
parent adc034f8f0
commit 26ff25bfd3
14 changed files with 180 additions and 45 deletions

8
dncore/forms.py Normal file
View File

@ -0,0 +1,8 @@
from django.contrib.auth.forms import UserCreationForm
from .models import User
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ("username",)

View File

@ -7,7 +7,7 @@ urlpatterns = [
url(r'^$', dncore_views.dashboard, name='dashboard'),
url(r'^login/$', auth_views.login, name='login'),
url(r'^login/$', auth_views.login, name='register'),
url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^profile/$', dncore_views.profile, name='profile'),
]

View File

@ -1,11 +1,17 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.views.generic import CreateView
from django.urls import reverse_lazy
from django.contrib import messages
from whoisdb.models import ASNumber, InetNum
from domains.models import Domain
from rrequests.models import Request
from .forms import CustomUserCreationForm
@login_required
def profile(request):
@ -25,3 +31,15 @@ def dashboard(request):
def index(request):
return render(request, "index.html", {})
class RegisterUser(CreateView):
template_name = "dncore/registration.html"
form_class = CustomUserCreationForm
success_url = reverse_lazy("user:login")
def form_valid(self, form):
ret = super(RegisterUser, self).form_valid(form)
messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username)
return ret

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-21 18:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('domains', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='reversezone',
name='created',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='reversezone',
name='last_modified',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@ -1,7 +1,7 @@
from django.db import models
from django.urls import reverse
from whoisdb.models import MntdObject, Contact, InetNum
from whoisdb.models import MntdObject, WhoisObject, Contact, InetNum
import ipaddress
@ -21,6 +21,9 @@ class Nameserver(MntdObject):
admin_c = models.ManyToManyField(Contact)
def getPK(self):
return self.name
def get_absolute_url(self):
return reverse("domains:nameserver-show", args=(self.name,))
@ -36,6 +39,9 @@ class Domain(MntdObject):
nameservers = models.ManyToManyField(Nameserver, blank=True)
admin_c = models.ManyToManyField(Contact)
def getPK(self):
return self.name
def get_absolute_url(self):
return reverse("domains:domain-show", args=(self.name,))
@ -52,13 +58,18 @@ class Domain(MntdObject):
return reasons
class ReverseZone(models.Model):
class ReverseZone(WhoisObject):
handle = None
parentNet = models.ForeignKey(InetNum)
address = models.GenericIPAddressField(db_index=True)
netmask = models.PositiveIntegerField()
nameservers = models.ManyToManyField(Nameserver)
def getPK(self):
return self.pk
def prefix(self):
""" Helper function, mainly used in templates """
return "%s/%s" % (self.address, self.netmask)

View File

@ -18,7 +18,7 @@ def overview(request):
# get all domains and nameservers
domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
nameservers = Nameserver.objects.filter(mnt_by__in=mnts).distinct()
reversezones = ReverseZone.objects.filter(parentNet__mnt_by__in=mnts).distinct()
reversezones = ReverseZone.objects.filter(Q(parentNet__mnt_by__in=mnts) | Q(parentNet__mnt_lower__in=mnts)).distinct()
return render(request, "domains/overview.html", {"domains": domains, "nameservers": nameservers, 'reversezones': reversezones})
@ -134,7 +134,7 @@ class ReverseZoneEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
kwargs = super(ReverseZoneEdit, self).get_form_kwargs(*args, **kwargs)
kwargs["user"] = self.request.user
if not "initial" in kwargs:
if "initial" not in kwargs:
kwargs["initial"] = {}
kwargs["initial"]["prefix"] = self.object.prefix()

View File

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Registration</div>
<div class="panel-body">
<p>
<span class="label label-info">Note:</span> Please choose a sane username.
</p>
<form method="POST" action="{% url 'user:register' %}">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary">Register</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -18,9 +18,9 @@
{% if object|userCanEdit:user %}
<tr>
<td>Actions</td>
{% with "domains:"|add:object.getClassName|lower|add:"-edit" as editView %}
{% with "domains:"|add:object.getClassName|lower|add:"-delete" as deleteView %}
<td><a href="{% url editView object.name %}">Edit object<a/>, <a href="{% url deleteView object.name %}">Delete object</a></td>
{% with request.resolver_match.namespaces.0|add:":"|add:object.getClassName|lower|add:"-edit" as editView %}
{% with request.resolver_match.namespaces.0|add:":"|add:object.getClassName|lower|add:"-delete" as deleteView %}
<td><a href="{% url editView object.getPK %}">Edit object<a/>, <a href="{% url deleteView object.getPK %}">Delete object</a></td>
{% endwith %}
{% endwith %}
</tr>

View File

@ -2,8 +2,8 @@ from django import forms
from django.db.models import Case, When, IntegerField
from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
from .validators import HandleValidatorWithSuffix, IP46CIDRValidator
import re
import ipaddress
@ -15,31 +15,36 @@ class WhoisObjectFormMixin(object):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self._create = False
self.fields['handle'].widget.attrs['readonly'] = True
#self.fields['handle'].disabled = True
else:
self._create = True
self.fields['handle'].help_text = "Handle for this object in uppercase with a suffix of -%s" % instance.handleSuffix
# only show users contacts and already present contacts
mnts = self._user.maintainer_set.all()
if 'admin_c' in self.fields:
self.fields['admin_c'].queryset = Contact.getMntQueryset(mnts, self.instance, "admin_c")
def clean_handle(self):
if not self._create:
return self.instance.handle
else:
return self.cleaned_data['handle']
HandleValidatorWithSuffix(self.instance.handleSuffix)(self.cleaned_data['handle'])
return self.cleaned_data['handle']
def clean(self):
cleaned_data = super(WhoisObjectFormMixin, self).clean()
if cleaned_data.get("handle") == "AUTO" and not self.errors:
cleaned_data['handle'] = self._meta.model.genGenericHandle(cleaned_data.get("name"))
name = cleaned_data.get("name")
if name is None:
name = self._user.username
cleaned_data['handle'] = self._meta.model.genGenericHandle(name)
return cleaned_data
class MntFormMixin(object):
protectedFields = []
def __init__(self, lower=False, *args, **kwargs):
super(MntFormMixin, self).__init__(*args, **kwargs)
@ -60,6 +65,7 @@ class MntFormMixin(object):
if "mnt_lower" in self.fields:
self.fields["mnt_lower"].queryset = mntQs
class MntForm(WhoisObjectFormMixin, forms.ModelForm):
class Meta:
model = Maintainer
@ -80,7 +86,8 @@ class ContactForm(WhoisObjectFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
if "mnt_by" in self.fields:
self.fields['mnt_by'].queryset = Maintainer.objects.filter(auth=self._user).distinct()
class ContactInitialForm(ContactForm):
@ -91,7 +98,7 @@ class ContactInitialForm(ContactForm):
class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
prefix = forms.CharField()
protectedFields = ['handle', 'protocol', 'parent_range', 'mnt_by', 'prefix']
protectedFields = ['protocol', 'parent_range', 'mnt_by', 'prefix']
class Meta:
model = InetNum
@ -114,9 +121,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
def clean_prefix(self):
# make sure this is a subnet we're getting
net = self.cleaned_data['prefix']
if not re.match(r"[0-9:.]+/[0-9]+", net):
raise forms.ValidationError("Address needs to be a subnet in the format of ip/cidr")
net = self.cleaned_data['prefix'].lower()
IP46CIDRValidator(net)
try:
net = ipaddress.ip_network(net)
except ValueError as e:
@ -126,9 +133,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
def clean_parent_range(self):
parent_range = self.cleaned_data.get('parent_range', None)
# allow parent range to be unset for already present objects
if not parent_range and (self._create or not self._create and self.instance.parent_range):
if not parent_range and (self._create or not self._create and self.instance.parent_range):
raise forms.ValidationError("Parent range must be set")
if not self._create and parent_range:
@ -171,8 +178,9 @@ class InetNumForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
return cleaned_data
class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
protectedFields = ['handle', 'parent_block', 'asBegin', 'asEnd', 'mnt_by']
protectedFields = ['parent_block', 'asBegin', 'asEnd', 'mnt_by']
# FIXME: Filter blocks
class Meta:
@ -190,6 +198,23 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
mnts = self._user.maintainer_set.all()
self.fields['parent_block'].queryset = ASBlock.getMntQueryset(mnts, self.instance, "parent_block")
def clean_parent_block(self):
parent_block = self.cleaned_data.get('parent_block', None)
# allow parent range to be unset for already present objects
if not parent_block and (self._create or not self._create and self.instance.parent_block):
raise forms.ValidationError("Parent block must be set")
if not self._create and parent_block:
# make sure we don't have circular dependencies
obj = parent_block
while obj.parent_block:
if obj.pk == self.instance.pk:
raise forms.ValidationError("No circular dependencies allowed")
obj = obj.parent_block
return parent_block
def clean(self):
cleaned_data = super(ASBlockForm, self).clean()
@ -198,7 +223,7 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
asEnd = cleaned_data['asEnd']
parent = cleaned_data['parent_block']
# check if somebody is already using this block
# check if in range
if asBegin > asEnd:
raise forms.ValidationError("AS beginning must be smaller or equal to AS end")
@ -207,7 +232,6 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
if parent.asnumber_set.count() > 0:
raise forms.ValidationError("The parent AS block is already references by following AS number objects: %s" % (", ".join(map(lambda _x: _x.handle, parent.asnumber_set.all())),))
# check if same range
if not (asBegin >= parent.asBegin and asEnd <= parent.asEnd):
raise forms.ValidationError("AS beginning and end must be inside the range of the parent AS block")
@ -221,14 +245,14 @@ class ASBlockForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
continue
if block.asBegin <= asBegin <= block.asEnd or block.asBegin <= asEnd <= block.asEnd or \
asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
raise forms.ValidationError("Block overlaps with block %s" % block.handle)
asBegin <= block.asBegin <= asEnd or asBegin <= block.asEnd <= asEnd:
raise forms.ValidationError("Block overlaps with block %s" % block.handle)
return cleaned_data
class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
protectedFields = ['handle', 'asblock', 'number', 'mnt_by']
protectedFields = ['asblock', 'number', 'mnt_by']
class Meta:
model = ASNumber
@ -267,4 +291,3 @@ class ASNumberForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
# has already other asblock?
if block.asblock_set.count() > 0:
raise forms.ValidationError("The given AS block is already references by following sub AS blocks: %s" % (", ".join(map(lambda _x: _x.handle, block.asblock_set.all())),))

View File

@ -3,9 +3,10 @@ from django.http import HttpResponseRedirect
from django.contrib import messages
from django.db.models import Q
class DeleteCheckView(DeleteView):
""" Check if object actually can be deleted. Provide reasons to template
if not.
if not.
"""
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
@ -17,7 +18,7 @@ class DeleteCheckView(DeleteView):
return self.get(request, *args, **kwargs)
else:
self.object.delete()
messages.info(request, "Object %s has been deleted" % self.object.handle)
messages.info(request, "Object %s has been deleted" % str(self.object))
return HttpResponseRedirect(success_url)
def get_context_data(self, **kwargs):

View File

@ -1,6 +1,7 @@
import whoisdb.models
import whoisdb.models
import domains.models
def _addFields(fields, obj, fieldNames):
for fieldName in fieldNames:
fields.append((fieldName.capitalize().replace("_", " "), getattr(obj, fieldName)))
@ -9,7 +10,7 @@ def _addFields(fields, obj, fieldNames):
def getWhoisObjectFields(obj, owner):
fields = []
if hasattr(obj, "handle"):
if getattr(obj, "handle", None):
_addFields(fields, obj, ["handle"])
c = type(obj)
@ -34,12 +35,13 @@ def getWhoisObjectFields(obj, owner):
elif c == domains.models.Nameserver:
_addFields(fields, obj, ["name", "glueIPv4", "glueIPv6", "mnt_by", "admin_c"])
elif c == domains.models.ReverseZone:
_addFields(fields, obj, ["name"])
#_addFields(fields, obj, ["name"])
fields.append(("Address CIDR", obj.prefix()))
_addFields(fields, obj, ["parentNet", "nameservers"])
return fields
def guessWhoisObject(self, handle):
# is it a normal handle?
pass

View File

@ -17,6 +17,20 @@ class WhoisObject(models.Model):
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now_add=True)
#def __init__(self, *args, **kwargs):
# super(WhoisObject, self).__init__(*args, **kwargs)
# if getattr(self, "handle"):
# field = self._meta.get_field("handle")
# if HandleValidatorWithSuffix not in map(type, field.validators):
# print(self.handle, "NOOOOT")
# field.validators.append(HandleValidatorWithSuffix(self.handleSuffix))
# else:
# print(self.handle, list(map(type, field.validators)))
def getPK(self):
return self.handle
def __str__(self):
return self.handle
@ -120,13 +134,14 @@ class MntdObject(WhoisObject):
qs = clazz.objects.filter(mntQ)
if attr and instance and instance.pk:
if type(getattr(instance, attr)) == models.ManyToManyField:
if type(instance._meta.get_field(attr)) == models.ManyToManyField:
qs |= getattr(instance, attr).all()
else:
qs |= clazz.objects.filter(pk=instance.pk)
elif getattr(instance, attr) is not None:
qs |= clazz.objects.filter(pk=getattr(instance, attr).pk)
return qs.distinct()
class Contact(MntdObject):
handleSuffix = "DN"
TYPE_PERSON = 'PERSON'
@ -205,6 +220,7 @@ class ASNumber(MntdObject):
def getResource(self):
return str(self.number)
class InetNum(MntdObject):
handleSuffix = "NET"

View File

@ -10,7 +10,7 @@ import ipaddress
@deconstructible
class HandleValidator(validators.RegexValidator):
regex = r'^(?:[A-Z]+[0-9]+(-[A-Z]+)|AUTO)'
regex = r'^(?:[A-Z]+[0-9]*(-[A-Z]+)|AUTO)'
message = _(
'Enter a valid handle (all uppercase)'
)
@ -22,15 +22,16 @@ class HandleValidatorWithSuffix(validators.RegexValidator):
flags = re.ASCII if six.PY3 else 0
def __init__(self, suffix):
self.regex = r'^(?:[A-Z]+[0-9]+-%s|AUTO)' % re.escape(suffix)
self.regex = r'^(?:[A-Z]+[0-9]*-%s|AUTO)' % re.escape(suffix)
self.message = _(
'Enter a valid handle with suffix %s (all uppercase)' % suffix
'Enter a valid handle with suffix %s (all uppercase), e.g. FOO3-%s' % (suffix, suffix)
)
super(HandleValidatorWithSuffix, self).__init__()
def IP46CIDRValidator(value):
if not re.match(r"[0-9:.]+/[0-9]+", value):
if not re.match(r"[0-9a-fA-F:.]+/[0-9]+", value):
raise ValidationError("Address needs to be a subnet in the format of ip/cidr")
try:

View File

@ -10,6 +10,7 @@ from .models import Maintainer, Contact, InetNum, ASBlock, ASNumber
from .forms import MntForm, MntInitialForm, ContactForm, ContactInitialForm, InetNumForm, ASBlockForm, ASNumberForm
from .generic import DeleteCheckView, MntGenericMixin
@login_required
def createObjectOverview(request):
mnts = request.user.maintainer_set.all()
@ -18,6 +19,7 @@ def createObjectOverview(request):
return render(request, "whoisdb/create_overview.html", {"netblocks": netblocks, "asblocks": asblocks})
@login_required
def dbDashboard(request):
mnts = request.user.maintainer_set.all()
@ -31,7 +33,7 @@ def dbDashboard(request):
mntForm = contactForm = None
if request.method == "POST":
mntForm = MntInitialForm(user=request.user, data=request.POST, prefix="mnt")
contactForm = ContactInitialForm(user=request.user, person=True, data=request.POST, prefix="contact")
contactForm = ContactInitialForm(user=request.user, data=request.POST, prefix="contact")
if mntForm.is_valid() and contactForm.is_valid():
mnt = mntForm.save(commit=False)
mnt.handleAuto(request.user.username)
@ -51,7 +53,7 @@ def dbDashboard(request):
return HttpResponseRedirect(reverse("whoisdb:dashboard"))
else:
mntForm = MntInitialForm(user=request.user, prefix="mnt", initial={'handle': 'AUTO', 'description': 'Primary maintainer of %s' % request.user.username})
contactForm = ContactInitialForm(user=request.user, person=True, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact')
contactForm = ContactInitialForm(user=request.user, initial={'handle': 'AUTO', 'name': request.user.username.capitalize()}, prefix='contact')
return render(request, "whoisdb/overview.html", {"mnts": mnts, "contacts": contacts, "mntForm": mntForm, "contactForm": contactForm, "netblocks": netblocks, "asblocks": asblocks, "asnumbers": asnumbers})
@ -239,6 +241,7 @@ class ASBlockDetail(DetailView):
slug_url_kwarg = "handle"
context_object_name = "asblock"
class ASBlockEdit(MntGenericMixin, LoginRequiredMixin, UpdateView):
template_name = "whoisdb/obj_edit.html"
model = ASBlock
@ -261,6 +264,7 @@ class ASBlockDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard")
# asnumber
class ASNumberCreate(LoginRequiredMixin, CreateView):
template_name = "whoisdb/obj_create.html"
@ -303,4 +307,3 @@ class ASNumberDelete(MntGenericMixin, LoginRequiredMixin, DeleteCheckView):
slug_field = "handle"
slug_url_kwarg = "handle"
success_url = reverse_lazy("whoisdb:dashboard")