Repository: inveniosoftware/invenio-app-ils (language: python)

Issue:
Make sure the views are correctly protected via permissions
check the different record endpoints / different request methods
check the custom endpoints




Discussion / hints:
The best thing to do is probably to add unit test for all the endpoints and verify permissions.

Fix patch:
diff --git a/invenio_app_ils/config.py b/invenio_app_ils/config.py
index decdf5b1b..9395d012e 100644
--- a/invenio_app_ils/config.py
+++ b/invenio_app_ils/config.py
@@ -508,7 +508,7 @@ def _(x):
         default_media_type="application/json",
         max_result_window=_RECORDS_REST_MAX_RESULT_WINDOW,
         error_handlers=dict(),
-        read_permission_factory_imp=record_read_permission_factory,
+        read_permission_factory_imp=backoffice_permission,
         list_permission_factory_imp=backoffice_permission,
         create_permission_factory_imp=backoffice_permission,
         update_permission_factory_imp=backoffice_permission,


Test patch:
diff --git a/tests/api/circulation/test_loan_list_permissions.py b/tests/api/circulation/test_loan_list_permissions.py
index 5ebf1f71a..955361fd4 100644
--- a/tests/api/circulation/test_loan_list_permissions.py
+++ b/tests/api/circulation/test_loan_list_permissions.py
@@ -11,7 +11,7 @@
 
 from flask import url_for
 
-from tests.helpers import user_login
+from tests.helpers import user_login, user_logout
 
 
 def _search_loans(client, json_headers, **kwargs):
@@ -86,3 +86,19 @@ def test_patrons_cannot_search_other_loans(
         q="patron_pid:{}".format(str(users["patron2"].id)),
     )
     assert res.status_code == 403
+
+
+def test_most_loaned_permissions(client, json_headers, users, testdata):
+    """Test that only the backoffice is able to list the most loaned items."""
+    url = url_for("invenio_app_ils_circulation_stats.most-loaned")
+    tests = [
+        ("admin", 200),
+        ("librarian", 200),
+        ("patron1", 403),
+        ("anonymous", 401)
+    ]
+    for username, status in tests:
+        user_login(client, username, users)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code == status
+        user_logout(client)
diff --git a/tests/api/circulation/test_loan_update.py b/tests/api/circulation/test_loan_update.py
index deb3a9fc0..e6a06f13a 100644
--- a/tests/api/circulation/test_loan_update.py
+++ b/tests/api/circulation/test_loan_update.py
@@ -66,6 +66,9 @@ def test_loan_update_date(client, json_headers, users, testdata):
     """Test the edition of the dates on a loan."""
     pid = testdata["loans"][4]["pid"]  # Item on loan
 
+    res = _post_loan_update(client, json_headers, pid)
+    assert res.status_code == 401
+
     # Patron cannot update loan
     user_login(client, "patron1", users)
     res = _post_loan_update(client, json_headers, pid)
diff --git a/tests/api/circulation/test_mails.py b/tests/api/circulation/test_mails.py
index ae95f4366..b0f481e39 100644
--- a/tests/api/circulation/test_mails.py
+++ b/tests/api/circulation/test_mails.py
@@ -42,6 +42,21 @@ def test_email_on_loan_checkout(
         assert len(outbox) == 1
 
 
+def test_email_on_overdue_permissions(client, testdata, json_headers, users):
+    """Test that only the backoffice can send a reminder."""
+    pid = testdata["loans"][0]["pid"]
+    url = url_for("invenio_app_ils_circulation.loanid_email",
+                  pid_value=pid)
+    tests = [
+        ("patron1", 403),
+        ("anonymous", 401)
+    ]
+    for username, expected_status in tests:
+        user_login(client, username, users)
+        res = client.post(url, headers=json_headers)
+        assert res.status_code == expected_status
+
+
 def test_email_on_overdue_loans(
     app_with_mail, db, users, testdata, mocker, client, json_headers
 ):
diff --git a/tests/api/ils/test_closures.py b/tests/api/ils/test_closures.py
index 912fcc211..566bc5220 100644
--- a/tests/api/ils/test_closures.py
+++ b/tests/api/ils/test_closures.py
@@ -16,7 +16,11 @@
 from invenio_app_ils.proxies import current_app_ils
 from tests.helpers import user_login
 
+_HTTP_OK = [200, 201, 204]
 _LOCATION_PID = "locid-1"
+_LOCATION_NAME = "Location name"
+_ITEM_ENDPOINT = "invenio_records_rest.locid_item"
+_LIST_ENDPOINT = "invenio_records_rest.locid_list"
 _WEEKDAYS = ["monday", "tuesday", "wednesday",
              "thursday", "friday", "saturday", "sunday"]
 _DEFAULT_TIMES = [{"start_time": "08:00", "end_time": "12:00"},
@@ -54,6 +58,72 @@ def _date_from_string(date_string):
     return arrow.get(date_string).date()
 
 
+def test_location_permissions(client, testdata, json_headers, users):
+    """Test location endpoints permissions."""
+    dummy_location = dict(
+        name=_LOCATION_NAME,
+        opening_weekdays=[{"weekday": w, "is_open": True,
+                           "times": _DEFAULT_TIMES} for w in _WEEKDAYS],
+        opening_exceptions=[]
+    )
+    tests = [
+        ("admin", _HTTP_OK, dummy_location),
+        ("librarian", _HTTP_OK, dummy_location),
+        ("patron1", [403], dummy_location),
+        ("anonymous", [401], dummy_location),
+    ]
+    read_statuses = [200]
+
+    def _test_list(expected_status):
+        """Test get list."""
+        url = url_for(_LIST_ENDPOINT)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    def _test_create(expected_status, data):
+        """Test record creation."""
+        url = url_for(_LIST_ENDPOINT)
+        res = client.post(url, headers=json_headers, data=json.dumps(data))
+        assert res.status_code in expected_status
+
+        if res.status_code < 400:
+            record = res.get_json()["metadata"]
+            assert record["name"] == _LOCATION_NAME
+            return record["pid"]
+
+    def _test_update(expected_status, data, pid):
+        """Test record update."""
+        pid_value = pid or _LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.put(url, headers=json_headers, data=json.dumps(data))
+        assert res.status_code in expected_status
+        if res.status_code < 400:
+            record = res.get_json()["metadata"]
+            assert record["name"] == _LOCATION_NAME
+
+    def _test_read(expected_status, pid):
+        """Test record read."""
+        pid_value = pid or _LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    def _test_delete(expected_status, pid):
+        """Test record delete."""
+        pid_value = pid or _LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.delete(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    for username, expected_status, data in tests:
+        user_login(client, username, users)
+        _test_list(read_statuses)
+        pid = _test_create(expected_status, data)
+        _test_update(expected_status, data, pid)
+        _test_read(read_statuses, pid)
+        _test_delete(expected_status, pid)
+
+
 def test_location_validation(client, json_headers, users, testdata):
     def _test_update_location_closures(data, expected_code):
         url = url_for("invenio_records_rest.locid_item",
diff --git a/tests/api/ils/test_internal_locations.py b/tests/api/ils/test_internal_locations.py
new file mode 100644
index 000000000..b486f3657
--- /dev/null
+++ b/tests/api/ils/test_internal_locations.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2018-2020 CERN.
+#
+# invenio-app-ils is free software; you can redistribute it and/or modify it
+# under the terms of the MIT License; see LICENSE file for more details.
+
+"""Test internal locations."""
+import json
+
+from flask import url_for
+
+from tests.helpers import user_login
+
+_HTTP_OK = [200, 201, 204]
+_INTERNAL_LOCATION_PID = "ilocid-1"
+_INTERNAL_LOCATION_NAME = "A location"
+_ITEM_ENDPOINT = "invenio_records_rest.ilocid_item"
+_LIST_ENDPOINT = "invenio_records_rest.ilocid_list"
+
+
+def test_internal_locations_permissions(client, testdata, json_headers, users):
+    """Test internal locations endpoints permissions."""
+    dummy_internal_location = dict(
+        location_pid=testdata["locations"][0]["pid"],
+        name=_INTERNAL_LOCATION_NAME
+    )
+    tests = [
+        ("admin", _HTTP_OK, dummy_internal_location),
+        ("librarian", _HTTP_OK, dummy_internal_location),
+        ("patron1", [403], dummy_internal_location),
+        ("anonymous", [401], dummy_internal_location),
+    ]
+
+    def _test_list(expected_status):
+        """Test get list."""
+        url = url_for(_LIST_ENDPOINT)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    def _test_create(expected_status, data):
+        """Test record creation."""
+        url = url_for(_LIST_ENDPOINT)
+        res = client.post(url, headers=json_headers, data=json.dumps(data))
+        assert res.status_code in expected_status
+
+        if res.status_code < 400:
+            record = res.get_json()["metadata"]
+            assert record["name"] == _INTERNAL_LOCATION_NAME
+            return record["pid"]
+
+    def _test_update(expected_status, data, pid):
+        """Test record update."""
+        pid_value = pid or _INTERNAL_LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.put(url, headers=json_headers, data=json.dumps(data))
+        assert res.status_code in expected_status
+        if res.status_code < 400:
+            record = res.get_json()["metadata"]
+            assert record["name"] == _INTERNAL_LOCATION_NAME
+
+    def _test_read(expected_status, pid):
+        """Test record read."""
+        pid_value = pid or _INTERNAL_LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    def _test_delete(expected_status, pid):
+        """Test record delete."""
+        pid_value = pid or _INTERNAL_LOCATION_PID
+        url = url_for(_ITEM_ENDPOINT, pid_value=pid_value)
+        res = client.delete(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    for username, expected_status, data in tests:
+        user_login(client, username, users)
+        _test_list(expected_status)
+        pid = _test_create(expected_status, data)
+        _test_update(expected_status, data, pid)
+        _test_read(expected_status, pid)
+        _test_delete(expected_status, pid)
diff --git a/tests/api/ils/test_patrons.py b/tests/api/ils/test_patrons.py
index 1293053b2..5f5584067 100644
--- a/tests/api/ils/test_patrons.py
+++ b/tests/api/ils/test_patrons.py
@@ -13,6 +13,8 @@
 
 from tests.helpers import user_login
 
+_LIST_ENDPOINT = "invenio_records_rest.patid_list"
+
 
 def _patron_loans_request(client, json_headers, document_pid):
     """Perform a patron loans request."""
@@ -40,6 +42,26 @@ def _assert_error_when_not_authenticated(client, json_headers, document_pid):
     assert resp["status"] == 401
 
 
+def test_patrons_permissions(client, testdata, json_headers, users):
+    """Test patron endpoints permissions."""
+    tests = [
+        ("admin", [200]),
+        ("librarian", [200]),
+        ("patron1", [403]),
+        ("anonymous", [401]),
+    ]
+
+    def _test_list(expected_status):
+        """Test get list."""
+        url = url_for(_LIST_ENDPOINT)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    for username, expected_status in tests:
+        user_login(client, username, users)
+        _test_list(expected_status)
+
+
 def test_patron_loans_information(
     client, json_headers, testdata_most_loaned, users
 ):
diff --git a/tests/api/ils/test_vocabularies.py b/tests/api/ils/test_vocabularies.py
new file mode 100644
index 000000000..54c8ec630
--- /dev/null
+++ b/tests/api/ils/test_vocabularies.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 CERN.
+#
+# invenio-app-ils is free software; you can redistribute it and/or modify it
+# under the terms of the MIT License; see LICENSE file for more details.
+
+"""Test vocabularies endpoint."""
+import json
+
+from flask import url_for
+
+from tests.helpers import user_login
+
+LIST_ENDPOINT = "invenio_records_rest.vocid_list"
+
+
+def test_vocabularies_permissions(client, testdata, json_headers, users):
+    """Test vocabularies endpoints permissions."""
+    tests = [
+        ("admin", [200]),
+        ("librarian", [200]),
+        ("patron1", [200]),
+        ("anonymous", [401])
+    ]
+
+    def _test_list(expected_status):
+        """Test get list."""
+        url = url_for(LIST_ENDPOINT)
+        res = client.get(url, headers=json_headers)
+        assert res.status_code in expected_status
+
+    for username, codes in tests:
+        user_login(client, username, users)
+        _test_list(codes)