From 6d9d9d2b4cb3a95826f3e48d5e0ffa517a53f3a9 Mon Sep 17 00:00:00 2001 From: Sam Arbid Date: Fri, 26 Apr 2024 11:13:03 +0200 Subject: [PATCH] moderation: add self-action prevention * Implement prevent_self_action decorator * Prevent self-block, deactivate, impersonate * Update tests for self-action prevention * closes --- invenio_users_resources/decorators.py | 34 +++++++++++++++++++ .../resources/users/resource.py | 6 ++++ tests/resources/test_resources_users.py | 15 ++++++++ 3 files changed, 55 insertions(+) create mode 100644 invenio_users_resources/decorators.py diff --git a/invenio_users_resources/decorators.py b/invenio_users_resources/decorators.py new file mode 100644 index 0000000..36a3ea5 --- /dev/null +++ b/invenio_users_resources/decorators.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2024 CERN. +# Copyright (C) 2024 KTH Royal Institute of Technology +# +# Invenio-Users-Resources is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. + +"""Users decorators.""" +from functools import wraps + +from flask import g +from flask_resources import resource_requestctx +from invenio_records_resources.resources.errors import PermissionDeniedError + + +def prevent_self_action(): + """Decorator that prevents users from taking actions on their own account.""" + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + user_id = str(resource_requestctx.view_args["id"]) + current_user_id = str(g.identity.id) + + if user_id == current_user_id: + raise PermissionDeniedError() + + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/invenio_users_resources/resources/users/resource.py b/invenio_users_resources/resources/users/resource.py index 8c71095..cbb7e79 100644 --- a/invenio_users_resources/resources/users/resource.py +++ b/invenio_users_resources/resources/users/resource.py @@ -3,6 +3,7 @@ # Copyright (C) 2022 TU Wien. # Copyright (C) 2022 CERN. # Copyright (C) 2022 European Union. +# Copyright (C) 2024 KTH Royal Institute of Technology # # Invenio-Users-Resources is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -20,6 +21,8 @@ ) from invenio_records_resources.resources.records.utils import search_preference +from invenio_users_resources.decorators import prevent_self_action + # # Resource @@ -108,6 +111,7 @@ def approve(self): return "", 200 @request_view_args + @prevent_self_action() def block(self): """Block user.""" self.service.block( @@ -126,6 +130,7 @@ def restore(self): return "", 200 @request_view_args + @prevent_self_action() def deactivate(self): """Deactive user.""" self.service.deactivate( @@ -144,6 +149,7 @@ def activate(self): return "", 200 @request_view_args + @prevent_self_action() def impersonate(self): """Impersonate the user.""" user = self.service.can_impersonate( diff --git a/tests/resources/test_resources_users.py b/tests/resources/test_resources_users.py index 30c456c..aaa70f6 100644 --- a/tests/resources/test_resources_users.py +++ b/tests/resources/test_resources_users.py @@ -2,6 +2,7 @@ # # Copyright (C) 2022 European Union. # Copyright (C) 2022 CERN. +# Copyright (C) 2024 KTH Royal Institute of Technology # # Invenio-Users-Resources is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -137,6 +138,13 @@ def test_block_user(client, headers, user_pub, user_moderator, db): assert res.status_code == 200 assert res.json["blocked_at"] is not None + # Test user tries to block themselves + res = client.post(f"/users/{user_moderator.id}/block", headers=headers) + assert res.status_code == 403 + + res = client.get(f"/users/{user_moderator.id}") + assert res.status_code == 200 + def test_deactivate_user(client, headers, user_pub, user_moderator, db): """Tests deactivate user endpoint.""" @@ -148,6 +156,13 @@ def test_deactivate_user(client, headers, user_pub, user_moderator, db): assert res.status_code == 200 assert res.json["active"] == False + # Test user tries to deactivate themselves + res = client.post(f"/users/{user_moderator.id}/deactivate", headers=headers) + assert res.status_code == 403 + + res = client.get(f"/users/{user_moderator.id}") + assert res.status_code == 200 + def test_management_permissions(client, headers, user_pub, db): """Test permissions at the resource level."""