diff --git a/api/preprints/serializers.py b/api/preprints/serializers.py index 305e7e1ebd04..bb88bdb615fa 100644 --- a/api/preprints/serializers.py +++ b/api/preprints/serializers.py @@ -36,7 +36,6 @@ NodeTagField, ) from api.base.metrics import MetricsSerializerMixin -from api.base.serializers import BaseAPISerializer from api.taxonomies.serializers import TaxonomizableSerializerMixin from framework.auth import Auth from framework.exceptions import PermissionsError diff --git a/api/preprints/views.py b/api/preprints/views.py index dfd69e9665da..ebb8074a2700 100644 --- a/api/preprints/views.py +++ b/api/preprints/views.py @@ -652,15 +652,15 @@ def get_queryset(self): return self.get_resource().affiliated_institutions.all() -class PrprintInstitutionsRelationship(JSONAPIBaseView, generics.RetrieveUpdateDestroyAPIView, generics.CreateAPIView, PreprintMixin): +class PreprintInstitutionsRelationshipList(JSONAPIBaseView, generics.RetrieveUpdateDestroyAPIView, PreprintMixin): """ """ permission_classes = ( drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, WriteOrPublicForRelationshipInstitutions, ) - required_read_scopes = [CoreScopes.NODE_BASE_READ] - required_write_scopes = [CoreScopes.NODE_BASE_WRITE] + required_read_scopes = [CoreScopes.PREPRINTS_READ] + required_write_scopes = [CoreScopes.PREPRINTS_WRITE] serializer_class = PreprintsInstitutionRelationshipSerializer parser_classes = (JSONAPIRelationshipParser, JSONAPIRelationshipParserForRegularJSON, ) @@ -668,7 +668,7 @@ class PrprintInstitutionsRelationship(JSONAPIBaseView, generics.RetrieveUpdateDe view_name = 'preprint-relationships-institutions' def get_resource(self): - return self.get_preprint(check_object_permissions=False) + return self.get_preprint() def get_object(self): preprint = self.get_resource() @@ -676,5 +676,20 @@ def get_object(self): 'data': preprint.affiliated_institutions.all(), 'self': preprint, } - self.check_object_permissions(self.request, obj) return obj + + def perform_destroy(self, instance): + data = self.request.data['data'] + user = self.request.user + current_insts = {inst._id: inst for inst in instance['data']} + node = instance['self'] + + for val in data: + if val['id'] in current_insts: + if not user.is_affiliated_with_institution(current_insts[val['id']]) and not node.has_permission(user, 'admin'): + raise PermissionDenied + node.remove_affiliated_institution(inst=current_insts[val['id']], user=user) + node.save() + + def create(self, *args, **kwargs): + return super().create(*args, **kwargs) diff --git a/api/requests/permissions.py b/api/requests/permissions.py index 961e107ea0e2..a0ff5f134657 100644 --- a/api/requests/permissions.py +++ b/api/requests/permissions.py @@ -99,4 +99,3 @@ def has_object_permission(self, request, view, obj): # Requesters may edit their comment or submit their request return is_requester return False - diff --git a/api_tests/preprints/views/test_preprint_institutions_relationship.py b/api_tests/preprints/views/test_preprint_institutions_relationship.py new file mode 100644 index 000000000000..6e1ad1c72b5a --- /dev/null +++ b/api_tests/preprints/views/test_preprint_institutions_relationship.py @@ -0,0 +1,163 @@ +import pytest + +from api.base.settings.defaults import API_BASE +from osf_tests.factories import ( + PreprintFactory, + AuthUserFactory, + InstitutionFactory, +) + + +@pytest.mark.django_db +class TestPreprintInstitutionsList: + + @pytest.fixture() + def user(self): + return AuthUserFactory() + + @pytest.fixture() + def admin_with_institutional_affilation(self, institution, preprint): + user = AuthUserFactory() + preprint.add_permission(user, 'admin') + user.add_or_update_affiliated_institution(institution) + return user + + @pytest.fixture() + def no_auth_with_institutional_affilation(self, institution): + user = AuthUserFactory() + user.add_or_update_affiliated_institution(institution) + return user + + @pytest.fixture() + def admin_without_institutional_affilation(self, institution, preprint): + user = AuthUserFactory() + preprint.add_permission(user, 'admin') + return user + + @pytest.fixture() + def institution(self): + return InstitutionFactory() + + @pytest.fixture() + def preprint(self): + return PreprintFactory() + + @pytest.fixture() + def url(self, preprint): + return f'/{API_BASE}preprints/{preprint._id}/relationships/institutions/' + + def test_update_affiliated_institutions_add(self, app, user, admin_with_institutional_affilation, admin_without_institutional_affilation, preprint, url, + institution): + update_institutions_payload = { + 'data': [{'type': 'institutions', 'id': institution._id}] + } + + res = app.put_json_api( + url, + update_institutions_payload, + auth=user.auth, + expect_errors=True + ) + assert res.status_code == 403 + + res = app.put_json_api( + url, + update_institutions_payload, + auth=admin_without_institutional_affilation.auth, + expect_errors=True + ) + assert res.status_code == 400 + assert res.json['errors'][0]['detail'] == f'User is not affiliated with {institution.name},' + + res = app.put_json_api( + url, + update_institutions_payload, + auth=admin_with_institutional_affilation.auth + ) + assert res.status_code == 201 + + preprint.reload() + assert institution in preprint.affiliated_institutions.all() + + log = preprint.logs.latest() + assert log.action == 'affiliated_institution_added' + assert log.params['institution'] == { + 'id': institution._id, + 'name': institution.name + } + + def test_update_affiliated_institutions_remove(self, app, user, admin_with_institutional_affilation, no_auth_with_institutional_affilation, admin_without_institutional_affilation, preprint, url, + institution): + + preprint.affiliated_institutions.add(institution) + preprint.save() + + update_institutions_payload = { + 'data': [] + } + + res = app.put_json_api( + url, + update_institutions_payload, + auth=user.auth, + expect_errors=True + ) + assert res.status_code == 403 + + res = app.put_json_api( + url, + update_institutions_payload, + auth=no_auth_with_institutional_affilation.auth, + expect_errors=True + ) + assert res.status_code == 403 + + res = app.put_json_api( + url, + update_institutions_payload, + auth=admin_without_institutional_affilation.auth, + expect_errors=True + ) + assert res.status_code == 201 # you can always remove it you are an admin + + res = app.put_json_api( + url, + update_institutions_payload, + auth=admin_with_institutional_affilation.auth + ) + assert res.status_code == 201 + + preprint.reload() + assert institution not in preprint.affiliated_institutions.all() + + log = preprint.logs.latest() + assert log.action == 'affiliated_institution_removed' + assert log.params['institution'] == { + 'id': institution._id, + 'name': institution.name + } + + def test_preprint_institutions_list_get(self, app, user, admin_with_institutional_affilation, admin_without_institutional_affilation, preprint, url, + institution): + # For testing purposes + preprint.is_public = False + preprint.save() + + res = app.get(url, expect_errors=True) + assert res.status_code == 401 + + res = app.get(url, auth=user.auth, expect_errors=True) + assert res.status_code == 403 + + res = app.get(url, auth=admin_without_institutional_affilation.auth, expect_errors=True) + assert res.status_code == 200 + + assert res.status_code == 200 + assert not res.json['data'] + + res = app.get(url, auth=admin_with_institutional_affilation.auth) + assert res.status_code == 200 + + preprint.add_institutional_affilation(institution) + assert res.json['data'][0]['id'] == institution._id + assert res.json['data'][0]['type'] == 'institution'