diff --git a/lms/djangoapps/discussion/rest_api/permissions.py b/lms/djangoapps/discussion/rest_api/permissions.py index cfcea5b32834..cb6ff4ea9673 100644 --- a/lms/djangoapps/discussion/rest_api/permissions.py +++ b/lms/djangoapps/discussion/rest_api/permissions.py @@ -6,7 +6,7 @@ from opaque_keys.edx.keys import CourseKey from rest_framework import permissions -from common.djangoapps.student.models import CourseAccessRole, CourseEnrollment +from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import ( CourseInstructorRole, CourseStaffRole, @@ -19,7 +19,7 @@ from openedx.core.djangoapps.django_comment_common.comment_client.comment import Comment from openedx.core.djangoapps.django_comment_common.comment_client.thread import Thread from openedx.core.djangoapps.django_comment_common.models import ( - Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_MODERATOR + FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_MODERATOR ) @@ -185,46 +185,3 @@ def has_permission(self, request, view): request.user.is_staff or is_user_staff and request.method == "GET" ) - - -def can_take_action_on_spam(user, course_id): - """ - Returns if the user has access to take action against forum spam posts - Parameters: - user: User object - course_id: CourseKey or string of course_id - """ - if GlobalStaff().has_user(user): - return True - - if isinstance(course_id, str): - course_id = CourseKey.from_string(course_id) - org_id = course_id.org - course_ids = CourseEnrollment.objects.filter(user=user).values_list('course_id', flat=True) - course_ids = [c_id for c_id in course_ids if c_id.org == org_id] - user_roles = set( - Role.objects.filter( - users=user, - course_id__in=course_ids, - ).values_list('name', flat=True).distinct() - ) - if bool(user_roles & {FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR}): - return True - - if CourseAccessRole.objects.filter(user=user, course_id__in=course_ids, role__in=["instructor", "staff"]).exists(): - return True - return False - - -class IsAllowedToBulkDelete(permissions.BasePermission): - """ - Permission that checks if the user is staff or an admin. - """ - - def has_permission(self, request, view): - """Returns true if the user can bulk delete posts""" - if not request.user.is_authenticated: - return False - - course_id = view.kwargs.get("course_id") - return can_take_action_on_spam(request.user, course_id) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py index 4247cbcab06c..3fb8952a1d78 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_views_v2.py @@ -917,10 +917,9 @@ def test_bulk_delete_denied_for_discussion_roles(self, role): thread_mock.count_documents.assert_not_called() comment_mock.count_documents.assert_not_called() - @ddt.data(FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR) - def test_bulk_delete_allowed_for_discussion_roles(self, role): + def test_bulk_delete_allowed_for_global_staff(self, role): """ - Test bulk delete user posts passed with discussion roles. + Test bulk delete user posts passed with global staff. """ self.mock_comment_and_thread_count(comment_count=1, thread_count=1) assign_role(self.course.id, self.user, role) diff --git a/lms/djangoapps/discussion/rest_api/views.py b/lms/djangoapps/discussion/rest_api/views.py index ba9818124e08..3430a4de5559 100644 --- a/lms/djangoapps/discussion/rest_api/views.py +++ b/lms/djangoapps/discussion/rest_api/views.py @@ -28,7 +28,6 @@ from lms.djangoapps.course_api.blocks.api import get_blocks from lms.djangoapps.course_goals.models import UserActivity from lms.djangoapps.discussion.rate_limit import is_content_creation_rate_limited -from lms.djangoapps.discussion.rest_api.permissions import IsAllowedToBulkDelete from lms.djangoapps.discussion.rest_api.tasks import delete_course_post_for_user from lms.djangoapps.discussion.toggles import ONLY_VERIFIED_USERS_CAN_POST from lms.djangoapps.discussion.django_comment_client import settings as cc_settings @@ -1549,31 +1548,34 @@ def post(self, request, course_id, rolename): class BulkDeleteUserPosts(DeveloperErrorViewMixin, APIView): """ - **Use Cases** - A privileged user that can delete all posts and comments made by a user. - It returns expected number of comments and threads that will be deleted - - **Example Requests**: - POST /api/discussion/v1/bulk_delete_user_posts/{course_id} - Query Parameters: - username: The username of the user whose posts are to be deleted - course_id: Course id for which posts are to be removed - execute: If True, runs deletion task - course_or_org: If 'course', deletes posts in the course, if 'org', deletes posts in all courses of the org - - **Example Response**: - Empty string + Bulk-delete posts for a forum user (generally in reponse to spam). """ authentication_classes = ( JwtAuthentication, BearerAuthentication, SessionAuthentication, ) - permission_classes = (permissions.IsAuthenticated, IsAllowedToBulkDelete) + permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) def post(self, request, course_id): """ - Implements the delete user posts endpoint. - TODO: Add support for MySQLBackend as well + Delete all posts and comments made by a user across a course or a course's entire org. + It returns expected number of comments and threads that will be deleted. + This only works on legacy MongoDB-backed forums, not MySQL backed forums. + + POST /api/discussion/v1/bulk_delete_user_posts/{course_id} + + Query Parameters: + * username: The username of the user whose posts are to be deleted. + * course_or_org: See below. + * course_id: If course_or_org=="course", then a user's posts in this org will be removed. + If course_or_org=="org", then a user's posts will be removed across the + whole org containing this course. + * execute: If True, runs deletion task; if False, just return # of comments and threads + that would be deleted. + + Notes: This API is a work-in-progress. We are experimentally releasing in Ulmo to superusers only. + In Verawood, we plan to allow course staff and forum moderators to bulk-delete posts for + contexts which they have access to. See https://github.com/openedx/edx-platform/issues/37402 for details. """ username = request.GET.get("username", None) execute_task = request.GET.get("execute", "false").lower() == "true"