Skip to content
Draft
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
47 changes: 2 additions & 45 deletions lms/djangoapps/discussion/rest_api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
)


Expand Down Expand Up @@ -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)
5 changes: 2 additions & 3 deletions lms/djangoapps/discussion/rest_api/tests/test_views_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 20 additions & 18 deletions lms/djangoapps/discussion/rest_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Loading