aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Fenner <fenner@electricrain.com>2010-02-23 23:10:58 +0000
committerBill Fenner <fenner@electricrain.com>2010-02-23 23:10:58 +0000
commit5c52490fc13d7f75cc3759b78d0c8ce6d60bb58b (patch)
treeb8d48dcaf40dc4c5ff517738e2877392356f2038
parentb01b3e3fac2db84b01aae61df6c8de3ff3d374c3 (diff)
A fairly functional cert editor (still copy-n-paste, though, the
file upload is todo). Dashboard is driven from the database, but is missing some critical functionality and is horroshow ugly. svn path=/portal-gui/rpki/TODO; revision=3003
-rw-r--r--portal-gui/rpki/TODO28
-rw-r--r--portal-gui/rpki/myrpki/admin.py32
-rw-r--r--portal-gui/rpki/myrpki/forms.py17
-rw-r--r--portal-gui/rpki/myrpki/models.py33
-rw-r--r--portal-gui/rpki/myrpki/urls.py2
-rw-r--r--portal-gui/rpki/myrpki/views.py111
-rw-r--r--portal-gui/rpki/settings.py8
-rw-r--r--portal-gui/rpki/templates/base.html5
-rw-r--r--portal-gui/rpki/templates/myrpki/cert_detail.html4
-rw-r--r--portal-gui/rpki/templates/myrpki/cert_form.html2
-rw-r--r--portal-gui/rpki/templates/myrpki/cert_list.html14
-rw-r--r--portal-gui/rpki/templates/myrpki/dashboard.html87
-rw-r--r--portal-gui/rpki/urls.py5
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&lt;something&gt; 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/'}),
+
)