diff options
Diffstat (limited to 'rpki/gui/app/models.py')
-rw-r--r-- | rpki/gui/app/models.py | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py new file mode 100644 index 00000000..7d643fdc --- /dev/null +++ b/rpki/gui/app/models.py @@ -0,0 +1,420 @@ +# 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 + +import rpki.resource_set +import rpki.exceptions +import rpki.irdb.models +import rpki.gui.models +import rpki.gui.routeview.models +from south.modelsinspector import add_introspection_rules + + +class TelephoneField(models.CharField): + def __init__(self, **kwargs): + if 'max_length' not in kwargs: + kwargs['max_length'] = 40 + models.CharField.__init__(self, **kwargs) + +add_introspection_rules([], ['^rpki\.gui\.app\.models\.TelephoneField']) + + +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' + + +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 <self> + 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. + + """ + return Child.objects.filter(issuer=self) + + @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 + q = models.Q() + for p in ResourceRangeAddressV4.objects.filter(cert__conf=self): + q |= models.Q(prefix_min__gte=p.prefix_min, + prefix_max__lte=p.prefix_max) + return RouteOrigin.objects.filter(q) + + @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 + 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) + + 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() + + + class Meta: + proxy = True + + +class ResourceCert(models.Model): + """Represents a resource certificate. + + This model is used to cache the output of <list_received_resources/>. + + """ + + # 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')) |