Add first set of document signatures API views
Add list, create, detail and edit API views for detached and embedded signatures. Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -110,6 +110,8 @@
|
|||||||
own module.
|
own module.
|
||||||
- Update label and icon of the document sign form
|
- Update label and icon of the document sign form
|
||||||
Label updated from "Save" to "Sign".
|
Label updated from "Save" to "Sign".
|
||||||
|
- Add list, create, detail and edit API views for
|
||||||
|
detached and embedded signatures.
|
||||||
|
|
||||||
3.2.9 (2019-11-03)
|
3.2.9 (2019-11-03)
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ TEST_FILE = os.path.join(
|
|||||||
'test_files', 'test_file.txt'
|
'test_files', 'test_file.txt'
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_KEY_DATA = '''-----BEGIN PGP PRIVATE KEY BLOCK-----
|
TEST_KEY_PRIVATE_DATA = '''-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
Version: GnuPG v1
|
Version: GnuPG v1
|
||||||
|
|
||||||
lQO+BFbxfC8BCACnUZoD96W4+CSIaU9G8I08kXu2zJLzy2XgUtwLx8VQ8dOHr0E/
|
lQO+BFbxfC8BCACnUZoD96W4+CSIaU9G8I08kXu2zJLzy2XgUtwLx8VQ8dOHr0E/
|
||||||
@@ -86,9 +86,9 @@ h4oCbUV5JHhOyB+89Y1w8haFU9LrgOER2kXff1xU6wMfLdcO5ApV/sRJcNdYL7Cg
|
|||||||
=JZ5G
|
=JZ5G
|
||||||
-----END PGP PRIVATE KEY BLOCK-----'''
|
-----END PGP PRIVATE KEY BLOCK-----'''
|
||||||
|
|
||||||
TEST_KEY_ID = '4125E9C571F378AC'
|
TEST_KEY_PRIVATE_ID = '4125E9C571F378AC'
|
||||||
TEST_KEY_FINGERPRINT = '6A24574E0A35004CDDFD22704125E9C571F378AC'
|
TEST_KEY_PRIVATE_FINGERPRINT = '6A24574E0A35004CDDFD22704125E9C571F378AC'
|
||||||
TEST_KEY_PASSPHRASE = 'testpassphrase'
|
TEST_KEY_PRIVATE_PASSPHRASE = 'testpassphrase'
|
||||||
|
|
||||||
TEST_KEYSERVERS = ['pool.sks-keyservers.net']
|
TEST_KEYSERVERS = ['pool.sks-keyservers.net']
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,48 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from ..models import Key
|
from ..models import Key
|
||||||
|
|
||||||
from .literals import TEST_KEY_DATA
|
from .literals import TEST_KEY_PRIVATE_DATA
|
||||||
|
|
||||||
|
|
||||||
|
class KeyAPIViewTestMixin(object):
|
||||||
|
def _request_test_key_create_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='rest_api:key-list', data={
|
||||||
|
'key_data': TEST_KEY_PRIVATE_DATA
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_key_delete_view(self):
|
||||||
|
return self.delete(
|
||||||
|
viewname='rest_api:key-detail', kwargs={
|
||||||
|
'pk': self.test_key_private.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_key_detail_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:key-detail', kwargs={
|
||||||
|
'pk': self.test_key_private.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class KeyTestMixin(object):
|
class KeyTestMixin(object):
|
||||||
def _create_test_key(self):
|
def _create_test_key_private(self):
|
||||||
self.test_key = Key.objects.create(key_data=TEST_KEY_DATA)
|
self.test_key_private = Key.objects.create(
|
||||||
|
key_data=TEST_KEY_PRIVATE_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyViewTestMixin(object):
|
||||||
|
def _request_test_key_download_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='django_gpg:key_download', kwargs={'pk': self.test_key_private.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_key_upload_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='django_gpg:key_upload', data={
|
||||||
|
'key_data': TEST_KEY_PRIVATE_DATA
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,27 +9,8 @@ from ..permissions import (
|
|||||||
permission_key_delete, permission_key_upload, permission_key_view
|
permission_key_delete, permission_key_upload, permission_key_view
|
||||||
)
|
)
|
||||||
|
|
||||||
from .literals import TEST_KEY_DATA, TEST_KEY_FINGERPRINT
|
from .literals import TEST_KEY_PRIVATE_FINGERPRINT
|
||||||
from .mixins import KeyTestMixin
|
from .mixins import KeyAPIViewTestMixin, KeyTestMixin
|
||||||
|
|
||||||
|
|
||||||
class KeyAPIViewTestMixin(object):
|
|
||||||
def _request_test_key_create_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='rest_api:key-list', data={
|
|
||||||
'key_data': TEST_KEY_DATA
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_key_delete_view(self):
|
|
||||||
return self.delete(
|
|
||||||
viewname='rest_api:key-detail', kwargs={'pk': self.test_key.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_key_detail_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='rest_api:key-detail', kwargs={'pk': self.test_key.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyAPITestCase(KeyTestMixin, KeyAPIViewTestMixin, BaseAPITestCase):
|
class KeyAPITestCase(KeyTestMixin, KeyAPIViewTestMixin, BaseAPITestCase):
|
||||||
@@ -44,14 +25,16 @@ class KeyAPITestCase(KeyTestMixin, KeyAPIViewTestMixin, BaseAPITestCase):
|
|||||||
|
|
||||||
response = self._request_test_key_create_view()
|
response = self._request_test_key_create_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(response.data['fingerprint'], TEST_KEY_FINGERPRINT)
|
self.assertEqual(
|
||||||
|
response.data['fingerprint'], TEST_KEY_PRIVATE_FINGERPRINT
|
||||||
|
)
|
||||||
|
|
||||||
key = Key.objects.first()
|
key = Key.objects.first()
|
||||||
self.assertEqual(Key.objects.count(), 1)
|
self.assertEqual(Key.objects.count(), 1)
|
||||||
self.assertEqual(key.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(key.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_key_delete_view_no_access(self):
|
def test_key_delete_view_no_access(self):
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
|
|
||||||
response = self._request_test_key_delete_view()
|
response = self._request_test_key_delete_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
@@ -59,9 +42,9 @@ class KeyAPITestCase(KeyTestMixin, KeyAPIViewTestMixin, BaseAPITestCase):
|
|||||||
self.assertEqual(Key.objects.count(), 1)
|
self.assertEqual(Key.objects.count(), 1)
|
||||||
|
|
||||||
def test_key_delete_view_with_access(self):
|
def test_key_delete_view_with_access(self):
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_key, permission=permission_key_delete
|
obj=self.test_key_private, permission=permission_key_delete
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_test_key_delete_view()
|
response = self._request_test_key_delete_view()
|
||||||
@@ -70,19 +53,19 @@ class KeyAPITestCase(KeyTestMixin, KeyAPIViewTestMixin, BaseAPITestCase):
|
|||||||
self.assertEqual(Key.objects.count(), 0)
|
self.assertEqual(Key.objects.count(), 0)
|
||||||
|
|
||||||
def test_key_detail_view_no_access(self):
|
def test_key_detail_view_no_access(self):
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
|
|
||||||
response = self._request_test_key_detail_view()
|
response = self._request_test_key_detail_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
def test_key_detail_view_with_access(self):
|
def test_key_detail_view_with_access(self):
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_key, permission=permission_key_view
|
obj=self.test_key_private, permission=permission_key_view
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_test_key_detail_view()
|
response = self._request_test_key_detail_view()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.data['fingerprint'], self.test_key.fingerprint
|
response.data['fingerprint'], self.test_key_private.fingerprint
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ from ..models import Key
|
|||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
MOCK_SEARCH_KEYS_RESPONSE, TEST_DETACHED_SIGNATURE, TEST_FILE,
|
MOCK_SEARCH_KEYS_RESPONSE, TEST_DETACHED_SIGNATURE, TEST_FILE,
|
||||||
TEST_KEY_DATA, TEST_KEY_FINGERPRINT, TEST_KEY_PASSPHRASE,
|
TEST_KEY_PRIVATE_DATA, TEST_KEY_PRIVATE_FINGERPRINT,
|
||||||
TEST_SEARCH_FINGERPRINT, TEST_SEARCH_UID, TEST_SIGNED_FILE,
|
TEST_KEY_PRIVATE_PASSPHRASE, TEST_SEARCH_FINGERPRINT, TEST_SEARCH_UID,
|
||||||
TEST_SIGNED_FILE_CONTENT
|
TEST_SIGNED_FILE, TEST_SIGNED_FILE_CONTENT
|
||||||
)
|
)
|
||||||
from .mocks import mock_recv_keys
|
from .mocks import mock_recv_keys
|
||||||
|
|
||||||
@@ -28,9 +28,9 @@ from .mocks import mock_recv_keys
|
|||||||
class KeyTestCase(BaseTestCase):
|
class KeyTestCase(BaseTestCase):
|
||||||
def test_key_instance_creation(self):
|
def test_key_instance_creation(self):
|
||||||
# Creating a Key instance is analogous to importing a key
|
# Creating a Key instance is analogous to importing a key
|
||||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
key = Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
self.assertEqual(key.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(key.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
@mock.patch.object(gnupg.GPG, 'search_keys', autospec=True)
|
@mock.patch.object(gnupg.GPG, 'search_keys', autospec=True)
|
||||||
def test_key_search(self, search_keys):
|
def test_key_search(self, search_keys):
|
||||||
@@ -69,36 +69,36 @@ class KeyTestCase(BaseTestCase):
|
|||||||
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
||||||
result = Key.objects.verify_file(signed_file)
|
result = Key.objects.verify_file(signed_file)
|
||||||
|
|
||||||
self.assertTrue(result.key_id in TEST_KEY_FINGERPRINT)
|
self.assertTrue(result.key_id in TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_embedded_verification_with_key(self):
|
def test_embedded_verification_with_key(self):
|
||||||
Key.objects.create(key_data=TEST_KEY_DATA)
|
Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
||||||
result = Key.objects.verify_file(signed_file)
|
result = Key.objects.verify_file(signed_file)
|
||||||
|
|
||||||
self.assertEqual(result.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(result.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_embedded_verification_with_correct_fingerprint(self):
|
def test_embedded_verification_with_correct_fingerprint(self):
|
||||||
Key.objects.create(key_data=TEST_KEY_DATA)
|
Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
||||||
result = Key.objects.verify_file(
|
result = Key.objects.verify_file(
|
||||||
signed_file, key_fingerprint=TEST_KEY_FINGERPRINT
|
signed_file, key_fingerprint=TEST_KEY_PRIVATE_FINGERPRINT
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(result.valid)
|
self.assertTrue(result.valid)
|
||||||
self.assertEqual(result.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(result.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_embedded_verification_with_incorrect_fingerprint(self):
|
def test_embedded_verification_with_incorrect_fingerprint(self):
|
||||||
Key.objects.create(key_data=TEST_KEY_DATA)
|
Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
||||||
with self.assertRaises(KeyDoesNotExist):
|
with self.assertRaises(KeyDoesNotExist):
|
||||||
Key.objects.verify_file(signed_file, key_fingerprint='999')
|
Key.objects.verify_file(signed_file, key_fingerprint='999')
|
||||||
|
|
||||||
def test_signed_file_decryption(self):
|
def test_signed_file_decryption(self):
|
||||||
Key.objects.create(key_data=TEST_KEY_DATA)
|
Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
with open(TEST_SIGNED_FILE, mode='rb') as signed_file:
|
||||||
result = Key.objects.decrypt_file(file_object=signed_file)
|
result = Key.objects.decrypt_file(file_object=signed_file)
|
||||||
@@ -122,10 +122,10 @@ class KeyTestCase(BaseTestCase):
|
|||||||
file_object=test_file, signature_file=signature_file
|
file_object=test_file, signature_file=signature_file
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(result.key_id in TEST_KEY_FINGERPRINT)
|
self.assertTrue(result.key_id in TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_detached_verification_with_key(self):
|
def test_detached_verification_with_key(self):
|
||||||
Key.objects.create(key_data=TEST_KEY_DATA)
|
Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_DETACHED_SIGNATURE, mode='rb') as signature_file:
|
with open(TEST_DETACHED_SIGNATURE, mode='rb') as signature_file:
|
||||||
with open(TEST_FILE, mode='rb') as test_file:
|
with open(TEST_FILE, mode='rb') as test_file:
|
||||||
@@ -134,10 +134,10 @@ class KeyTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertEqual(result.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(result.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|
||||||
def test_detached_signing_no_passphrase(self):
|
def test_detached_signing_no_passphrase(self):
|
||||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
key = Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with self.assertRaises(NeedPassphrase):
|
with self.assertRaises(NeedPassphrase):
|
||||||
with open(TEST_FILE, mode='rb') as test_file:
|
with open(TEST_FILE, mode='rb') as test_file:
|
||||||
@@ -146,7 +146,7 @@ class KeyTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_detached_signing_bad_passphrase(self):
|
def test_detached_signing_bad_passphrase(self):
|
||||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
key = Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with self.assertRaises(PassphraseError):
|
with self.assertRaises(PassphraseError):
|
||||||
with open(TEST_FILE, mode='rb') as test_file:
|
with open(TEST_FILE, mode='rb') as test_file:
|
||||||
@@ -156,12 +156,12 @@ class KeyTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_detached_signing_with_passphrase(self):
|
def test_detached_signing_with_passphrase(self):
|
||||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
key = Key.objects.create(key_data=TEST_KEY_PRIVATE_DATA)
|
||||||
|
|
||||||
with open(TEST_FILE, mode='rb') as test_file:
|
with open(TEST_FILE, mode='rb') as test_file:
|
||||||
detached_signature = key.sign_file(
|
detached_signature = key.sign_file(
|
||||||
file_object=test_file, detached=True,
|
file_object=test_file, detached=True,
|
||||||
passphrase=TEST_KEY_PASSPHRASE
|
passphrase=TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
)
|
)
|
||||||
|
|
||||||
signature_file = io.BytesIO()
|
signature_file = io.BytesIO()
|
||||||
@@ -175,4 +175,4 @@ class KeyTestCase(BaseTestCase):
|
|||||||
|
|
||||||
signature_file.close()
|
signature_file.close()
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertEqual(result.fingerprint, TEST_KEY_FINGERPRINT)
|
self.assertEqual(result.fingerprint, TEST_KEY_PRIVATE_FINGERPRINT)
|
||||||
|
|||||||
@@ -7,25 +7,13 @@ from mayan.apps.common.tests.base import GenericViewTestCase
|
|||||||
from ..models import Key
|
from ..models import Key
|
||||||
from ..permissions import permission_key_download, permission_key_upload
|
from ..permissions import permission_key_download, permission_key_upload
|
||||||
|
|
||||||
from .literals import TEST_KEY_DATA, TEST_KEY_FINGERPRINT
|
from .literals import TEST_KEY_PRIVATE_FINGERPRINT
|
||||||
from .mixins import KeyTestMixin
|
from .mixins import KeyTestMixin, KeyViewTestMixin
|
||||||
|
|
||||||
|
|
||||||
class KeyViewTestMixin(object):
|
|
||||||
def _request_test_key_download_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='django_gpg:key_download', kwargs={'pk': self.test_key.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_key_upload_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='django_gpg:key_upload', data={'key_data': TEST_KEY_DATA}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class KeyViewTestCase(KeyTestMixin, KeyViewTestMixin, GenericViewTestCase):
|
class KeyViewTestCase(KeyTestMixin, KeyViewTestMixin, GenericViewTestCase):
|
||||||
def test_key_download_view_no_permission(self):
|
def test_key_download_view_no_permission(self):
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
|
|
||||||
response = self._request_test_key_download_view()
|
response = self._request_test_key_download_view()
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
@@ -33,16 +21,16 @@ class KeyViewTestCase(KeyTestMixin, KeyViewTestMixin, GenericViewTestCase):
|
|||||||
def test_key_download_view_with_permission(self):
|
def test_key_download_view_with_permission(self):
|
||||||
self.expected_content_type = 'application/octet-stream; charset=utf-8'
|
self.expected_content_type = 'application/octet-stream; charset=utf-8'
|
||||||
|
|
||||||
self._create_test_key()
|
self._create_test_key_private()
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_key, permission=permission_key_download
|
obj=self.test_key_private, permission=permission_key_download
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._request_test_key_download_view()
|
response = self._request_test_key_download_view()
|
||||||
assert_download_response(
|
assert_download_response(
|
||||||
self, response=response, content=self.test_key.key_data,
|
self, response=response, content=self.test_key_private.key_data,
|
||||||
basename=self.test_key.key_id,
|
basename=self.test_key_private.key_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_key_upload_view_no_permission(self):
|
def test_key_upload_view_no_permission(self):
|
||||||
@@ -59,5 +47,5 @@ class KeyViewTestCase(KeyTestMixin, KeyViewTestMixin, GenericViewTestCase):
|
|||||||
|
|
||||||
self.assertEqual(Key.objects.count(), 1)
|
self.assertEqual(Key.objects.count(), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
Key.objects.first().fingerprint, TEST_KEY_FINGERPRINT
|
Key.objects.first().fingerprint, TEST_KEY_PRIVATE_FINGERPRINT
|
||||||
)
|
)
|
||||||
|
|||||||
252
mayan/apps/document_signatures/api_views.py
Normal file
252
mayan/apps/document_signatures/api_views.py
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from mayan.apps.acls.models import AccessControlList
|
||||||
|
from mayan.apps.documents.models import Document
|
||||||
|
from mayan.apps.rest_api import generics
|
||||||
|
|
||||||
|
from .models import DetachedSignature, EmbeddedSignature
|
||||||
|
from .permissions import (
|
||||||
|
permission_document_version_sign_detached,
|
||||||
|
permission_document_version_sign_embedded,
|
||||||
|
permission_document_version_signature_delete,
|
||||||
|
permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
from .serializers import (
|
||||||
|
DetachedSignatureSerializer, EmbeddedSignatureSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentDetachedSignatureListView(generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
get: Returns a list of all the detached signatures of a document version.
|
||||||
|
post: Create an detached signature for a document version.
|
||||||
|
"""
|
||||||
|
serializer_class = DetachedSignatureSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_queryset(), pk=self.kwargs['document_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'POST':
|
||||||
|
permission = permission_document_version_sign_detached
|
||||||
|
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission, queryset=Document.objects.all(),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_version_queryset(),
|
||||||
|
pk=self.kwargs['document_version_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version_queryset(self):
|
||||||
|
return self.get_document().versions.all()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return DetachedSignature.objects.filter(
|
||||||
|
document_version=self.get_document_version()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
if not self.request:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return super(
|
||||||
|
APIDocumentDetachedSignatureListView, self
|
||||||
|
).get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
"""
|
||||||
|
Extra context provided to the serializer class.
|
||||||
|
"""
|
||||||
|
context = super(
|
||||||
|
APIDocumentDetachedSignatureListView, self
|
||||||
|
).get_serializer_context()
|
||||||
|
if self.kwargs:
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
'document_version': self.get_document_version(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentDetachedSignatureView(generics.RetrieveDestroyAPIView):
|
||||||
|
"""
|
||||||
|
delete: Delete an detached signature of the selected document.
|
||||||
|
get: Returns the details of the selected detached signature.
|
||||||
|
"""
|
||||||
|
lookup_url_kwarg = 'detached_signature_id'
|
||||||
|
serializer_class = DetachedSignatureSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_queryset(), pk=self.kwargs['document_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'POST':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'DELETE':
|
||||||
|
permission = permission_document_version_signature_delete
|
||||||
|
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission, queryset=Document.objects.all(),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_version_queryset(),
|
||||||
|
pk=self.kwargs['document_version_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version_queryset(self):
|
||||||
|
return self.get_document().versions.all()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return DetachedSignature.objects.filter(
|
||||||
|
document_version=self.get_document_version()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
"""
|
||||||
|
Extra context provided to the serializer class.
|
||||||
|
"""
|
||||||
|
context = super(APIDocumentDetachedSignatureView, self).get_serializer_context()
|
||||||
|
if self.kwargs:
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
'document': self.get_document(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentEmbeddedSignatureListView(generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
get: Returns a list of all the embedded signatures of a document version.
|
||||||
|
post: Create an embedded signature for a document version.
|
||||||
|
"""
|
||||||
|
serializer_class = EmbeddedSignatureSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_queryset(), pk=self.kwargs['document_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'POST':
|
||||||
|
permission = permission_document_version_sign_embedded
|
||||||
|
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission, queryset=Document.objects.all(),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_version_queryset(),
|
||||||
|
pk=self.kwargs['document_version_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version_queryset(self):
|
||||||
|
return self.get_document().versions.all()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return EmbeddedSignature.objects.filter(
|
||||||
|
document_version=self.get_document_version()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
if not self.request:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return super(
|
||||||
|
APIDocumentEmbeddedSignatureListView, self
|
||||||
|
).get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
"""
|
||||||
|
Extra context provided to the serializer class.
|
||||||
|
"""
|
||||||
|
context = super(
|
||||||
|
APIDocumentEmbeddedSignatureListView, self
|
||||||
|
).get_serializer_context()
|
||||||
|
if self.kwargs:
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
'document_version': self.get_document_version(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocumentEmbeddedSignatureView(generics.RetrieveDestroyAPIView):
|
||||||
|
"""
|
||||||
|
delete: Delete an embedded signature of the selected document.
|
||||||
|
get: Returns the details of the selected embedded signature.
|
||||||
|
"""
|
||||||
|
lookup_url_kwarg = 'embedded_signature_id'
|
||||||
|
serializer_class = EmbeddedSignatureSerializer
|
||||||
|
|
||||||
|
def get_document(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_queryset(), pk=self.kwargs['document_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'POST':
|
||||||
|
permission = permission_document_version_signature_view
|
||||||
|
elif self.request.method == 'DELETE':
|
||||||
|
permission = permission_document_version_signature_delete
|
||||||
|
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission, queryset=Document.objects.all(),
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version(self):
|
||||||
|
return get_object_or_404(
|
||||||
|
klass=self.get_document_version_queryset(),
|
||||||
|
pk=self.kwargs['document_version_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_document_version_queryset(self):
|
||||||
|
return self.get_document().versions.all()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return EmbeddedSignature.objects.filter(
|
||||||
|
document_version=self.get_document_version()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
"""
|
||||||
|
Extra context provided to the serializer class.
|
||||||
|
"""
|
||||||
|
context = super(APIDocumentEmbeddedSignatureView, self).get_serializer_context()
|
||||||
|
if self.kwargs:
|
||||||
|
context.update(
|
||||||
|
{
|
||||||
|
'document': self.get_document(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return context
|
||||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class DocumentSignaturesApp(MayanAppConfig):
|
class DocumentSignaturesApp(MayanAppConfig):
|
||||||
app_namespace = 'signatures'
|
app_namespace = 'signatures'
|
||||||
app_url = 'signatures'
|
app_url = 'signatures'
|
||||||
|
has_rest_api = True
|
||||||
has_tests = True
|
has_tests = True
|
||||||
name = 'mayan.apps.document_signatures'
|
name = 'mayan.apps.document_signatures'
|
||||||
verbose_name = _('Document signatures')
|
verbose_name = _('Document signatures')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from django.core.files import File
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from mayan.apps.django_gpg.exceptions import DecryptionError
|
from mayan.apps.django_gpg.exceptions import DecryptionError
|
||||||
@@ -13,6 +14,29 @@ from mayan.apps.storage.utils import mkstemp
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignatureManager(models.Manager):
|
||||||
|
def sign_document_version(
|
||||||
|
self, document_version, key, passphrase=None, user=None
|
||||||
|
):
|
||||||
|
temporary_file_object, temporary_filename = mkstemp()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with document_version.open() as file_object:
|
||||||
|
key.sign_file(
|
||||||
|
binary=True, detached=True, file_object=file_object,
|
||||||
|
output=temporary_filename, passphrase=passphrase
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return self.create(
|
||||||
|
document_version=document_version,
|
||||||
|
signature_file=File(temporary_file_object)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
os.unlink(temporary_filename)
|
||||||
|
|
||||||
|
|
||||||
class EmbeddedSignatureManager(models.Manager):
|
class EmbeddedSignatureManager(models.Manager):
|
||||||
def open_signed(self, file_object, document_version):
|
def open_signed(self, file_object, document_version):
|
||||||
for signature in self.filter(document_version=document_version):
|
for signature in self.filter(document_version=document_version):
|
||||||
@@ -28,7 +52,9 @@ class EmbeddedSignatureManager(models.Manager):
|
|||||||
else:
|
else:
|
||||||
return file_object
|
return file_object
|
||||||
|
|
||||||
def sign_document_version(self, document_version, key, passphrase=None, user=None):
|
def sign_document_version(
|
||||||
|
self, document_version, key, passphrase=None, user=None
|
||||||
|
):
|
||||||
temporary_file_object, temporary_filename = mkstemp()
|
temporary_file_object, temporary_filename = mkstemp()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -44,11 +70,13 @@ class EmbeddedSignatureManager(models.Manager):
|
|||||||
new_version = document_version.document.new_version(
|
new_version = document_version.document.new_version(
|
||||||
file_object=file_object, _user=user
|
file_object=file_object, _user=user
|
||||||
)
|
)
|
||||||
|
# This is a potential race condition but we have not way
|
||||||
|
# to access the final signature at this point.
|
||||||
|
signature = self.filter(document_version=new_version).first()
|
||||||
|
return signature or self.none()
|
||||||
finally:
|
finally:
|
||||||
os.unlink(temporary_filename)
|
os.unlink(temporary_filename)
|
||||||
|
|
||||||
return new_version
|
|
||||||
|
|
||||||
def unsigned_document_versions(self):
|
def unsigned_document_versions(self):
|
||||||
return DocumentVersion.objects.exclude(
|
return DocumentVersion.objects.exclude(
|
||||||
pk__in=self.values('document_version')
|
pk__in=self.values('document_version')
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from mayan.apps.django_gpg.exceptions import VerificationError
|
|||||||
from mayan.apps.django_gpg.models import Key
|
from mayan.apps.django_gpg.models import Key
|
||||||
from mayan.apps.documents.models import DocumentVersion
|
from mayan.apps.documents.models import DocumentVersion
|
||||||
|
|
||||||
from .managers import EmbeddedSignatureManager
|
from .managers import DetachedSignatureManager, EmbeddedSignatureManager
|
||||||
from .storages import storage_detachedsignature
|
from .storages import storage_detachedsignature
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -57,6 +57,7 @@ class SignatureBaseModel(models.Model):
|
|||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('pk',)
|
||||||
verbose_name = _('Document version signature')
|
verbose_name = _('Document version signature')
|
||||||
verbose_name_plural = _('Document version signatures')
|
verbose_name_plural = _('Document version signatures')
|
||||||
|
|
||||||
@@ -131,8 +132,7 @@ class DetachedSignature(SignatureBaseModel):
|
|||||||
upload_to=upload_to, verbose_name=_('Signature file')
|
upload_to=upload_to, verbose_name=_('Signature file')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Don't inherit the SignatureBaseModel manager
|
objects = DetachedSignatureManager()
|
||||||
objects = models.Manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Document version detached signature')
|
verbose_name = _('Document version detached signature')
|
||||||
|
|||||||
147
mayan/apps/document_signatures/serializers.py
Normal file
147
mayan/apps/document_signatures/serializers.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from mayan.apps.acls.models import AccessControlList
|
||||||
|
from mayan.apps.django_gpg.models import Key
|
||||||
|
from mayan.apps.django_gpg.permissions import permission_key_sign
|
||||||
|
from mayan.apps.rest_api.relations import MultiKwargHyperlinkedIdentityField
|
||||||
|
|
||||||
|
from .models import DetachedSignature, EmbeddedSignature
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignatureSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
document_version_url = MultiKwargHyperlinkedIdentityField(
|
||||||
|
view_kwargs=(
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version_id',
|
||||||
|
'lookup_url_kwarg': 'version_pk',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version.document.pk',
|
||||||
|
'lookup_url_kwarg': 'pk',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
view_name='rest_api:documentversion-detail'
|
||||||
|
)
|
||||||
|
|
||||||
|
url = MultiKwargHyperlinkedIdentityField(
|
||||||
|
view_kwargs=(
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version.document.pk',
|
||||||
|
'lookup_url_kwarg': 'document_id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version_id',
|
||||||
|
'lookup_url_kwarg': 'document_version_id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'pk',
|
||||||
|
'lookup_url_kwarg': 'detached_signature_id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
view_name='rest_api:detachedsignature-detail'
|
||||||
|
)
|
||||||
|
passphrase = serializers.CharField(required=False, write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = (
|
||||||
|
'date', 'document_version_url', 'key_id', 'signature_id',
|
||||||
|
'passphrase', 'public_key_fingerprint', 'url'
|
||||||
|
)
|
||||||
|
model = DetachedSignature
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
key_id = validated_data.pop('key_id')
|
||||||
|
passphrase = validated_data.pop('passphrase', None)
|
||||||
|
|
||||||
|
key_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_key_sign, queryset=Key.objects.all(),
|
||||||
|
user=self.context['request'].user
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = key_queryset.get(fingerprint__endswith=key_id)
|
||||||
|
except Key.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
'key_id': [
|
||||||
|
'Key "{}" not found.'.format(key_id)
|
||||||
|
]
|
||||||
|
}, code='invalid'
|
||||||
|
)
|
||||||
|
|
||||||
|
return DetachedSignature.objects.sign_document_version(
|
||||||
|
document_version=self.context['document_version'], key=key,
|
||||||
|
passphrase=passphrase, user=self.context['request'].user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddedSignatureSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
document_version_url = MultiKwargHyperlinkedIdentityField(
|
||||||
|
view_kwargs=(
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version_id',
|
||||||
|
'lookup_url_kwarg': 'version_pk',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version.document.pk',
|
||||||
|
'lookup_url_kwarg': 'pk',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
view_name='rest_api:documentversion-detail'
|
||||||
|
)
|
||||||
|
|
||||||
|
url = MultiKwargHyperlinkedIdentityField(
|
||||||
|
view_kwargs=(
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version.document.pk',
|
||||||
|
'lookup_url_kwarg': 'document_id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'document_version_id',
|
||||||
|
'lookup_url_kwarg': 'document_version_id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lookup_field': 'pk',
|
||||||
|
'lookup_url_kwarg': 'embedded_signature_id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
view_name='rest_api:embeddedsignature-detail'
|
||||||
|
)
|
||||||
|
passphrase = serializers.CharField(required=False, write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = (
|
||||||
|
'date', 'document_version_url', 'key_id', 'signature_id',
|
||||||
|
'passphrase', 'public_key_fingerprint', 'url'
|
||||||
|
)
|
||||||
|
model = EmbeddedSignature
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
key_id = validated_data.pop('key_id')
|
||||||
|
passphrase = validated_data.pop('passphrase', None)
|
||||||
|
|
||||||
|
key_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_key_sign, queryset=Key.objects.all(),
|
||||||
|
user=self.context['request'].user
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = key_queryset.get(fingerprint__endswith=key_id)
|
||||||
|
except Key.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
'key_id': [
|
||||||
|
'Key "{}" not found.'.format(key_id)
|
||||||
|
]
|
||||||
|
}, code='invalid'
|
||||||
|
)
|
||||||
|
|
||||||
|
signature = EmbeddedSignature.objects.sign_document_version(
|
||||||
|
document_version=self.context['document_version'], key=key,
|
||||||
|
passphrase=passphrase, user=self.context['request'].user
|
||||||
|
)
|
||||||
|
|
||||||
|
return signature
|
||||||
@@ -12,9 +12,9 @@ TEST_SIGNATURE_FILE_PATH = os.path.join(
|
|||||||
settings.BASE_DIR, 'apps', 'document_signatures', 'tests', 'contrib',
|
settings.BASE_DIR, 'apps', 'document_signatures', 'tests', 'contrib',
|
||||||
'sample_documents', 'mayan_11_1.pdf.sig'
|
'sample_documents', 'mayan_11_1.pdf.sig'
|
||||||
)
|
)
|
||||||
TEST_KEY_FILE = os.path.join(
|
TEST_KEY_FILE_PATH = os.path.join(
|
||||||
settings.BASE_DIR, 'apps', 'document_signatures', 'tests', 'contrib',
|
settings.BASE_DIR, 'apps', 'document_signatures', 'tests', 'contrib',
|
||||||
'sample_documents', 'key0x5F3F7F75D210724D.asc'
|
'sample_documents', 'key0x5F3F7F75D210724D.asc'
|
||||||
)
|
)
|
||||||
TEST_KEY_ID = '5F3F7F75D210724D'
|
TEST_KEY_PUBLIC_ID = '5F3F7F75D210724D'
|
||||||
TEST_SIGNATURE_ID = 'XVkoGKw35yU1iq11dZPiv7uAY7k'
|
TEST_SIGNATURE_ID = 'XVkoGKw35yU1iq11dZPiv7uAY7k'
|
||||||
|
|||||||
@@ -3,13 +3,116 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
|
||||||
from mayan.apps.django_gpg.models import Key
|
from mayan.apps.django_gpg.models import Key
|
||||||
|
from mayan.apps.django_gpg.tests.literals import TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
|
|
||||||
from ..models import DetachedSignature
|
from ..models import DetachedSignature
|
||||||
|
|
||||||
from .literals import TEST_KEY_FILE, TEST_SIGNATURE_FILE_PATH
|
from .literals import TEST_KEY_FILE_PATH, TEST_SIGNATURE_FILE_PATH
|
||||||
|
|
||||||
|
|
||||||
class SignaturesTestMixin(object):
|
class DetachedSignatureAPIViewTestMixin(object):
|
||||||
|
def _request_test_document_signature_detached_create_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='rest_api:document-version-signature-detached-list',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk
|
||||||
|
}, data={
|
||||||
|
'key_id': self.test_key_private.key_id,
|
||||||
|
'passphrase': TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_detached_delete_view(self):
|
||||||
|
return self.delete(
|
||||||
|
viewname='rest_api:detachedsignature-detail',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk,
|
||||||
|
'detached_signature_id': self.test_document_version.signatures.first().pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_detached_detail_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:detachedsignature-detail',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk,
|
||||||
|
'detached_signature_id': self.test_document_version.signatures.first().pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_detached_list_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:document-version-signature-detached-list',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignatureViewTestMixin(object):
|
||||||
|
def _request_test_document_version_signature_download_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='signatures:document_version_signature_download',
|
||||||
|
kwargs={'pk': self.test_signature.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_version_signature_upload_view(self):
|
||||||
|
with open(TEST_SIGNATURE_FILE_PATH, mode='rb') as file_object:
|
||||||
|
return self.post(
|
||||||
|
viewname='signatures:document_version_signature_upload',
|
||||||
|
kwargs={'pk': self.test_document.latest_version.pk},
|
||||||
|
data={'signature_file': file_object}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddedSignatureAPIViewTestMixin(object):
|
||||||
|
def _request_test_document_signature_embedded_create_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='rest_api:document-version-signature-embedded-list',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk
|
||||||
|
}, data={
|
||||||
|
'key_id': self.test_key_private.key_id,
|
||||||
|
'passphrase': TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_embedded_delete_view(self):
|
||||||
|
return self.delete(
|
||||||
|
viewname='rest_api:embeddedsignature-detail',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk,
|
||||||
|
'embedded_signature_id': self.test_document_version.signatures.first().pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_embedded_detail_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:embeddedsignature-detail',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk,
|
||||||
|
'embedded_signature_id': self.test_document_version.signatures.first().pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_signature_embedded_list_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='rest_api:document-version-signature-embedded-list',
|
||||||
|
kwargs={
|
||||||
|
'document_id': self.test_document.pk,
|
||||||
|
'document_version_id': self.test_document_version.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureTestMixin(object):
|
||||||
def _create_test_detached_signature(self):
|
def _create_test_detached_signature(self):
|
||||||
with open(TEST_SIGNATURE_FILE_PATH, mode='rb') as file_object:
|
with open(TEST_SIGNATURE_FILE_PATH, mode='rb') as file_object:
|
||||||
self.test_signature = DetachedSignature.objects.create(
|
self.test_signature = DetachedSignature.objects.create(
|
||||||
@@ -17,6 +120,33 @@ class SignaturesTestMixin(object):
|
|||||||
signature_file=File(file_object)
|
signature_file=File(file_object)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_test_key(self):
|
def _create_test_public_key(self):
|
||||||
with open(TEST_KEY_FILE, mode='rb') as file_object:
|
with open(TEST_KEY_FILE_PATH, mode='rb') as file_object:
|
||||||
self.test_key = Key.objects.create(key_data=file_object.read())
|
self.test_key_public = Key.objects.create(
|
||||||
|
key_data=file_object.read()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureViewTestMixin(object):
|
||||||
|
def _request_test_document_version_signature_delete_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='signatures:document_version_signature_delete',
|
||||||
|
kwargs={'pk': self.test_signature.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_version_signature_details_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='signatures:document_version_signature_details',
|
||||||
|
kwargs={'pk': self.test_signature.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_test_document_version_signature_list_view(self, document):
|
||||||
|
return self.get(
|
||||||
|
viewname='signatures:document_version_signature_list',
|
||||||
|
kwargs={'pk': self.test_document.latest_version.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_all_test_document_version_signature_verify_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='signatures:all_document_version_signature_verify'
|
||||||
|
)
|
||||||
|
|||||||
341
mayan/apps/document_signatures/tests/test_api.py
Normal file
341
mayan/apps/document_signatures/tests/test_api.py
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from mayan.apps.django_gpg.permissions import permission_key_sign
|
||||||
|
from mayan.apps.django_gpg.tests.mixins import KeyTestMixin
|
||||||
|
from mayan.apps.documents.tests.mixins import DocumentTestMixin
|
||||||
|
from mayan.apps.rest_api.tests.base import BaseAPITestCase
|
||||||
|
|
||||||
|
from ..permissions import (
|
||||||
|
permission_document_version_sign_detached,
|
||||||
|
permission_document_version_sign_embedded,
|
||||||
|
permission_document_version_signature_delete,
|
||||||
|
permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
|
||||||
|
from .literals import TEST_KEY_PUBLIC_ID, TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
from .mixins import (
|
||||||
|
DetachedSignatureAPIViewTestMixin, EmbeddedSignatureAPIViewTestMixin,
|
||||||
|
SignatureTestMixin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignatureDocumentAPIViewTestCase(
|
||||||
|
DocumentTestMixin, DetachedSignatureAPIViewTestMixin,
|
||||||
|
KeyTestMixin, SignatureTestMixin, BaseAPITestCase
|
||||||
|
):
|
||||||
|
auto_upload_document = False
|
||||||
|
|
||||||
|
def test_document_signature_detached_delete_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_delete_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_delete_with_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_delete
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_delete_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_detail_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_detail_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def test_document_signature_detached_detail_with_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_detail_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response.data['key_id'], TEST_KEY_PUBLIC_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_create_view_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_create_view_with_document_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_sign_detached
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_create_view_with_key_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_key_private,
|
||||||
|
permission=permission_key_sign
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_create_view_with_full_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_sign_detached
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_key_private,
|
||||||
|
permission=permission_key_sign
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_detached_list_view_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_list_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def test_document_signature_detached_list_view_with_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_detached_list_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data['results'][0]['key_id'], TEST_KEY_PUBLIC_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddedSignatureDocumentAPIViewTestCase(
|
||||||
|
DocumentTestMixin, EmbeddedSignatureAPIViewTestMixin,
|
||||||
|
KeyTestMixin, BaseAPITestCase
|
||||||
|
):
|
||||||
|
auto_upload_document = False
|
||||||
|
|
||||||
|
def test_document_signature_embedded_delete_no_permission(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_delete_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_delete_with_access(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_delete
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_delete_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_detail_no_permission(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_detail_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_detail_with_access(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_detail_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response.data['key_id'], TEST_KEY_PUBLIC_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_create_view_no_permission(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_create_view_with_document_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_sign_embedded
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_create_view_with_key_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_key_private,
|
||||||
|
permission=permission_key_sign
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_create_view_with_full_access(self):
|
||||||
|
self.upload_document()
|
||||||
|
self._create_test_key_private()
|
||||||
|
|
||||||
|
signatures = self.test_document.latest_version.signatures.count()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_sign_embedded
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_key_private,
|
||||||
|
permission=permission_key_sign
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_create_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.test_document.latest_version.signatures.count(),
|
||||||
|
signatures + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_list_view_no_permission(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_list_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def test_document_signature_embedded_list_view_with_access(self):
|
||||||
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_signature_embedded_list_view()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data['results'][0]['key_id'], TEST_KEY_PUBLIC_ID
|
||||||
|
)
|
||||||
@@ -14,10 +14,12 @@ from ..permissions import (
|
|||||||
permission_document_version_signature_view
|
permission_document_version_signature_view
|
||||||
)
|
)
|
||||||
from .literals import TEST_SIGNED_DOCUMENT_PATH
|
from .literals import TEST_SIGNED_DOCUMENT_PATH
|
||||||
from .mixins import SignaturesTestMixin
|
from .mixins import SignatureTestMixin
|
||||||
|
|
||||||
|
|
||||||
class DocumentSignatureLinksTestCase(SignaturesTestMixin, GenericDocumentViewTestCase):
|
class DocumentSignatureLinksTestCase(
|
||||||
|
SignatureTestMixin, GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def test_document_version_signature_detail_link_no_permission(self):
|
def test_document_version_signature_detail_link_no_permission(self):
|
||||||
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ from __future__ import unicode_literals
|
|||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from mayan.apps.django_gpg.models import Key
|
from mayan.apps.django_gpg.tests.literals import TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
from mayan.apps.django_gpg.tests.literals import (
|
from mayan.apps.django_gpg.tests.mixins import KeyTestMixin
|
||||||
TEST_KEY_DATA, TEST_KEY_PASSPHRASE
|
|
||||||
)
|
|
||||||
from mayan.apps.documents.models import DocumentVersion
|
from mayan.apps.documents.models import DocumentVersion
|
||||||
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
||||||
from mayan.apps.documents.tests.literals import TEST_DOCUMENT_PATH
|
from mayan.apps.documents.tests.literals import TEST_DOCUMENT_PATH
|
||||||
@@ -14,11 +12,13 @@ from mayan.apps.documents.tests.literals import TEST_DOCUMENT_PATH
|
|||||||
from ..models import DetachedSignature, EmbeddedSignature
|
from ..models import DetachedSignature, EmbeddedSignature
|
||||||
from ..tasks import task_verify_missing_embedded_signature
|
from ..tasks import task_verify_missing_embedded_signature
|
||||||
|
|
||||||
from .literals import TEST_SIGNED_DOCUMENT_PATH, TEST_KEY_ID, TEST_SIGNATURE_ID
|
from .literals import (
|
||||||
from .mixins import SignaturesTestMixin
|
TEST_SIGNED_DOCUMENT_PATH, TEST_KEY_PUBLIC_ID, TEST_SIGNATURE_ID
|
||||||
|
)
|
||||||
|
from .mixins import SignatureTestMixin
|
||||||
|
|
||||||
|
|
||||||
class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
class DocumentSignaturesTestCase(SignatureTestMixin, GenericDocumentTestCase):
|
||||||
auto_upload_document = False
|
auto_upload_document = False
|
||||||
|
|
||||||
def test_embedded_signature_no_key(self):
|
def test_embedded_signature_no_key(self):
|
||||||
@@ -31,7 +31,7 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signature.document_version, self.test_document.latest_version
|
signature.document_version, self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
self.assertEqual(signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(signature.signature_id, None)
|
self.assertEqual(signature.signature_id, None)
|
||||||
|
|
||||||
def test_embedded_signature_post_key_verify(self):
|
def test_embedded_signature_post_key_verify(self):
|
||||||
@@ -44,17 +44,17 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signature.document_version, self.test_document.latest_version
|
signature.document_version, self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
self.assertEqual(signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(signature.signature_id, None)
|
self.assertEqual(signature.signature_id, None)
|
||||||
|
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
signature = EmbeddedSignature.objects.first()
|
signature = EmbeddedSignature.objects.first()
|
||||||
|
|
||||||
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
||||||
|
|
||||||
def test_embedded_signature_post_no_key_verify(self):
|
def test_embedded_signature_post_no_key_verify(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
|
|
||||||
@@ -65,17 +65,17 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signature.document_version, self.test_document.latest_version
|
signature.document_version, self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
self.assertEqual(signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
||||||
|
|
||||||
self.test_key.delete()
|
self.test_key_public.delete()
|
||||||
|
|
||||||
signature = EmbeddedSignature.objects.first()
|
signature = EmbeddedSignature.objects.first()
|
||||||
|
|
||||||
self.assertEqual(signature.signature_id, None)
|
self.assertEqual(signature.signature_id, None)
|
||||||
|
|
||||||
def test_embedded_signature_with_key(self):
|
def test_embedded_signature_with_key(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
self.test_document_path = TEST_SIGNED_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
|
|
||||||
@@ -87,9 +87,9 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
signature.document_version,
|
signature.document_version,
|
||||||
self.test_document.latest_version
|
self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
self.assertEqual(signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signature.public_key_fingerprint, self.test_key.fingerprint
|
signature.public_key_fingerprint, self.test_key_public.fingerprint
|
||||||
)
|
)
|
||||||
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
self.assertEqual(signature.signature_id, TEST_SIGNATURE_ID)
|
||||||
|
|
||||||
@@ -102,13 +102,14 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.assertEqual(DetachedSignature.objects.count(), 1)
|
self.assertEqual(DetachedSignature.objects.count(), 1)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.test_signature.document_version, self.test_document.latest_version
|
self.test_signature.document_version,
|
||||||
|
self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(self.test_signature.key_id, TEST_KEY_ID)
|
self.assertEqual(self.test_signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(self.test_signature.public_key_fingerprint, None)
|
self.assertEqual(self.test_signature.public_key_fingerprint, None)
|
||||||
|
|
||||||
def test_detached_signature_with_key(self):
|
def test_detached_signature_with_key(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
|
|
||||||
@@ -117,12 +118,13 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.assertEqual(DetachedSignature.objects.count(), 1)
|
self.assertEqual(DetachedSignature.objects.count(), 1)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.test_signature.document_version, self.test_document.latest_version
|
self.test_signature.document_version,
|
||||||
|
self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(self.test_signature.key_id, TEST_KEY_ID)
|
self.assertEqual(self.test_signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.test_signature.public_key_fingerprint,
|
self.test_signature.public_key_fingerprint,
|
||||||
self.test_key.fingerprint
|
self.test_key_public.fingerprint
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_detached_signature_post_key_verify(self):
|
def test_detached_signature_post_key_verify(self):
|
||||||
@@ -137,19 +139,19 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.test_signature.document_version,
|
self.test_signature.document_version,
|
||||||
self.test_document.latest_version
|
self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(self.test_signature.key_id, TEST_KEY_ID)
|
self.assertEqual(self.test_signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(self.test_signature.public_key_fingerprint, None)
|
self.assertEqual(self.test_signature.public_key_fingerprint, None)
|
||||||
|
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
signature = DetachedSignature.objects.first()
|
signature = DetachedSignature.objects.first()
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signature.public_key_fingerprint, self.test_key.fingerprint
|
signature.public_key_fingerprint, self.test_key_public.fingerprint
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_detached_signature_post_no_key_verify(self):
|
def test_detached_signature_post_no_key_verify(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
|
|
||||||
@@ -161,13 +163,13 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
self.test_signature.document_version,
|
self.test_signature.document_version,
|
||||||
self.test_document.latest_version
|
self.test_document.latest_version
|
||||||
)
|
)
|
||||||
self.assertEqual(self.test_signature.key_id, TEST_KEY_ID)
|
self.assertEqual(self.test_signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.test_signature.public_key_fingerprint,
|
self.test_signature.public_key_fingerprint,
|
||||||
self.test_key.fingerprint
|
self.test_key_public.fingerprint
|
||||||
)
|
)
|
||||||
|
|
||||||
self.test_key.delete()
|
self.test_key_public.delete()
|
||||||
|
|
||||||
signature = DetachedSignature.objects.first()
|
signature = DetachedSignature.objects.first()
|
||||||
|
|
||||||
@@ -198,10 +200,10 @@ class DocumentSignaturesTestCase(SignaturesTestMixin, GenericDocumentTestCase):
|
|||||||
signature = EmbeddedSignature.objects.first()
|
signature = EmbeddedSignature.objects.first()
|
||||||
|
|
||||||
self.assertEqual(signature.document_version, signed_version)
|
self.assertEqual(signature.document_version, signed_version)
|
||||||
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
self.assertEqual(signature.key_id, TEST_KEY_PUBLIC_ID)
|
||||||
|
|
||||||
|
|
||||||
class EmbeddedSignaturesTestCase(GenericDocumentTestCase):
|
class EmbeddedSignaturesTestCase(KeyTestMixin, GenericDocumentTestCase):
|
||||||
auto_upload_document = False
|
auto_upload_document = False
|
||||||
|
|
||||||
def test_unsigned_document_version_method(self):
|
def test_unsigned_document_version_method(self):
|
||||||
@@ -255,7 +257,7 @@ class EmbeddedSignaturesTestCase(GenericDocumentTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_signing(self):
|
def test_signing(self):
|
||||||
self.test_key = Key.objects.create(key_data=TEST_KEY_DATA)
|
self._create_test_key_private()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -266,15 +268,15 @@ class EmbeddedSignaturesTestCase(GenericDocumentTestCase):
|
|||||||
file_object.seek(0)
|
file_object.seek(0)
|
||||||
original_hash = hashlib.sha256(file_object.read()).hexdigest()
|
original_hash = hashlib.sha256(file_object.read()).hexdigest()
|
||||||
|
|
||||||
new_version = EmbeddedSignature.objects.sign_document_version(
|
signature = EmbeddedSignature.objects.sign_document_version(
|
||||||
document_version=self.test_document.latest_version,
|
document_version=self.test_document.latest_version,
|
||||||
key=self.test_key,
|
key=self.test_key_private,
|
||||||
passphrase=TEST_KEY_PASSPHRASE
|
passphrase=TEST_KEY_PRIVATE_PASSPHRASE
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(EmbeddedSignature.objects.count(), 1)
|
self.assertEqual(EmbeddedSignature.objects.count(), 1)
|
||||||
|
|
||||||
with new_version.open() as file_object:
|
with signature.document_version.open() as file_object:
|
||||||
file_object.seek(0, 2)
|
file_object.seek(0, 2)
|
||||||
new_size = file_object.tell()
|
new_size = file_object.tell()
|
||||||
file_object.seek(0)
|
file_object.seek(0)
|
||||||
|
|||||||
@@ -15,59 +15,23 @@ from ..permissions import (
|
|||||||
permission_document_version_signature_view
|
permission_document_version_signature_view
|
||||||
)
|
)
|
||||||
|
|
||||||
from .literals import TEST_SIGNATURE_FILE_PATH, TEST_SIGNED_DOCUMENT_PATH
|
from .literals import TEST_SIGNED_DOCUMENT_PATH
|
||||||
from .mixins import SignaturesTestMixin
|
from .mixins import (
|
||||||
|
DetachedSignatureViewTestMixin, SignatureTestMixin,
|
||||||
|
SignatureViewTestMixin
|
||||||
|
)
|
||||||
|
|
||||||
TEST_UNSIGNED_DOCUMENT_COUNT = 4
|
TEST_UNSIGNED_DOCUMENT_COUNT = 4
|
||||||
TEST_SIGNED_DOCUMENT_COUNT = 2
|
TEST_SIGNED_DOCUMENT_COUNT = 2
|
||||||
|
|
||||||
|
|
||||||
class SignaturesViewTestMixin(object):
|
|
||||||
def _request_test_document_version_signature_delete_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='signatures:document_version_signature_delete',
|
|
||||||
kwargs={'pk': self.test_signature.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_document_version_signature_details_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='signatures:document_version_signature_details',
|
|
||||||
kwargs={'pk': self.test_signature.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_document_version_signature_download_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='signatures:document_version_signature_download',
|
|
||||||
kwargs={'pk': self.test_signature.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_document_version_signature_list_view(self, document):
|
|
||||||
return self.get(
|
|
||||||
viewname='signatures:document_version_signature_list',
|
|
||||||
kwargs={'pk': self.test_document.latest_version.pk}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_test_document_version_signature_upload_view(self):
|
|
||||||
with open(TEST_SIGNATURE_FILE_PATH, mode='rb') as file_object:
|
|
||||||
return self.post(
|
|
||||||
viewname='signatures:document_version_signature_upload',
|
|
||||||
kwargs={'pk': self.test_document.latest_version.pk},
|
|
||||||
data={'signature_file': file_object}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _request_all_test_document_version_signature_verify_view(self):
|
|
||||||
return self.post(
|
|
||||||
viewname='signatures:all_document_version_signature_verify'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SignaturesViewTestCase(
|
class SignaturesViewTestCase(
|
||||||
SignaturesTestMixin, SignaturesViewTestMixin, GenericDocumentViewTestCase
|
SignatureTestMixin, SignatureViewTestMixin, GenericDocumentViewTestCase
|
||||||
):
|
):
|
||||||
auto_upload_document = False
|
auto_upload_document = False
|
||||||
|
|
||||||
def test_signature_delete_view_no_permission(self):
|
def test_signature_delete_view_no_permission(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -84,7 +48,7 @@ class SignaturesViewTestCase(
|
|||||||
self.assertEqual(DetachedSignature.objects.count(), 1)
|
self.assertEqual(DetachedSignature.objects.count(), 1)
|
||||||
|
|
||||||
def test_signature_delete_view_with_access(self):
|
def test_signature_delete_view_with_access(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -105,7 +69,7 @@ class SignaturesViewTestCase(
|
|||||||
self.assertEqual(DetachedSignature.objects.count(), 0)
|
self.assertEqual(DetachedSignature.objects.count(), 0)
|
||||||
|
|
||||||
def test_signature_detail_view_no_permission(self):
|
def test_signature_detail_view_no_permission(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -116,7 +80,7 @@ class SignaturesViewTestCase(
|
|||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_signature_detail_view_with_access(self):
|
def test_signature_detail_view_with_access(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -134,37 +98,8 @@ class SignaturesViewTestCase(
|
|||||||
status_code=200
|
status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_signature_download_view_no_permission(self):
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
|
||||||
self.upload_document()
|
|
||||||
|
|
||||||
self._create_test_detached_signature()
|
|
||||||
|
|
||||||
response = self._request_test_document_version_signature_download_view()
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_signature_download_view_with_access(self):
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
|
||||||
self.upload_document()
|
|
||||||
|
|
||||||
self._create_test_detached_signature()
|
|
||||||
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document,
|
|
||||||
permission=permission_document_version_signature_download
|
|
||||||
)
|
|
||||||
|
|
||||||
self.expected_content_type = 'application/octet-stream; charset=utf-8'
|
|
||||||
|
|
||||||
response = self._request_test_document_version_signature_download_view()
|
|
||||||
|
|
||||||
with self.test_signature.signature_file as file_object:
|
|
||||||
assert_download_response(
|
|
||||||
self, response=response, content=file_object.read(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_signature_list_view_no_permission(self):
|
def test_signature_list_view_no_permission(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -174,10 +109,10 @@ class SignaturesViewTestCase(
|
|||||||
response = self._request_test_document_version_signature_list_view(
|
response = self._request_test_document_version_signature_list_view(
|
||||||
document=self.test_document
|
document=self.test_document
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_signature_list_view_with_access(self):
|
def test_signature_list_view_with_access(self):
|
||||||
self._create_test_key()
|
self._create_test_public_key()
|
||||||
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
self.upload_document()
|
self.upload_document()
|
||||||
@@ -195,29 +130,6 @@ class SignaturesViewTestCase(
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.context['object_list'].count(), 1)
|
self.assertEqual(response.context['object_list'].count(), 1)
|
||||||
|
|
||||||
def test_signature_upload_view_no_permission(self):
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
|
||||||
self.upload_document()
|
|
||||||
|
|
||||||
response = self._request_test_document_version_signature_upload_view()
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
self.assertEqual(DetachedSignature.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_signature_upload_view_with_access(self):
|
|
||||||
self.test_document_path = TEST_DOCUMENT_PATH
|
|
||||||
self.upload_document()
|
|
||||||
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document,
|
|
||||||
permission=permission_document_version_signature_upload
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self._request_test_document_version_signature_upload_view()
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
self.assertEqual(DetachedSignature.objects.count(), 1)
|
|
||||||
|
|
||||||
def test_missing_signature_verify_view_no_permission(self):
|
def test_missing_signature_verify_view_no_permission(self):
|
||||||
# Silence converter logging
|
# Silence converter logging
|
||||||
self._silence_logger(name='mayan.apps.converter.backends')
|
self._silence_logger(name='mayan.apps.converter.backends')
|
||||||
@@ -287,3 +199,62 @@ class SignaturesViewTestCase(
|
|||||||
EmbeddedSignature.objects.unsigned_document_versions().count(),
|
EmbeddedSignature.objects.unsigned_document_versions().count(),
|
||||||
TEST_UNSIGNED_DOCUMENT_COUNT
|
TEST_UNSIGNED_DOCUMENT_COUNT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignaturesViewTestCase(
|
||||||
|
SignatureTestMixin, DetachedSignatureViewTestMixin,
|
||||||
|
GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
|
auto_upload_document = False
|
||||||
|
|
||||||
|
def test_signature_download_view_no_permission(self):
|
||||||
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
response = self._request_test_document_version_signature_download_view()
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_signature_download_view_with_access(self):
|
||||||
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._create_test_detached_signature()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_download
|
||||||
|
)
|
||||||
|
|
||||||
|
self.expected_content_type = 'application/octet-stream; charset=utf-8'
|
||||||
|
|
||||||
|
response = self._request_test_document_version_signature_download_view()
|
||||||
|
|
||||||
|
with self.test_signature.signature_file as file_object:
|
||||||
|
assert_download_response(
|
||||||
|
self, response=response, content=file_object.read(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_signature_upload_view_no_permission(self):
|
||||||
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
response = self._request_test_document_version_signature_upload_view()
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.assertEqual(DetachedSignature.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_signature_upload_view_with_access(self):
|
||||||
|
self.test_document_path = TEST_DOCUMENT_PATH
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_version_signature_upload
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_version_signature_upload_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertEqual(DetachedSignature.objects.count(), 1)
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .api_views import (
|
||||||
|
APIDocumentDetachedSignatureListView, APIDocumentDetachedSignatureView,
|
||||||
|
APIDocumentEmbeddedSignatureListView, APIDocumentEmbeddedSignatureView
|
||||||
|
)
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
AllDocumentSignatureVerifyView, DocumentVersionDetachedSignatureCreateView,
|
AllDocumentSignatureVerifyView, DocumentVersionDetachedSignatureCreateView,
|
||||||
DocumentVersionEmbeddedSignatureCreateView,
|
DocumentVersionEmbeddedSignatureCreateView,
|
||||||
@@ -52,3 +57,26 @@ urlpatterns = [
|
|||||||
name='all_document_version_signature_verify'
|
name='all_document_version_signature_verify'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
api_urls = [
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_id>[0-9]+)/versions/(?P<document_version_id>[0-9]+)/signatures/detached/$',
|
||||||
|
view=APIDocumentDetachedSignatureListView.as_view(),
|
||||||
|
name='document-version-signature-detached-list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_id>[0-9]+)/versions/(?P<document_version_id>[0-9]+)/signatures/detached/(?P<detached_signature_id>[0-9]+)/$',
|
||||||
|
view=APIDocumentDetachedSignatureView.as_view(),
|
||||||
|
name='detachedsignature-detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_id>[0-9]+)/versions/(?P<document_version_id>[0-9]+)/signatures/embedded/$',
|
||||||
|
view=APIDocumentEmbeddedSignatureListView.as_view(),
|
||||||
|
name='document-version-signature-embedded-list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^documents/(?P<document_id>[0-9]+)/versions/(?P<document_version_id>[0-9]+)/signatures/embedded/(?P<embedded_signature_id>[0-9]+)/$',
|
||||||
|
view=APIDocumentEmbeddedSignatureView.as_view(),
|
||||||
|
name='embeddedsignature-detail'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.files import File
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
@@ -16,10 +15,10 @@ from mayan.apps.common.generics import (
|
|||||||
ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||||
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView
|
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView
|
||||||
)
|
)
|
||||||
|
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||||
from mayan.apps.django_gpg.exceptions import NeedPassphrase, PassphraseError
|
from mayan.apps.django_gpg.exceptions import NeedPassphrase, PassphraseError
|
||||||
from mayan.apps.django_gpg.permissions import permission_key_sign
|
from mayan.apps.django_gpg.permissions import permission_key_sign
|
||||||
from mayan.apps.documents.models import DocumentVersion
|
from mayan.apps.documents.models import DocumentVersion
|
||||||
from mayan.apps.storage.utils import TemporaryFile
|
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
DocumentVersionSignatureCreateForm,
|
DocumentVersionSignatureCreateForm,
|
||||||
@@ -35,7 +34,7 @@ from .links import (
|
|||||||
link_document_version_signature_embedded_create,
|
link_document_version_signature_embedded_create,
|
||||||
link_document_version_signature_upload
|
link_document_version_signature_upload
|
||||||
)
|
)
|
||||||
from .models import DetachedSignature, SignatureBaseModel
|
from .models import DetachedSignature, EmbeddedSignature, SignatureBaseModel
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_document_version_sign_detached,
|
permission_document_version_sign_detached,
|
||||||
permission_document_version_sign_embedded,
|
permission_document_version_sign_embedded,
|
||||||
@@ -62,11 +61,10 @@ class DocumentVersionDetachedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.get_document_version().open() as file_object:
|
DetachedSignature.objects.sign_document_version(
|
||||||
detached_signature = key.sign_file(
|
document_version=self.get_document_version(),
|
||||||
file_object=file_object, detached=True,
|
key=key, passphrase=passphrase, user=self.request.user
|
||||||
passphrase=passphrase
|
)
|
||||||
)
|
|
||||||
except NeedPassphrase:
|
except NeedPassphrase:
|
||||||
messages.error(
|
messages.error(
|
||||||
message=_('Passphrase is needed to unlock this key.'),
|
message=_('Passphrase is needed to unlock this key.'),
|
||||||
@@ -90,17 +88,6 @@ class DocumentVersionDetachedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
temporary_file_object = TemporaryFile()
|
|
||||||
temporary_file_object.write(detached_signature.data)
|
|
||||||
temporary_file_object.seek(0)
|
|
||||||
|
|
||||||
DetachedSignature.objects.create(
|
|
||||||
document_version=self.get_document_version(),
|
|
||||||
signature_file=File(temporary_file_object)
|
|
||||||
)
|
|
||||||
|
|
||||||
temporary_file_object.close()
|
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
message=_('Document version signed successfully.'),
|
message=_('Document version signed successfully.'),
|
||||||
request=self.request
|
request=self.request
|
||||||
@@ -156,10 +143,10 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.get_document_version().open() as file_object:
|
new_version = EmbeddedSignature.objects.sign_document_version(
|
||||||
signature_result = key.sign_file(
|
document_version=self.get_document_version(),
|
||||||
binary=True, file_object=file_object, passphrase=passphrase
|
key=key, passphrase=passphrase, user=self.request.user
|
||||||
)
|
)
|
||||||
except NeedPassphrase:
|
except NeedPassphrase:
|
||||||
messages.error(
|
messages.error(
|
||||||
message=_('Passphrase is needed to unlock this key.'),
|
message=_('Passphrase is needed to unlock this key.'),
|
||||||
@@ -183,16 +170,6 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
temporary_file_object = TemporaryFile()
|
|
||||||
temporary_file_object.write(signature_result.data)
|
|
||||||
temporary_file_object.seek(0)
|
|
||||||
|
|
||||||
new_version = self.get_document_version().document.new_version(
|
|
||||||
file_object=temporary_file_object, _user=self.request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
temporary_file_object.close()
|
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
message=_('Document version signed successfully.'),
|
message=_('Document version signed successfully.'),
|
||||||
request=self.request
|
request=self.request
|
||||||
@@ -285,20 +262,11 @@ class DocumentVersionSignatureDownloadView(SingleObjectDownloadView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionSignatureListView(SingleObjectListView):
|
class DocumentVersionSignatureListView(
|
||||||
def dispatch(self, request, *args, **kwargs):
|
ExternalObjectMixin, SingleObjectListView
|
||||||
AccessControlList.objects.check_access(
|
):
|
||||||
obj=self.get_document_version(),
|
external_object_class = DocumentVersion
|
||||||
permissions=(permission_document_version_signature_view,),
|
external_object_permission = permission_document_version_signature_view
|
||||||
user=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(
|
|
||||||
DocumentVersionSignatureListView, self
|
|
||||||
).dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_document_version(self):
|
|
||||||
return get_object_or_404(klass=DocumentVersion, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
@@ -314,34 +282,34 @@ class DocumentVersionSignatureListView(SingleObjectListView):
|
|||||||
link_document_version_signature_detached_create.resolve(
|
link_document_version_signature_detached_create.resolve(
|
||||||
RequestContext(
|
RequestContext(
|
||||||
request=self.request, dict_={
|
request=self.request, dict_={
|
||||||
'object': self.get_document_version()
|
'object': self.external_object
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
link_document_version_signature_embedded_create.resolve(
|
link_document_version_signature_embedded_create.resolve(
|
||||||
RequestContext(
|
RequestContext(
|
||||||
request=self.request, dict_={
|
request=self.request, dict_={
|
||||||
'object': self.get_document_version()
|
'object': self.external_object
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
link_document_version_signature_upload.resolve(
|
link_document_version_signature_upload.resolve(
|
||||||
RequestContext(
|
RequestContext(
|
||||||
request=self.request, dict_={
|
request=self.request, dict_={
|
||||||
'object': self.get_document_version()
|
'object': self.external_object
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
'no_results_title': _('There are no signatures for this document.'),
|
'no_results_title': _('There are no signatures for this document.'),
|
||||||
'object': self.get_document_version(),
|
'object': self.external_object,
|
||||||
'title': _(
|
'title': _(
|
||||||
'Signatures for document version: %s'
|
'Signatures for document version: %s'
|
||||||
) % self.get_document_version(),
|
) % self.external_object,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_source_queryset(self):
|
def get_source_queryset(self):
|
||||||
return self.get_document_version().signatures.all()
|
return self.external_object.signatures.all()
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionSignatureUploadView(SingleObjectCreateView):
|
class DocumentVersionSignatureUploadView(SingleObjectCreateView):
|
||||||
|
|||||||
91
mayan/apps/rest_api/relations.py
Normal file
91
mayan/apps/rest_api/relations.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db.models import Manager
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.relations import HyperlinkedIdentityField
|
||||||
|
|
||||||
|
from mayan.apps.common.utils import resolve_attribute
|
||||||
|
|
||||||
|
|
||||||
|
class FilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.source_model = kwargs.pop('source_model', None)
|
||||||
|
self.source_permission = kwargs.pop('source_permission', None)
|
||||||
|
self.source_queryset = kwargs.pop('source_queryset', None)
|
||||||
|
self.source_queryset_method = kwargs.pop('source_queryset_method', None)
|
||||||
|
super(FilteredPrimaryKeyRelatedField, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
AccessControlList = apps.get_model(
|
||||||
|
app_label='acls', model_name='AccessControlList'
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.source_model:
|
||||||
|
queryset = self.source_model._meta.default_manager.all()
|
||||||
|
elif self.source_queryset:
|
||||||
|
queryset = self.source_queryset
|
||||||
|
if isinstance(queryset, (QuerySet, Manager)):
|
||||||
|
# Ensure queryset is re-evaluated whenever used.
|
||||||
|
queryset = queryset.all()
|
||||||
|
else:
|
||||||
|
method_name = self.source_queryset_method or 'get_{}_queryset'.format(
|
||||||
|
self.field_name
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
queryset = getattr(self.parent, method_name)()
|
||||||
|
except AttributeError:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Need to provide a source_model, a '
|
||||||
|
'source_queryset, a source_queryset_method, or '
|
||||||
|
'a method named "%s".' % method_name
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'request' in self.context, (
|
||||||
|
"`%s` requires the request in the serializer"
|
||||||
|
" context. Add `context={'request': request}` when instantiating "
|
||||||
|
"the serializer." % self.__class__.__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
|
||||||
|
if self.source_permission:
|
||||||
|
return AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=self.source_permission, queryset=queryset,
|
||||||
|
user=request.user
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class MultiKwargHyperlinkedIdentityField(HyperlinkedIdentityField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.view_kwargs = kwargs.pop('view_kwargs', [])
|
||||||
|
super(MultiKwargHyperlinkedIdentityField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_url(self, obj, view_name, request, format):
|
||||||
|
"""
|
||||||
|
Extends HyperlinkedRelatedField to allow passing more than one view
|
||||||
|
keyword argument.
|
||||||
|
----
|
||||||
|
Given an object, return the URL that hyperlinks to the object.
|
||||||
|
|
||||||
|
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
||||||
|
attributes are not configured to correctly match the URL conf.
|
||||||
|
"""
|
||||||
|
# Unsaved objects will not yet have a valid URL.
|
||||||
|
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
||||||
|
return None
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
for entry in self.view_kwargs:
|
||||||
|
kwargs[entry['lookup_url_kwarg']] = resolve_attribute(
|
||||||
|
obj=obj, attribute=entry['lookup_field']
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.reverse(
|
||||||
|
viewname=view_name, kwargs=kwargs, request=request, format=format
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user