diff options
author | Rob Austein <sra@hactrn.net> | 2014-04-05 22:42:12 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2014-04-05 22:42:12 +0000 |
commit | fe0bf509f528dbdc50c7182f81057c6a4e15e4bd (patch) | |
tree | 07c9a923d4a0ccdfea11c49cd284f6d5757c5eda /rpkid/rpki/gui/app/views.py | |
parent | aa28ef54c271fbe4d52860ff8cf13cab19e2207c (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.py | 1314 |
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}) |