aboutsummaryrefslogtreecommitdiff
path: root/rpki/gui/cacheview
diff options
context:
space:
mode:
Diffstat (limited to 'rpki/gui/cacheview')
-rw-r--r--rpki/gui/cacheview/__init__.py0
-rw-r--r--rpki/gui/cacheview/forms.py51
-rw-r--r--rpki/gui/cacheview/misc.py31
-rw-r--r--rpki/gui/cacheview/models.py237
-rw-r--r--rpki/gui/cacheview/templates/cacheview/addressrange_detail.html18
-rw-r--r--rpki/gui/cacheview/templates/cacheview/cacheview_base.html10
-rw-r--r--rpki/gui/cacheview/templates/cacheview/cert_detail.html105
-rw-r--r--rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html13
-rw-r--r--rpki/gui/cacheview/templates/cacheview/global_summary.html26
-rw-r--r--rpki/gui/cacheview/templates/cacheview/query_result.html21
-rw-r--r--rpki/gui/cacheview/templates/cacheview/roa_detail.html18
-rw-r--r--rpki/gui/cacheview/templates/cacheview/search_form.html17
-rw-r--r--rpki/gui/cacheview/templates/cacheview/search_result.html42
-rw-r--r--rpki/gui/cacheview/templates/cacheview/signedobject_detail.html58
-rw-r--r--rpki/gui/cacheview/tests.py23
-rw-r--r--rpki/gui/cacheview/urls.py32
-rw-r--r--rpki/gui/cacheview/util.py432
-rw-r--r--rpki/gui/cacheview/views.py172
18 files changed, 1306 insertions, 0 deletions
diff --git a/rpki/gui/cacheview/__init__.py b/rpki/gui/cacheview/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rpki/gui/cacheview/__init__.py
diff --git a/rpki/gui/cacheview/forms.py b/rpki/gui/cacheview/forms.py
new file mode 100644
index 00000000..28b8ff24
--- /dev/null
+++ b/rpki/gui/cacheview/forms.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions
+# Copyright (C) 2013 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.
+
+__version__ = '$Id$'
+
+from django import forms
+
+from rpki.gui.cacheview.misc import parse_ipaddr
+from rpki.exceptions import BadIPResource
+from rpki.resource_set import resource_range_as
+
+
+class SearchForm(forms.Form):
+ asn = forms.CharField(required=False, help_text='AS or range', label='AS')
+ addr = forms.CharField(required=False, max_length=40, help_text='range/CIDR', label='IP Address')
+
+ def clean(self):
+ asn = self.cleaned_data.get('asn')
+ addr = self.cleaned_data.get('addr')
+ if (asn and addr) or ((not asn) and (not addr)):
+ raise forms.ValidationError, 'Please specify either an AS or IP range, not both'
+
+ if asn:
+ try:
+ resource_range_as.parse_str(asn)
+ except ValueError:
+ raise forms.ValidationError, 'invalid AS range'
+
+ if addr:
+ #try:
+ parse_ipaddr(addr)
+ #except BadIPResource:
+ # raise forms.ValidationError, 'invalid IP address range/prefix'
+
+ return self.cleaned_data
+
+
+class SearchForm2(forms.Form):
+ resource = forms.CharField(required=True)
diff --git a/rpki/gui/cacheview/misc.py b/rpki/gui/cacheview/misc.py
new file mode 100644
index 00000000..9a69645c
--- /dev/null
+++ b/rpki/gui/cacheview/misc.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions
+#
+# 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.
+
+from rpki.resource_set import resource_range_ipv4, resource_range_ipv6
+from rpki.exceptions import BadIPResource
+
+def parse_ipaddr(s):
+ # resource_set functions only accept str
+ if isinstance(s, unicode):
+ s = s.encode()
+ s = s.strip()
+ r = resource_range_ipv4.parse_str(s)
+ try:
+ r = resource_range_ipv4.parse_str(s)
+ return 4, r
+ except BadIPResource:
+ r = resource_range_ipv6.parse_str(s)
+ return 6, r
+
+# vim:sw=4 ts=8 expandtab
diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/cacheview/models.py
new file mode 100644
index 00000000..c3ee8421
--- /dev/null
+++ b/rpki/gui/cacheview/models.py
@@ -0,0 +1,237 @@
+# Copyright (C) 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.
+
+__version__ = '$Id$'
+
+from datetime import datetime
+import time
+
+from django.db import models
+from django.core.urlresolvers import reverse
+
+import rpki.resource_set
+import rpki.gui.models
+
+
+class TelephoneField(models.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = 255
+ models.CharField.__init__(self, *args, **kwargs)
+
+
+class AddressRange(rpki.gui.models.PrefixV4):
+ @models.permalink
+ def get_absolute_url(self):
+ return ('rpki.gui.cacheview.views.addressrange_detail', [str(self.pk)])
+
+
+class AddressRangeV6(rpki.gui.models.PrefixV6):
+ @models.permalink
+ def get_absolute_url(self):
+ return ('rpki.gui.cacheview.views.addressrange_detail_v6',
+ [str(self.pk)])
+
+
+class ASRange(rpki.gui.models.ASN):
+ @models.permalink
+ def get_absolute_url(self):
+ return ('rpki.gui.cacheview.views.asrange_detail', [str(self.pk)])
+
+kinds = list(enumerate(('good', 'warn', 'bad')))
+kinds_dict = dict((v, k) for k, v in kinds)
+
+
+class ValidationLabel(models.Model):
+ """
+ Represents a specific error condition defined in the rcynic XML
+ output file.
+ """
+ label = models.CharField(max_length=79, db_index=True, unique=True)
+ status = models.CharField(max_length=255)
+ kind = models.PositiveSmallIntegerField(choices=kinds)
+
+ def __unicode__(self):
+ return self.label
+
+
+class RepositoryObject(models.Model):
+ """
+ Represents a globally unique RPKI repository object, specified by its URI.
+ """
+ uri = models.URLField(unique=True, db_index=True)
+
+generations = list(enumerate(('current', 'backup')))
+generations_dict = dict((val, key) for (key, val) in generations)
+
+
+class ValidationStatus(models.Model):
+ timestamp = models.DateTimeField()
+ generation = models.PositiveSmallIntegerField(choices=generations, null=True)
+ status = models.ForeignKey(ValidationLabel)
+ repo = models.ForeignKey(RepositoryObject, related_name='statuses')
+
+
+class SignedObject(models.Model):
+ """
+ Abstract class to hold common metadata for all signed objects.
+ The signing certificate is ommitted here in order to give a proper
+ value for the 'related_name' attribute.
+ """
+ repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True)
+
+ # on-disk file modification time
+ mtime = models.PositiveIntegerField(default=0)
+
+ # SubjectName
+ name = models.CharField(max_length=255)
+
+ # value from the SKI extension
+ keyid = models.CharField(max_length=60, db_index=True)
+
+ # validity period from EE cert which signed object
+ not_before = models.DateTimeField()
+ not_after = models.DateTimeField()
+
+ def mtime_as_datetime(self):
+ """
+ convert the local timestamp to UTC and convert to a datetime object
+ """
+ return datetime.utcfromtimestamp(self.mtime + time.timezone)
+
+ def status_id(self):
+ """
+ Returns a HTML class selector for the current object based on its validation status.
+ The selector is chosen based on the current generation only. If there is any bad status,
+ return bad, else if there are any warn status, return warn, else return good.
+ """
+ for x in reversed(kinds):
+ if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]):
+ return x[1]
+ return None # should not happen
+
+ def __unicode__(self):
+ return u'%s' % self.name
+
+
+class Cert(SignedObject):
+ """
+ Object representing a resource certificate.
+ """
+ addresses = models.ManyToManyField(AddressRange, related_name='certs')
+ addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs')
+ asns = models.ManyToManyField(ASRange, related_name='certs')
+ issuer = models.ForeignKey('self', related_name='children', null=True)
+ sia = models.CharField(max_length=255)
+
+ def get_absolute_url(self):
+ return reverse('cert-detail', args=[str(self.pk)])
+
+ def get_cert_chain(self):
+ """Return a list containing the complete certificate chain for this
+ certificate."""
+ cert = self
+ x = [cert]
+ while cert != cert.issuer:
+ cert = cert.issuer
+ x.append(cert)
+ x.reverse()
+ return x
+ cert_chain = property(get_cert_chain)
+
+
+class ROAPrefix(models.Model):
+ "Abstract base class for ROA mixin."
+
+ max_length = models.PositiveSmallIntegerField()
+
+ class Meta:
+ abstract = True
+
+ def as_roa_prefix(self):
+ "Return value as a rpki.resource_set.roa_prefix_ip object."
+ rng = self.as_resource_range()
+ return self.roa_cls(rng.min, rng.prefixlen(), self.max_length)
+
+ def __unicode__(self):
+ p = self.as_resource_range()
+ if p.prefixlen() == self.max_length:
+ return str(p)
+ return '%s-%s' % (str(p), self.max_length)
+
+
+# ROAPrefix is declared first, so subclass picks up __unicode__ from it.
+class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4):
+ "One v4 prefix in a ROA."
+
+ roa_cls = rpki.resource_set.roa_prefix_ipv4
+
+ @property
+ def routes(self):
+ """return all routes covered by this roa prefix"""
+ return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min,
+ prefix_max__lte=self.prefix_max)
+
+ class Meta:
+ ordering = ('prefix_min',)
+
+
+# ROAPrefix is declared first, so subclass picks up __unicode__ from it.
+class ROAPrefixV6(ROAPrefix, rpki.gui.models.PrefixV6):
+ "One v6 prefix in a ROA."
+
+ roa_cls = rpki.resource_set.roa_prefix_ipv6
+
+ class Meta:
+ ordering = ('prefix_min',)
+
+
+class ROA(SignedObject):
+ asid = models.PositiveIntegerField()
+ prefixes = models.ManyToManyField(ROAPrefixV4, related_name='roas')
+ prefixes_v6 = models.ManyToManyField(ROAPrefixV6, related_name='roas')
+ issuer = models.ForeignKey('Cert', related_name='roas')
+
+ def get_absolute_url(self):
+ return reverse('roa-detail', args=[str(self.pk)])
+
+ class Meta:
+ ordering = ('asid',)
+
+ def __unicode__(self):
+ return u'ROA for AS%d' % self.asid
+
+
+class Ghostbuster(SignedObject):
+ full_name = models.CharField(max_length=40)
+ email_address = models.EmailField(blank=True, null=True)
+ organization = models.CharField(blank=True, null=True, max_length=255)
+ telephone = TelephoneField(blank=True, null=True)
+ issuer = models.ForeignKey('Cert', related_name='ghostbusters')
+
+ def get_absolute_url(self):
+ # note that ghostbuster-detail is different from gbr-detail! sigh
+ return reverse('ghostbuster-detail', args=[str(self.pk)])
+
+ def __unicode__(self):
+ if self.full_name:
+ return self.full_name
+ if self.organization:
+ return self.organization
+ if self.email_address:
+ return self.email_address
+ return self.telephone
+
+
+from rpki.gui.routeview.models import RouteOrigin
diff --git a/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html b/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html
new file mode 100644
index 00000000..76edc1ba
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/addressrange_detail.html
@@ -0,0 +1,18 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block content %}
+<h1>{% block title %}IP Range Detail{% endblock %}</h1>
+
+<p>
+IP Range: {{ object }}
+</p>
+
+<p>Covered by the following resource certs:</p>
+
+<ul>
+{% for cert in object.certs.all %}
+<li><a href="{{ cert.get_absolute_url }}">{{ cert }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/cacheview_base.html b/rpki/gui/cacheview/templates/cacheview/cacheview_base.html
new file mode 100644
index 00000000..ec71d740
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/cacheview_base.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+{% load url from future %}
+
+{% block sidebar %}
+<form method='post' action='{% url 'res-search' %}'>
+ {% csrf_token %}
+ <input type='text' id='id_resource' name='resource' placeholder='prefix or AS'>
+ <button type='submit'>Search</button>
+</form>
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/cert_detail.html b/rpki/gui/cacheview/templates/cacheview/cert_detail.html
new file mode 100644
index 00000000..256e7780
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/cert_detail.html
@@ -0,0 +1,105 @@
+{% extends "cacheview/signedobject_detail.html" %}
+
+{% block title %}
+Resource Certificate Detail
+{% endblock %}
+
+{% block detail %}
+
+<h2>RFC3779 Resources</h2>
+
+<table class='table table-striped'>
+ <thead>
+ <tr><th>AS Ranges</th><th>IP Ranges</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style='text-align:left;vertical-align:top'>
+ <ul class='compact'>
+ {% for asn in object.asns.all %}
+ <li><a href="{{ asn.get_absolute_url }}">{{ asn }}</a></li>
+ {% endfor %}
+ </ul>
+ </td>
+ <td style='text-align:left;vertical-align:top'>
+ <ul class='compact'>
+ {% for rng in object.addresses.all %}
+ <li><a href="{{ rng.get_absolute_url }}">{{ rng }}</a></li>
+ {% endfor %}
+ </ul>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<div class='section'>
+<h2>Issued Objects</h2>
+<ul>
+
+{% if object.ghostbusters.all %}
+ <li>
+<h3>Ghostbusters</h3>
+
+<table class='table table-striped'>
+ <thead>
+ <tr><th>Name</th><th>Expires</th></tr>
+ </thead>
+ <tbody>
+
+{% for g in object.ghostbusters.all %}
+ <tr class='{{ g.status_id }}'>
+ <td><a href="{{ g.get_absolute_url }}">{{ g }}</a></td>
+ <td>{{ g.not_after }}</td>
+ </tr>
+ </tbody>
+{% endfor %}
+
+</table>
+{% endif %}
+
+{% if object.roas.all %}
+ <li>
+<h3>ROAs</h3>
+<table class='table table-striped'>
+ <thead>
+ <tr><th>#</th><th>Prefix</th><th>AS</th><th>Expires</th></tr>
+ </thead>
+ <tbody>
+ {% for roa in object.roas.all %}
+ {% for pfx in roa.prefixes.all %}
+ <tr class='{{ roa.status_id }}'>
+ <td><a href="{{ roa.get_absolute_url }}">#</a></td>
+ <td>{{ pfx }}</td>
+ <td>{{ roa.asid }}</td>
+ <td>{{ roa.not_after }}</td>
+ </tr>
+ {% endfor %}
+ {% endfor %}
+ </tbody>
+</table>
+{% endif %}
+
+{% if object.children.all %}
+<li>
+<h3>Children</h3>
+<table class='table table-striped'>
+ <thead>
+ <tr><th>Name</th><th>Expires</th></tr>
+ </thead>
+ <tbody>
+
+ {% for child in object.children.all %}
+ <tr class='{{ child.status_id }}'>
+ <td><a href="{{ child.get_absolute_url }}">{{ child.name }}</a></td>
+ <td>{{ child.not_after }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endif %}
+
+</ul>
+
+</div><!--issued objects-->
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html b/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html
new file mode 100644
index 00000000..4215f757
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/ghostbuster_detail.html
@@ -0,0 +1,13 @@
+{% extends "cacheview/signedobject_detail.html" %}
+
+{% block title %}Ghostbuster Detail{% endblock %}
+
+{% block detail %}
+<p>
+<table class='table'>
+ <tr><td>Full Name</td><td>{{ object.full_name }}</td></tr>
+ <tr><td>Organization</td><td>{{ object.organization }}</td></tr>
+ <tr><td>Email</td><td>{{ object.email_address }}</td></tr>
+ <tr><td>Telephone</td><td>{{ object.telephone }}</td></tr>
+</table>
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/global_summary.html b/rpki/gui/cacheview/templates/cacheview/global_summary.html
new file mode 100644
index 00000000..0dbd0ffc
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/global_summary.html
@@ -0,0 +1,26 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block content %}
+<div class='page-header'>
+ <h1>Browse Global RPKI</h1>
+</div>
+
+<table class="table table-striped">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Expires</th>
+ <th>URI</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for r in roots %}
+ <tr>
+ <td><a href="{{ r.get_absolute_url }}">{{ r.name }}</a></td>
+ <td>{{ r.not_after }}</td>
+ <td>{{ r.repo.uri }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+{% endblock content %}
diff --git a/rpki/gui/cacheview/templates/cacheview/query_result.html b/rpki/gui/cacheview/templates/cacheview/query_result.html
new file mode 100644
index 00000000..0694c531
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/query_result.html
@@ -0,0 +1,21 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block content %}
+
+<h1>{% block title %}Query Results{% endblock %}</h1>
+
+<table>
+ <tr><th>Prefix</th><th>AS</th><th>Valid</th><th>Until</th></tr>
+ {% for object in object_list %}
+ <tr class='{{ object.1.status.kind_as_str }}'>
+ <td>{{ object.0 }}</td>
+ <td>{{ object.1.asid }}</td>
+ <td><a href="{{ object.1.get_absolute_url }}">{{ object.1.ok }}</a></td>
+ <td>{{ object.1.not_after }}</td>
+ </tr>
+ {% endfor %}
+</table>
+
+<p><a href="{% url rpki.gui.cacheview.views.query_view %}">new query</a></p>
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/roa_detail.html b/rpki/gui/cacheview/templates/cacheview/roa_detail.html
new file mode 100644
index 00000000..39cc547b
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/roa_detail.html
@@ -0,0 +1,18 @@
+{% extends "cacheview/signedobject_detail.html" %}
+
+{% block title %}ROA Detail{% endblock %}
+
+{% block detail %}
+<p>
+<table>
+ <tr><td>AS</td><td>{{ object.asid }}</td></tr>
+</table>
+
+<h2>Prefixes</h2>
+
+<ul>
+{% for pfx in object.prefixes.all %}
+<li>{{ pfx }}
+{% endfor %}
+</ul>
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/search_form.html b/rpki/gui/cacheview/templates/cacheview/search_form.html
new file mode 100644
index 00000000..1141615d
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/search_form.html
@@ -0,0 +1,17 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block title %}
+{{ search_type }} Search
+{% endblock %}
+
+{% block content %}
+
+<h1>{{search_type}} Search</h1>
+
+<form method='post' action='{{ request.url }}'>
+ {% csrf_token %}
+ {{ form.as_p }}
+ <input type='submit' name='Search'>
+</form>
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/search_result.html b/rpki/gui/cacheview/templates/cacheview/search_result.html
new file mode 100644
index 00000000..7cbf852e
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/search_result.html
@@ -0,0 +1,42 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block content %}
+
+<div class='page-header'>
+ <h1>Search Results <small>{{ resource }}</small></h1>
+</div>
+
+<h2>Matching Resource Certificates</h2>
+{% if certs %}
+<ul>
+{% for cert in certs %}
+<li><a href="{{ cert.get_absolute_url }}">{{ cert }}</a>
+{% endfor %}
+</ul>
+{% else %}
+<p>none</p>
+{% endif %}
+
+<h2>Matching ROAs</h2>
+{% if roas %}
+<table class='table table-striped'>
+ <thead>
+ <tr>
+ <th>#</th><th>Prefix</th><th>AS</th>
+ </tr>
+ </thead>
+ <tbody>
+{% for roa in roas %}
+<tr>
+ <td><a href="{{ roa.get_absolute_url }}">#</a></td>
+ <td>{{ roa.prefixes.all.0 }}</td>
+ <td>{{ roa.asid }}</td>
+</tr>
+{% endfor %}
+</tbody>
+</table>
+{% else %}
+<p>none</p>
+{% endif %}
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html b/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html
new file mode 100644
index 00000000..22ae3d27
--- /dev/null
+++ b/rpki/gui/cacheview/templates/cacheview/signedobject_detail.html
@@ -0,0 +1,58 @@
+{% extends "cacheview/cacheview_base.html" %}
+
+{% block content %}
+<div class='page-header'>
+<h1>{% block title %}Signed Object Detail{% endblock %}</h1>
+</div>
+
+<h2>Cert Info</h2>
+<table class='table table-striped'>
+ <tr><td>Subject Name</td><td>{{ object.name }}</td></tr>
+ <tr><td>SKI</td><td>{{ object.keyid }}</td></tr>
+ {% if object.sia %}
+ <tr><td>SIA</td><td>{{ object.sia }}</td></tr>
+ {% endif %}
+ <tr><td>Not Before</td><td>{{ object.not_before }}</td></tr>
+ <tr><td>Not After</td><td>{{ object.not_after }}</td></tr>
+</table>
+
+<h2>Metadata</h2>
+
+<table class='table table-striped'>
+ <tr><td>URI</td><td>{{ object.repo.uri }}</td></tr>
+ <tr><td>Last Modified</td><td>{{ object.mtime_as_datetime|date:"DATETIME_FORMAT" }}</td></tr>
+</table>
+
+<h2>Validation Status</h2>
+<table class='table table-striped'>
+ <thead>
+ <tr><th>Timestamp</th><th>Generation</th><th>Status</th></tr>
+ </thead>
+ <tbody>
+ {% for status in object.repo.statuses.all %}
+ <tr class="{{ status.status.get_kind_display }}"><td>{{ status.timestamp }}</td><td>{{ status.get_generation_display }}</td><td>{{ status.status.status }}</td></tr>
+ {% endfor %}
+ </tbody>
+</table>
+
+<h2>X.509 Certificate Chain</h2>
+
+<table class='table table-striped'>
+ <thead>
+ <tr><th>Depth</th><th>Name</th></tr>
+ </thead>
+ <tbody>
+
+{% for cert in chain %}
+<tr class='{{ cert.1.status_id }}'>
+ <td>{{ cert.0 }}</td>
+ <td><a href="{{ cert.1.get_absolute_url }}">{{ cert.1.name }}</a></td>
+</tr>
+{% endfor %}
+</tbody>
+
+</table>
+
+{% block detail %}{% endblock %}
+
+{% endblock %}
diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py
new file mode 100644
index 00000000..2247054b
--- /dev/null
+++ b/rpki/gui/cacheview/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/rpki/gui/cacheview/urls.py b/rpki/gui/cacheview/urls.py
new file mode 100644
index 00000000..cc03a587
--- /dev/null
+++ b/rpki/gui/cacheview/urls.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions
+# Copyright (C) 2013 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.
+
+__version__ = '$Id$'
+
+from django.conf.urls import patterns, url
+from rpki.gui.cacheview.views import (CertDetailView, RoaDetailView,
+ GhostbusterDetailView)
+
+urlpatterns = patterns('',
+ url(r'^search$', 'rpki.gui.cacheview.views.search_view',
+ name='res-search'),
+ url(r'^cert/(?P<pk>[^/]+)$', CertDetailView.as_view(), name='cert-detail'),
+ url(r'^gbr/(?P<pk>[^/]+)$', GhostbusterDetailView.as_view(),
+ name='ghostbuster-detail'),
+ url(r'^roa/(?P<pk>[^/]+)$', RoaDetailView.as_view(), name='roa-detail'),
+ (r'^$', 'rpki.gui.cacheview.views.global_summary'),
+)
+
+# vim:sw=4 ts=8 expandtab
diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py
new file mode 100644
index 00000000..0d3d7ae3
--- /dev/null
+++ b/rpki/gui/cacheview/util.py
@@ -0,0 +1,432 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham
+# Copyright (C) 2012, 2013 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.
+
+__version__ = '$Id$'
+__all__ = ('import_rcynic_xml')
+
+default_logfile = '/var/rcynic/data/rcynic.xml'
+default_root = '/var/rcynic/data'
+object_accepted = None # set by import_rcynic_xml()
+
+import time
+import vobject
+import logging
+import os
+import stat
+from socket import getfqdn
+from cStringIO import StringIO
+
+from django.db import transaction
+import django.db.models
+
+import rpki
+import rpki.gui.app.timestamp
+from rpki.gui.app.models import Conf, Alert
+from rpki.gui.cacheview import models
+from rpki.rcynic import rcynic_xml_iterator, label_iterator
+from rpki.sundial import datetime
+from rpki.irdb.zookeeper import Zookeeper
+
+logger = logging.getLogger(__name__)
+
+
+def rcynic_cert(cert, obj):
+ obj.sia = cert.sia_directory_uri
+
+ # object must be saved for the related manager methods below to work
+ obj.save()
+
+ # for the root cert, we can't set inst.issuer = inst until
+ # after inst.save() has been called.
+ if obj.issuer is None:
+ obj.issuer = obj
+ obj.save()
+
+ # resources can change when a cert is updated
+ obj.asns.clear()
+ obj.addresses.clear()
+
+ if cert.resources.asn.inherit:
+ # FIXME: what happens when the parent's resources change and the child
+ # cert is not reissued?
+ obj.asns.add(*obj.issuer.asns.all())
+ else:
+ for asr in cert.resources.asn:
+ logger.debug('processing %s' % asr)
+
+ attrs = {'min': asr.min, 'max': asr.max}
+ q = models.ASRange.objects.filter(**attrs)
+ if not q:
+ obj.asns.create(**attrs)
+ else:
+ obj.asns.add(q[0])
+
+ # obj.issuer is None the first time we process the root cert in the
+ # hierarchy, so we need to guard against dereference
+ for cls, addr_obj, addrset, parentset in (
+ models.AddressRange, obj.addresses, cert.resources.v4,
+ obj.issuer.addresses.all() if obj.issuer else []
+ ), (
+ models.AddressRangeV6, obj.addresses_v6, cert.resources.v6,
+ obj.issuer.addresses_v6.all() if obj.issuer else []
+ ):
+ if addrset.inherit:
+ addr_obj.add(*parentset)
+ else:
+ for rng in addrset:
+ logger.debug('processing %s' % rng)
+
+ attrs = {'prefix_min': rng.min, 'prefix_max': rng.max}
+ q = cls.objects.filter(**attrs)
+ if not q:
+ addr_obj.create(**attrs)
+ else:
+ addr_obj.add(q[0])
+
+
+def rcynic_roa(roa, obj):
+ obj.asid = roa.asID
+ # object must be saved for the related manager methods below to work
+ obj.save()
+ obj.prefixes.clear()
+ obj.prefixes_v6.clear()
+ for pfxset in roa.prefix_sets:
+ if pfxset.__class__.__name__ == 'roa_prefix_set_ipv6':
+ roa_cls = models.ROAPrefixV6
+ prefix_obj = obj.prefixes_v6
+ else:
+ roa_cls = models.ROAPrefixV4
+ prefix_obj = obj.prefixes
+
+ for pfx in pfxset:
+ attrs = {'prefix_min': pfx.min(),
+ 'prefix_max': pfx.max(),
+ 'max_length': pfx.max_prefixlen}
+ q = roa_cls.objects.filter(**attrs)
+ if not q:
+ prefix_obj.create(**attrs)
+ else:
+ prefix_obj.add(q[0])
+
+
+def rcynic_gbr(gbr, obj):
+ vcard = vobject.readOne(gbr.vcard)
+ obj.full_name = vcard.fn.value if hasattr(vcard, 'fn') else None
+ obj.email_address = vcard.email.value if hasattr(vcard, 'email') else None
+ obj.telephone = vcard.tel.value if hasattr(vcard, 'tel') else None
+ obj.organization = vcard.org.value[0] if hasattr(vcard, 'org') else None
+ obj.save()
+
+LABEL_CACHE = {}
+
+# dict keeping mapping of uri to (handle, old status, new status) for objects
+# published by the local rpkid
+uris = {}
+
+dispatch = {
+ 'rcynic_certificate': rcynic_cert,
+ 'rcynic_roa': rcynic_roa,
+ 'rcynic_ghostbuster': rcynic_gbr
+}
+
+model_class = {
+ 'rcynic_certificate': models.Cert,
+ 'rcynic_roa': models.ROA,
+ 'rcynic_ghostbuster': models.Ghostbuster
+}
+
+
+def save_status(repo, vs):
+ timestamp = datetime.fromXMLtime(vs.timestamp).to_sql()
+ status = LABEL_CACHE[vs.status]
+ g = models.generations_dict[vs.generation] if vs.generation else None
+ repo.statuses.create(generation=g, timestamp=timestamp, status=status)
+
+ # if this object is in our interest set, update with the current validation
+ # status
+ if repo.uri in uris:
+ x, y, z, q = uris[repo.uri]
+ valid = z or (status is object_accepted) # don't clobber previous True value
+ uris[repo.uri] = x, y, valid, repo
+
+ if status is not object_accepted:
+ return
+
+ cls = model_class[vs.file_class.__name__]
+ # find the instance of the signedobject subclass that is associated with
+ # this repo instance (may be empty when not accepted)
+ inst_qs = cls.objects.filter(repo=repo)
+
+ logger.debug('processing %s' % vs.filename)
+
+ if not inst_qs:
+ inst = cls(repo=repo)
+ logger.debug('object not found in db, creating new object cls=%s id=%s' % (
+ cls,
+ id(inst)
+ ))
+ else:
+ inst = inst_qs[0]
+
+ try:
+ # determine if the object is changed/new
+ mtime = os.stat(vs.filename)[stat.ST_MTIME]
+ except OSError as e:
+ logger.error('unable to stat %s: %s %s' % (
+ vs.filename, type(e), e))
+ # treat as if missing from rcynic.xml
+ # use inst_qs rather than deleting inst so that we don't raise an
+ # exception for newly created objects (inst_qs will be empty)
+ inst_qs.delete()
+ return
+
+ if mtime != inst.mtime:
+ inst.mtime = mtime
+ try:
+ obj = vs.obj # causes object to be lazily loaded
+ except Exception, e:
+ logger.warning('Caught %s while processing %s: %s' % (
+ type(e), vs.filename, e))
+ return
+
+ inst.not_before = obj.notBefore.to_sql()
+ inst.not_after = obj.notAfter.to_sql()
+ inst.name = obj.subject
+ inst.keyid = obj.ski
+
+ # look up signing cert
+ if obj.issuer == obj.subject:
+ # self-signed cert (TA)
+ assert(isinstance(inst, models.Cert))
+ inst.issuer = None
+ else:
+ # if an object has moved in the repository, the entry for
+ # the old location will still be in the database, but
+ # without any object_accepted in its validtion status
+ qs = models.Cert.objects.filter(
+ keyid=obj.aki,
+ name=obj.issuer,
+ repo__statuses__status=object_accepted
+ )
+ ncerts = len(qs)
+ if ncerts == 0:
+ logger.warning('unable to find signing cert with ski=%s (%s)' % (obj.aki, obj.issuer))
+ return
+ else:
+ if ncerts > 1:
+ # multiple matching certs, all of which are valid
+ logger.warning('Found multiple certs matching ski=%s sn=%s' % (obj.aki, obj.issuer))
+ for c in qs:
+ logger.warning(c.repo.uri)
+ # just use the first match
+ inst.issuer = qs[0]
+
+ try:
+ # do object-specific tasks
+ dispatch[vs.file_class.__name__](obj, inst)
+ except:
+ logger.error('caught exception while processing rcynic_object:\n'
+ 'vs=' + repr(vs) + '\nobj=' + repr(obj))
+ # .show() writes to stdout
+ obj.show()
+ raise
+
+ logger.debug('object saved id=%s' % id(inst))
+ else:
+ logger.debug('object is unchanged')
+
+
+@transaction.commit_on_success
+def process_cache(root, xml_file):
+
+ last_uri = None
+ repo = None
+
+ logger.info('clearing validation statuses')
+ models.ValidationStatus.objects.all().delete()
+
+ logger.info('updating validation status')
+ for vs in rcynic_xml_iterator(root, xml_file):
+ if vs.uri != last_uri:
+ repo, created = models.RepositoryObject.objects.get_or_create(uri=vs.uri)
+ last_uri = vs.uri
+ save_status(repo, vs)
+
+ # garbage collection
+ # remove all objects which have no ValidationStatus references, which
+ # means they did not appear in the last XML output
+ logger.info('performing garbage collection')
+
+ # Delete all objects that have zero validation status elements.
+ models.RepositoryObject.objects.annotate(num_statuses=django.db.models.Count('statuses')).filter(num_statuses=0).delete()
+
+ # Delete all SignedObject instances that were not accepted. There may
+ # exist rows for objects that were previously accepted.
+ # See https://trac.rpki.net/ticket/588#comment:30
+ #
+ # We have to do this here rather than in save_status() because the
+ # <validation_status/> elements are not guaranteed to be consecutive for a
+ # given URI. see https://trac.rpki.net/ticket/625#comment:5
+ models.SignedObject.objects.exclude(repo__statuses__status=object_accepted).delete()
+
+ # ROAPrefixV* objects are M2M so they are not automatically deleted when
+ # their ROA object disappears
+ models.ROAPrefixV4.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete()
+ models.ROAPrefixV6.objects.annotate(num_roas=django.db.models.Count('roas')).filter(num_roas=0).delete()
+ logger.info('done with garbage collection')
+
+
+@transaction.commit_on_success
+def process_labels(xml_file):
+ logger.info('updating labels...')
+
+ for label, kind, desc in label_iterator(xml_file):
+ logger.debug('label=%s kind=%s desc=%s' % (label, kind, desc))
+ if kind:
+ q = models.ValidationLabel.objects.filter(label=label)
+ if not q:
+ obj = models.ValidationLabel(label=label)
+ else:
+ obj = q[0]
+
+ obj.kind = models.kinds_dict[kind]
+ obj.status = desc
+ obj.save()
+
+ LABEL_CACHE[label] = obj
+
+
+def fetch_published_objects():
+ """Query rpkid for all objects published by local users, and look up the
+ current validation status of each object. The validation status is used
+ later to send alerts for objects which have transitioned to invalid.
+
+ """
+ logger.info('querying for published objects')
+
+ handles = [conf.handle for conf in Conf.objects.all()]
+ req = [rpki.left_right.list_published_objects_elt.make_pdu(action='list', self_handle=h, tag=h) for h in handles]
+ z = Zookeeper()
+ pdus = z.call_rpkid(*req)
+ for pdu in pdus:
+ if isinstance(pdu, rpki.left_right.list_published_objects_elt):
+ # Look up the object in the rcynic cache
+ qs = models.RepositoryObject.objects.filter(uri=pdu.uri)
+ if qs:
+ # get the current validity state
+ valid = qs[0].statuses.filter(status=object_accepted).exists()
+ uris[pdu.uri] = (pdu.self_handle, valid, False, None)
+ logger.debug('adding ' + pdu.uri)
+ else:
+ # this object is not in the cache. it was either published
+ # recently, or disappared previously. if it disappeared
+ # previously, it has already been alerted. in either case, we
+ # omit the uri from the list since we are interested only in
+ # objects which were valid and are no longer valid
+ pass
+ elif isinstance(pdu, rpki.left_right.report_error_elt):
+ logging.error('rpkid reported an error: %s' % pdu.error_code)
+
+
+class Handle(object):
+ def __init__(self):
+ self.invalid = []
+ self.missing = []
+
+ def add_invalid(self, v):
+ self.invalid.append(v)
+
+ def add_missing(self, v):
+ self.missing.append(v)
+
+
+def notify_invalid():
+ """Send email alerts to the addresses registered in ghostbuster records for
+ any invalid objects that were published by users of this system.
+
+ """
+
+ logger.info('sending notifications for invalid objects')
+
+ # group invalid objects by user
+ notify = {}
+ for uri, v in uris.iteritems():
+ handle, old_status, new_status, obj = v
+
+ if obj is None:
+ # object went missing
+ n = notify.get(handle, Handle())
+ n.add_missing(uri)
+ # only select valid->invalid
+ elif old_status and not new_status:
+ n = notify.get(handle, Handle())
+ n.add_invalid(obj)
+
+ for handle, v in notify.iteritems():
+ conf = Conf.objects.get(handle)
+
+ msg = StringIO()
+ msg.write('This is an alert about problems with objects published by '
+ 'the resource handle %s.\n\n' % handle)
+
+ if v.invalid:
+ msg.write('The following objects were previously valid, but are '
+ 'now invalid:\n')
+
+ for o in v.invalid:
+ msg.write('\n')
+ msg.write(o.repo.uri)
+ msg.write('\n')
+ for s in o.statuses.all():
+ msg.write('\t')
+ msg.write(s.status.label)
+ msg.write(': ')
+ msg.write(s.status.status)
+ msg.write('\n')
+
+ if v.missing:
+ msg.write('The following objects were previously valid but are no '
+ 'longer in the cache:\n')
+
+ for o in v.missing:
+ msg.write(o)
+ msg.write('\n')
+
+ msg.write("""--
+You are receiving this email because your address is published in a Ghostbuster
+record, or is the default email address for this resource holder account on
+%s.""" % getfqdn())
+
+ from_email = 'root@' + getfqdn()
+ subj = 'invalid RPKI object alert for resource handle %s' % conf.handle
+ conf.send_alert(subj, msg.getvalue(), from_email, severity=Alert.ERROR)
+
+
+def import_rcynic_xml(root=default_root, logfile=default_logfile):
+ """Load the contents of rcynic.xml into the rpki.gui.cacheview database."""
+
+ global object_accepted
+
+ start = time.time()
+ process_labels(logfile)
+ object_accepted = LABEL_CACHE['object_accepted']
+ fetch_published_objects()
+ process_cache(root, logfile)
+ notify_invalid()
+
+ rpki.gui.app.timestamp.update('rcynic_import')
+
+ stop = time.time()
+ logger.info('elapsed time %d seconds.' % (stop - start))
diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py
new file mode 100644
index 00000000..94870eb2
--- /dev/null
+++ b/rpki/gui/cacheview/views.py
@@ -0,0 +1,172 @@
+# Copyright (C) 2011 SPARTA, Inc. dba Cobham Analytic Solutions
+# Copyright (C) 2013 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.
+
+__version__ = '$Id$'
+
+from django.views.generic import DetailView
+from django.shortcuts import render
+from django.db.models import F
+
+from rpki.gui.cacheview import models, forms, misc
+from rpki.resource_set import resource_range_as, resource_range_ip
+from rpki.POW import IPAddress
+from rpki.exceptions import BadIPResource
+
+
+def cert_chain(obj):
+ """
+ returns an iterator covering all certs from the root cert down to the EE.
+ """
+ chain = [obj]
+ while obj != obj.issuer:
+ obj = obj.issuer
+ chain.append(obj)
+ return zip(range(len(chain)), reversed(chain))
+
+
+class SignedObjectDetailView(DetailView):
+ def get_context_data(self, **kwargs):
+ context = super(SignedObjectDetailView,
+ self).get_context_data(**kwargs)
+ context['chain'] = cert_chain(self.object)
+ return context
+
+
+class RoaDetailView(SignedObjectDetailView):
+ model = models.ROA
+
+
+class CertDetailView(SignedObjectDetailView):
+ model = models.Cert
+
+
+class GhostbusterDetailView(SignedObjectDetailView):
+ model = models.Ghostbuster
+
+
+def search_view(request):
+ certs = None
+ roas = None
+
+ if request.method == 'POST':
+ form = forms.SearchForm2(request.POST, request.FILES)
+ if form.is_valid():
+ resource = form.cleaned_data.get('resource')
+ # try to determine the type of input given
+ try:
+ r = resource_range_as.parse_str(resource)
+ certs = models.Cert.objects.filter(asns__min__gte=r.min,
+ asns__max__lte=r.max)
+ roas = models.ROA.objects.filter(asid__gte=r.min,
+ asid__lte=r.max)
+ except:
+ try:
+ r = resource_range_ip.parse_str(resource)
+ if r.version == 4:
+ certs = models.Cert.objects.filter(
+ addresses__prefix_min__lte=r.min,
+ addresses__prefix_max__gte=r.max)
+ roas = models.ROA.objects.filter(
+ prefixes__prefix_min__lte=r.min,
+ prefixes__prefix_max__gte=r.max)
+ else:
+ certs = models.Cert.objects.filter(
+ addresses_v6__prefix_min__lte=r.min,
+ addresses_v6__prefix_max__gte=r.max)
+ roas = models.ROA.objects.filter(
+ prefixes_v6__prefix_min__lte=r.min,
+ prefixes_v6__prefix_max__gte=r.max)
+ except BadIPResource:
+ pass
+
+ return render(request, 'cacheview/search_result.html',
+ {'resource': resource, 'certs': certs, 'roas': roas})
+
+
+def cmp_prefix(x, y):
+ r = cmp(x[0].family, y[0].family)
+ if r == 0:
+ r = cmp(x[2], y[2]) # integer address
+ if r == 0:
+ r = cmp(x[0].bits, y[0].bits)
+ if r == 0:
+ r = cmp(x[0].max_length, y[0].max_length)
+ if r == 0:
+ r = cmp(x[1].asid, y[1].asid)
+ return r
+
+
+#def cmp_prefix(x,y):
+# for attr in ('family', 'prefix', 'bits', 'max_length'):
+# r = cmp(getattr(x[0], attr), getattr(y[0], attr))
+# if r:
+# return r
+# return cmp(x[1].asid, y[1].asid)
+
+
+def query_view(request):
+ """
+ Allow the user to search for an AS or prefix, and show all published ROA
+ information.
+ """
+
+ if request.method == 'POST':
+ form = forms.SearchForm(request.POST, request.FILES)
+ if form.is_valid():
+ certs = None
+ roas = None
+
+ addr = form.cleaned_data.get('addr')
+ asn = form.cleaned_data.get('asn')
+
+ if addr:
+ family, r = misc.parse_ipaddr(addr)
+ prefixes = models.ROAPrefix.objects.filter(family=family, prefix=str(r.min))
+
+ prefix_list = []
+ for pfx in prefixes:
+ for roa in pfx.roas.all():
+ prefix_list.append((pfx, roa))
+ elif asn:
+ r = resource_range_as.parse_str(asn)
+ roas = models.ROA.objects.filter(asid__gte=r.min, asid__lte=r.max)
+
+ # display the results sorted by prefix
+ prefix_list = []
+ for roa in roas:
+ for pfx in roa.prefixes.all():
+ addr = IPAddress(pfx.prefix.encode())
+ prefix_list.append((pfx, roa, addr))
+ prefix_list.sort(cmp=cmp_prefix)
+
+ return render('cacheview/query_result.html',
+ {'object_list': prefix_list}, request)
+ else:
+ form = forms.SearchForm()
+
+ return render('cacheview/search_form.html', {
+ 'form': form, 'search_type': 'ROA '}, request)
+
+
+def global_summary(request):
+ """Display a table summarizing the state of the global RPKI."""
+
+ roots = models.Cert.objects.filter(issuer=F('pk')) # self-signed
+
+ return render(request, 'cacheview/global_summary.html', {
+ 'roots': roots
+ })
+
+# vim:sw=4 ts=8 expandtab