diff --git a/PKG-INFO b/PKG-INFO index dc95e1696b2d2e7338ec69f8531b8abc4cfcafaf..12b2ae113fa1f861b1b2a00d330875fa7744fa7c 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: swh.deposit -Version: 0.0.9 +Version: 0.0.10 Summary: Software Heritage Deposit Server Home-page: https://forge.softwareheritage.org/source/swh-deposit/ Author: Software Heritage developers diff --git a/swh.deposit.egg-info/PKG-INFO b/swh.deposit.egg-info/PKG-INFO index dc95e1696b2d2e7338ec69f8531b8abc4cfcafaf..12b2ae113fa1f861b1b2a00d330875fa7744fa7c 100644 --- a/swh.deposit.egg-info/PKG-INFO +++ b/swh.deposit.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: swh.deposit -Version: 0.0.9 +Version: 0.0.10 Summary: Software Heritage Deposit Server Home-page: https://forge.softwareheritage.org/source/swh-deposit/ Author: Software Heritage developers diff --git a/swh.deposit.egg-info/SOURCES.txt b/swh.deposit.egg-info/SOURCES.txt index 046e8ab97a91b736964994a8a066d06f6a96bd1a..cee19e9975e45be089281fad898af32ae2ceb62d 100644 --- a/swh.deposit.egg-info/SOURCES.txt +++ b/swh.deposit.egg-info/SOURCES.txt @@ -28,6 +28,7 @@ swh/deposit/__init__.py swh/deposit/admin.py swh/deposit/apps.py swh/deposit/auth.py +swh/deposit/config.py swh/deposit/create_user.py swh/deposit/errors.py swh/deposit/models.py @@ -36,6 +37,8 @@ swh/deposit/urls.py swh/deposit/wsgi.py swh/deposit/api/common.py swh/deposit/api/deposit.py +swh/deposit/api/deposit_status.py +swh/deposit/api/deposit_update.py swh/deposit/api/service_document.py swh/deposit/fixtures/__init__.py swh/deposit/fixtures/deposit_data.yaml @@ -61,4 +64,5 @@ swh/deposit/tests/objstorage.py swh/deposit/tests/api/__init__.py swh/deposit/tests/api/test_common.py swh/deposit/tests/api/test_deposit.py +swh/deposit/tests/api/test_deposit_status.py swh/deposit/tests/api/test_service_document.py \ No newline at end of file diff --git a/swh/deposit/api/common.py b/swh/deposit/api/common.py index efc2b4b9d6887eadfa44f38e20fde2f0e7fad375..4e9e1569d93d25a0736731b179532dd15fcbbef5 100644 --- a/swh/deposit/api/common.py +++ b/swh/deposit/api/common.py @@ -3,13 +3,9 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -import logging - from django.http import HttpResponse from rest_framework.views import APIView -from swh.core.config import SWHConfig - ACCEPT_PACKAGINGS = ['http://purl.org/net/sword/package/SimpleZip'] ACCEPT_CONTENT_TYPES = ['application/zip'] @@ -19,25 +15,6 @@ def index(req): return HttpResponse('SWH Deposit API') -class SWHDefaultConfig(SWHConfig): - """Mixin intended to enrich views with SWH configuration. - - """ - CONFIG_BASE_FILENAME = 'deposit/server' - - DEFAULT_CONFIG = { - 'max_upload_size': ('int', 209715200), - 'verbose': ('bool', False), - 'noop': ('bool', False), - } - - def __init__(self, **config): - super().__init__() - self.config = self.parse_config_file() - self.config.update(config) - self.log = logging.getLogger('swh.deposit') - - class SWHAPIView(APIView): """Mixin intended as a based API view to enforce the basic authentication check diff --git a/swh/deposit/api/deposit.py b/swh/deposit/api/deposit.py index 56aade9a287cc410c198d918cbd8528cc51905d5..93dc49c3abf8b9abf7d9f62c2a7e1d0bc1e1547f 100644 --- a/swh/deposit/api/deposit.py +++ b/swh/deposit/api/deposit.py @@ -14,76 +14,15 @@ from rest_framework import status from swh.objstorage import get_objstorage from swh.model.hashutil import hash_to_hex +from ..config import SWHDefaultConfig from ..models import Deposit, DepositRequest, DepositType -from ..models import DEPOSIT_STATUS_DETAIL from ..parsers import SWHFileUploadParser, SWHAtomEntryParser from ..parsers import SWHMultiPartParser, parse_xml from ..errors import MAX_UPLOAD_SIZE_EXCEEDED, BAD_REQUEST, ERROR_CONTENT -from ..errors import CHECKSUM_MISMATCH, MEDIATION_NOT_ALLOWED, NOT_FOUND -from ..errors import METHOD_NOT_ALLOWED, make_error, make_error_response +from ..errors import CHECKSUM_MISMATCH, MEDIATION_NOT_ALLOWED, make_error +from ..errors import METHOD_NOT_ALLOWED, make_error_response -from .common import SWHDefaultConfig, SWHAPIView, ACCEPT_PACKAGINGS - - -class SWHDepositStatus(SWHDefaultConfig, SWHAPIView): - """Deposit status. - - What's known as 'State IRI' in the sword specification. - - HTTP verbs supported: GET - - """ - def get(self, req, client_name, deposit_id, format=None): - try: - deposit = Deposit.objects.get(pk=deposit_id) - # FIXME: Find why Deposit.objects.get(pk=deposit_id, - # client=User(username=client_name)) does not work - if deposit.client.username != client_name: - raise Deposit.DoesNotExist - except Deposit.DoesNotExist: - err = make_error( - NOT_FOUND, - 'deposit %s for client %s does not exist' % ( - deposit_id, client_name)) - return make_error_response(req, err['error']) - - context = { - 'deposit_id': deposit.id, - 'status': deposit.status, - 'status_detail': DEPOSIT_STATUS_DETAIL[deposit.status], - } - - return render(req, 'deposit/status.xml', - context=context, - content_type='application/xml', - status=status.HTTP_200_OK) - - -class SWHUpdateArchiveDeposit(SWHDefaultConfig, SWHAPIView): - """Deposit request class defining api endpoints for sword deposit. - - What's known as 'EM IRI' in the sword specification. - - HTTP verbs supported: PUT - - """ - def put(self, req, client_name, deposit_name, format=None): - pass - - -class SWHUpdateMetadataDeposit(SWHDefaultConfig, SWHAPIView): - """Deposit request class defining api endpoints for sword deposit. - - What's known as 'Edit IRI' (and SE IRI) in the sword specification. - - HTTP verbs supported: POST (SE IRI), PUT (Edit IRI) - - """ - def post(self, req, client_name, deposit_name, format=None): - pass - - def put(self, req, client_name, deposit_name, format=None): - pass +from .common import SWHAPIView, ACCEPT_PACKAGINGS class SWHDeposit(SWHDefaultConfig, SWHAPIView): diff --git a/swh/deposit/api/deposit_status.py b/swh/deposit/api/deposit_status.py new file mode 100644 index 0000000000000000000000000000000000000000..016bdb193f1201da91c9803f2dcf27ee36140e06 --- /dev/null +++ b/swh/deposit/api/deposit_status.py @@ -0,0 +1,47 @@ +# Copyright (C) 2017 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from django.shortcuts import render +from rest_framework import status + +from ..config import SWHDefaultConfig +from ..errors import NOT_FOUND, make_error, make_error_response +from ..models import DEPOSIT_STATUS_DETAIL, Deposit + +from .common import SWHAPIView + + +class SWHDepositStatus(SWHDefaultConfig, SWHAPIView): + """Deposit status. + + What's known as 'State IRI' in the sword specification. + + HTTP verbs supported: GET + + """ + def get(self, req, client_name, deposit_id, format=None): + try: + deposit = Deposit.objects.get(pk=deposit_id) + # FIXME: Find why Deposit.objects.get(pk=deposit_id, + # client=User(username=client_name)) does not work + if deposit.client.username != client_name: + raise Deposit.DoesNotExist + except Deposit.DoesNotExist: + err = make_error( + NOT_FOUND, + 'deposit %s for client %s does not exist' % ( + deposit_id, client_name)) + return make_error_response(req, err['error']) + + context = { + 'deposit_id': deposit.id, + 'status': deposit.status, + 'status_detail': DEPOSIT_STATUS_DETAIL[deposit.status], + } + + return render(req, 'deposit/status.xml', + context=context, + content_type='application/xml', + status=status.HTTP_200_OK) diff --git a/swh/deposit/api/deposit_update.py b/swh/deposit/api/deposit_update.py new file mode 100644 index 0000000000000000000000000000000000000000..bdf22efbdb49d9f214dce90e00cd38a18ff0d159 --- /dev/null +++ b/swh/deposit/api/deposit_update.py @@ -0,0 +1,34 @@ +# Copyright (C) 2017 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from ..config import SWHDefaultConfig +from .common import SWHAPIView + + +class SWHUpdateArchiveDeposit(SWHDefaultConfig, SWHAPIView): + """Deposit request class defining api endpoints for sword deposit. + + What's known as 'EM IRI' in the sword specification. + + HTTP verbs supported: PUT + + """ + def put(self, req, client_name, deposit_name, format=None): + pass + + +class SWHUpdateMetadataDeposit(SWHDefaultConfig, SWHAPIView): + """Deposit request class defining api endpoints for sword deposit. + + What's known as 'Edit IRI' (and SE IRI) in the sword specification. + + HTTP verbs supported: POST (SE IRI), PUT (Edit IRI) + + """ + def post(self, req, client_name, deposit_name, format=None): + pass + + def put(self, req, client_name, deposit_name, format=None): + pass diff --git a/swh/deposit/api/service_document.py b/swh/deposit/api/service_document.py index b6213b6a688c1ed04befdfc84cacc5b8d1e6fa8c..7f7362fb170d1409b246cb4bb7286c54c9e26a79 100644 --- a/swh/deposit/api/service_document.py +++ b/swh/deposit/api/service_document.py @@ -6,8 +6,8 @@ from django.contrib.auth.models import User from django.shortcuts import render -from .common import SWHDefaultConfig, SWHAPIView, ACCEPT_PACKAGINGS -from .common import ACCEPT_CONTENT_TYPES +from ..config import SWHDefaultConfig +from .common import SWHAPIView, ACCEPT_PACKAGINGS, ACCEPT_CONTENT_TYPES class SWHServiceDocument(SWHDefaultConfig, SWHAPIView): diff --git a/swh/deposit/auth.py b/swh/deposit/auth.py index 6ae62c1d617c557f48bf7887b8e9b88570987301..2e4e7ccd0755400c7b49e923b3f2ff166cae0b41 100644 --- a/swh/deposit/auth.py +++ b/swh/deposit/auth.py @@ -5,10 +5,9 @@ import base64 -from swh.core.config import SWHConfig - from django.contrib.auth import authenticate, login +from .config import SWHDefaultConfig from .errors import UNAUTHORIZED, make_error, make_error_response @@ -49,29 +48,36 @@ def view_or_basicauth(view, request, test_func, realm="", *args, **kwargs): return response -class HttpBasicAuthMiddleware(SWHConfig): +class HttpBasicAuthMiddleware(SWHDefaultConfig): """Middleware to install or not the basic authentication layer according to swh's yaml configuration. - Note: / is white-listed from authentication - """ - CONFIG_BASE_FILENAME = 'deposit/server' + Note: white-list authentication is supported (cf. DEFAULT_CONFIG) - DEFAULT_CONFIG = { - 'authentication': ('bool', True), + """ + ADDITIONAL_CONFIG = { + 'authentication': ('dict', { + 'activated': 'true', + 'white-list': { + 'GET': ['/'], + } + }) } def __init__(self, get_response): super().__init__() self.get_response = get_response - self.config = self.parse_config_file() + self.auth = self.config['authentication'] + self.auth_activated = self.auth['activated'] + if self.auth_activated: + self.whitelist = self.auth.get('white-list', {}) def __call__(self, request): - # white-list / - if request.method == 'GET' and request.path == '/': - return self.get_response(request) + if self.auth_activated: + whitelist = self.whitelist.get(request.method) + if whitelist and request.path in whitelist: + return self.get_response(request) - if self.config['authentication']: r = view_or_basicauth(view=self.get_response, request=request, test_func=lambda u: u.is_authenticated()) diff --git a/swh/deposit/config.py b/swh/deposit/config.py new file mode 100644 index 0000000000000000000000000000000000000000..958b079b27fd54e2757f0c8080313d7a116125e9 --- /dev/null +++ b/swh/deposit/config.py @@ -0,0 +1,27 @@ +# Copyright (C) 2017 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import logging + +from swh.core.config import SWHConfig + + +class SWHDefaultConfig(SWHConfig): + """Mixin intended to enrich views with SWH configuration. + + """ + CONFIG_BASE_FILENAME = 'deposit/server' + + DEFAULT_CONFIG = { + 'max_upload_size': ('int', 209715200), + 'verbose': ('bool', False), + 'noop': ('bool', False), + } + + def __init__(self, **config): + super().__init__() + self.config = self.parse_config_file() + self.config.update(config) + self.log = logging.getLogger('swh.deposit') diff --git a/swh/deposit/tests/__init__.py b/swh/deposit/tests/__init__.py index 069e27ebee44b412ce2a2e47401e68cff50c59fc..b540862e677b77cbc7ed4532ad5183b42956e00a 100644 --- a/swh/deposit/tests/__init__.py +++ b/swh/deposit/tests/__init__.py @@ -23,6 +23,12 @@ TEST_CONFIG = { 'max_upload_size': 209715200, 'verbose': False, 'noop': False, + 'authentication': { + 'activated': 'true', + 'white-list': { + 'GET': ['/'], + }, + }, } @@ -33,11 +39,11 @@ def parse_config_file(base_filename=None, config_filename=None, os.environ.setdefault("DJANGO_SETTINGS_MODULE", "swh.web.settings.development") -from swh.deposit.api.deposit import SWHDeposit # noqa -from swh.deposit.api.service_document import SWHServiceDocument # noqa +from swh.deposit.config import SWHDefaultConfig # noqa -# monkey patch :\ -SWHServiceDocument.parse_config_file = parse_config_file -SWHDeposit.parse_config_file = parse_config_file +# monkey patch this class method permits to override, for tests +# purposes, the default configuration without side-effect, i.e do not +# load the configuration from disk +SWHDefaultConfig.parse_config_file = parse_config_file django.setup() diff --git a/swh/deposit/tests/api/test_deposit.py b/swh/deposit/tests/api/test_deposit.py index e83ef507e1cbdaf72ca5788648969c90a3ff6337..72cb1f8b7aae5900e049266bbc4f2a45c214e516 100644 --- a/swh/deposit/tests/api/test_deposit.py +++ b/swh/deposit/tests/api/test_deposit.py @@ -210,19 +210,6 @@ and other stuff</description> self.assertEqual(response._headers['location'], ('Location', status_url)) - # check status - status_response = self.client.get(status_url) - - self.assertEqual(status_response.status_code, status.HTTP_200_OK) - r = parse_xml(BytesIO(status_response.content)) - - self.assertEqual(r['{http://www.w3.org/2005/Atom}deposit_id'], - deposit.id) - self.assertEqual(r['{http://www.w3.org/2005/Atom}status'], - 'ready') - self.assertEqual(r['{http://www.w3.org/2005/Atom}detail'], - 'deposit is fully received and ready for injection') - def test_post_deposit_binary_upload_2_steps(self): """Binary upload should be accepted diff --git a/swh/deposit/tests/api/test_deposit_status.py b/swh/deposit/tests/api/test_deposit_status.py new file mode 100644 index 0000000000000000000000000000000000000000..4692882bb9311a37170ab4b72f9fb5c43b813c5f --- /dev/null +++ b/swh/deposit/tests/api/test_deposit_status.py @@ -0,0 +1,70 @@ +# Copyright (C) 2017 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import hashlib + +from django.core.urlresolvers import reverse +from io import BytesIO +from rest_framework import status +from rest_framework.test import APITestCase + +from swh.deposit.models import Deposit +from swh.deposit.parsers import parse_xml + +from ..common import BasicTestCase, WithAuthTestCase + + +class DepositStatusTestCase(APITestCase, WithAuthTestCase, BasicTestCase): + """Status on deposit + + """ + + def test_post_deposit_with_status_check(self): + """Binary upload should be accepted + + """ + # given + url = reverse('upload', args=[self.username]) + data_text = b'some content' + md5sum = hashlib.md5(data_text).hexdigest() + + external_id = 'some-external-id-1' + + # when + response = self.client.post( + url, + content_type='application/zip', # as zip + data=data_text, + # + headers + HTTP_SLUG=external_id, + HTTP_CONTENT_MD5=md5sum, + HTTP_PACKAGING='http://purl.org/net/sword/package/SimpleZip', + HTTP_IN_PROGRESS='false', + HTTP_CONTENT_LENGTH=len(data_text), + HTTP_CONTENT_DISPOSITION='attachment; filename=filename0') + + # then + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + deposit = Deposit.objects.get(external_id=external_id) + + status_url = reverse('status', + args=[self.username, deposit.id]) + + self.assertEqual(response._headers['location'], + ('Location', status_url)) + + # check status + status_response = self.client.get(status_url) + + self.assertEqual(status_response.status_code, status.HTTP_200_OK) + r = parse_xml(BytesIO(status_response.content)) + + self.assertEqual(r['{http://www.w3.org/2005/Atom}deposit_id'], + deposit.id) + self.assertEqual(r['{http://www.w3.org/2005/Atom}status'], + 'ready') + self.assertEqual(r['{http://www.w3.org/2005/Atom}detail'], + 'deposit is fully received and ready for injection') diff --git a/swh/deposit/urls.py b/swh/deposit/urls.py index 42ffc83749f7a7d4f19234f8061552a47fa632c0..dbef352fcb835b6f959169b75ef9b2cd0ef57dee 100644 --- a/swh/deposit/urls.py +++ b/swh/deposit/urls.py @@ -23,8 +23,10 @@ from django.contrib import admin from rest_framework.urlpatterns import format_suffix_patterns from .api.common import index -from .api.deposit import SWHDeposit, SWHDepositStatus -from .api.deposit import SWHUpdateMetadataDeposit, SWHUpdateArchiveDeposit +from .api.deposit import SWHDeposit +from .api.deposit_status import SWHDepositStatus +from .api.deposit_update import SWHUpdateMetadataDeposit +from .api.deposit_update import SWHUpdateArchiveDeposit from .api.service_document import SWHServiceDocument diff --git a/version.txt b/version.txt index 96d3c4748fdc36907783e78023c2589e0b0b4291..2678a04646067abc1ac65c53069774790957af1a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.0.9-0-gb661938 \ No newline at end of file +v0.0.10-0-g7209ccc \ No newline at end of file