Skip to content

Commit aacdcad

Browse files
committed
fix(documents): allow anonymous document listing
* DocumentPermission.list now always allows listing, following the rero-ils any_user model * search_factory applies the masked/IP visibility filter to any non-privileged request, independent of the view parameter * default the no-view anonymous scope to global (all organisations) * logged_user documents.list permission reflects the now-public document listing * add tests for anonymous list/read with masked and IP-restricted documents * Closes #884 Co-Authored-by: Bertrand Zuchuat <bertrand.zuchuat@rero.ch>
1 parent 16c1837 commit aacdcad

5 files changed

Lines changed: 75 additions & 23 deletions

File tree

sonar/modules/documents/permissions.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
"""Permissions for documents."""
55

6-
from flask import request
76
from invenio_files_rest.models import Bucket, ObjectVersion
87
from invenio_pidstore.errors import PIDDoesNotExistError
98
from invenio_pidstore.models import PersistentIdentifier
@@ -26,14 +25,9 @@ def list(cls, user, record=None):
2625
:param record: Record to check.
2726
:returns: True is action can be done.
2827
"""
29-
30-
# Documents are accessible in public view, but eventually filtered
31-
# later by organisation
32-
if request.args.get("view"):
33-
return True
34-
35-
# Only for moderators users.
36-
return bool(user and user.is_moderator and current_organisation)
28+
# Listing is always allowed. Anonymous and non-moderator users are
29+
# restricted to the safe (non-masked) scope in the search factory.
30+
return True
3731

3832
@classmethod
3933
def create(cls, user, record=None):

sonar/modules/documents/query.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ def search_factory(self, search, query_parser=None):
4444
return (search, urlkwargs)
4545

4646
view = request.args.get("view")
47+
is_privileged = current_user_record and current_user_record.is_moderator
4748

48-
# Public search
49-
if view:
49+
# Public search: anonymous or non-moderator users, with or without a view.
50+
if not is_privileged:
5051
# Don't display masked records
5152
search = search.filter(
5253
"bool",
@@ -64,17 +65,26 @@ def search_factory(self, search, query_parser=None):
6465
],
6566
)
6667

67-
# Filter record by organisation view.
68-
if view != current_app.config.get("SONAR_APP_DEFAULT_ORGANISATION"):
68+
# Filter record by organisation view. No view means the global scope
69+
# (all organisations).
70+
if view and view != current_app.config.get("SONAR_APP_DEFAULT_ORGANISATION"):
6971
search = search.filter("term", organisation__pid=view)
7072

7173
# Filter collection
7274
if request.args.get("collection_view"):
7375
search = search.filter("term", collections__pid=request.args["collection_view"])
74-
# Admin
76+
# Moderator/admin
7577
else:
78+
if view:
79+
# Filter record by organisation view.
80+
if view != current_app.config.get("SONAR_APP_DEFAULT_ORGANISATION"):
81+
search = search.filter("term", organisation__pid=view)
82+
83+
# Filter collection
84+
if request.args.get("collection_view"):
85+
search = search.filter("term", collections__pid=request.args["collection_view"])
7686
# Filters records by user's organisation
77-
if not current_user_record.is_superuser:
87+
elif not current_user_record.is_superuser:
7888
search = search.filter("term", organisation__pid=current_organisation["pid"])
7989

8090
return (search, urlkwargs)

tests/api/documents/test_documents_permissions.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ def test_list(
2727
make_document(organisation=None, with_file=True)
2828
make_document(organisation="org", with_file=True)
2929

30-
# Not logged
30+
# Not logged: public, global scope, no view required.
3131
res = client.get(url_for("invenio_records_rest.doc_list"))
32-
assert res.status_code == 401
32+
assert res.status_code == 200
33+
assert res.json["hits"]["total"]["value"] == 2
3334

3435
# Not logged but permission checks disabled
3536
app.config.update(SONAR_APP_DISABLE_PERMISSION_CHECKS=True)
@@ -38,15 +39,17 @@ def test_list(
3839
assert res.json["hits"]["total"]["value"] == 2
3940
app.config.update(SONAR_APP_DISABLE_PERMISSION_CHECKS=False)
4041

41-
# Logged as user
42+
# Logged as user: same public, global scope as anonymous.
4243
login_user_via_session(client, email=user["email"])
4344
res = client.get(url_for("invenio_records_rest.doc_list"))
44-
assert res.status_code == 403
45+
assert res.status_code == 200
46+
assert res.json["hits"]["total"]["value"] == 2
4547

46-
# Logged as submitter
48+
# Logged as submitter: same public, global scope as anonymous.
4749
login_user_via_session(client, email=submitter["email"])
4850
res = client.get(url_for("invenio_records_rest.doc_list"))
49-
assert res.status_code == 403
51+
assert res.status_code == 200
52+
assert res.json["hits"]["total"]["value"] == 2
5053

5154
# Logged as moderator
5255
login_user_via_session(client, email=moderator["email"])

tests/api/documents/test_documents_query.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,48 @@ def test_masked_document(db, client, organisation, document, search_clear):
110110
res = client.get(url_for("invenio_records_rest.doc_list", view="global"))
111111
assert res.status_code == 200
112112
assert res.json["hits"]["total"]["value"] == 0
113+
114+
115+
def test_anonymous_document_list_without_view(db, client, organisation, document, search_clear):
116+
"""Test anonymous document list defaults to the global, non-masked scope."""
117+
# No view: behaves like the global view for anonymous users.
118+
res = client.get(url_for("invenio_records_rest.doc_list"))
119+
assert res.status_code == 200
120+
assert res.json["hits"]["total"]["value"] == 1
121+
122+
# Masked documents are excluded, with or without a view.
123+
document["masked"] = "masked_for_all"
124+
document.commit()
125+
document.reindex()
126+
db.session.commit()
127+
res = client.get(url_for("invenio_records_rest.doc_list"))
128+
assert res.status_code == 200
129+
assert res.json["hits"]["total"]["value"] == 0
130+
131+
# Masked for external IPs, IP is not allowed.
132+
document["masked"] = "masked_for_external_ips"
133+
document.commit()
134+
document.reindex()
135+
db.session.commit()
136+
res = client.get(url_for("invenio_records_rest.doc_list"))
137+
assert res.status_code == 200
138+
assert res.json["hits"]["total"]["value"] == 0
139+
140+
# Masked for external IPs, IP is allowed.
141+
organisation["allowedIps"] = "127.0.0.1/32"
142+
organisation.commit()
143+
db.session.commit()
144+
organisation.reindex()
145+
document.reindex()
146+
res = client.get(url_for("invenio_records_rest.doc_list"))
147+
assert res.status_code == 200
148+
assert res.json["hits"]["total"]["value"] == 1
149+
150+
# Org-scoped view still filters by organisation for anonymous users.
151+
res = client.get(url_for("invenio_records_rest.doc_list", view="org"))
152+
assert res.status_code == 200
153+
assert res.json["hits"]["total"]["value"] == 1
154+
155+
res = client.get(url_for("invenio_records_rest.doc_list", view="org2"))
156+
assert res.status_code == 200
157+
assert res.json["hits"]["total"]["value"] == 0

tests/ui/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_logged_user(app, client, superuser, admin, moderator, submitter, user):
123123
assert not res.json["metadata"]["permissions"]["organisations"]["add"]
124124
assert not res.json["metadata"]["permissions"]["users"]["add"]
125125
assert res.json["metadata"]["permissions"]["deposits"]["add"]
126-
assert not res.json["metadata"]["permissions"]["documents"]["list"]
126+
assert res.json["metadata"]["permissions"]["documents"]["list"]
127127
assert not res.json["metadata"]["permissions"]["organisations"]["list"]
128128
assert res.json["metadata"]["permissions"]["users"]["list"]
129129
assert res.json["metadata"]["permissions"]["deposits"]["list"]
@@ -136,7 +136,7 @@ def test_logged_user(app, client, superuser, admin, moderator, submitter, user):
136136
assert not res.json["metadata"]["permissions"]["organisations"]["add"]
137137
assert not res.json["metadata"]["permissions"]["users"]["add"]
138138
assert not res.json["metadata"]["permissions"]["deposits"]["add"]
139-
assert not res.json["metadata"]["permissions"]["documents"]["list"]
139+
assert res.json["metadata"]["permissions"]["documents"]["list"]
140140
assert not res.json["metadata"]["permissions"]["organisations"]["list"]
141141
assert res.json["metadata"]["permissions"]["users"]["list"]
142142
assert not res.json["metadata"]["permissions"]["deposits"]["list"]

0 commit comments

Comments
 (0)