Add key upload view, permission, link and test.
This commit is contained in:
@@ -14,7 +14,8 @@ from navigation import SourceColumn
|
||||
from .classes import KeyStub
|
||||
from .links import (
|
||||
link_key_delete, link_key_detail, link_key_download, link_key_query,
|
||||
link_key_receive, link_key_setup, link_private_keys, link_public_keys
|
||||
link_key_receive, link_key_setup, link_key_upload, link_private_keys,
|
||||
link_public_keys
|
||||
)
|
||||
from .permissions import (
|
||||
permission_key_delete, permission_key_download, permission_key_sign,
|
||||
@@ -104,7 +105,7 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
)
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=(link_key_query,),
|
||||
links=(link_key_query, link_key_upload),
|
||||
sources=(
|
||||
'django_gpg:key_public_list', 'django_gpg:key_private_list',
|
||||
'django_gpg:key_query', 'django_gpg:key_query_results', Key,
|
||||
|
||||
@@ -6,17 +6,9 @@ from navigation import Link
|
||||
|
||||
from .permissions import (
|
||||
permission_key_delete, permission_key_download, permission_key_receive,
|
||||
permission_key_view, permission_keyserver_query
|
||||
permission_key_view, permission_key_upload, permission_keyserver_query
|
||||
)
|
||||
|
||||
link_private_keys = Link(
|
||||
permissions=(permission_key_view,), text=_('Private keys'),
|
||||
view='django_gpg:key_private_list'
|
||||
)
|
||||
link_public_keys = Link(
|
||||
permissions=(permission_key_view,), text=_('Public keys'),
|
||||
view='django_gpg:key_public_list'
|
||||
)
|
||||
link_key_delete = Link(
|
||||
permissions=(permission_key_delete,), tags='dangerous', text=_('Delete'),
|
||||
view='django_gpg:key_delete', args=('resolved_object.pk',)
|
||||
@@ -41,3 +33,15 @@ link_key_setup = Link(
|
||||
icon='fa fa-key', permissions=(permission_key_view,),
|
||||
text=_('Key management'), view='django_gpg:key_public_list'
|
||||
)
|
||||
link_key_upload = Link(
|
||||
permissions=(permission_key_upload,), text=_('Upload key'),
|
||||
view='django_gpg:key_upload'
|
||||
)
|
||||
link_private_keys = Link(
|
||||
permissions=(permission_key_view,), text=_('Private keys'),
|
||||
view='django_gpg:key_private_list'
|
||||
)
|
||||
link_public_keys = Link(
|
||||
permissions=(permission_key_view,), text=_('Public keys'),
|
||||
view='django_gpg:key_public_list'
|
||||
)
|
||||
|
||||
@@ -13,16 +13,45 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='Key',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True
|
||||
)
|
||||
),
|
||||
('data', models.TextField(verbose_name='Data')),
|
||||
('key_id', models.CharField(unique=True, max_length=16, verbose_name='Key ID')),
|
||||
('creation_date', models.DateField(verbose_name='Creation date')),
|
||||
('expiration_date', models.DateField(null=True, verbose_name='Expiration date', blank=True)),
|
||||
('fingerprint', models.CharField(unique=True, max_length=40, verbose_name='Fingerprint')),
|
||||
(
|
||||
'key_id', models.CharField(
|
||||
unique=True, max_length=16, verbose_name='Key ID'
|
||||
)
|
||||
),
|
||||
(
|
||||
'creation_date', models.DateField(
|
||||
verbose_name='Creation date'
|
||||
)
|
||||
),
|
||||
(
|
||||
'expiration_date', models.DateField(
|
||||
null=True, verbose_name='Expiration date', blank=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'fingerprint', models.CharField(
|
||||
unique=True, max_length=40, verbose_name='Fingerprint'
|
||||
)
|
||||
),
|
||||
('length', models.PositiveIntegerField(verbose_name='Length')),
|
||||
('algorithm', models.PositiveIntegerField(verbose_name='Algorithm')),
|
||||
(
|
||||
'algorithm', models.PositiveIntegerField(
|
||||
verbose_name='Algorithm'
|
||||
)
|
||||
),
|
||||
('user_id', models.TextField(verbose_name='User ID')),
|
||||
('key_type', models.CharField(max_length=3, verbose_name='Type')),
|
||||
(
|
||||
'key_type', models.CharField(
|
||||
max_length=3, verbose_name='Type'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Key',
|
||||
|
||||
@@ -14,22 +14,32 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='algorithm',
|
||||
field=models.PositiveIntegerField(verbose_name='Algorithm', editable=False),
|
||||
field=models.PositiveIntegerField(
|
||||
verbose_name='Algorithm', editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='creation_date',
|
||||
field=models.DateField(verbose_name='Creation date', editable=False),
|
||||
field=models.DateField(
|
||||
verbose_name='Creation date', editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='expiration_date',
|
||||
field=models.DateField(verbose_name='Expiration date', null=True, editable=False, blank=True),
|
||||
field=models.DateField(
|
||||
verbose_name='Expiration date', null=True, editable=False,
|
||||
blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='fingerprint',
|
||||
field=models.CharField(verbose_name='Fingerprint', unique=True, max_length=40, editable=False),
|
||||
field=models.CharField(
|
||||
verbose_name='Fingerprint', unique=True, max_length=40,
|
||||
editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
@@ -39,17 +49,24 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='key_id',
|
||||
field=models.CharField(verbose_name='Key ID', unique=True, max_length=16, editable=False),
|
||||
field=models.CharField(
|
||||
verbose_name='Key ID', unique=True, max_length=16,
|
||||
editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='key_type',
|
||||
field=models.CharField(verbose_name='Type', max_length=3, editable=False),
|
||||
field=models.CharField(
|
||||
verbose_name='Type', max_length=3, editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='length',
|
||||
field=models.PositiveIntegerField(verbose_name='Length', editable=False),
|
||||
field=models.PositiveIntegerField(
|
||||
verbose_name='Length', editable=False
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
|
||||
@@ -19,6 +19,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
name='key_type',
|
||||
field=models.CharField(verbose_name='Type', max_length=3, editable=False, choices=[('pub', 'Public'), ('sec', 'Secret')]),
|
||||
field=models.CharField(
|
||||
verbose_name='Type', max_length=3, editable=False,
|
||||
choices=[('pub', 'Public'), ('sec', 'Secret')]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -43,7 +43,10 @@ def gpg_command(function):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Key(models.Model):
|
||||
key_data = models.TextField(verbose_name=_('Key data'))
|
||||
key_data = models.TextField(
|
||||
help_text=_('ASCII armored version of the key.'),
|
||||
verbose_name=_('Key data')
|
||||
)
|
||||
creation_date = models.DateField(
|
||||
editable=False, verbose_name=_('Creation date')
|
||||
)
|
||||
@@ -80,7 +83,10 @@ class Key(models.Model):
|
||||
import_results = gpg_command(function=import_key)
|
||||
|
||||
if not import_results.count:
|
||||
raise ValidationError('Invalid key data')
|
||||
raise ValidationError(_('Invalid key data'))
|
||||
|
||||
if Key.objects.filter(fingerprint=import_results.fingerprints[0]).exists():
|
||||
raise ValidationError(_('Key already exists.'))
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('django_gpg:key_detail', args=(self.pk,))
|
||||
|
||||
@@ -18,6 +18,9 @@ permission_key_receive = namespace.add_permission(
|
||||
permission_key_sign = namespace.add_permission(
|
||||
name='key_sign', label=_('Use keys to sign content')
|
||||
)
|
||||
permission_key_upload = namespace.add_permission(
|
||||
name='key_upload', label=_('Upload keys')
|
||||
)
|
||||
permission_key_view = namespace.add_permission(
|
||||
name='key_view', label=_('View keys')
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ class KeyTestCase(TestCase):
|
||||
|
||||
with self.assertRaises(NeedPassphrase):
|
||||
with open(TEST_FILE) as test_file:
|
||||
detached_signature = key.sign_file(
|
||||
key.sign_file(
|
||||
file_object=test_file, detached=True,
|
||||
)
|
||||
|
||||
@@ -137,7 +137,7 @@ class KeyTestCase(TestCase):
|
||||
|
||||
with self.assertRaises(PassphraseError):
|
||||
with open(TEST_FILE) as test_file:
|
||||
detached_signature = key.sign_file(
|
||||
key.sign_file(
|
||||
file_object=test_file, detached=True,
|
||||
passphrase='bad passphrase'
|
||||
)
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.core.files import File
|
||||
|
||||
from django_downloadview.test import assert_download_response
|
||||
|
||||
from documents.models import Document, DocumentVersion
|
||||
from documents.tests.literals import TEST_DOCUMENT_PATH
|
||||
from documents.tests.test_views import GenericDocumentViewTestCase
|
||||
from common.tests.test_views import GenericViewTestCase
|
||||
from user_management.tests import (
|
||||
TEST_USER_USERNAME, TEST_USER_PASSWORD
|
||||
)
|
||||
|
||||
from ..models import Key
|
||||
from ..permissions import permission_key_download
|
||||
from ..permissions import permission_key_download, permission_key_upload
|
||||
|
||||
from .literals import (
|
||||
TEST_DETACHED_SIGNATURE, TEST_FILE, TEST_KEY_DATA, TEST_KEY_FINGERPRINT,
|
||||
TEST_KEY_PASSPHRASE, TEST_SEARCH_FINGERPRINT, TEST_SEARCH_UID,
|
||||
TEST_SIGNED_FILE, TEST_SIGNED_FILE_CONTENT
|
||||
)
|
||||
from .literals import TEST_KEY_DATA, TEST_KEY_FINGERPRINT
|
||||
|
||||
|
||||
class KeyViewTestCase(GenericDocumentViewTestCase):
|
||||
class KeyViewTestCase(GenericViewTestCase):
|
||||
def test_key_download_view_no_permission(self):
|
||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
||||
|
||||
@@ -48,3 +40,28 @@ class KeyViewTestCase(GenericDocumentViewTestCase):
|
||||
self, response=response, content=key.key_data,
|
||||
basename=key.key_id,
|
||||
)
|
||||
|
||||
def test_key_upload_view_no_permission(self):
|
||||
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
|
||||
|
||||
response = self.post(
|
||||
viewname='django_gpg:key_upload', data={'key_data': TEST_KEY_DATA}
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertEqual(Key.objects.count(), 0)
|
||||
|
||||
def test_key_upload_view_with_permission(self):
|
||||
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
|
||||
|
||||
self.role.permissions.add(permission_key_upload.stored_permission)
|
||||
|
||||
response = self.post(
|
||||
viewname='django_gpg:key_upload', data={'key_data': TEST_KEY_DATA},
|
||||
follow=True
|
||||
)
|
||||
|
||||
self.assertContains(response, 'created', status_code=200)
|
||||
|
||||
self.assertEqual(Key.objects.count(), 1)
|
||||
self.assertEqual(Key.objects.first().fingerprint, TEST_KEY_FINGERPRINT)
|
||||
|
||||
@@ -4,7 +4,8 @@ from django.conf.urls import patterns, url
|
||||
|
||||
from .views import (
|
||||
KeyDeleteView, KeyDetailView, KeyDownloadView, KeyQueryView,
|
||||
KeyQueryResultView, KeyReceive, PrivateKeyListView, PublicKeyListView
|
||||
KeyQueryResultView, KeyReceive, KeyUploadView, PrivateKeyListView,
|
||||
PublicKeyListView
|
||||
)
|
||||
|
||||
urlpatterns = patterns(
|
||||
@@ -26,6 +27,9 @@ urlpatterns = patterns(
|
||||
url(
|
||||
r'^list/public/$', PublicKeyListView.as_view(), name='key_public_list'
|
||||
),
|
||||
url(
|
||||
r'^upload/$', KeyUploadView.as_view(), name='key_upload'
|
||||
),
|
||||
url(r'^query/$', KeyQueryView.as_view(), name='key_query'),
|
||||
url(
|
||||
r'^query/results/$', KeyQueryResultView.as_view(),
|
||||
|
||||
@@ -8,8 +8,9 @@ from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.generics import (
|
||||
ConfirmView, SimpleView, SingleObjectDeleteView, SingleObjectDetailView,
|
||||
SingleObjectDownloadView, SingleObjectListView
|
||||
ConfirmView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView,
|
||||
SimpleView
|
||||
)
|
||||
|
||||
from .forms import KeyDetailForm, KeySearchForm
|
||||
@@ -17,7 +18,7 @@ from .literals import KEY_TYPE_PUBLIC
|
||||
from .models import Key
|
||||
from .permissions import (
|
||||
permission_key_delete, permission_key_download, permission_key_receive,
|
||||
permission_key_view, permission_keyserver_query
|
||||
permission_key_upload, permission_key_view, permission_keyserver_query
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -126,6 +127,18 @@ class KeyQueryResultView(SingleObjectListView):
|
||||
return ()
|
||||
|
||||
|
||||
class KeyUploadView(SingleObjectCreateView):
|
||||
fields = ('key_data',)
|
||||
model = Key
|
||||
post_action_redirect = reverse_lazy('django_gpg:key_public_list')
|
||||
view_permission = permission_key_upload
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'title': _('Upload new key'),
|
||||
}
|
||||
|
||||
|
||||
class PublicKeyListView(SingleObjectListView):
|
||||
object_permission = permission_key_view
|
||||
queryset = Key.objects.public_keys()
|
||||
|
||||
Reference in New Issue
Block a user