diff --git a/poetry.lock b/poetry.lock index 49a2b931..f45ba26a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4525,6 +4525,25 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-mock" +version = "1.11.0" +description = "Mock out responses from the requests package" +optional = false +python-versions = "*" +files = [ + {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, + {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, +] + +[package.dependencies] +requests = ">=2.3,<3" +six = "*" + +[package.extras] +fixture = ["fixtures"] +test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] + [[package]] name = "requests-oauthlib" version = "1.1.0" @@ -5454,4 +5473,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">= 3.9, <3.10" -content-hash = "333138adfc14816cffb148984a47016955fb90e93a3d7b6fee40176d1423a5cb" +content-hash = "7929a80e571855788ae63579a2a7ef73928acabc8a9d92b1d1c111d485d9a2c0" diff --git a/pyproject.toml b/pyproject.toml index 831f9d4d..e0dc501e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ charset-normalizer = "<2.1.0" python-levenshtein = "<0.20.0" jsonschema = "<4.0.0" pydocstyle = ">=6.1.1,<6.2" -requests-mock = "^1.9.3" +requests-mock = "^1.11.0" [tool.poetry.dev-dependencies] Flask-Debugtoolbar = ">=0.10.1" diff --git a/sonar/modules/documents/extensions.py b/sonar/modules/documents/extensions.py index 26c8abc9..274384a3 100644 --- a/sonar/modules/documents/extensions.py +++ b/sonar/modules/documents/extensions.py @@ -22,6 +22,7 @@ from invenio_pidstore.models import PersistentIdentifier, PIDDoesNotExistError from invenio_records.extensions import RecordExtension + from sonar.modules.documents.tasks import register_urn_code_from_document from sonar.modules.documents.urn import Urn diff --git a/sonar/modules/documents/permissions.py b/sonar/modules/documents/permissions.py index 309ff700..1f93b2b2 100644 --- a/sonar/modules/documents/permissions.py +++ b/sonar/modules/documents/permissions.py @@ -18,7 +18,9 @@ """Permissions for documents.""" from flask import request -from invenio_files_rest.models import Bucket +from invenio_files_rest.models import Bucket, ObjectVersion +from invenio_pidstore.errors import PIDDoesNotExistError +from invenio_pidstore.models import PersistentIdentifier from sonar.modules.documents.api import DocumentRecord from sonar.modules.organisations.api import current_organisation @@ -104,7 +106,6 @@ def update(cls, user, record): return False user = user.replace_refs() - return document.has_subdivision(user.get('subdivision', {}).get('pid')) @classmethod @@ -119,8 +120,33 @@ def delete(cls, user, record): if not user or not user.is_admin: return False - # Same rules as update - return cls.update(user, record) + # Check delete conditions and consider same rules as update + return cls.can_delete(record) and cls.update(user, record) + + @classmethod + def can_delete(cls, record): + """Delete permission conditions. + + :param record: Record to check. + :returns: True if action can be done. + """ + # Delete only documents with no URN or no registred URN + document = DocumentRecord.get_record_by_pid(record['pid']) + + if document: + # check if document has urn + try: + urn_identifier = PersistentIdentifier\ + .get_by_object('urn', 'rec', document.id) + except PIDDoesNotExistError: + return True + + # check if urn is registered + if urn_identifier.is_registered(): + return False + + return True + class DocumentFilesPermission(FilesPermission): """Documents files permissions. @@ -193,6 +219,12 @@ def delete(cls, user, record, pid, parent_record): :param pid: The :class:`invenio_pidstore.models.PersistentIdentifier` instance. :param parent_record: the record related to the bucket. - :returns: True is action can be done. + :returns: True if action can be done. """ + document = cls.get_document(parent_record) + if isinstance(record, ObjectVersion): + file_type = document.files[record.key]['type'] + if file_type == 'file' and record.mimetype == 'application/pdf': + return DocumentPermission.can_delete(parent_record)\ + and cls.update(user, record, pid, parent_record) return cls.update(user, record, pid, parent_record) diff --git a/sonar/modules/documents/urn.py b/sonar/modules/documents/urn.py index ce322304..62cd26a7 100644 --- a/sonar/modules/documents/urn.py +++ b/sonar/modules/documents/urn.py @@ -98,9 +98,13 @@ def create_urn(cls, record): object_uuid=record.id, status=PIDStatus.NEW, ) - record["identifiedBy"].append( - {"type": "bf:Urn", "value": urn_code} - ) + if "identifiedBy" in record: + record["identifiedBy"].append( + {"type": "bf:Urn", "value": urn_code} + ) + else: + record["identifiedBy"] = \ + [{"type": "bf:Urn", "value": urn_code}] except PIDAlreadyExists: current_app.logger.error( 'generated urn already exist for document: ' @@ -177,7 +181,6 @@ def register_urn_pid(cls, urn=None): pid.status = PIDStatus.REGISTERED db.session.commit() - @classmethod def get_documents_to_generate_urns(cls): """Get documents that need a URN code.. diff --git a/tests/api/documents/test_documents_files_permissions.py b/tests/api/documents/test_documents_files_permissions.py index 8137ebf1..9e70a3f9 100644 --- a/tests/api/documents/test_documents_files_permissions.py +++ b/tests/api/documents/test_documents_files_permissions.py @@ -17,6 +17,7 @@ """Test documents files permissions.""" +from io import BytesIO from flask import url_for from flask_security import url_for_security @@ -46,7 +47,6 @@ def test_update_delete(client, superuser, admin, moderator, assert res.status_code == status - def test_read_metadata(client, superuser, admin, moderator, submitter, user, document_with_file): """Test read files permissions.""" @@ -74,6 +74,7 @@ def test_read_metadata(client, superuser, admin, moderator, res = client.get(url_files) assert res.status_code == status + def test_read_content(client, superuser, admin, moderator, submitter, user, user_without_org, document_with_file): """Test read documents permissions.""" @@ -120,3 +121,32 @@ def test_read_content(client, superuser, admin, moderator, client.get(url_for_security('logout')) res = client.get(url_file_content) assert res.status_code == status + + +def test_file_of_document_with_urn_delete(client, superuser, + minimal_thesis_document): + """Test delete file of document with registered URN identifier.""" + # Logged as superuser + login_user_via_session(client, email=superuser['email']) + + # Add pdf file to document + minimal_thesis_document.files['test.pdf'] = BytesIO(b'File content') + minimal_thesis_document.files['test.pdf']['type'] = 'file' + minimal_thesis_document.commit() + + url_file_content = url_for( + 'invenio_records_files.doc_object_api', + pid_value=minimal_thesis_document['pid'], key='test.pdf') + res = client.delete(url_file_content) + assert res.status_code == 403 + + # Add png file to document + minimal_thesis_document.files['test.png'] = BytesIO(b'File content') + minimal_thesis_document.files['test.png']['type'] = 'file' + minimal_thesis_document.commit() + + url_file_content = url_for( + 'invenio_records_files.doc_object_api', + pid_value=minimal_thesis_document['pid'], key='test.png') + res = client.delete(url_file_content) + assert res.status_code == 204 diff --git a/tests/api/documents/test_documents_permissions.py b/tests/api/documents/test_documents_permissions.py index c362f90a..14f48133 100644 --- a/tests/api/documents/test_documents_permissions.py +++ b/tests/api/documents/test_documents_permissions.py @@ -18,6 +18,7 @@ """Test documents permissions.""" import json +from io import BytesIO import mock from flask import url_for @@ -403,3 +404,18 @@ def test_delete(client, document, make_document, make_user, superuser, admin, assert res.status_code == 204 assert PersistentIdentifier.get('ark', ark_id).status == \ PIDStatus.DELETED + + +def test_document_with_urn_delete(client, superuser, minimal_thesis_document): + """Test delete document with registered URN identifier.""" + # Add file to document + minimal_thesis_document.files['test.pdf'] = BytesIO(b'File content') + minimal_thesis_document.files['test.pdf']['type'] = 'file' + minimal_thesis_document.commit() + + # Logged as superuser + login_user_via_session(client, email=superuser['email']) + pid = minimal_thesis_document['pid'] + res = client.delete(url_for('invenio_records_rest.doc_item', + pid_value=pid)) + assert res.status_code == 403