aboutsummaryrefslogtreecommitdiff
path: root/rpkid/rpki/gui/app/views.py
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2014-04-05 22:42:12 +0000
committerRob Austein <sra@hactrn.net>2014-04-05 22:42:12 +0000
commitfe0bf509f528dbdc50c7182f81057c6a4e15e4bd (patch)
tree07c9a923d4a0ccdfea11c49cd284f6d5757c5eda /rpkid/rpki/gui/app/views.py
parentaa28ef54c271fbe4d52860ff8cf13cab19e2207c (diff)
Source tree reorg, phase 1. Almost everything moved, no file contents changed.
svn path=/branches/tk685/; revision=5757
Diffstat (limited to 'rpkid/rpki/gui/app/views.py')
-rw-r--r--rpkid/rpki/gui/app/views.py1314
1 files changed, 0 insertions, 1314 deletions
diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py
deleted file mode 100644
index db4cf0c1..00000000
--- a/rpkid/rpki/gui/app/views.py
+++ /dev/null
@@ -1,1314 +0,0 @@
-# Copyright (C) 2010, 2011 SPARTA, Inc. dba Cobham Analytic Solutions
-# Copyright (C) 2012 SPARTA, Inc. a Parsons Company
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-"""
-This module contains the view functions implementing the web portal
-interface.
-
-"""
-
-__version__ = '$Id$'
-
-import os
-import os.path
-from tempfile import NamedTemporaryFile
-import cStringIO
-import csv
-import logging
-
-from django.utils.decorators import method_decorator
-from django.contrib.auth.decorators import login_required
-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, reverse_lazy
-from django.contrib.auth.models import User
-from django.views.generic import DetailView, ListView, DeleteView
-from django.core.paginator import Paginator, InvalidPage
-from django.forms.formsets import formset_factory, BaseFormSet
-import django.db.models
-from django.contrib import messages
-
-from rpki.irdb import Zookeeper, ChildASN, ChildNet, ROARequestPrefix
-from rpki.gui.app import models, forms, glue, range_list
-from rpki.resource_set import (resource_range_as, resource_range_ip,
- roa_prefix_ipv4)
-from rpki import sundial
-import rpki.exceptions
-
-from rpki.gui.cacheview.models import ROA
-from rpki.gui.routeview.models import RouteOrigin
-from rpki.gui.decorators import tls_required
-
-logger = logging.getLogger(__name__)
-
-
-def superuser_required(f):
- """Decorator which returns HttpResponseForbidden if the user does
- not have superuser permissions.
-
- """
- @login_required
- def _wrapped(request, *args, **kwargs):
- if not request.user.is_superuser:
- return http.HttpResponseForbidden()
- return f(request, *args, **kwargs)
- return _wrapped
-
-
-def handle_required(f):
- """Decorator for view functions which require the user to be logged in and
- a resource handle selected for the session.
-
- """
- @login_required
- @tls_required
- def wrapped_fn(request, *args, **kwargs):
- if 'handle' not in request.session:
- if request.user.is_superuser:
- conf = models.Conf.objects.all()
- else:
- conf = models.Conf.objects.filter(confacl__user=request.user)
-
- if conf.count() == 1:
- request.session['handle'] = conf[0]
- elif conf.count() == 0:
- return render(request, 'app/conf_empty.html', {})
- else:
- url = '%s?next=%s' % (reverse(conf_list),
- urlquote(request.get_full_path()))
- return http.HttpResponseRedirect(url)
-
- return f(request, *args, **kwargs)
- return wrapped_fn
-
-
-@handle_required
-def generic_import(request, queryset, configure, form_class=None,
- post_import_redirect=None):
- """
- Generic view function for importing XML files used in the setup
- process.
-
- queryset
- queryset containing all objects of the type being imported
-
- configure
- method on Zookeeper to invoke with the imported XML file
-
- form_class
- specifies the form to use for import. If None, uses the generic
- forms.ImportForm.
-
- post_import_redirect
- if None (default), the user will be redirected to the detail page for
- the imported object. Otherwise, the user will be redirected to the
- specified URL.
-
- """
- conf = request.session['handle']
- if form_class is None:
- form_class = forms.ImportForm
- if request.method == 'POST':
- form = form_class(request.POST, request.FILES)
- if form.is_valid():
- tmpf = NamedTemporaryFile(prefix='import', suffix='.xml',
- delete=False)
- tmpf.write(form.cleaned_data['xml'].read())
- tmpf.close()
- z = Zookeeper(handle=conf.handle)
- handle = form.cleaned_data.get('handle')
- # CharField uses an empty string for the empty value, rather than
- # None. Convert to none in this case, since configure_child/parent
- # expects it.
- if handle == '':
- handle = None
- # configure_repository returns None, so can't use tuple expansion
- # here. Unpack the tuple below if post_import_redirect is None.
- r = configure(z, tmpf.name, handle)
- # force rpkid run now
- z.synchronize_ca(poke=True)
- os.remove(tmpf.name)
- if post_import_redirect:
- url = post_import_redirect
- else:
- _, handle = r
- url = queryset.get(issuer=conf,
- handle=handle).get_absolute_url()
- return http.HttpResponseRedirect(url)
- else:
- form = form_class()
-
- return render(request, 'app/app_form.html', {
- 'form': form,
- 'form_title': 'Import ' + queryset.model._meta.verbose_name.capitalize(),
- })
-
-
-@handle_required
-def dashboard(request):
- conf = request.session['handle']
-
- used_asns = range_list.RangeList()
-
- # asns used in my roas
- qs = models.ROARequest.objects.filter(issuer=conf)
- roa_asns = set((obj.asn for obj in qs))
- used_asns.extend((resource_range_as(asn, asn) for asn in roa_asns))
-
- # asns given to my children
- child_asns = ChildASN.objects.filter(child__in=conf.children.all())
- used_asns.extend((resource_range_as(obj.start_as, obj.end_as) for obj in child_asns))
-
- # my received asns
- asns = models.ResourceRangeAS.objects.filter(cert__conf=conf)
- my_asns = range_list.RangeList([resource_range_as(obj.min, obj.max) for obj in asns])
-
- unused_asns = my_asns.difference(used_asns)
-
- used_prefixes = range_list.RangeList()
- used_prefixes_v6 = range_list.RangeList()
-
- # prefixes used in my roas
- for obj in models.ROARequestPrefix.objects.filter(roa_request__issuer=conf,
- version='IPv4'):
- used_prefixes.append(obj.as_resource_range())
-
- for obj in models.ROARequestPrefix.objects.filter(roa_request__issuer=conf,
- version='IPv6'):
- used_prefixes_v6.append(obj.as_resource_range())
-
- # prefixes given to my children
- for obj in ChildNet.objects.filter(child__in=conf.children.all(),
- version='IPv4'):
- used_prefixes.append(obj.as_resource_range())
-
- for obj in ChildNet.objects.filter(child__in=conf.children.all(),
- version='IPv6'):
- used_prefixes_v6.append(obj.as_resource_range())
-
- # my received prefixes
- prefixes = models.ResourceRangeAddressV4.objects.filter(cert__conf=conf).all()
- prefixes_v6 = models.ResourceRangeAddressV6.objects.filter(cert__conf=conf).all()
- my_prefixes = range_list.RangeList([obj.as_resource_range() for obj in prefixes])
- my_prefixes_v6 = range_list.RangeList([obj.as_resource_range() for obj in prefixes_v6])
-
- unused_prefixes = my_prefixes.difference(used_prefixes)
- # monkey-patch each object with a boolean value indicating whether or not
- # it is a prefix. We have to do this here because in the template there is
- # no way to catch the MustBePrefix exception.
- for x in unused_prefixes:
- try:
- x.prefixlen()
- x.is_prefix = True
- except rpki.exceptions.MustBePrefix:
- 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
-
- return render(request, 'app/dashboard.html', {
- 'conf': conf,
- 'unused_asns': unused_asns,
- 'unused_prefixes': unused_prefixes,
- 'unused_prefixes_v6': unused_prefixes_v6,
- 'asns': asns,
- 'prefixes': prefixes,
- 'prefixes_v6': prefixes_v6,
- 'clients': clients,
- })
-
-
-@login_required
-def conf_list(request, **kwargs):
- """Allow the user to select a handle."""
- log = request.META['wsgi.errors']
- next_url = request.GET.get('next', reverse(dashboard))
- if request.user.is_superuser:
- qs = models.Conf.objects.all()
- else:
- qs = models.Conf.objects.filter(confacl__user=request.user)
- return render(request, 'app/conf_list.html', {
- 'conf_list': qs,
- 'next_url': next_url
- })
-
-
-@login_required
-def conf_select(request):
- """Change the handle for the current session."""
- if not 'handle' in request.GET:
- return redirect(conf_list)
- handle = request.GET['handle']
- next_url = request.GET.get('next', reverse(dashboard))
- if request.user.is_superuser:
- request.session['handle'] = get_object_or_404(models.Conf, handle=handle)
- else:
- request.session['handle'] = get_object_or_404(
- models.Conf, confacl__user=request.user, handle=handle
- )
- return http.HttpResponseRedirect(next_url)
-
-
-def serve_xml(content, basename, ext='xml'):
- """
- Generate a HttpResponse object with the content type set to XML.
-
- `content` is a string.
-
- `basename` is the prefix to specify for the XML filename.
-
- `csv` is the type (default: xml)
-
- """
- resp = http.HttpResponse(content, mimetype='application/%s' % ext)
- resp['Content-Disposition'] = 'attachment; filename=%s.%s' % (basename, ext)
- return resp
-
-
-@handle_required
-def conf_export(request):
- """Return the identity.xml for the current handle."""
- conf = request.session['handle']
- z = Zookeeper(handle=conf.handle)
- xml = z.generate_identity()
- return serve_xml(str(xml), '%s.identity' % conf.handle)
-
-
-@handle_required
-def export_asns(request):
- """Export CSV file containing ASN allocations to children."""
- conf = request.session['handle']
- s = cStringIO.StringIO()
- csv_writer = csv.writer(s, delimiter=' ')
- for childasn in ChildASN.objects.filter(child__issuer=conf):
- csv_writer.writerow([childasn.child.handle, str(childasn.as_resource_range())])
- return serve_xml(s.getvalue(), '%s.asns' % conf.handle, ext='csv')
-
-
-@handle_required
-def import_asns(request):
- conf = request.session['handle']
- if request.method == 'POST':
- form = forms.ImportCSVForm(request.POST, request.FILES)
- if form.is_valid():
- f = NamedTemporaryFile(prefix='asns', suffix='.csv', delete=False)
- f.write(request.FILES['csv'].read())
- f.close()
- z = Zookeeper(handle=conf.handle)
- z.load_asns(f.name)
- z.run_rpkid_now()
- os.unlink(f.name)
- messages.success(request, 'Successfully imported AS delgations from CSV file.')
- return redirect(dashboard)
- else:
- form = forms.ImportCSVForm()
- return render(request, 'app/import_resource_form.html', {
- 'form_title': 'Import CSV containing ASN delegations',
- 'form': form,
- 'cancel_url': reverse(dashboard)
- })
-
-
-@handle_required
-def export_prefixes(request):
- """Export CSV file containing ASN allocations to children."""
- conf = request.session['handle']
- s = cStringIO.StringIO()
- csv_writer = csv.writer(s, delimiter=' ')
- for childnet in ChildNet.objects.filter(child__issuer=conf):
- csv_writer.writerow([childnet.child.handle, str(childnet.as_resource_range())])
- return serve_xml(s.getvalue(), '%s.prefixes' % conf.handle, ext='csv')
-
-
-@handle_required
-def import_prefixes(request):
- conf = request.session['handle']
- if request.method == 'POST':
- form = forms.ImportCSVForm(request.POST, request.FILES)
- if form.is_valid():
- f = NamedTemporaryFile(prefix='prefixes', suffix='.csv', delete=False)
- f.write(request.FILES['csv'].read())
- f.close()
- z = Zookeeper(handle=conf.handle)
- z.load_prefixes(f.name)
- z.run_rpkid_now()
- os.unlink(f.name)
- messages.success(request, 'Successfully imported prefix delegations from CSV file.')
- return redirect(dashboard)
- else:
- form = forms.ImportCSVForm()
- return render(request, 'app/import_resource_form.html', {
- 'form_title': 'Import CSV containing Prefix delegations',
- 'form': form,
- 'cancel_url': reverse(dashboard)
- })
-
-
-@handle_required
-def parent_import(request):
- conf = request.session['handle']
- return generic_import(request, conf.parents, Zookeeper.configure_parent)
-
-
-@handle_required
-def parent_detail(request, pk):
- return render(request, 'app/parent_detail.html', {
- 'object': get_object_or_404(request.session['handle'].parents, pk=pk)})
-
-
-@handle_required
-def parent_delete(request, pk):
- conf = request.session['handle']
- obj = get_object_or_404(conf.parents, pk=pk) # confirm permission
- log = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- z = Zookeeper(handle=conf.handle, logstream=log)
- z.delete_parent(obj.handle)
- z.synchronize_ca()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.Empty()
- return render(request, 'app/object_confirm_delete.html', {
- 'object': obj,
- 'form': form,
- 'parent_template': 'app/parent_detail.html'
- })
-
-
-@handle_required
-def parent_export(request, pk):
- """Export XML repository request for a given parent."""
- conf = request.session['handle']
- parent = get_object_or_404(conf.parents, pk=pk)
- z = Zookeeper(handle=conf.handle)
- xml = z.generate_repository_request(parent)
- return serve_xml(str(xml), '%s.repository' % parent.handle)
-
-
-@handle_required
-def child_import(request):
- conf = request.session['handle']
- return generic_import(request, conf.children, Zookeeper.configure_child)
-
-
-@handle_required
-def child_add_prefix(request, pk):
- logstream = request.META['wsgi.errors']
- conf = request.session['handle']
- child = get_object_or_404(conf.children, pk=pk)
- if request.method == 'POST':
- form = forms.AddNetForm(request.POST, child=child)
- if form.is_valid():
- address_range = form.cleaned_data.get('address_range')
- 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()
- return http.HttpResponseRedirect(child.get_absolute_url())
- else:
- form = forms.AddNetForm(child=child)
- return render(request, 'app/app_form.html',
- {'object': child, 'form': form, 'form_title': 'Add Prefix'})
-
-
-@handle_required
-def child_add_asn(request, pk):
- logstream = request.META['wsgi.errors']
- conf = request.session['handle']
- child = get_object_or_404(conf.children, pk=pk)
- if request.method == 'POST':
- form = forms.AddASNForm(request.POST, child=child)
- if form.is_valid():
- asns = form.cleaned_data.get('asns')
- r = resource_range_as.parse_str(asns)
- child.asns.create(start_as=r.min, end_as=r.max)
- Zookeeper(handle=conf.handle, logstream=logstream).run_rpkid_now()
- return http.HttpResponseRedirect(child.get_absolute_url())
- else:
- form = forms.AddASNForm(child=child)
- return render(request, 'app/app_form.html',
- {'object': child, 'form': form, 'form_title': 'Add ASN'})
-
-
-@handle_required
-def child_detail(request, pk):
- child = get_object_or_404(request.session['handle'].children, pk=pk)
- return render(request, 'app/child_detail.html', {'object': child})
-
-
-@handle_required
-def child_edit(request, pk):
- """Edit the end validity date for a resource handle's child."""
- log = request.META['wsgi.errors']
- conf = request.session['handle']
- child = get_object_or_404(conf.children.all(), pk=pk)
- form_class = forms.ChildForm(child)
- if request.method == 'POST':
- form = form_class(request.POST, request.FILES)
- if form.is_valid():
- child.valid_until = sundial.datetime.from_datetime(form.cleaned_data.get('valid_until'))
- child.save()
- # remove AS & prefixes that are not selected in the form
- models.ChildASN.objects.filter(child=child).exclude(pk__in=form.cleaned_data.get('as_ranges')).delete()
- models.ChildNet.objects.filter(child=child).exclude(pk__in=form.cleaned_data.get('address_ranges')).delete()
- Zookeeper(handle=conf.handle, logstream=log).run_rpkid_now()
- return http.HttpResponseRedirect(child.get_absolute_url())
- else:
- form = form_class(initial={
- 'as_ranges': child.asns.all(),
- 'address_ranges': child.address_ranges.all()})
-
- return render(request, 'app/app_form.html', {
- 'object': child,
- 'form': form,
- 'form_title': 'Edit Child: ' + child.handle,
- })
-
-
-@handle_required
-def child_response(request, pk):
- """
- Export the XML file containing the output of the configure_child
- to send back to the client.
-
- """
- conf = request.session['handle']
- child = get_object_or_404(models.Child, issuer=conf, pk=pk)
- z = Zookeeper(handle=conf.handle)
- xml = z.generate_parental_response(child)
- resp = serve_xml(str(xml), child.handle)
- return resp
-
-
-@handle_required
-def child_delete(request, pk):
- logstream = request.META['wsgi.errors']
- conf = request.session['handle']
- child = get_object_or_404(conf.children, pk=pk)
- if request.method == 'POST':
- form = forms.Empty(request.POST)
- if form.is_valid():
- z = Zookeeper(handle=conf.handle, logstream=logstream)
- z.delete_child(child.handle)
- z.synchronize_ca()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.Empty()
- return render(request, 'app/object_confirm_delete.html', {
- 'object': child,
- 'form': form,
- 'parent_template': 'app/child_detail.html'
- })
-
-
-@handle_required
-def roa_detail(request, pk):
- conf = request.session['handle']
- obj = get_object_or_404(conf.roas, pk=pk)
- return render(request, 'app/roa_detail.html', {'object': obj})
-
-
-def get_covered_routes(rng, max_prefixlen, asn):
- """Returns a list of routeview.models.RouteOrigin objects which would
- change validation status if a ROA were created with the parameters to this
- function.
-
- A "newstatus" attribute is monkey-patched on the RouteOrigin objects which
- can be used in the template. "status" remains the current validation
- status of the object.
-
- """
-
- # find all routes that match or are completed covered by the proposed new roa
- qs = RouteOrigin.objects.filter(
- prefix_min__gte=rng.min,
- prefix_max__lte=rng.max
- )
- routes = []
- for route in qs:
- status = route.status
- # 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 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.newstatus = 'valid'
- else:
- route.newstatus = 'invalid'
- routes.append(route)
- elif 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.newstatus = 'valid'
- routes.append(route)
-
- return routes
-
-
-@handle_required
-def roa_create(request):
- """Present the user with a form to create a ROA.
-
- Doesn't use the generic create_object() form because we need to
- create both the ROARequest and ROARequestPrefix objects.
-
- """
-
- conf = request.session['handle']
- if request.method == 'POST':
- form = forms.ROARequest(request.POST, request.FILES, conf=conf)
- if form.is_valid():
- asn = form.cleaned_data.get('asn')
- rng = form._as_resource_range() # FIXME calling "private" method
- max_prefixlen = int(form.cleaned_data.get('max_prefixlen'))
-
- routes = get_covered_routes(rng, max_prefixlen, asn)
-
- prefix = str(rng)
- form = forms.ROARequestConfirm(initial={'asn': asn,
- 'prefix': prefix,
- 'max_prefixlen': max_prefixlen})
- return render(request, 'app/roarequest_confirm_form.html',
- {'form': form,
- 'asn': asn,
- 'prefix': prefix,
- 'max_prefixlen': max_prefixlen,
- 'routes': routes})
- else:
- # pull initial values from query parameters
- d = {}
- for s in ('asn', 'prefix'):
- if s in request.GET:
- d[s] = request.GET[s]
- form = forms.ROARequest(initial=d)
-
- 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'))
- # FIXME: This won't do the right thing in the event that a
- # route is covered by multiple ROAs created in the form.
- # You will see duplicate entries, each with a potentially
- # different validation status.
- 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
- request. It is responsible for updating the IRDB.
-
- """
- conf = request.session['handle']
- log = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.ROARequestConfirm(request.POST, request.FILES)
- if form.is_valid():
- asn = form.cleaned_data.get('asn')
- prefix = form.cleaned_data.get('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 = '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 http.HttpResponseRedirect(reverse(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))
-
-
-@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.
-
- Uses a form for double confirmation, displaying how the route
- validation status may change as a result.
-
- """
-
- conf = request.session['handle']
- roa = get_object_or_404(conf.roas, pk=pk)
- if request.method == 'POST':
- roa.delete()
- Zookeeper(handle=conf.handle).run_rpkid_now()
- return redirect(reverse(dashboard))
-
- ### Process GET ###
-
- # note: assumes we only generate one prefix per ROA
- roa_prefix = roa.prefixes.all()[0]
- rng = roa_prefix.as_resource_range()
-
- routes = []
- for route in roa.routes:
- # select all roas which cover this route
- # excluding the current roa
- # note: we can't identify the exact ROA here, because we only know what
- # was requested to rpkid
- roas = route.roas.exclude(
- asid=roa.asn,
- prefixes__prefix_min=rng.min,
- prefixes__prefix_max=rng.max,
- prefixes__max_length=roa_prefix.max_prefixlen
- )
-
- # subselect exact match
- if route.asn != 0 and roas.filter(asid=route.asn,
- prefixes__max_length__gte=route.prefixlen).exists():
- route.newstatus = 'valid'
- elif roas.exists():
- route.newstatus = 'invalid'
- else:
- route.newstatus = 'unknown'
- # we may want to ignore routes for which there is no status change,
- # but the user may want to see that nothing has changed explicitly
- routes.append(route)
-
- return render(request, 'app/roarequest_confirm_delete.html',
- {'object': roa, 'routes': routes})
-
-
-@handle_required
-def roa_clone(request, pk):
- conf = request.session['handle']
- roa = get_object_or_404(conf.roas, pk=pk)
- return redirect(
- reverse(roa_create_multi) + "?roa=" + str(roa.prefixes.all()[0].as_roa_prefix())
- )
-
-
-@handle_required
-def roa_import(request):
- """Import CSV containing ROA declarations."""
- if request.method == 'POST':
- form = forms.ImportCSVForm(request.POST, request.FILES)
- if form.is_valid():
- import tempfile
- tmp = tempfile.NamedTemporaryFile(suffix='.csv', prefix='roas', delete=False)
- tmp.write(request.FILES['csv'].read())
- tmp.close()
- z = Zookeeper(handle=request.session['handle'])
- z.load_roa_requests(tmp.name)
- z.run_rpkid_now()
- os.unlink(tmp.name)
- messages.success(request, 'Successfully imported ROAs.')
- return redirect(dashboard)
- else:
- form = forms.ImportCSVForm()
- return render(request, 'app/import_resource_form.html', {
- 'form_title': 'Import ROAs from CSV',
- 'form': form,
- 'cancel_url': reverse(dashboard)
- })
-
-
-@handle_required
-def roa_export(request):
- """Export CSV containing ROA declarations."""
- # FIXME: remove when Zookeeper can do this
- f = cStringIO.StringIO()
- csv_writer = csv.writer(f, delimiter=' ')
- conf = request.session['handle']
- # each roa prefix gets a unique group so rpkid will issue separate roas
- for group, roapfx in enumerate(ROARequestPrefix.objects.filter(roa_request__issuer=conf)):
- csv_writer.writerow([str(roapfx.as_roa_prefix()), roapfx.roa_request.asn, '%s-%d' % (conf.handle, group)])
- resp = http.HttpResponse(f.getvalue(), mimetype='application/csv')
- resp['Content-Disposition'] = 'attachment; filename=roas.csv'
- return resp
-
-
-class GhostbusterDetailView(DetailView):
- def get_queryset(self):
- return self.request.session['handle'].ghostbusters
-
-
-@handle_required
-def ghostbuster_delete(request, pk):
- conf = request.session['handle']
- logstream = request.META['wsgi.errors']
- obj = get_object_or_404(conf.ghostbusters, pk=pk)
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- obj.delete()
- Zookeeper(handle=conf.handle, logstream=logstream).run_rpkid_now()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.Empty(request.POST, request.FILES)
- return render(request, 'app/object_confirm_delete.html', {
- 'object': obj,
- 'form': form,
- 'parent_template': 'app/ghostbusterrequest_detail.html'
- })
-
-
-@handle_required
-def ghostbuster_create(request):
- conf = request.session['handle']
- logstream = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.GhostbusterRequestForm(request.POST, request.FILES,
- conf=conf)
- if form.is_valid():
- obj = form.save(commit=False)
- obj.vcard = glue.ghostbuster_to_vcard(obj)
- obj.save()
- Zookeeper(handle=conf.handle, logstream=logstream).run_rpkid_now()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.GhostbusterRequestForm(conf=conf)
- return render(request, 'app/app_form.html',
- {'form': form, 'form_title': 'New Ghostbuster Request'})
-
-
-@handle_required
-def ghostbuster_edit(request, pk):
- conf = request.session['handle']
- obj = get_object_or_404(conf.ghostbusters, pk=pk)
- logstream = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.GhostbusterRequestForm(request.POST, request.FILES,
- conf=conf, instance=obj)
- if form.is_valid():
- obj = form.save(commit=False)
- obj.vcard = glue.ghostbuster_to_vcard(obj)
- obj.save()
- Zookeeper(handle=conf.handle, logstream=logstream).run_rpkid_now()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.GhostbusterRequestForm(conf=conf, instance=obj)
- return render(request, 'app/app_form.html',
- {'form': form, 'form_title': 'Edit Ghostbuster Request'})
-
-
-@handle_required
-def refresh(request):
- """
- Query rpkid, update the db, and redirect back to the dashboard.
-
- """
- glue.list_received_resources(request.META['wsgi.errors'],
- request.session['handle'])
- return http.HttpResponseRedirect(reverse(dashboard))
-
-
-@handle_required
-def route_view(request):
- """
- Display a list of global routing table entries which match resources
- listed in received certificates.
-
- """
- conf = request.session['handle']
- count = request.GET.get('count', 25)
- page = request.GET.get('page', 1)
-
- paginator = Paginator(conf.routes, count)
- try:
- routes = paginator.page(page)
- except InvalidPage:
- # page was empty, or page number was invalid
- routes = []
- ts = dict((attr['name'], attr['ts']) for attr in models.Timestamp.objects.values())
- return render(request, 'app/routes_view.html',
- {'routes': routes, 'timestamp': ts})
-
-
-def route_detail(request, pk):
- """Show a list of ROAs that match a given IPv4 route."""
- route = get_object_or_404(models.RouteOrigin, pk=pk)
- # when running rootd, viewing the 0.0.0.0/0 route will cause a fetch of all
- # roas, so we paginate here, even though in the general case the number of
- # objects will be small enough to fit a single page
- count = request.GET.get('count', 25)
- page = request.GET.get('page', 1)
- paginator = Paginator(route.roa_prefixes.all(), count)
- return render(request, 'app/route_detail.html', {
- 'object': route,
- 'roa_prefixes': paginator.page(page),
- })
-
-
-def route_suggest(request):
- """Handles POSTs from the route view and redirects to the ROA creation
- page based on selected route objects. The form should contain elements of
- the form "pk-NUM" where NUM is the RouteOrigin object id.
-
- """
- if request.method == 'POST':
- routes = []
- for pk in request.POST.iterkeys():
- logger.debug(pk)
- if pk.startswith("pk-"):
- n = int(pk[3:])
- routes.append(n)
- qs = RouteOrigin.objects.filter(pk__in=routes)
- s = []
- for r in qs:
- s.append('roa=%s/%d,%d' % (str(r.prefix_min), r.prefixlen, r.asn))
- p = '&'.join(s)
- return redirect(reverse(roa_create_multi) + '?' + p)
-
-
-@handle_required
-def repository_detail(request, pk):
- conf = request.session['handle']
- return render(request,
- 'app/repository_detail.html',
- {'object': get_object_or_404(conf.repositories, pk=pk)})
-
-
-@handle_required
-def repository_delete(request, pk):
- log = request.META['wsgi.errors']
- conf = request.session['handle']
- # Ensure the repository being deleted belongs to the current user.
- obj = get_object_or_404(models.Repository, issuer=conf, pk=pk)
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- z = Zookeeper(handle=conf.handle, logstream=log)
- z.delete_repository(obj.handle)
- z.synchronize_ca()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.Empty()
- return render(request, 'app/object_confirm_delete.html', {
- 'object': obj,
- 'form': form,
- 'parent_template':
- 'app/repository_detail.html',
- })
-
-
-@handle_required
-def repository_import(request):
- """Import XML response file from repository operator."""
- return generic_import(request,
- models.Repository.objects,
- Zookeeper.configure_repository,
- form_class=forms.ImportRepositoryForm,
- post_import_redirect=reverse(dashboard))
-
-
-@superuser_required
-def client_list(request):
- """display a list of all repository client (irdb.models.Client)"""
-
- return render(request, 'app/client_list.html', {
- 'object_list': models.Client.objects.all()
- })
-
-
-@superuser_required
-def client_detail(request, pk):
- return render(request, 'app/client_detail.html',
- {'object': get_object_or_404(models.Client, pk=pk)})
-
-
-@superuser_required
-def client_delete(request, pk):
- log = request.META['wsgi.errors']
- obj = get_object_or_404(models.Client, pk=pk)
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- z = Zookeeper(logstream=log)
- z.delete_publication_client(obj.handle)
- z.synchronize_pubd()
- return http.HttpResponseRedirect(reverse(dashboard))
- else:
- form = forms.Empty()
- return render(request, 'app/object_confirm_delete.html', {
- 'object': obj,
- 'form': form,
- 'parent_template': 'app/client_detail.html'
- })
-
-
-@superuser_required
-def client_import(request):
- return generic_import(request, models.Client.objects,
- Zookeeper.configure_publication_client,
- form_class=forms.ImportClientForm,
- post_import_redirect=reverse(dashboard))
-
-
-@superuser_required
-def client_export(request, pk):
- """Return the XML file resulting from a configure_publication_client
- request.
-
- """
- client = get_object_or_404(models.Client, pk=pk)
- z = Zookeeper()
- xml = z.generate_repository_response(client)
- return serve_xml(str(xml), '%s.repo' % z.handle)
-
-
-### Routines for managing resource handles serviced by this server
-
-@superuser_required
-def resource_holder_list(request):
- """Display a list of all the RPKI handles managed by this server."""
- return render(request, 'app/resource_holder_list.html', {
- 'object_list': models.Conf.objects.all()
- })
-
-
-@superuser_required
-def resource_holder_edit(request, pk):
- """Display a list of all the RPKI handles managed by this server."""
- conf = get_object_or_404(models.Conf, pk=pk)
- if request.method == 'POST':
- form = forms.ResourceHolderForm(request.POST, request.FILES)
- if form.is_valid():
- models.ConfACL.objects.filter(conf=conf).delete()
- for user in form.cleaned_data.get('users'):
- models.ConfACL.objects.create(user=user, conf=conf)
- return redirect(resource_holder_list)
- else:
- users = [acl.user for acl in models.ConfACL.objects.filter(conf=conf).all()]
- form = forms.ResourceHolderForm(initial={
- 'users': users
- })
- return render(request, 'app/app_form.html', {
- 'form_title': "Edit Resource Holder: " + conf.handle,
- 'form': form,
- 'cancel_url': reverse(resource_holder_list)
- })
-
-
-@superuser_required
-def resource_holder_delete(request, pk):
- conf = get_object_or_404(models.Conf, pk=pk)
- log = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.Empty(request.POST)
- if form.is_valid():
- z = Zookeeper(handle=conf.handle, logstream=log)
- z.delete_self()
- z.synchronize_deleted_ca()
- return redirect(resource_holder_list)
- else:
- form = forms.Empty()
- return render(request, 'app/app_confirm_delete.html', {
- 'form_title': 'Delete Resource Holder: ' + conf.handle,
- 'form': form,
- 'cancel_url': reverse(resource_holder_list)
- })
-
-
-@superuser_required
-def resource_holder_create(request):
- log = request.META['wsgi.errors']
- if request.method == 'POST':
- form = forms.ResourceHolderCreateForm(request.POST, request.FILES)
- if form.is_valid():
- handle = form.cleaned_data.get('handle')
- parent = form.cleaned_data.get('parent')
-
- zk_child = Zookeeper(handle=handle, logstream=log)
- identity_xml = zk_child.initialize_resource_bpki()
- if parent:
- # FIXME etree_wrapper should allow us to deal with file objects
- t = NamedTemporaryFile(delete=False)
- t.close()
-
- identity_xml.save(t.name)
- zk_parent = Zookeeper(handle=parent.handle, logstream=log)
- parent_response, _ = zk_parent.configure_child(t.name)
- parent_response.save(t.name)
- zk_parent.synchronize_ca()
- repo_req, _ = zk_child.configure_parent(t.name)
- repo_req.save(t.name)
- repo_resp, _ = zk_parent.configure_publication_client(t.name)
- repo_resp.save(t.name)
- zk_parent.synchronize_pubd()
- zk_child.configure_repository(t.name)
- os.remove(t.name)
- zk_child.synchronize_ca()
- return redirect(resource_holder_list)
- else:
- form = forms.ResourceHolderCreateForm()
- return render(request, 'app/app_form.html', {
- 'form': form,
- 'form_title': 'Create Resource Holder',
- 'cancel_url': reverse(resource_holder_list)
- })
-
-
-### views for managing user logins to the web interface
-
-@superuser_required
-def user_create(request):
- if request.method == 'POST':
- form = forms.UserCreateForm(request.POST, request.FILES)
- if form.is_valid():
- username = form.cleaned_data.get('username')
- pw = form.cleaned_data.get('password')
- email = form.cleaned_data.get('email')
- user = User.objects.create_user(username, email, pw)
- for conf in form.cleaned_data.get('resource_holders'):
- models.ConfACL.objects.create(user=user, conf=conf)
- return redirect(user_list)
- else:
- form = forms.UserCreateForm()
-
- return render(request, 'app/app_form.html', {
- 'form': form,
- 'form_title': 'Create User',
- 'cancel_url': reverse(user_list),
- })
-
-
-@superuser_required
-def user_list(request):
- """Display a list of all the RPKI handles managed by this server."""
- return render(request, 'app/user_list.html', {
- 'object_list': User.objects.all()
- })
-
-
-@superuser_required
-def user_delete(request, pk):
- user = get_object_or_404(User, pk=pk)
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- user.delete()
- return redirect(user_list)
- else:
- form = forms.Empty()
- return render(request, 'app/app_confirm_delete.html', {
- 'form_title': 'Delete User: ' + user.username,
- 'form': form,
- 'cancel_url': reverse(user_list)
- })
-
-
-@superuser_required
-def user_edit(request, pk):
- user = get_object_or_404(User, pk=pk)
- if request.method == 'POST':
- form = forms.UserEditForm(request.POST)
- if form.is_valid():
- pw = form.cleaned_data.get('pw')
- if pw:
- user.set_password(pw)
- user.email = form.cleaned_data.get('email')
- user.save()
- models.ConfACL.objects.filter(user=user).delete()
- handles = form.cleaned_data.get('resource_holders')
- for conf in handles:
- models.ConfACL.objects.create(user=user, conf=conf)
- return redirect(user_list)
- else:
- form = forms.UserEditForm(initial={
- 'email': user.email,
- 'resource_holders': models.Conf.objects.filter(confacl__user=user).all()
- })
- return render(request, 'app/app_form.html', {
- 'form': form,
- 'form_title': 'Edit User: ' + user.username,
- 'cancel_url': reverse(user_list)
- })
-
-
-class AlertListView(ListView):
- # this nonsense is required to decorate CBVs
- @method_decorator(handle_required)
- def dispatch(self, request, *args, **kwargs):
- return super(AlertListView, self).dispatch(request, *args, **kwargs)
-
- def get_queryset(self, **kwargs):
- conf = self.request.session['handle']
- return conf.alerts.all()
-
-
-class AlertDetailView(DetailView):
- # this nonsense is required to decorate CBVs
- @method_decorator(handle_required)
- def dispatch(self, request, *args, **kwargs):
- return super(AlertDetailView, self).dispatch(request, *args, **kwargs)
-
- def get_queryset(self, **kwargs):
- conf = self.request.session['handle']
- return conf.alerts.all()
-
- def get_object(self, **kwargs):
- obj = super(AlertDetailView, self).get_object(**kwargs)
- # mark alert as read by the user
- obj.seen = True
- obj.save()
- return obj
-
-
-class AlertDeleteView(DeleteView):
- success_url = reverse_lazy('alert-list')
-
- # this nonsense is required to decorate CBVs
- @method_decorator(handle_required)
- def dispatch(self, request, *args, **kwargs):
- return super(AlertDeleteView, self).dispatch(request, *args, **kwargs)
-
- def get_queryset(self, **kwargs):
- conf = self.request.session['handle']
- return conf.alerts.all()
-
-
-@handle_required
-def alert_clear_all(request):
- """Clear all alerts associated with the current resource holder."""
- if request.method == 'POST':
- form = forms.Empty(request.POST, request.FILES)
- if form.is_valid():
- # delete alerts
- request.session['handle'].clear_alerts()
- return redirect('alert-list')
- else:
- form = forms.Empty()
- return render(request, 'app/alert_confirm_clear.html', {'form': form})