diff options
-rw-r--r-- | portal-gui/rpki/TODO | 28 | ||||
-rw-r--r-- | portal-gui/rpki/myrpki/admin.py | 32 | ||||
-rw-r--r-- | portal-gui/rpki/myrpki/forms.py | 17 | ||||
-rw-r--r-- | portal-gui/rpki/myrpki/models.py | 33 | ||||
-rw-r--r-- | portal-gui/rpki/myrpki/urls.py | 2 | ||||
-rw-r--r-- | portal-gui/rpki/myrpki/views.py | 111 | ||||
-rw-r--r-- | portal-gui/rpki/settings.py | 8 | ||||
-rw-r--r-- | portal-gui/rpki/templates/base.html | 5 | ||||
-rw-r--r-- | portal-gui/rpki/templates/myrpki/cert_detail.html | 4 | ||||
-rw-r--r-- | portal-gui/rpki/templates/myrpki/cert_form.html | 2 | ||||
-rw-r--r-- | portal-gui/rpki/templates/myrpki/cert_list.html | 14 | ||||
-rw-r--r-- | portal-gui/rpki/templates/myrpki/dashboard.html | 87 | ||||
-rw-r--r-- | portal-gui/rpki/urls.py | 5 |
13 files changed, 289 insertions, 59 deletions
diff --git a/portal-gui/rpki/TODO b/portal-gui/rpki/TODO new file mode 100644 index 00000000..2f51377e --- /dev/null +++ b/portal-gui/rpki/TODO @@ -0,0 +1,28 @@ +Use RequestContext (helper function for render_to_response) and a default +list of context processors for the generic functions + +Teach cert_delete about children, conf*, parent* to say what the ramifications +of deleting a cert are. + +Teach cert form about file upload + +Redirect /accounts/profile/ to /dashboard/ + +Teach dashboard view about looking up resources from parent. +There are 3 types of resources: +- Ones we've accepted and match +- Ones we've accepted but don't match + - two subtypes: + * the parent is now giving us a superset of what they used to. + This is relatively easily handled by keeping the subdivisions + we've made and just making the superset resource the new parent + of the existing resource (e.g., we had accepted 18.5.0.0/16 and + they're now giving us 18.0.0.0/8) + * the parent is now giving us a subset (including none) of what they + used to. Two sub-cases: + - The part that they took away is neither delegated nor roa'd. + - The part that they took away is either delegated or roa'd or both. +- Ones we haven't accepted yet + +The roa needs to learn to handle its prefix children. It may need to +create the covering set of prefixes for an address range. diff --git a/portal-gui/rpki/myrpki/admin.py b/portal-gui/rpki/myrpki/admin.py new file mode 100644 index 00000000..a5a91043 --- /dev/null +++ b/portal-gui/rpki/myrpki/admin.py @@ -0,0 +1,32 @@ +from django import forms +from django.contrib import admin +from rpki.myrpki import models + +class CertAdmin( admin.ModelAdmin ): + pass + +class ConfAdmin( admin.ModelAdmin ): + pass + +class ChildAdmin( admin.ModelAdmin ): + pass + +class AddressRangeAdmin( admin.ModelAdmin ): + pass + +class AsnAdmin( admin.ModelAdmin ): + pass + +class ParentAdmin( admin.ModelAdmin ): + pass + +class RoaAdmin( admin.ModelAdmin ): + pass + +admin.site.register( models.Cert, CertAdmin ) +admin.site.register( models.Conf, ConfAdmin ) +admin.site.register( models.Child, ChildAdmin ) +admin.site.register( models.AddressRange, AddressRangeAdmin ) +admin.site.register( models.Asn, AsnAdmin ) +admin.site.register( models.Parent, ParentAdmin ) +admin.site.register( models.Roa, RoaAdmin ) diff --git a/portal-gui/rpki/myrpki/forms.py b/portal-gui/rpki/myrpki/forms.py index fda6faaa..b91f6acc 100644 --- a/portal-gui/rpki/myrpki/forms.py +++ b/portal-gui/rpki/myrpki/forms.py @@ -1,8 +1,17 @@ from django import forms from myrpki import models -# TODO: Point the cert.conf to the handle from the session -class CertForm( forms.ModelForm ): - class Meta: - model = models.Cert +def ConfCertForm( request ): + class CertForm( forms.ModelForm ): + class Meta: + model = models.Cert + exclude = ( 'conf' ) + + def save( self ): + obj = forms.ModelForm.save( self, commit=False ) + obj.conf = request.session[ 'handle' ] + obj.save() + return obj + + return CertForm diff --git a/portal-gui/rpki/myrpki/models.py b/portal-gui/rpki/myrpki/models.py index 5c765eae..0e909a68 100644 --- a/portal-gui/rpki/myrpki/models.py +++ b/portal-gui/rpki/myrpki/models.py @@ -12,24 +12,31 @@ class IPAddressField( models.CharField ): models.CharField.__init__( self, max_length=40, **kwargs ) class Cert( models.Model ): + '''A certificate, relating to a single configuration.''' + conf = models.ForeignKey( 'Conf', related_name='certs' ) name = models.CharField( unique=True, max_length=255 ) data = models.TextField() def __unicode__( self ): - return self.name + return "%s's %s" % ( self.conf, self.name ) class Conf( models.Model ): - '''This is the center of the universe. + '''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. ''' handle = HandleField( unique=True, db_index=True ) repository_bpki_cert = models.ForeignKey( Cert, - related_name='conf_bpki_cert' ) - my_bpki_ta = models.ForeignKey( Cert, related_name='conf_my_ta' ) + related_name='conf_bpki_cert', + null=True, blank=True ) + my_bpki_ta = models.ForeignKey( Cert, related_name='conf_my_ta', + null=True, blank=True ) repository_handle = HandleField() owner = models.OneToOneField( Group ) def __unicode__( self ): return self.handle class AddressRange( models.Model ): + '''An address range / prefix.''' lo = IPAddressField() hi = IPAddressField() parent = models.ForeignKey( 'AddressRange', related_name='children', @@ -38,6 +45,7 @@ class AddressRange( models.Model ): return u"address range %s-%s" % ( self.lo, self.hi ) class Asn( models.Model ): + '''An ASN or range thereof.''' min = models.IntegerField() max = models.IntegerField() parent = models.ForeignKey( 'Asn', related_name='children', @@ -49,17 +57,20 @@ class Asn( models.Model ): return u"ASNs %d-%d" % ( self.min, self.max ) class Child( models.Model ): - conf = models.ForeignKey( Conf ) + conf = models.ForeignKey( Conf, related_name='children' ) handle = HandleField() validity = models.DateTimeField() - bpki_cert = models.ForeignKey( Cert ) + bpki_cert = models.ForeignKey( Cert, related_name='child_bpki' ) address_range = models.ManyToManyField( AddressRange ) asn = models.ManyToManyField( Asn ) def __unicode__( self ): return u"%s's child %s" % ( self.conf, self.handle ) + class Meta: + verbose_name_plural = "children" + class Parent( models.Model ): - conf = models.ForeignKey( Conf ) + conf = models.ForeignKey( Conf, related_name='parents' ) handle = HandleField( unique=True ) service_uri = models.URLField( verify_exists=False ) cms_bpki_cert = models.ForeignKey( Cert, related_name='parent_cms' ) @@ -72,14 +83,8 @@ class Parent( models.Model ): def __unicode__( self ): return u"%s's parent %s" % ( self.conf, self.handle ) -# This table is really owned by the publication server. -#class PubClient( models.Model ): -# handle = models.CharField( unique=True, max_length=255 ) -# bpki_cert = models.ForeignKey( Cert ) -# sia_base = models.URLField( verify_exists=False ) - class Roa( models.Model ): - conf = models.ForeignKey( Conf ) + conf = models.ForeignKey( Conf, related_name='roas' ) prefix = models.ManyToManyField( AddressRange ) max_len = models.IntegerField() asn = models.IntegerField() diff --git a/portal-gui/rpki/myrpki/urls.py b/portal-gui/rpki/myrpki/urls.py index f0fc56d6..f5ba7c90 100644 --- a/portal-gui/rpki/myrpki/urls.py +++ b/portal-gui/rpki/myrpki/urls.py @@ -1,7 +1,9 @@ from django.conf.urls.defaults import * +from django.views.generic.list_detail import object_list import views urlpatterns = patterns('', + (r'^cert/$', views.cert_list ), (r'^cert/add/$', views.cert_add ), (r'^cert/(?P<id>\d+)/$', views.cert_view ), (r'^cert/(?P<id>\d+)/edit/$', views.cert_edit ), diff --git a/portal-gui/rpki/myrpki/views.py b/portal-gui/rpki/myrpki/views.py index bd0e5a3c..68cf7eba 100644 --- a/portal-gui/rpki/myrpki/views.py +++ b/portal-gui/rpki/myrpki/views.py @@ -1,8 +1,12 @@ from django.views.generic.create_update import create_object, update_object, \ delete_object -from django.views.generic.list_detail import object_detail +from django.views.generic.list_detail import object_detail, object_list from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, render_to_response +from django.utils.http import urlquote +from django.template import RequestContext +from django import http +from functools import update_wrapper import models import forms @@ -11,53 +15,82 @@ import forms # an update view. We heavily leverage the generic views, only # adding our own idea of authorization. -def handle( request ): - '''If the session has a handle, return the config. If the user only has - one config that he can access, return that one; else return None.''' - if 'handle' in request.session: - return Conf.objects.get( handle=request.session[ 'handle' ] ) - conf = Conf.objects.all().filter( owner__in=request.user.groups ) - if conf.count() == 1: - return conf[ 0 ] - return None - -def choose_handle( request ): - '''The logged-in user can access multiple (or no) handles. - Ask them to pick which one(s) they want to access.''' - raise NotImplementedError - -@login_required +class handle_required(object): + '''A decorator to require picking a configuration. __call__ is + decorated with login_required so that we can be sure that the + request has a user. + + We don't support picking the configuration yet -- if multiple + configurations match, we redirect to handle_picker, which should + allow a staff member to pick any handle. + ''' + + def __init__(self, f): + self.f = f + update_wrapper( self, f ) + + @login_required + def __call__(self, request, *args, **kwargs): + if 'handle' not in request.session: + conf = models.Conf.objects.all().filter( + owner__in=request.user.groups.all() ) + if conf.count() == 1: + handle = conf[ 0 ] + else: + # Should reverse the view for this instead of hardcoding + # the URL. + return http.HttpResponseRedirect( '/handle_picker/?next=%s' % + urlquote(request.get_full_path()) ) + request.session[ 'handle' ] = handle + return self.f(request, *args, **kwargs) + +def render( template, context, request ): + return render_to_response( template, context, + context_instance=RequestContext(request) ) + +@handle_required def dashboard( request ): - '''The user's dashboard. If the handle is not specified, - see what the user has access to based on his groups. If - multiple, give him a selector and store the result in the - session.''' - handle = handle( request ) - if handle is None: - return choose_handle( request ) + '''The user's dashboard.''' + handle = request.session[ 'handle' ] # ... pick out data for the dashboard and return it - return render_to_response( 'myrpki/dashboard.html', context={ 'conf': handle } ) + # my parents + # the resources that my parents have given me + # the reousrces that I have accepted from my parents + # my children + # the resources that I have given my children + # my roas + return render( 'myrpki/dashboard.html', { 'conf': handle }, request ) -@login_required +@handle_required def cert_add( request ): - # todo: enforce that the saved form points to this conf - return create_object( request, form_class=forms.CertForm ) + return create_object( request, form_class=forms.ConfCertForm( request ), + post_save_redirect='/myrpki/cert/' ) -@login_required +@handle_required def cert_view( request, id ): - handle = handle( request ) - queryset = Cert.objects.filter( conf=handle ) - return object_detail( queryset=queryset, object_id=id ) + handle = request.session[ 'handle' ] + queryset = models.Cert.objects.filter( conf=handle ) + return object_detail( request, queryset=queryset, object_id=id, + template_object_name='cert' ) + +@handle_required +def cert_list( request ): + handle = request.session[ 'handle' ] + queryset = models.Cert.objects.filter( conf=handle ) + return object_list( request, queryset=queryset, + template_object_name='cert' ) -@login_required +@handle_required def cert_edit( request, id ): - cert = get_object_or_404( models.Cert, pk=id ) - # make sure it is owned by the current handle - return update_object( request, form_class=forms.CertForm, object_id=id ) + handle = request.session[ 'handle' ] + cert = get_object_or_404( models.Cert, pk=id, conf=handle ) + return update_object( request, form_class=forms.ConfCertForm( request ), + object_id=id, + post_save_redirect='/myrpki/cert/' ) -@login_required +@handle_required def cert_delete( request, id ): - cert = get_object_or_404( models.Cert, pk=id ) - # make sure it is owned by the current handle + handle = request.session[ 'handle' ] + cert = get_object_or_404( models.Cert, pk=id, conf=handle ) return delete_object( request, model=models.Cert, object_id=id, post_delete_redirect='/dashboard/' ) diff --git a/portal-gui/rpki/settings.py b/portal-gui/rpki/settings.py index c4147149..af8417e8 100644 --- a/portal-gui/rpki/settings.py +++ b/portal-gui/rpki/settings.py @@ -83,3 +83,11 @@ INSTALLED_APPS = ( 'rpki.myrpki', 'rpki.django_extensions', ) + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.core.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.request", +) diff --git a/portal-gui/rpki/templates/base.html b/portal-gui/rpki/templates/base.html index 43def531..1f6018bf 100644 --- a/portal-gui/rpki/templates/base.html +++ b/portal-gui/rpki/templates/base.html @@ -8,9 +8,14 @@ </style> </head> <body> + <div id="header"> + <img src="/site_media/img/my.png"> + <img src="/site_media/img/rpki.png"> + </div> <div id="content"> {% if user.is_authenticated %} <span style="float: right; font-size: 80%;">Logged in as {{ user }} | + {% if user.is_staff %}<a href="/admin/">admin</a> |{% endif %} <a href="{% url django.contrib.auth.views.logout %}">Log Out</a></span> {% else %} <span style="float: right; font-size: 80%;"><a href="{% url django.contrib.auth.views.login %}">Log In</a></span> diff --git a/portal-gui/rpki/templates/myrpki/cert_detail.html b/portal-gui/rpki/templates/myrpki/cert_detail.html index d9a86c80..e9d11074 100644 --- a/portal-gui/rpki/templates/myrpki/cert_detail.html +++ b/portal-gui/rpki/templates/myrpki/cert_detail.html @@ -3,10 +3,10 @@ {% block content %} <h1>Cert {{ cert.name }}</h1> -<p>I dunno if this is interesting, but if it is: - <pre> {{ cert.data }} </pre> +<p><a href="edit/">[edit]</a> | <a href="delete/">[delete]</a> + {% endblock %} diff --git a/portal-gui/rpki/templates/myrpki/cert_form.html b/portal-gui/rpki/templates/myrpki/cert_form.html index 31a0fa3f..2ff95fb0 100644 --- a/portal-gui/rpki/templates/myrpki/cert_form.html +++ b/portal-gui/rpki/templates/myrpki/cert_form.html @@ -4,7 +4,9 @@ <h1>Add/Edit Cert</h1> <form action="" method="post"> +<table> {{ form }} +</table> <input type="submit" value="Submit" /> </form> diff --git a/portal-gui/rpki/templates/myrpki/cert_list.html b/portal-gui/rpki/templates/myrpki/cert_list.html new file mode 100644 index 00000000..882a1301 --- /dev/null +++ b/portal-gui/rpki/templates/myrpki/cert_list.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block content %} +<h1>Certificates for {{ request.session.handle }}</h1> + +<p> +<a href="add/">[add]</a> +<p> +<ul> +{% for cert in cert_list %} +<li><a href="{{ cert.id }}/">{{ cert.name }}</a> +{% endfor %} +</ul> +{% endblock %} diff --git a/portal-gui/rpki/templates/myrpki/dashboard.html b/portal-gui/rpki/templates/myrpki/dashboard.html new file mode 100644 index 00000000..aef2b6cd --- /dev/null +++ b/portal-gui/rpki/templates/myrpki/dashboard.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} + +{% block content %} +<div style="border: inset"> +<h1 style="text-align: center">Parents</h1> +<a href="#">[add]</a> +<ul> +{% for parent in request.session.handle.parents.all %} +<li>{{ parent.handle }} (knows me as {{ parent.my_handle }}) +{% if parent.asn.count or parent.address_range.count %} +<p>Accepted resources: +<ul> +{% for asn in parent.asn.all %} +<li>{{ asn }} +{% endfor %} +{% for address in parent.address_range.all %} +<li>{{ address }} +{% endfor %} +</ul> +{% endif %} +{% endfor %} +</ul> +</div> +<span> +<div style="border: outset"> +<h1 style="text-align: center">Children</h1> +<a href="#">[add]</a> +<p> +<ul> +{% for child in request.session.handle.children.all %} +<li>{{ child.handle }} +{% if child.address_range.count or child.asn.count %} +<p>Delegated resources: +<ul> +{% for asn in child.asn.all %} +<li>{{ asn }} +{% endfor %} +{% for address in child.address_range.all %} +<li>{{ address }} +{% endfor %} +</ul> +{% endif %} +{% endfor %} +</ul> +</div> +<div style="border: outset"> +<h1 style="text-align: center">My ROA [request]s</h1> +{% if request.session.handle.roas.count %} +<table style="border: groove; width: 100%" border="1"> +<tr> +<th>Prefix</th> +<th>ASN</th> +<th>Max Len</th> +<th>Active</th> +<th>Comments</th> +</tr> +{% for roa in request.session.handle.roas.all %} +{% for address_range in roa.prefix.all %} +<tr> +<td>{{ address_range }}</td> +<td>{{ roa.asn }}</td> +<td>{{ roa.max_len }}</td> +<td>{{ roa.active }}</td> +<td>{{ roa.comments }}</td> +</tr> +{% endfor %} +{% endfor %} +</table> +{% else %} +<p> +-- none -- +{% endif %} +</div> +<div style="border: outset"> +<h1 style="text-align: center">Un<something> Resources</h1> +<ul> +<li>Address range 172.17.2.0-172.17.255.255 + <a href="#">[subdivide]</a> | + <a href="#">[give to child]</a> | + <a href="#">[issue roa]</a> +<li>ASN 1 <a href="#">[give to child]</a> +<li>ASNs 3-100 <a href="#">[subdivide]</a> | + <a href="#">[give to child]</a> +</ul> +</div> +</span> +{% endblock %} diff --git a/portal-gui/rpki/urls.py b/portal-gui/rpki/urls.py index e8e783c9..f754fb23 100644 --- a/portal-gui/rpki/urls.py +++ b/portal-gui/rpki/urls.py @@ -19,4 +19,9 @@ urlpatterns = patterns('', (r'^accounts/login/$', 'django.contrib.auth.views.login'), (r'^accounts/logout/$', 'django.contrib.auth.views.logout'), + +#XXX +(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': '/Users/fenner/src/portal-gui/media/'}), + ) |