Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read-only mode #293

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions invenio_requests/services/permissions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 CERN.
# Copyright (C) 2021 Northwestern University.
# Copyright (C) 2021 TU Wien.
# Copyright (C) 2021 CERN.
# Copyright (C) 2021 Northwestern University.
# Copyright (C) 2021-2024 TU Wien.
#
# Invenio-Requests is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
Expand All @@ -15,6 +15,7 @@
AnyUser,
AuthenticatedUser,
Disable,
DisableIfReadOnly,
SystemProcess,
SystemProcessWithoutSuperUser,
)
Expand All @@ -26,7 +27,8 @@ class PermissionPolicy(RecordPermissionPolicy):
"""Permission policy."""

# Ability in general to create requests (not which request you can create)
can_create = [AuthenticatedUser(), SystemProcess()]
can_create = [AuthenticatedUser(), SystemProcess(), DisableIfReadOnly()]

# Just about ability to perform a search (not what requests you can access)
can_search = [AuthenticatedUser(), SystemProcess()]

Expand All @@ -47,6 +49,7 @@ class PermissionPolicy(RecordPermissionPolicy):
SystemProcess(),
Status(["created"], [Creator()]),
Status(["submitted"], [Creator(), Receiver()]),
DisableIfReadOnly(),
]

can_manage_access_options = [Disable()]
Expand All @@ -58,32 +61,35 @@ class PermissionPolicy(RecordPermissionPolicy):
[Disable()],
),
SystemProcess(),
DisableIfReadOnly(),
]

# Submit, cancel, expire, accept and decline actions only deals
# with requests in a **single state** and thus doesn't need to take the
# request status into account.
can_action_submit = [Creator(), SystemProcess()]
can_action_cancel = [Creator(), SystemProcess()]
can_action_submit = [Creator(), SystemProcess(), DisableIfReadOnly()]
can_action_cancel = [Creator(), SystemProcess(), DisableIfReadOnly()]
# `SystemProcessWithoutSuperUser`: expire is an automatic action done only by
# the system, therefore the `superuser-action` must be explicitly excluded
# as it's added by default to any permission.
can_action_expire = [SystemProcessWithoutSuperUser()]
can_action_accept = [Receiver(), SystemProcess()]
can_action_decline = [Receiver(), SystemProcess()]
can_action_expire = [SystemProcessWithoutSuperUser(), DisableIfReadOnly()]
can_action_accept = [Receiver(), SystemProcess(), DisableIfReadOnly()]
can_action_decline = [Receiver(), SystemProcess(), DisableIfReadOnly()]

# Request events/comments
# Events are in most cases protected by the associated request.
can_update_comment = [
Commenter(),
SystemProcess(),
DisableIfReadOnly(),
]
can_delete_comment = [
Commenter(),
SystemProcess(),
DisableIfReadOnly(),
]
# If you can read the request you can create events for the request.
can_create_comment = can_read
can_create_comment = can_read + [DisableIfReadOnly()]

# Needed by the search events permission because a permission_action must
# be provided to create_search(), but the event search is already protected
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Copyright (C) 2021-2024 CERN.
# Copyright (C) 2021 Northwestern University.
# Copyright (C) 2021 TU Wien.
# Copyright (C) 2021-2024 TU Wien.
# Copyright (C) 2023 Graz University of Technology.
#
# Invenio-Requests is free software; you can redistribute it and/or modify it
Expand Down
26 changes: 26 additions & 0 deletions tests/resources/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 TU Wien.
#
# Invenio-Requests is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Pytest configuration.
See https://pytest-invenio.readthedocs.io/ for documentation on which test fixtures
are available.
"""

import pytest


@pytest.fixture()
def rw_app(app):
"""Fixture that resets the read-only mode before and after tests.
This is done in order to prevent permission issues with other fixtures when the
app isn't freshly initialized for each test.
"""
app.config["RECORDS_PERMISSIONS_READ_ONLY"] = False
yield app
app.config["RECORDS_PERMISSIONS_READ_ONLY"] = False
65 changes: 65 additions & 0 deletions tests/resources/events/test_request_events_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# Copyright (C) 2021 CERN.
# Copyright (C) 2021 Northwestern University.
# Copyright (C) 2022-2024 TU Wien.
#
# Invenio-Requests is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
Expand Down Expand Up @@ -215,3 +216,67 @@ def test_empty_comment(
)
assert 400 == response.status_code
assert expected_json == response.json


#
# Read-only mode
#


def test_comment_request_ro(
rw_app, client_logged_as, headers, events_resource_data, example_request
):
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
request_id = example_request.id
client = client_logged_as("[email protected]")

# Commenting on a request in read-only mode should fail
response = client.post(
f"/requests/{request_id}/comments", headers=headers, json=events_resource_data
)
assert response.status_code == 403


def test_update_comment_request_ro(
rw_app, client_logged_as, headers, events_resource_data, example_request
):
request_id = example_request.id
client = client_logged_as("[email protected]")

response = client.post(
f"/requests/{request_id}/comments", headers=headers, json=events_resource_data
)
comment_id = response.json["id"]
assert response.status_code == 201

# Updating the comment in read-only mode should fail
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
data = copy.deepcopy(events_resource_data)
data["payload"]["content"] = "I've revised my comment."
response = client.put(
f"/requests/{request_id}/comments/{comment_id}",
headers=headers,
json=data,
)
assert response.status_code == 403


def test_delete_comment_request_ro(
rw_app, client_logged_as, headers, events_resource_data, example_request
):
request_id = example_request.id
client = client_logged_as("[email protected]")

response = client.post(
f"/requests/{request_id}/comments", headers=headers, json=events_resource_data
)
comment_id = response.json["id"]
assert response.status_code == 201

# Updating the comment in read-only mode should fail
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
response = client.delete(
f"/requests/{request_id}/comments/{comment_id}",
headers=headers,
)
assert response.status_code == 403
47 changes: 46 additions & 1 deletion tests/resources/requests/test_requests_resources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 TU Wien.
# Copyright (C) 2021-2024 TU Wien.
#
# Invenio-Requests is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
Expand Down Expand Up @@ -198,3 +198,48 @@ def test_simple_request_flow(app, client_logged_as, headers, example_request):
}
)
assert_api_response(response, 200, expected_data)


#
# Read-only mode
#


def test_update_request_ro(rw_app, client_logged_as, headers, example_request):
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
request_id = example_request.id
client = client_logged_as("[email protected]")
response = client.put(
f"/requests/{request_id}", headers=headers, data=example_request
)
assert response.status_code == 403


def test_delete_request_ro(rw_app, client_logged_as, headers, example_request):
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
request_id = example_request.id
client = client_logged_as("[email protected]")
response = client.delete(f"/requests/{request_id}", headers=headers)
assert response.status_code == 403


def test_submit_request_ro(rw_app, client_logged_as, headers, example_request):
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
request_id = example_request.id
client = client_logged_as("[email protected]")
response = client.post(f"/requests/{request_id}/actions/submit", headers=headers)
assert response.status_code == 403


def test_request_actions_ro(rw_app, client_logged_as, headers, example_request):
request_id = example_request.id
client = client_logged_as("[email protected]")
response = client.post(f"/requests/{request_id}/actions/submit", headers=headers)
assert response.status_code == 200

for action in ["accept", "decline", "cancel"]:
rw_app.config["RECORDS_PERMISSIONS_READ_ONLY"] = True
response = client.post(
f"/requests/{request_id}/actions/{action}", headers=headers
)
assert response.status_code == 403
Loading