From 26c5c9c8f20ad6bc07a7e300bb4b0cb82af11d2c Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Wed, 20 Feb 2013 08:30:32 +0000 Subject: Move .rpkic_history from current directory to $HOME. Fixes #422. svn path=/trunk/; revision=5046 --- rpkid/rpki/rpkic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpkid/rpki/rpkic.py b/rpkid/rpki/rpkic.py index b7e340ab..a32010e0 100644 --- a/rpkid/rpki/rpkic.py +++ b/rpkid/rpki/rpkic.py @@ -112,7 +112,7 @@ class main(rpki.cli.Cmd): cfg = rpki.config.parser(self.cfg_file, "myrpki") cfg.set_global_flags() - self.histfile = cfg.get("history_file", ".rpkic_history") + self.histfile = cfg.get("history_file", os.path.expanduser("~/.rpkic_history")) self.autosync = cfg.getboolean("autosync", True, section = "rpkic") from django.conf import settings -- cgit v1.2.3 From 505b5c0c90480309109ca56dc8775003fd9bf06e Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Wed, 20 Feb 2013 21:11:52 +0000 Subject: use resource_range_ip.parse_str() rather than calling through resource_range_ipv*() closes #415 svn path=/trunk/; revision=5048 --- rpkid/rpki/gui/app/forms.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py index 1057ee01..676116cb 100644 --- a/rpkid/rpki/gui/app/forms.py +++ b/rpkid/rpki/gui/app/forms.py @@ -18,8 +18,7 @@ __version__ = '$Id$' from django.contrib.auth.models import User from django import forms -from rpki.resource_set import (resource_range_as, resource_range_ipv4, - resource_range_ipv6) +from rpki.resource_set import (resource_range_as, resource_range_ip) from rpki.gui.app import models from rpki.exceptions import BadIPResource from rpki.gui.app.glue import str_to_resource_range @@ -199,7 +198,7 @@ class ROARequest(forms.Form): mask = p.bits - (8 * (prefixlen / 8)) prefix = prefix + '/' + str(mask) - return str_to_resource_range(prefix) + return resource_range_ip.parse_str(prefix) def clean_asn(self): value = self.cleaned_data.get('asn') @@ -213,7 +212,7 @@ class ROARequest(forms.Form): except: raise forms.ValidationError('invalid IP address') - manager = models.ResourceRangeAddressV4 if isinstance(r, resource_range_ipv4) else models.ResourceRangeAddressV6 + manager = models.ResourceRangeAddressV4 if r.version == 4 else models.ResourceRangeAddressV6 if not manager.objects.filter(cert__conf=self.conf, prefix_min__lte=r.min, prefix_max__gte=r.max).exists(): @@ -333,12 +332,11 @@ class AddNetForm(forms.Form): def clean_address_range(self): address_range = self.cleaned_data.get('address_range') try: - if ':' in address_range: - r = resource_range_ipv6.parse_str(address_range) + r = resource_range_ip.parse_str(address_range) + if r.version == 6: qs = models.ResourceRangeAddressV6 version = 'IPv6' else: - r = resource_range_ipv4.parse_str(address_range) qs = models.ResourceRangeAddressV4 version = 'IPv4' except BadIPResource: -- cgit v1.2.3 From dde4e64efeb7c6cc930e49faa19502d5346bdaf6 Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Wed, 20 Feb 2013 21:40:53 +0000 Subject: add is_prefix attribute to ipv6 objects so that the create roa button appears in the dashboard svn path=/trunk/; revision=5049 --- rpkid/rpki/gui/app/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index 2d674c95..c5e6ece1 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -209,6 +209,12 @@ def dashboard(request): x.is_prefix = False unused_prefixes_v6 = my_prefixes_v6.difference(used_prefixes_v6) + for x in unused_prefixes_v6: + try: + x.prefixlen() + x.is_prefix = True + except rpki.exceptions.MustBePrefix: + x.is_prefix = False clients = models.Client.objects.all() if request.user.is_superuser else None -- cgit v1.2.3 From 919df96a39a0250bfba160c3a36a29452d38fa0c Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Wed, 20 Feb 2013 23:39:41 +0000 Subject: remove glue.str_to_resource_range() since resource_range_ip.parse_str() can now be used for both v4/v6 svn path=/trunk/; revision=5050 --- rpkid/rpki/gui/app/forms.py | 7 +++---- rpkid/rpki/gui/app/glue.py | 8 -------- rpkid/rpki/gui/app/views.py | 18 +++++++----------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py index 676116cb..355f9d2c 100644 --- a/rpkid/rpki/gui/app/forms.py +++ b/rpkid/rpki/gui/app/forms.py @@ -21,7 +21,6 @@ from django import forms from rpki.resource_set import (resource_range_as, resource_range_ip) from rpki.gui.app import models from rpki.exceptions import BadIPResource -from rpki.gui.app.glue import str_to_resource_range from rpki.POW import IPAddress @@ -210,7 +209,7 @@ class ROARequest(forms.Form): try: r = self._as_resource_range() except: - raise forms.ValidationError('invalid IP address') + raise forms.ValidationError('invalid prefix') manager = models.ResourceRangeAddressV4 if r.version == 4 else models.ResourceRangeAddressV6 if not manager.objects.filter(cert__conf=self.conf, @@ -259,14 +258,14 @@ class ROARequestConfirm(forms.Form): def clean_prefix(self): try: - r = str_to_resource_range(self.cleaned_data.get('prefix')) + r = resource_range_ip.parse_str(self.cleaned_data.get('prefix')) except BadIPResource: raise forms.ValidationError('invalid prefix') return str(r) def clean(self): try: - r = str_to_resource_range(self.cleaned_data.get('prefix')) + r = resource_range_ip.parse_str(self.cleaned_data.get('prefix')) if r.prefixlen() > self.cleaned_data.get('max_prefixlen'): raise forms.ValidationError('max length is smaller than mask') except BadIPResource: diff --git a/rpkid/rpki/gui/app/glue.py b/rpkid/rpki/gui/app/glue.py index cad48147..03225de7 100644 --- a/rpkid/rpki/gui/app/glue.py +++ b/rpkid/rpki/gui/app/glue.py @@ -110,14 +110,6 @@ def list_received_resources(log, conf): print >>log, "error: unexpected pdu from rpkid type=%s" % type(pdu) -def str_to_resource_range(prefix): - try: - r = resource_range_ipv4.parse_str(prefix) - except BadIPResource: - r = resource_range_ipv6.parse_str(prefix) - return r - - def get_email_list(conf): """Return a list of the contact emails for this user. diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index c5e6ece1..08e43605 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -35,8 +35,8 @@ from django.views.generic import DetailView from rpki.irdb import Zookeeper, ChildASN, ChildNet from rpki.gui.app import models, forms, glue, range_list -from rpki.resource_set import (resource_range_as, resource_range_ipv4, - resource_range_ipv6, roa_prefix_ipv4) +from rpki.resource_set import (resource_range_as, resource_range_ip, + roa_prefix_ipv4) from rpki import sundial import rpki.exceptions @@ -331,12 +331,8 @@ def child_add_prefix(request, pk): form = forms.AddNetForm(request.POST, child=child) if form.is_valid(): address_range = form.cleaned_data.get('address_range') - if ':' in address_range: - r = resource_range_ipv6.parse_str(address_range) - version = 'IPv6' - else: - r = resource_range_ipv4.parse_str(address_range) - version = 'IPv4' + r = resource_range_ip.parse_str(address_range) + version = 'IPv%d' % r.version child.address_ranges.create(start_ip=str(r.min), end_ip=str(r.max), version=version) Zookeeper(handle=conf.handle, logstream=logstream).run_rpkid_now() @@ -529,12 +525,12 @@ def roa_create_confirm(request): if form.is_valid(): asn = form.cleaned_data.get('asn') prefix = form.cleaned_data.get('prefix') - rng = glue.str_to_resource_range(prefix) + rng = resource_range_ip.parse_str(prefix) max_prefixlen = form.cleaned_data.get('max_prefixlen') # Always create ROA requests with a single prefix. # https://trac.rpki.net/ticket/32 roa = models.ROARequest.objects.create(issuer=conf, asn=asn) - v = 'IPv4' if isinstance(rng, resource_range_ipv4) else 'IPv6' + v = 'IPv%d' % rng.version roa.prefixes.create(version=v, prefix=str(rng.min), prefixlen=rng.prefixlen(), max_prefixlen=max_prefixlen) @@ -659,7 +655,7 @@ def refresh(request): def roa_match(rng): """Return a list of tuples of matching routes and roas.""" - if isinstance(rng, resource_range_ipv6): + if rng.min.version == 6: route_manager = models.RouteOriginV6.objects pfx = 'prefixes_v6' else: -- cgit v1.2.3 From d03ec2d3721d2b9b640fd44a0e7c9775494f5729 Mon Sep 17 00:00:00 2001 From: RPKI Documentation Robot Date: Thu, 21 Feb 2013 14:00:10 +0000 Subject: Automatic pull of documentation from Wiki. svn path=/trunk/; revision=5052 --- doc/doc.RPKI.Installation | 6 +++++- doc/manual.pdf | Bin 487118 -> 487217 bytes 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/doc.RPKI.Installation b/doc/doc.RPKI.Installation index b0adf8c3..9e5299c7 100644 --- a/doc/doc.RPKI.Installation +++ b/doc/doc.RPKI.Installation @@ -92,7 +92,11 @@ Packages you will need: interface to the CA tools requires this. Django 1.3 or later is required. o FreeBSD: /usr/ports/www/py-django (py27-django) - o Ubuntu: python-django + o Ubuntu: Do not use the python-django package (Django 1.3.1) in 12.04 LTS, + as it is known not to work. + Instead, install a recent version using easy_install (or pip, or ...): + + $ sudo easy_install django * http://vobject.skyhouseconsulting.com/, a Python library for parsing VCards. The GUI uses this to parse the payload of RPKI Ghostbuster objects. diff --git a/doc/manual.pdf b/doc/manual.pdf index d0654e37..a36ba9ac 100644 Binary files a/doc/manual.pdf and b/doc/manual.pdf differ -- cgit v1.2.3 From 2ccd83627db9c376ad285c0bcea697cbb21b0e09 Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Thu, 21 Feb 2013 18:37:01 +0000 Subject: add support for breaking the route view into multiple pages svn path=/trunk/; revision=5053 --- rpkid/rpki/gui/app/templates/app/routes_view.html | 4 +++ rpkid/rpki/gui/app/templatetags/bootstrap_pager.py | 39 ++++++++++++++++++++++ rpkid/rpki/gui/app/views.py | 8 ++++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 rpkid/rpki/gui/app/templatetags/bootstrap_pager.py diff --git a/rpkid/rpki/gui/app/templates/app/routes_view.html b/rpkid/rpki/gui/app/templates/app/routes_view.html index a1406398..f9b0ffad 100644 --- a/rpkid/rpki/gui/app/templates/app/routes_view.html +++ b/rpkid/rpki/gui/app/templates/app/routes_view.html @@ -1,5 +1,7 @@ {% extends "app/app_base.html" %} +{% load bootstrap_pager %} + {% block sidebar_extra %}

BGP data updated
@@ -38,4 +40,6 @@ This view shows currently advertised routes for the prefixes listed in resource {% endfor %} +{% bootstrap_pager request routes %} + {% endblock %} diff --git a/rpkid/rpki/gui/app/templatetags/bootstrap_pager.py b/rpkid/rpki/gui/app/templatetags/bootstrap_pager.py new file mode 100644 index 00000000..be7d7ca3 --- /dev/null +++ b/rpkid/rpki/gui/app/templatetags/bootstrap_pager.py @@ -0,0 +1,39 @@ +from django import template + +register = template.Library() + + +class BootstrapPagerNode(template.Node): + def __init__(self, request, pager_object): + self.request = template.Variable(request) + self.pager_object = template.Variable(pager_object) + + def render(self, context): + request = self.request.resolve(context) + pager_object = self.pager_object.resolve(context) + r = ['

') + return '\n'.join(r) + + +@register.tag +def bootstrap_pager(parser, token): + try: + tag_name, request, pager_object = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError("%r tag requires two arguments" % token.contents.split()[0]) + return BootstrapPagerNode(request, pager_object) diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index 08e43605..de4ea488 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -32,6 +32,7 @@ from django import http from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.views.generic import DetailView +from django.core.paginator import Paginator from rpki.irdb import Zookeeper, ChildASN, ChildNet from rpki.gui.app import models, forms, glue, range_list @@ -716,6 +717,8 @@ def route_view(request): """ conf = request.session['handle'] log = request.META['wsgi.errors'] + count = request.GET.get('count', 25) + page = request.GET.get('page', 1) routes = [] for p in models.ResourceRangeAddressV4.objects.filter(cert__conf=conf): @@ -727,9 +730,12 @@ def route_view(request): print >>log, 'querying for routes matching %s' % r routes.extend([validate_route(*x) for x in roa_match(r)]) + paginator = Paginator(routes, count) + content = paginator.page(page) + ts = dict((attr['name'], attr['ts']) for attr in models.Timestamp.objects.values()) return render(request, 'app/routes_view.html', - {'routes': routes, 'timestamp': ts}) + {'routes': content, 'timestamp': ts}) def route_detail(request, pk): -- cgit v1.2.3 From b033927cf90652a52ce2d71d95a4572527602d8f Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Sat, 23 Feb 2013 00:19:54 +0000 Subject: add new roa creation form allowing multiple roas to be entered add links for creating roas for IP ranges by automatically splitting the range into prefixes closes #399 closes #420 svn path=/trunk/; revision=5055 --- rpkid/rpki/gui/app/forms.py | 24 ++- rpkid/rpki/gui/app/templates/app/dashboard.html | 12 +- .../app/roarequest_confirm_multi_form.html | 64 +++++++ .../app/templates/app/roarequest_multi_form.html | 27 +++ rpkid/rpki/gui/app/urls.py | 2 + rpkid/rpki/gui/app/views.py | 205 ++++++++++++++++++--- 6 files changed, 293 insertions(+), 41 deletions(-) create mode 100644 rpkid/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html create mode 100644 rpkid/rpki/gui/app/templates/app/roarequest_multi_form.html diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py index 355f9d2c..1d354521 100644 --- a/rpkid/rpki/gui/app/forms.py +++ b/rpkid/rpki/gui/app/forms.py @@ -159,11 +159,24 @@ class ROARequest(forms.Form): Handles both IPv4 and IPv6.""" prefix = forms.CharField( - widget=forms.TextInput(attrs={'autofocus': 'true', 'size': '50'}) + widget=forms.TextInput(attrs={ + 'autofocus': 'true', 'placeholder': 'Prefix', + 'class': 'span4' + }) + ) + max_prefixlen = forms.CharField( + required=False, + widget=forms.TextInput(attrs={ + 'placeholder': 'Max len', + 'class': 'span1' + }) + ) + asn = forms.IntegerField( + widget=forms.TextInput(attrs={ + 'placeholder': 'ASN', + 'class': 'span1' + }) ) - max_prefixlen = forms.CharField(required=False, - label='Max Prefix Length') - asn = forms.IntegerField(label='AS') confirmed = forms.BooleanField(widget=forms.HiddenInput, required=False) def __init__(self, *args, **kwargs): @@ -173,8 +186,11 @@ class ROARequest(forms.Form): """ conf = kwargs.pop('conf', None) + kwargs['auto_id'] = False super(ROARequest, self).__init__(*args, **kwargs) self.conf = conf + self.inline = True + self.use_table = False def _as_resource_range(self): """Convert the prefix in the form to a diff --git a/rpkid/rpki/gui/app/templates/app/dashboard.html b/rpkid/rpki/gui/app/templates/app/dashboard.html index 0af4bae6..3349c3cd 100644 --- a/rpkid/rpki/gui/app/templates/app/dashboard.html +++ b/rpkid/rpki/gui/app/templates/app/dashboard.html @@ -81,10 +81,7 @@ {{ addr }} - {# if addr can be represented as a prefix, add a button for issuing a roa #} - {% if addr.is_prefix %} - ROA - {% endif %} + ROA {% endfor %} @@ -99,10 +96,7 @@ {{ addr }} - {# if addr can be represented as a prefix, add a button for issuing a roa #} - {% if addr.is_prefix %} - roa - {% endif %} + ROA {% endfor %} @@ -132,7 +126,7 @@ {% endfor %} - Create + Create
diff --git a/rpkid/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html b/rpkid/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html new file mode 100644 index 00000000..cd0ed3c2 --- /dev/null +++ b/rpkid/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html @@ -0,0 +1,64 @@ +{% extends "app/app_base.html" %} + +{% block content %} +
+

Confirm ROA Requests

+
+ +
+
+
+

Please confirm that you would like to create the following ROA(s). + The accompanying table indicates how the validation status may change as a result. +

+ + + + + + + + {% for roa in roas %} + + + + + + {% endfor %} +
PrefixMax LengthAS
{{ roa.prefix }}{{ roa.max_prefixlen }}{{ roa.asn }}
+ +
+ {% csrf_token %} + {{ formset.management_form }} + {% for form in formset %} + {% include "app/bootstrap_form.html" %} + {% endfor %} + +
+ + Cancel +
+
+
+ +
+

Matched Routes

+ + + + + + + + {% for r in routes %} + + + + + + {% endfor %} +
PrefixOrigin ASValidation Status
{{ r.get_prefix_display }}{{ r.asn }}{{ r.status }}
+
+ +
+{% endblock content %} diff --git a/rpkid/rpki/gui/app/templates/app/roarequest_multi_form.html b/rpkid/rpki/gui/app/templates/app/roarequest_multi_form.html new file mode 100644 index 00000000..91151036 --- /dev/null +++ b/rpkid/rpki/gui/app/templates/app/roarequest_multi_form.html @@ -0,0 +1,27 @@ +{% extends "app/app_base.html" %} + +{% block content %} +
+

Create ROA Requests

+
+ +
+ {% csrf_token %} + {{ formset.management_form }} + {% for form in formset %} +
+ {{ form.prefix }} + {{ form.max_prefixlen }} + {{ form.asn }} + + {% if form.errors %}{{ form.errors }}{% endif %} + {% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %} +
+ {% endfor %} + +
+ + Cancel +
+
+{% endblock %} diff --git a/rpkid/rpki/gui/app/urls.py b/rpkid/rpki/gui/app/urls.py index c5b85c39..8f11c5be 100644 --- a/rpkid/rpki/gui/app/urls.py +++ b/rpkid/rpki/gui/app/urls.py @@ -49,7 +49,9 @@ urlpatterns = patterns( (r'^repo/(?P\d+)/delete$', views.repository_delete), (r'^roa/(?P\d+)/$', views.roa_detail), (r'^roa/create$', views.roa_create), + (r'^roa/create_multi$', views.roa_create_multi), (r'^roa/confirm$', views.roa_create_confirm), + (r'^roa/confirm_multi$', views.roa_create_multi_confirm), (r'^roa/(?P\d+)/delete$', views.roa_delete), (r'^route/$', views.route_view), (r'^route/(?P\d+)/$', views.route_detail), diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index de4ea488..3e1cdbe2 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -26,13 +26,14 @@ import os.path from tempfile import NamedTemporaryFile from django.contrib.auth.decorators import login_required -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, render, redirect from django.utils.http import urlquote from django import http from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.views.generic import DetailView from django.core.paginator import Paginator +from django.forms.formsets import formset_factory, BaseFormSet from rpki.irdb import Zookeeper, ChildASN, ChildNet from rpki.gui.app import models, forms, glue, range_list @@ -447,6 +448,39 @@ def roa_detail(request, pk): }) +def get_covered_routes(rng, max_prefixlen, asn): + """find list of matching routes""" + + routes = [] + match = roa_match(rng) + for route, roas in match: + validate_route(route, roas) + # tweak the validation status due to the presence of the + # new ROA. Don't need to check the prefix bounds here + # because all the matches routes will be covered by this + # new ROA + if route.status == 'unknown': + # if the route was previously unknown (no covering + # ROAs), then: + # if the AS matches, it is valid, otherwise invalid + if (route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen): + route.status = 'valid' + route.status_label = 'label-success' + else: + route.status = 'invalid' + route.status_label = 'label-important' + elif route.status == 'invalid': + # if the route was previously invalid, but this new ROA + # matches the ASN, it is now valid + if route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen: + route.status = 'valid' + route.status_label = 'label-success' + + routes.append(route) + + return routes + + @handle_required def roa_create(request): """Present the user with a form to create a ROA. @@ -464,33 +498,34 @@ def roa_create(request): rng = form._as_resource_range() # FIXME calling "private" method max_prefixlen = int(form.cleaned_data.get('max_prefixlen')) - # find list of matching routes - routes = [] - match = roa_match(rng) - for route, roas in match: - validate_route(route, roas) - # tweak the validation status due to the presence of the - # new ROA. Don't need to check the prefix bounds here - # because all the matches routes will be covered by this - # new ROA - if route.status == 'unknown': - # if the route was previously unknown (no covering - # ROAs), then: - # if the AS matches, it is valid, otherwise invalid - if (route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen): - route.status = 'valid' - route.status_label = 'label-success' - else: - route.status = 'invalid' - route.status_label = 'label-important' - elif route.status == 'invalid': - # if the route was previously invalid, but this new ROA - # matches the ASN, it is now valid - if route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen: - route.status = 'valid' - route.status_label = 'label-success' - - routes.append(route) +# # find list of matching routes +# routes = [] +# match = roa_match(rng) +# for route, roas in match: +# validate_route(route, roas) +# # tweak the validation status due to the presence of the +# # new ROA. Don't need to check the prefix bounds here +# # because all the matches routes will be covered by this +# # new ROA +# if route.status == 'unknown': +# # if the route was previously unknown (no covering +# # ROAs), then: +# # if the AS matches, it is valid, otherwise invalid +# if (route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen): +# route.status = 'valid' +# route.status_label = 'label-success' +# else: +# route.status = 'invalid' +# route.status_label = 'label-important' +# elif route.status == 'invalid': +# # if the route was previously invalid, but this new ROA +# # matches the ASN, it is now valid +# if route.asn != 0 and route.asn == asn and route.prefixlen() <= max_prefixlen: +# route.status = 'valid' +# route.status_label = 'label-success' +# +# routes.append(route) + routes = get_covered_routes(rng, max_prefixlen, asn) prefix = str(rng) form = forms.ROARequestConfirm(initial={'asn': asn, @@ -513,6 +548,90 @@ def roa_create(request): return render(request, 'app/roarequest_form.html', {'form': form}) +class ROARequestFormSet(BaseFormSet): + """There is no way to pass arbitrary keyword arguments to the form + constructor, so we have to override BaseFormSet to allow it. + + """ + def __init__(self, *args, **kwargs): + self.conf = kwargs.pop('conf') + super(ROARequestFormSet, self).__init__(*args, **kwargs) + + def _construct_forms(self): + self.forms = [] + for i in xrange(self.total_form_count()): + self.forms.append(self._construct_form(i, conf=self.conf)) + + +def split_with_default(s): + xs = s.split(',') + if len(xs) == 1: + return xs[0], None + return xs + + +@handle_required +def roa_create_multi(request): + """version of roa_create that uses a formset to allow entry of multiple + roas on a single page. + + ROAs can be specified in the GET query string, as such: + + ?roa=prefix,asn + + Mulitple ROAs may be specified: + + ?roa=prefix,asn+roa=prefix2,asn2 + + If an IP range is specified, it will be automatically split into multiple + prefixes: + + ?roa=1.1.1.1-2.2.2.2,42 + + The ASN may optionally be omitted. + + """ + + conf = request.session['handle'] + if request.method == 'GET': + init = [] + for x in request.GET.getlist('roa'): + rng, asn = split_with_default(x) + rng = resource_range_ip.parse_str(rng) + if rng.can_be_prefix: + init.append({'asn': asn, 'prefix': str(rng)}) + else: + v = [] + rng.chop_into_prefixes(v) + init.extend([{'asn': asn, 'prefix': str(p)} for p in v]) + formset = formset_factory(forms.ROARequest, formset=ROARequestFormSet, + can_delete=True)(initial=init, conf=conf) + elif request.method == 'POST': + formset = formset_factory(forms.ROARequest, formset=ROARequestFormSet, + extra=0, can_delete=True)(request.POST, request.FILES, conf=conf) + if formset.is_valid(): + routes = [] + v = [] + # as of Django 1.4.5 we still can't use formset.cleaned_data + # because deleted forms are not excluded, which causes an + # AttributeError to be raised. + for form in formset: + if hasattr(form, 'cleaned_data') and form.cleaned_data: # exclude empty forms + asn = form.cleaned_data.get('asn') + rng = resource_range_ip.parse_str(form.cleaned_data.get('prefix')) + max_prefixlen = int(form.cleaned_data.get('max_prefixlen')) + routes.extend(get_covered_routes(rng, max_prefixlen, asn)) + v.append({'prefix': str(rng), 'max_prefixlen': max_prefixlen, + 'asn': asn}) + # if there were no rows, skip the confirmation step + if v: + formset = formset_factory(forms.ROARequestConfirm, extra=0)(initial=v) + return render(request, 'app/roarequest_confirm_multi_form.html', + {'routes': routes, 'formset': formset, 'roas': v}) + return render(request, 'app/roarequest_multi_form.html', + {'formset': formset}) + + @handle_required def roa_create_confirm(request): """This function is called when the user confirms the creation of a ROA @@ -542,6 +661,36 @@ def roa_create_confirm(request): return http.HttpResponseRedirect(reverse(roa_create)) +@handle_required +def roa_create_multi_confirm(request): + """This function is called when the user confirms the creation of a ROA + request. It is responsible for updating the IRDB. + + """ + conf = request.session['handle'] + log = request.META['wsgi.errors'] + if request.method == 'POST': + formset = formset_factory(forms.ROARequestConfirm, extra=0)(request.POST, request.FILES) + if formset.is_valid(): + for cleaned_data in formset.cleaned_data: + asn = cleaned_data.get('asn') + prefix = cleaned_data.get('prefix') + rng = resource_range_ip.parse_str(prefix) + max_prefixlen = cleaned_data.get('max_prefixlen') + # Always create ROA requests with a single prefix. + # https://trac.rpki.net/ticket/32 + roa = models.ROARequest.objects.create(issuer=conf, asn=asn) + v = 'IPv%d' % rng.version + roa.prefixes.create(version=v, prefix=str(rng.min), + prefixlen=rng.prefixlen(), + max_prefixlen=max_prefixlen) + Zookeeper(handle=conf.handle, logstream=log).run_rpkid_now() + return redirect(dashboard) + # What should happen when the submission form isn't valid? For now + # just fall through and redirect back to the ROA creation form + return http.HttpResponseRedirect(reverse(roa_create_multi)) + + @handle_required def roa_delete(request, pk): """Handles deletion of a single ROARequest object. -- cgit v1.2.3