diff --git a/dncore/forms.py b/dncore/forms.py new file mode 100644 index 0000000..8624c32 --- /dev/null +++ b/dncore/forms.py @@ -0,0 +1,8 @@ +from django.contrib.auth.forms import UserCreationForm +from .models import User + + +class CustomUserCreationForm(UserCreationForm): + class Meta: + model = User + fields = ("username",) diff --git a/dncore/urls.py b/dncore/urls.py index e4dbd30..d5c296b 100644 --- a/dncore/urls.py +++ b/dncore/urls.py @@ -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'), ] diff --git a/dncore/views.py b/dncore/views.py index 3d11c7d..9b00b2f 100644 --- a/dncore/views.py +++ b/dncore/views.py @@ -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 diff --git a/domains/migrations/0002_auto_20170321_1854.py b/domains/migrations/0002_auto_20170321_1854.py new file mode 100644 index 0000000..00875ba --- /dev/null +++ b/domains/migrations/0002_auto_20170321_1854.py @@ -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, + ), + ] diff --git a/domains/models.py b/domains/models.py index c39ad84..428beb3 100644 --- a/domains/models.py +++ b/domains/models.py @@ -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) diff --git a/domains/views.py b/domains/views.py index 848d99f..a337f56 100644 --- a/domains/views.py +++ b/domains/views.py @@ -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() diff --git a/templates/dncore/registration.html b/templates/dncore/registration.html new file mode 100644 index 0000000..557a2e0 --- /dev/null +++ b/templates/dncore/registration.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+
+
+
Registration
+
+

+ Note: Please choose a sane username. +

+
+ {% csrf_token %} + {{ form|crispy }} + +
+
+
+
+
+{% endblock %} + diff --git a/templates/domains/handle_show.html b/templates/domains/handle_show.html index b6881b3..1d909ba 100644 --- a/templates/domains/handle_show.html +++ b/templates/domains/handle_show.html @@ -18,9 +18,9 @@ {% if object|userCanEdit:user %} Actions - {% with "domains:"|add:object.getClassName|lower|add:"-edit" as editView %} - {% with "domains:"|add:object.getClassName|lower|add:"-delete" as deleteView %} - Edit object, Delete object + {% 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 %} + Edit object, Delete object {% endwith %} {% endwith %} diff --git a/whoisdb/forms.py b/whoisdb/forms.py index 4a0cf8d..66e2451 100644 --- a/whoisdb/forms.py +++ b/whoisdb/forms.py @@ -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())),)) - diff --git a/whoisdb/generic.py b/whoisdb/generic.py index 9cdabeb..b2dce2e 100644 --- a/whoisdb/generic.py +++ b/whoisdb/generic.py @@ -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): diff --git a/whoisdb/helpers.py b/whoisdb/helpers.py index de25cb6..ca256c4 100644 --- a/whoisdb/helpers.py +++ b/whoisdb/helpers.py @@ -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 diff --git a/whoisdb/models.py b/whoisdb/models.py index eb9cac4..d9e70d1 100644 --- a/whoisdb/models.py +++ b/whoisdb/models.py @@ -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" diff --git a/whoisdb/validators.py b/whoisdb/validators.py index 02fc9d4..bf62130 100644 --- a/whoisdb/validators.py +++ b/whoisdb/validators.py @@ -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: diff --git a/whoisdb/views.py b/whoisdb/views.py index e2450b5..764d6e8 100644 --- a/whoisdb/views.py +++ b/whoisdb/views.py @@ -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") -