aboutsummaryrefslogtreecommitdiff
path: root/rpkid
diff options
context:
space:
mode:
authorMichael Elkins <melkins@tislabs.com>2013-02-26 18:57:15 +0000
committerMichael Elkins <melkins@tislabs.com>2013-02-26 18:57:15 +0000
commit3c03fb86f9797f5295ea088a717ab62c27f8a4fc (patch)
tree5e95e40f365e799c9b411a0ce6a2084fdaa91f18 /rpkid
parent72a19be015aa858ff0780c83d792c2e6af8ce38b (diff)
add support for multiple users managing the same resource holder
svn path=/trunk/; revision=5081
Diffstat (limited to 'rpkid')
-rw-r--r--rpkid/rpki/gui/app/forms.py67
-rw-r--r--rpkid/rpki/gui/app/migrations/0006_add_conf_acl.py168
-rw-r--r--rpkid/rpki/gui/app/migrations/0007_default_acls.py165
-rw-r--r--rpkid/rpki/gui/app/models.py14
-rw-r--r--rpkid/rpki/gui/app/templates/app/app_base.html8
-rw-r--r--rpkid/rpki/gui/app/templates/app/app_confirm_delete.html21
-rw-r--r--rpkid/rpki/gui/app/templates/app/app_form.html4
-rw-r--r--rpkid/rpki/gui/app/templates/app/resource_holder_list.html35
-rw-r--r--rpkid/rpki/gui/app/templates/app/user_confirm_delete.html20
-rw-r--r--rpkid/rpki/gui/app/templates/app/user_list.html43
-rw-r--r--rpkid/rpki/gui/app/urls.py4
-rw-r--r--rpkid/rpki/gui/app/views.py198
12 files changed, 623 insertions, 124 deletions
diff --git a/rpkid/rpki/gui/app/forms.py b/rpkid/rpki/gui/app/forms.py
index 1d354521..ae694568 100644
--- a/rpkid/rpki/gui/app/forms.py
+++ b/rpkid/rpki/gui/app/forms.py
@@ -108,32 +108,29 @@ class ImportClientForm(forms.Form):
class UserCreateForm(forms.Form):
- handle = forms.CharField(max_length=30, help_text='handle for new child')
+ username = forms.CharField(max_length=30)
email = forms.CharField(max_length=30,
help_text='email address for new user')
password = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput,
label='Confirm Password')
- parent = forms.ModelChoiceField(required=False,
- queryset=models.Conf.objects.all(),
- help_text='optionally make a child of')
+ resource_holders = forms.ModelMultipleChoiceField(
+ queryset=models.Conf.objects.all(),
+ help_text='allowed to manage these resource holders'
- def clean_handle(self):
- handle = self.cleaned_data.get('handle')
- if (handle and models.Conf.objects.filter(handle=handle).exists() or
- User.objects.filter(username=handle).exists()):
+ )
+
+ def clean_username(self):
+ username = self.cleaned_data.get('username')
+ if User.objects.filter(username=username).exists():
raise forms.ValidationError('user already exists')
- return handle
+ return username
def clean(self):
p1 = self.cleaned_data.get('password')
p2 = self.cleaned_data.get('password2')
if p1 != p2:
raise forms.ValidationError('passwords do not match')
- handle = self.cleaned_data.get('handle')
- parent = self.cleaned_data.get('parent')
- if handle and parent and parent.children.filter(handle=handle).exists():
- raise forms.ValidationError('parent already has a child by that name')
return self.cleaned_data
@@ -143,7 +140,11 @@ class UserEditForm(forms.Form):
pw = forms.CharField(widget=forms.PasswordInput, label='Password',
required=False)
pw2 = forms.CharField(widget=forms.PasswordInput, label='Confirm password',
- required=False)
+ required=False)
+ resource_holders = forms.ModelMultipleChoiceField(
+ queryset=models.Conf.objects.all(),
+ help_text='allowed to manage these resource holders'
+ )
def clean(self):
p1 = self.cleaned_data.get('pw')
@@ -400,3 +401,41 @@ def ChildForm(instance):
class Empty(forms.Form):
"""Stub form for views requiring confirmation."""
pass
+
+
+class ResourceHolderForm(forms.Form):
+ """form for editing ACL on Conf objects."""
+ users = forms.ModelMultipleChoiceField(
+ queryset=User.objects.all(),
+ help_text='users allowed to mange this resource holder'
+ )
+
+
+class ResourceHolderCreateForm(forms.Form):
+ """form for creating new resource holdres."""
+ handle = forms.CharField(max_length=30)
+ parent = forms.ModelChoiceField(
+ required=False,
+ queryset=models.Conf.objects.all(),
+ help_text='optionally make the new resource holder a child of this resource holder'
+ )
+ users = forms.ModelMultipleChoiceField(
+ required=False,
+ queryset=User.objects.all(),
+ help_text='users allowed to mange this resource holder'
+ )
+
+ def clean_handle(self):
+ handle = self.cleaned_data.get('handle')
+ if models.Conf.objects.filter(handle=handle).exists():
+ raise forms.ValidationError(
+ 'a resource holder with that handle already exists'
+ )
+ return handle
+
+ def clean(self):
+ handle = self.cleaned_data.get('handle')
+ parent = self.cleaned_data.get('parent')
+ if handle and parent and parent.children.filter(handle=handle).exists():
+ raise forms.ValidationError('parent already has a child by that name')
+ return self.cleaned_data
diff --git a/rpkid/rpki/gui/app/migrations/0006_add_conf_acl.py b/rpkid/rpki/gui/app/migrations/0006_add_conf_acl.py
new file mode 100644
index 00000000..88fe8171
--- /dev/null
+++ b/rpkid/rpki/gui/app/migrations/0006_add_conf_acl.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'ConfACL'
+ db.create_table('app_confacl', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('conf', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['irdb.ResourceHolderCA'])),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+ ))
+ db.send_create_signal('app', ['ConfACL'])
+
+ # Adding unique constraint on 'ConfACL', fields ['user', 'conf']
+ db.create_unique('app_confacl', ['user_id', 'conf_id'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'ConfACL', fields ['user', 'conf']
+ db.delete_unique('app_confacl', ['user_id', 'conf_id'])
+
+ # Deleting model 'ConfACL'
+ db.delete_table('app_confacl')
+
+
+ models = {
+ 'app.confacl': {
+ 'Meta': {'unique_together': "(('user', 'conf'),)", 'object_name': 'ConfACL'},
+ 'conf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['irdb.ResourceHolderCA']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'app.ghostbusterrequest': {
+ 'Meta': {'ordering': "('family_name', 'given_name')", 'object_name': 'GhostbusterRequest', '_ormbases': ['irdb.GhostbusterRequest']},
+ 'additional_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'box': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'country': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'extended': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'family_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'ghostbusterrequest_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.GhostbusterRequest']", 'unique': 'True', 'primary_key': 'True'}),
+ 'given_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'honorific_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'honorific_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'organization': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'region': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'street': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'telephone': ('rpki.gui.app.models.TelephoneField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'})
+ },
+ 'app.resourcecert': {
+ 'Meta': {'object_name': 'ResourceCert'},
+ 'conf': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'not_after': ('django.db.models.fields.DateTimeField', [], {}),
+ 'not_before': ('django.db.models.fields.DateTimeField', [], {}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'null': 'True', 'to': "orm['irdb.Parent']"}),
+ 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'app.resourcerangeaddressv4': {
+ 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV4'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'prefix_max': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}),
+ 'prefix_min': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'})
+ },
+ 'app.resourcerangeaddressv6': {
+ 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV6'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges_v6'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'prefix_max': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}),
+ 'prefix_min': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'})
+ },
+ 'app.resourcerangeas': {
+ 'Meta': {'ordering': "('min', 'max')", 'object_name': 'ResourceRangeAS'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asn_ranges'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'max': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'min': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'app.timestamp': {
+ 'Meta': {'object_name': 'Timestamp'},
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
+ 'ts': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'irdb.ghostbusterrequest': {
+ 'Meta': {'object_name': 'GhostbusterRequest'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'null': 'True', 'to': "orm['irdb.Parent']"}),
+ 'vcard': ('django.db.models.fields.TextField', [], {})
+ },
+ 'irdb.parent': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Parent', '_ormbases': ['irdb.Turtle']},
+ 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'child_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parents'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'referral_authorization': ('rpki.irdb.models.SignedReferralField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+ 'referrer': ('rpki.irdb.models.HandleField', [], {'max_length': '120', 'null': 'True', 'blank': 'True'}),
+ 'repository_type': ('rpki.irdb.models.EnumField', [], {}),
+ 'ta': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ 'irdb.resourceholderca': {
+ 'Meta': {'object_name': 'ResourceHolderCA'},
+ 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'unique': 'True', 'max_length': '120'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_crl_update': ('rpki.irdb.models.SundialField', [], {}),
+ 'latest_crl': ('rpki.irdb.models.CRLField', [], {'default': 'None', 'blank': 'True'}),
+ 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'next_crl_update': ('rpki.irdb.models.SundialField', [], {}),
+ 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'private_key': ('rpki.irdb.models.RSAKeyField', [], {'default': 'None', 'blank': 'True'})
+ },
+ 'irdb.turtle': {
+ 'Meta': {'object_name': 'Turtle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['app'] \ No newline at end of file
diff --git a/rpkid/rpki/gui/app/migrations/0007_default_acls.py b/rpkid/rpki/gui/app/migrations/0007_default_acls.py
new file mode 100644
index 00000000..40656d0f
--- /dev/null
+++ b/rpkid/rpki/gui/app/migrations/0007_default_acls.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from django.core.exceptions import ObjectDoesNotExist
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..."
+ for conf in orm['irdb.ResourceHolderCA'].objects.all():
+ try:
+ user = orm['auth.User'].objects.get(username=conf.handle)
+ orm['app.ConfACL'].objects.create(
+ conf=conf,
+ user=user
+ )
+ except ObjectDoesNotExist:
+ pass
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ orm['app.ConfACL'].objects.all().delete()
+
+ models = {
+ 'app.confacl': {
+ 'Meta': {'unique_together': "(('user', 'conf'),)", 'object_name': 'ConfACL'},
+ 'conf': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['irdb.ResourceHolderCA']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'app.ghostbusterrequest': {
+ 'Meta': {'ordering': "('family_name', 'given_name')", 'object_name': 'GhostbusterRequest', '_ormbases': ['irdb.GhostbusterRequest']},
+ 'additional_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'box': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'country': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'extended': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'family_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'ghostbusterrequest_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.GhostbusterRequest']", 'unique': 'True', 'primary_key': 'True'}),
+ 'given_name': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'honorific_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'honorific_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'organization': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'region': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'street': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'telephone': ('rpki.gui.app.models.TelephoneField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'})
+ },
+ 'app.resourcecert': {
+ 'Meta': {'object_name': 'ResourceCert'},
+ 'conf': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'not_after': ('django.db.models.fields.DateTimeField', [], {}),
+ 'not_before': ('django.db.models.fields.DateTimeField', [], {}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'certs'", 'null': 'True', 'to': "orm['irdb.Parent']"}),
+ 'uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'app.resourcerangeaddressv4': {
+ 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV4'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'prefix_max': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'}),
+ 'prefix_min': ('rpki.gui.models.IPv4AddressField', [], {'db_index': 'True'})
+ },
+ 'app.resourcerangeaddressv6': {
+ 'Meta': {'ordering': "('prefix_min',)", 'object_name': 'ResourceRangeAddressV6'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'address_ranges_v6'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'prefix_max': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'}),
+ 'prefix_min': ('rpki.gui.models.IPv6AddressField', [], {'db_index': 'True'})
+ },
+ 'app.resourcerangeas': {
+ 'Meta': {'ordering': "('min', 'max')", 'object_name': 'ResourceRangeAS'},
+ 'cert': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'asn_ranges'", 'to': "orm['app.ResourceCert']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'max': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'min': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'app.timestamp': {
+ 'Meta': {'object_name': 'Timestamp'},
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
+ 'ts': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'irdb.ghostbusterrequest': {
+ 'Meta': {'object_name': 'GhostbusterRequest'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ghostbuster_requests'", 'null': 'True', 'to': "orm['irdb.Parent']"}),
+ 'vcard': ('django.db.models.fields.TextField', [], {})
+ },
+ 'irdb.parent': {
+ 'Meta': {'unique_together': "(('issuer', 'handle'),)", 'object_name': 'Parent', '_ormbases': ['irdb.Turtle']},
+ 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'child_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'parents'", 'to': "orm['irdb.ResourceHolderCA']"}),
+ 'parent_handle': ('rpki.irdb.models.HandleField', [], {'max_length': '120'}),
+ 'referral_authorization': ('rpki.irdb.models.SignedReferralField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+ 'referrer': ('rpki.irdb.models.HandleField', [], {'max_length': '120', 'null': 'True', 'blank': 'True'}),
+ 'repository_type': ('rpki.irdb.models.EnumField', [], {}),
+ 'ta': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'turtle_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['irdb.Turtle']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ 'irdb.resourceholderca': {
+ 'Meta': {'object_name': 'ResourceHolderCA'},
+ 'certificate': ('rpki.irdb.models.CertificateField', [], {'default': 'None', 'blank': 'True'}),
+ 'handle': ('rpki.irdb.models.HandleField', [], {'unique': 'True', 'max_length': '120'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_crl_update': ('rpki.irdb.models.SundialField', [], {}),
+ 'latest_crl': ('rpki.irdb.models.CRLField', [], {'default': 'None', 'blank': 'True'}),
+ 'next_crl_number': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'next_crl_update': ('rpki.irdb.models.SundialField', [], {}),
+ 'next_serial': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+ 'private_key': ('rpki.irdb.models.RSAKeyField', [], {'default': 'None', 'blank': 'True'})
+ },
+ 'irdb.turtle': {
+ 'Meta': {'object_name': 'Turtle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'service_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['app']
+ symmetrical = True
diff --git a/rpkid/rpki/gui/app/models.py b/rpkid/rpki/gui/app/models.py
index fdb18b73..9c4c9890 100644
--- a/rpkid/rpki/gui/app/models.py
+++ b/rpkid/rpki/gui/app/models.py
@@ -16,6 +16,7 @@
__version__ = '$Id$'
from django.db import models
+from django.contrib.auth.models import User
import rpki.resource_set
import rpki.exceptions
@@ -289,3 +290,16 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6):
@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'))
diff --git a/rpkid/rpki/gui/app/templates/app/app_base.html b/rpkid/rpki/gui/app/templates/app/app_base.html
index f7ec0927..2738e4da 100644
--- a/rpkid/rpki/gui/app/templates/app/app_base.html
+++ b/rpkid/rpki/gui/app/templates/app/app_base.html
@@ -13,10 +13,14 @@
<li><a href="{% url rpki.gui.app.views.route_view %}">routes</a></li>
</ul>
-{% if request.user.is_superuser %}
<ul class='unstyled'>
<li><a href="{% url rpki.gui.app.views.conf_list %}" title="select a different resource handle to manage">select identity</a></li>
- <li><a href="{% url rpki.gui.app.views.user_list %}" title="manage users"><i class="icon-user"></i> users</a></li>
+</ul>
+
+{% if request.user.is_superuser %}
+<ul class='unstyled'>
+ <li><a href="{% url rpki.gui.app.views.user_list %}" title="manage users"><i class="icon-user"></i> web users</a></li>
+ <li><a href="{% url rpki.gui.app.views.resource_holder_list %}" title="manage resource holders"><i class="icon-user"></i> resource holders</a></li>
<li><a href="{% url rpki.gui.app.views.client_list %}" title="manage repository clients">repository clients</a></li>
</ul>
{% endif %}
diff --git a/rpkid/rpki/gui/app/templates/app/app_confirm_delete.html b/rpkid/rpki/gui/app/templates/app/app_confirm_delete.html
new file mode 100644
index 00000000..7c35a733
--- /dev/null
+++ b/rpkid/rpki/gui/app/templates/app/app_confirm_delete.html
@@ -0,0 +1,21 @@
+{% extends "app/app_base.html" %}
+
+{% block content %}
+<div class='page-title'>
+ <h1>{{ form_title }}</h1>
+</div>
+
+<div class='alert alert-block'>
+ <h4>Warning!</h4>
+ <strong>Please confirm</strong> that you would like to delete this object.
+</div>
+
+<form method='POST' action="">
+ {% csrf_token %}
+ {{ form }}
+ <div class="form-actions">
+ <input class='btn btn-danger' value='Delete' type='submit'>
+ <a class='btn' href="{{ cancel_url }}">Cancel</a>
+ </div>
+</form>
+{% endblock content %}
diff --git a/rpkid/rpki/gui/app/templates/app/app_form.html b/rpkid/rpki/gui/app/templates/app/app_form.html
index 4688d726..c2c58ae6 100644
--- a/rpkid/rpki/gui/app/templates/app/app_form.html
+++ b/rpkid/rpki/gui/app/templates/app/app_form.html
@@ -5,12 +5,12 @@
<h1>{{ form_title }}</h1>
</div>
-<form method='POST' action='{{ request.get_full_path }}' class="form-horizontal">
+<form method="POST" action="" class="form-horizontal">
{% csrf_token %}
{% include "app/bootstrap_form.html" %}
<div class="form-actions">
<input class='btn btn-primary' type='submit' value='Save'>
- <a class='btn' href="{% url rpki.gui.app.views.dashboard %}">Cancel</a>
+ <a class='btn' href="{{ cancel_url }}">Cancel</a>
</div>
</form>
{% endblock %}
diff --git a/rpkid/rpki/gui/app/templates/app/resource_holder_list.html b/rpkid/rpki/gui/app/templates/app/resource_holder_list.html
new file mode 100644
index 00000000..3d1c2366
--- /dev/null
+++ b/rpkid/rpki/gui/app/templates/app/resource_holder_list.html
@@ -0,0 +1,35 @@
+{% extends "app/app_base.html" %}
+
+{% block content %}
+<div class='page-title'>
+ <h1>Resource Holders</h1>
+</div>
+
+<p>
+This page lists all of the resource holders that are currently managed by this server.
+Note that this is distinct from the
+<a href="{% url rpki.gui.app.views.user_list %}">list of web interface users</a>.
+</p>
+
+<table class='table table-striped'>
+ <thead>
+ <tr>
+ <th>Handle</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for conf in object_list %}
+ <tr>
+ <td>{{ conf.handle }}</td>
+ <td>
+ <a class='btn btn-small' href='{% url rpki.gui.app.views.resource_holder_edit conf.pk %}' title="Edit"><i class="icon-edit"></i></a>
+ <a class='btn btn-small' href='{% url rpki.gui.app.views.resource_holder_delete conf.pk %}' title="Delete"><i class="icon-trash"></i></a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+
+<a class="btn" href="{% url rpki.gui.app.views.resource_holder_create %}"><i class="icon-plus-sign"></i> Create</a>
+{% endblock content %}
diff --git a/rpkid/rpki/gui/app/templates/app/user_confirm_delete.html b/rpkid/rpki/gui/app/templates/app/user_confirm_delete.html
deleted file mode 100644
index 76c66775..00000000
--- a/rpkid/rpki/gui/app/templates/app/user_confirm_delete.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "app/app_base.html" %}
-
-{% block content %}
-<div class='page-title'>
- <h1>Delete User</h1>
-</div>
-
-<div class='alert-message block-message warning'>
- <p><strong>Please confirm</strong> that you would like to delete the following user account.
- <h2>{{ object.handle }}</h2>
- <div class='alert-actions'>
- <form method='POST' action='{{ request.get_full_path }}'>
- {% csrf_token %}
- {{ form }}
- <input class='btn danger' value='Delete' type='submit'>
- <a class='btn' href='{% url rpki.gui.app.views.user_list %}'>Cancel</a>
- </form>
- </div>
-</div>
-{% endblock content %}
diff --git a/rpkid/rpki/gui/app/templates/app/user_list.html b/rpkid/rpki/gui/app/templates/app/user_list.html
index fed943f0..16ebf6c0 100644
--- a/rpkid/rpki/gui/app/templates/app/user_list.html
+++ b/rpkid/rpki/gui/app/templates/app/user_list.html
@@ -5,23 +5,32 @@
<h1>Users</h1>
</div>
-<table class='table table-striped table-condensed'>
- <tr>
- <th>Username</th>
- <th>Email</th>
- <th></th>
- </tr>
- {% for u in users %}
- <tr>
- <td>{{ u.0.handle }}</td>
- <td>{{ u.1.email }}</td>
- <td>
- <a class='btn btn-small' href='{% url rpki.gui.app.views.user_edit u.0.pk %}' title="Edit"><i class="icon-edit"></i></a>
- <a class='btn btn-small' href='{% url rpki.gui.app.views.user_delete u.0.pk %}' title="Delete"><i class="icon-trash"></i></a>
- </td>
- </tr>
- {% endfor %}
+<p>
+This page lists all user accounts in the web interface. Note that this is distinct from the
+<a href="{% url rpki.gui.app.views.resource_holder_list %}">list of resource holders</a>.
+</p>
+
+<table class='table table-striped'>
+ <thead>
+ <tr>
+ <th>Username</th>
+ <th>Email</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for user in object_list %}
+ <tr>
+ <td>{{ user.username }}</td>
+ <td>{{ user.email }}</td>
+ <td>
+ <a class='btn btn-small' href='{% url rpki.gui.app.views.user_edit user.pk %}' title="Edit"><i class="icon-edit"></i></a>
+ <a class='btn btn-small' href='{% url rpki.gui.app.views.user_delete user.pk %}' title="Delete"><i class="icon-trash"></i></a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
-<a class='btn' href="{% url rpki.gui.app.views.user_create %}" title="create a new locally hosted resource handle">Create</a>
+<a class='btn' href="{% url rpki.gui.app.views.user_create %}" title="create a new locally hosted resource handle"><i class="icon-plus-sign"></i> Create</a>
{% endblock content %}
diff --git a/rpkid/rpki/gui/app/urls.py b/rpkid/rpki/gui/app/urls.py
index 93936ad4..81c56f4e 100644
--- a/rpkid/rpki/gui/app/urls.py
+++ b/rpkid/rpki/gui/app/urls.py
@@ -48,6 +48,10 @@ urlpatterns = patterns(
(r'^repo/import$', views.repository_import),
(r'^repo/(?P<pk>\d+)/$', views.repository_detail),
(r'^repo/(?P<pk>\d+)/delete$', views.repository_delete),
+ (r'^resource_holder/$', views.resource_holder_list),
+ (r'^resource_holder/create$', views.resource_holder_create),
+ (r'^resource_holder/(?P<pk>\d+)/delete$', views.resource_holder_delete),
+ (r'^resource_holder/(?P<pk>\d+)/edit$', views.resource_holder_edit),
(r'^roa/(?P<pk>\d+)/$', views.roa_detail),
(r'^roa/create$', views.roa_create),
(r'^roa/create_multi$', views.roa_create_multi),
diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py
index 0a393007..a82c7c1d 100644
--- a/rpkid/rpki/gui/app/views.py
+++ b/rpkid/rpki/gui/app/views.py
@@ -72,15 +72,13 @@ def handle_required(f):
if request.user.is_superuser:
conf = models.Conf.objects.all()
else:
- conf = models.Conf.objects.filter(handle=request.user.username)
+ conf = models.Conf.objects.filter(confacl__user=request.user)
if conf.count() == 1:
request.session['handle'] = conf[0]
elif conf.count() == 0:
return render(request, 'app/conf_empty.html', {})
else:
- # Should reverse the view for this instead of hardcoding
- # the URL.
url = '%s?next=%s' % (reverse(conf_list),
urlquote(request.get_full_path()))
return http.HttpResponseRedirect(url)
@@ -232,23 +230,29 @@ def dashboard(request):
})
-@superuser_required
+@login_required
def conf_list(request, **kwargs):
"""Allow the user to select a handle."""
- return render(request, 'app/conf_list.html',
- {'conf_list': models.Conf.objects.all()})
+ next_url = request.GET.get('next', reverse(dashboard))
+ return render(request, 'app/conf_list.html', {
+ 'conf_list': models.Conf.objects.filter(confacl__user=request.user),
+ 'next_url': next_url
+ })
-@superuser_required
+@login_required
def conf_select(request):
"""Change the handle for the current session."""
if not 'handle' in request.GET:
- return http.HttpResponseRedirect('/myrpki/conf/select')
+ return redirect(conf_list)
handle = request.GET['handle']
next_url = request.GET.get('next', reverse(dashboard))
- if next_url == '':
- next_url = reverse(dashboard)
- request.session['handle'] = get_object_or_404(models.Conf, handle=handle)
+ if request.user.is_superuser:
+ request.session['handle'] = get_object_or_404(models.Conf, handle=handle)
+ else:
+ request.session['handle'] = get_object_or_404(
+ models.Conf, confacl__user=request.user, handle=handle
+ )
return http.HttpResponseRedirect(next_url)
@@ -996,86 +1000,68 @@ def client_export(request, pk):
return serve_xml(str(xml), '%s.repo' % z.handle)
+### Routines for managing resource handles serviced by this server
+
@superuser_required
-def user_list(request):
+def resource_holder_list(request):
"""Display a list of all the RPKI handles managed by this server."""
- # create a list of tuples of (Conf, User)
- users = []
- for conf in models.Conf.objects.all():
- try:
- u = User.objects.get(username=conf.handle)
- except User.DoesNotExist:
- u = None
- users.append((conf, u))
- return render(request, 'app/user_list.html', {'users': users})
+ return render(request, 'app/resource_holder_list.html', {
+ 'object_list': models.Conf.objects.all()
+ })
@superuser_required
-def user_delete(request, pk):
- conf = models.Conf.objects.get(pk=pk)
- log = request.META['wsgi.errors']
+def resource_holder_edit(request, pk):
+ """Display a list of all the RPKI handles managed by this server."""
+ conf = get_object_or_404(models.Conf, pk=pk)
if request.method == 'POST':
- form = forms.Empty(request.POST)
+ form = forms.ResourceHolderForm(request.POST, request.FILES)
if form.is_valid():
- User.objects.filter(username=conf.handle).delete()
- z = Zookeeper(handle=conf.handle, logstream=log)
- z.delete_self()
- z.synchronize()
- return http.HttpResponseRedirect(reverse(user_list))
+ models.ConfACL.objects.filter(conf=conf).delete()
+ for user in form.cleaned_data.get('users'):
+ models.ConfACL.objects.create(user=user, conf=conf)
+ return redirect(resource_holder_list)
else:
- form = forms.Empty()
- return render(request, 'app/user_confirm_delete.html',
- {'object': conf, 'form': form})
+ users = [acl.user for acl in models.ConfACL.objects.filter(conf=conf).all()]
+ form = forms.ResourceHolderForm(initial={
+ 'users': users
+ })
+ return render(request, 'app/app_form.html', {
+ 'form_title': "Edit Resource Holder: " + conf.handle,
+ 'form': form,
+ 'cancel_url': reverse(resource_holder_list)
+ })
@superuser_required
-def user_edit(request, pk):
+def resource_holder_delete(request, pk):
conf = get_object_or_404(models.Conf, pk=pk)
- # in the old model, there may be users with a different name, so create a
- # new user object if it is missing.
- try:
- user = User.objects.get(username=conf.handle)
- except User.DoesNotExist:
- user = User(username=conf.handle)
-
+ log = request.META['wsgi.errors']
if request.method == 'POST':
- form = forms.UserEditForm(request.POST)
+ form = forms.Empty(request.POST)
if form.is_valid():
- pw = form.cleaned_data.get('pw')
- if pw:
- user.set_password(pw)
- user.email = form.cleaned_data.get('email')
- user.save()
- return http.HttpResponseRedirect(reverse(user_list))
+ z = Zookeeper(handle=conf.handle, logstream=log)
+ z.delete_self()
+ z.synchronize()
+ return redirect(resource_holder_list)
else:
- form = forms.UserEditForm(initial={'email': user.email})
- return render(request, 'app/app_form.html', {
- 'object': user,
+ form = forms.Empty()
+ return render(request, 'app/app_confirm_delete.html', {
+ 'form_title': 'Delete Resource Holder: ' + conf.handle,
'form': form,
- 'form_title': 'Edit User: ' + user.username,
+ 'cancel_url': reverse(resource_holder_list)
})
-@handle_required
-def user_create(request):
- """
- Wizard mode to create a new locally hosted child.
-
- """
- if not request.user.is_superuser:
- return http.HttpResponseForbidden()
-
+@superuser_required
+def resource_holder_create(request):
log = request.META['wsgi.errors']
if request.method == 'POST':
- form = forms.UserCreateForm(request.POST, request.FILES)
+ form = forms.ResourceHolderCreateForm(request.POST, request.FILES)
if form.is_valid():
handle = form.cleaned_data.get('handle')
- pw = form.cleaned_data.get('password')
- email = form.cleaned_data.get('email')
parent = form.cleaned_data.get('parent')
- User.objects.create_user(handle, email, pw)
-
zk_child = Zookeeper(handle=handle, logstream=log)
identity_xml = zk_child.initialize()
if parent:
@@ -1094,14 +1080,88 @@ def user_create(request):
zk_child.configure_repository(t.name)
os.remove(t.name)
zk_child.synchronize()
+ return redirect(resource_holder_list)
+ else:
+ form = forms.ResourceHolderCreateForm()
+ return render(request, 'app/app_form.html', {
+ 'form': form,
+ 'form_title': 'Create Resource Holder',
+ 'cancel_url': reverse(resource_holder_list)
+ })
- return http.HttpResponseRedirect(reverse(dashboard))
+
+### views for managing user logins to the web interface
+
+@superuser_required
+def user_create(request):
+ if request.method == 'POST':
+ form = forms.UserCreateForm(request.POST, request.FILES)
+ if form.is_valid():
+ username = form.cleaned_data.get('username')
+ pw = form.cleaned_data.get('password')
+ email = form.cleaned_data.get('email')
+ user = User.objects.create_user(username, email, pw)
+ for conf in form.cleaned_data.get('resource_holders'):
+ models.ConfACL.objects.create(user=user, conf=conf)
+ return redirect(user_list)
else:
- conf = request.session['handle']
- form = forms.UserCreateForm(initial={'parent': conf})
+ form = forms.UserCreateForm()
return render(request, 'app/app_form.html', {
'form': form,
'form_title': 'Create User',
'cancel_url': reverse(user_list),
})
+
+
+@superuser_required
+def user_list(request):
+ """Display a list of all the RPKI handles managed by this server."""
+ return render(request, 'app/user_list.html', {
+ 'object_list': User.objects.all()
+ })
+
+
+@superuser_required
+def user_delete(request, pk):
+ user = get_object_or_404(User, pk=pk)
+ if request.method == 'POST':
+ form = forms.Empty(request.POST, request.FILES)
+ if form.is_valid():
+ user.delete()
+ return redirect(user_list)
+ else:
+ form = forms.Empty()
+ return render(request, 'app/app_confirm_delete.html', {
+ 'form_title': 'Delete User: ' + user.username,
+ 'form': form,
+ 'cancel_url': reverse(user_list)
+ })
+
+
+@superuser_required
+def user_edit(request, pk):
+ user = get_object_or_404(User, pk=pk)
+ if request.method == 'POST':
+ form = forms.UserEditForm(request.POST)
+ if form.is_valid():
+ pw = form.cleaned_data.get('pw')
+ if pw:
+ user.set_password(pw)
+ user.email = form.cleaned_data.get('email')
+ user.save()
+ models.ConfACL.objects.filter(user=user).delete()
+ handles = form.cleaned_data.get('resource_holders')
+ for conf in handles:
+ models.ConfACL.objects.create(user=user, conf=conf)
+ return redirect(user_list)
+ else:
+ form = forms.UserEditForm(initial={
+ 'email': user.email,
+ 'resource_holders': models.Conf.objects.filter(confacl__user=user).all()
+ })
+ return render(request, 'app/app_form.html', {
+ 'form': form,
+ 'form_title': 'Edit User: ' + user.username,
+ 'cancel_url': reverse(user_list)
+ })