aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/gui/app/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid/rpki/gui/app/views.py')
-rw-r--r--rpkid/rpki/gui/app/views.py213
1 files changed, 184 insertions, 29 deletions
diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py
index 08e43605..3e1cdbe2 100644
--- a/rpkid/rpki/gui/app/views.py
+++ b/rpkid/rpki/gui/app/views.py
@@ -26,12 +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
@@ -446,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.
@@ -463,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,
@@ -512,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 +662,36 @@ def roa_create_confirm(request):
@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.
@@ -716,6 +866,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 +879,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):