# Copyright (C) 2010 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 django.db import models
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.db.models import Q
import rpki.resource_set
import rpki.exceptions
import rpki.irdb.models
import rpki.gui.models
import rpki.gui.routeview.models
import rpki.oids
class TelephoneField(models.CharField):
def __init__(self, **kwargs):
if 'max_length' not in kwargs:
kwargs['max_length'] = 40
models.CharField.__init__(self, **kwargs)
class Parent(rpki.irdb.models.Parent):
"""proxy model for irdb Parent"""
def __unicode__(self):
return u"%s's parent %s" % (self.issuer.handle, self.handle)
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.parent_detail', [str(self.pk)])
class Meta:
proxy = True
class Child(rpki.irdb.models.Child):
"""proxy model for irdb Child"""
def __unicode__(self):
return u"%s's child %s" % (self.issuer.handle, self.handle)
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.child_detail', [str(self.pk)])
class Meta:
proxy = True
verbose_name_plural = 'children'
@property
def routes(self):
"Return a list of RouteOrigin objects (potentially) originated by this child."
query = Q()
for r in self.address_ranges.filter(version='IPv4'):
rng = r.as_resource_range()
query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max)
return RouteOrigin.objects.filter(query)
class ChildASN(rpki.irdb.models.ChildASN):
"""Proxy model for irdb ChildASN."""
class Meta:
proxy = True
def __unicode__(self):
return u'AS%s' % self.as_resource_range()
class ChildNet(rpki.irdb.models.ChildNet):
"""Proxy model for irdb ChildNet."""
class Meta:
proxy = True
def __unicode__(self):
return u'%s' % self.as_resource_range()
class Alert(models.Model):
"""Stores alert messages intended to be consumed by the user."""
INFO = 0
WARNING = 1
ERROR = 2
SEVERITY_CHOICES = (
(INFO, 'info'),
(WARNING, 'warning'),
(ERROR, 'error'),
)
conf = models.ForeignKey('Conf', related_name='alerts')
severity = models.SmallIntegerField(choices=SEVERITY_CHOICES, default=INFO)
when = models.DateTimeField(auto_now_add=True)
seen = models.BooleanField(default=False)
subject = models.CharField(max_length=66)
text = models.TextField()
@models.permalink
def get_absolute_url(self):
return ('alert-detail', [str(self.pk)])
class Conf(rpki.irdb.models.ResourceHolderCA):
"""This is the center of the universe, also known as a place to
have a handle on a resource-holding entity. It's the
in the rpkid schema.
"""
@property
def parents(self):
"""Simulates irdb.models.Parent.objects, but returns app.models.Parent
proxy objects.
"""
return Parent.objects.filter(issuer=self)
@property
def children(self):
"""Simulates irdb.models.Child.objects, but returns app.models.Child
proxy objects.
When running rootd, we need to exclude the Child object for self.
"""
return Child.objects.filter(issuer=self).exclude(handle=self.handle)
@property
def child_routes(self):
"""Return currently announced routes for prefixes covered by child
sub-allocations.
"""
query = Q()
for pfx in ChildNet.objects.filter(child__issuer=self, version='IPv4'):
rng = pfx.as_resource_range()
query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max)
return RouteOrigin.objects.filter(query)
@property
def ghostbusters(self):
return GhostbusterRequest.objects.filter(issuer=self)
@property
def repositories(self):
return Repository.objects.filter(issuer=self)
@property
def roas(self):
return ROARequest.objects.filter(issuer=self)
@property
def routes(self):
"""Return all IPv4 routes covered by RPKI certs issued to this resource
holder.
"""
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
prefixes = ResourceRangeAddressV4.objects.filter(cert__conf=self)
if prefixes:
q = models.Q()
for p in prefixes:
q |= models.Q(prefix_min__gte=p.prefix_min,
prefix_max__lte=p.prefix_max)
return RouteOrigin.objects.filter(q)
else:
return RouteOrigin.objects.none()
@property
def routes_v6(self):
"""Return all IPv6 routes covered by RPKI certs issued to this resource
holder.
"""
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
prefixes = ResourceRangeAddressV6.objects.filter(cert__conf=self)
if prefixes:
q = models.Q()
for p in ResourceRangeAddressV6.objects.filter(cert__conf=self):
q |= models.Q(prefix_min__gte=p.prefix_min,
prefix_max__lte=p.prefix_max)
return RouteOriginV6.objects.filter(q)
else:
return RouteOriginV6.objects.none()
def send_alert(self, subject, message, from_email, severity=Alert.INFO):
"""Store an alert for this resource holder."""
self.alerts.create(subject=subject, text=message, severity=severity)
send_mail(
subject=subject,
message=message,
from_email=from_email,
recipient_list=self.email_list
)
@property
def email_list(self):
"""Return a list of the contact emails for this resource holder.
Contact emails are extract from any ghostbuster requests, and any
linked user accounts.
"""
notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address]
notify_emails.extend(
[acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email]
)
return notify_emails
def clear_alerts(self):
self.alerts.all().delete()
@property
def router_certs(self):
"""returns a query set of all rpki router certs associated with this
resource holder."""
return self.ee_certificate_requests.filter(eku=rpki.oids.id_kp_bgpsec_router)
class Meta:
proxy = True
class ResourceCert(models.Model):
"""Represents a resource certificate.
This model is used to cache the output of .
"""
# Handle to which this cert was issued
conf = models.ForeignKey(Conf, related_name='certs')
# The parent that issued the cert. This field is marked null=True because
# the root has no parent
parent = models.ForeignKey(Parent, related_name='certs', null=True)
# certificate validity period
not_before = models.DateTimeField()
not_after = models.DateTimeField()
# Locator for this object. Used to look up the validation status, expiry
# of ancestor certs in cacheview
uri = models.CharField(max_length=255)
def __unicode__(self):
if self.parent:
return u"%s's cert from %s" % (self.conf.handle,
self.parent.handle)
else:
return u"%s's root cert" % self.conf.handle
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
cert = self
x = [cert]
while cert.issuer:
cert = cert.issuer
x.append(cert)
x.reverse()
return x
cert_chain = property(get_cert_chain)
class ResourceRangeAddressV4(rpki.gui.models.PrefixV4):
cert = models.ForeignKey(ResourceCert, related_name='address_ranges')
class ResourceRangeAddressV6(rpki.gui.models.PrefixV6):
cert = models.ForeignKey(ResourceCert, related_name='address_ranges_v6')
class ResourceRangeAS(rpki.gui.models.ASN):
cert = models.ForeignKey(ResourceCert, related_name='asn_ranges')
class ROARequest(rpki.irdb.models.ROARequest):
class Meta:
proxy = True
def __unicode__(self):
return u"%s's ROA request for AS%d" % (self.issuer.handle, self.asn)
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.roa_detail', [str(self.pk)])
@property
def routes(self):
"Return all IPv4 routes covered by this roa prefix."
# this assumes one prefix per ROA
rng = self.prefixes.filter(version=4)[0].as_resource_range()
return rpki.gui.routeview.models.RouteOrigin.objects.filter(
prefix_min__gte=rng.min,
prefix_max__lte=rng.max
)
@property
def routes_v6(self):
"Return all IPv6 routes covered by this roa prefix."
# this assumes one prefix per ROA
rng = self.prefixes.filter(version=6)[0].as_resource_range()
return rpki.gui.routeview.models.RouteOriginV6.objects.filter(
prefix_min__gte=rng.min,
prefix_max__lte=rng.max
)
class ROARequestPrefix(rpki.irdb.models.ROARequestPrefix):
class Meta:
proxy = True
def __unicode__(self):
return u'ROA Request Prefix %s' % str(self.as_roa_prefix())
class GhostbusterRequest(rpki.irdb.models.GhostbusterRequest):
"""
Stores the information require to fill out a vCard entry to
populate a ghostbusters record.
This model is inherited from the irdb GhostBusterRequest model so
that the broken out fields can be included for ease of editing.
"""
full_name = models.CharField(max_length=40)
# components of the vCard N type
family_name = models.CharField(max_length=20)
given_name = models.CharField(max_length=20)
additional_name = models.CharField(max_length=20, blank=True, null=True)
honorific_prefix = models.CharField(max_length=10, blank=True, null=True)
honorific_suffix = models.CharField(max_length=10, blank=True, null=True)
email_address = models.EmailField(blank=True, null=True)
organization = models.CharField(blank=True, null=True, max_length=255)
telephone = TelephoneField(blank=True, null=True)
# elements of the ADR type
box = models.CharField(verbose_name='P.O. Box', blank=True, null=True,
max_length=40)
extended = models.CharField(blank=True, null=True, max_length=255)
street = models.CharField(blank=True, null=True, max_length=255)
city = models.CharField(blank=True, null=True, max_length=40)
region = models.CharField(blank=True, null=True, max_length=40,
help_text='state or province')
code = models.CharField(verbose_name='Postal Code', blank=True, null=True,
max_length=40)
country = models.CharField(blank=True, null=True, max_length=40)
def __unicode__(self):
return u"%s's GBR: %s" % (self.issuer.handle, self.full_name)
@models.permalink
def get_absolute_url(self):
return ('gbr-detail', [str(self.pk)])
class Meta:
ordering = ('family_name', 'given_name')
class Timestamp(models.Model):
"""Model to hold metadata about the collection of external data.
This model is a hash table mapping a timestamp name to the
timestamp value. All timestamps values are in UTC.
The utility function rpki.gui.app.timestmap.update(name) should be used to
set timestamps rather than updating this model directly."""
name = models.CharField(max_length=30, primary_key=True)
ts = models.DateTimeField(null=False)
def __unicode__(self):
return '%s: %s' % (self.name, self.ts)
class Repository(rpki.irdb.models.Repository):
class Meta:
proxy = True
verbose_name = 'Repository'
verbose_name_plural = 'Repositories'
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.repository_detail', [str(self.pk)])
def __unicode__(self):
return "%s's repository %s" % (self.issuer.handle, self.handle)
class Client(rpki.irdb.models.Client):
"Proxy model for pubd clients."
class Meta:
proxy = True
verbose_name = 'Client'
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.client_detail', [str(self.pk)])
def __unicode__(self):
return self.handle
class RouteOrigin(rpki.gui.routeview.models.RouteOrigin):
class Meta:
proxy = True
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.route_detail', [str(self.pk)])
class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6):
class Meta:
proxy = True
@models.permalink
def get_absolute_url(self):
return ('rpki.gui.app.views.route_detail', [str(self.pk)])
class ConfACL(models.Model):
"""Stores access control for which users are allowed to manage a given
resource handle.
"""
conf = models.ForeignKey(Conf)
user = models.ForeignKey(User)
class Meta:
unique_together = (('user', 'conf'))