Files
edx-platform/lms/djangoapps/discussion/rest_api/permissions.py
ayesha waris 3bc03b33ac fix: fixed permissions for voted (#34993)
* fix: fixed permissions for voted

* fix: fixed test cases
2024-06-26 15:55:48 +05:00

188 lines
6.6 KiB
Python

"""
Discussion API permission logic
"""
from typing import Dict, Set, Union
from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import (
CourseInstructorRole,
CourseStaffRole,
GlobalStaff,
)
from lms.djangoapps.discussion.django_comment_client.utils import (
get_user_role_names,
has_discussion_privileges,
)
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 (
FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_MODERATOR
)
def _is_author(cc_content, context):
"""
Return True if the requester authored the given content, False otherwise
"""
return context["cc_requester"]["id"] == cc_content["user_id"]
def _is_author_or_privileged(cc_content, context):
"""
Return True if the requester authored the given content or is a privileged
user, False otherwise
"""
return context["has_moderation_privilege"] or _is_author(cc_content, context)
NON_UPDATABLE_THREAD_FIELDS = {"course_id"}
NON_UPDATABLE_COMMENT_FIELDS = {"thread_id", "parent_id"}
def get_initializable_thread_fields(context):
"""
Return the set of fields that the requester can initialize for a thread
Any field that is editable by the author should also be initializable.
"""
ret = get_editable_fields(
Thread(user_id=context["cc_requester"]["id"], type="thread"),
context
)
ret |= NON_UPDATABLE_THREAD_FIELDS
return ret
def get_initializable_comment_fields(context):
"""
Return the set of fields that the requester can initialize for a comment
Any field that is editable by the author should also be initializable.
"""
ret = get_editable_fields(
Comment(user_id=context["cc_requester"]["id"], type="comment"),
context
)
ret |= NON_UPDATABLE_COMMENT_FIELDS
return ret
def _filter_fields(editable_fields: Dict[str, bool]) -> Set[str]:
"""
Helper function that returns only the keys marked as True.
Args:
editable_fields (Dict[str, bool]): A mapping of strings to a bool value
that indicates whether they should be in the output set
Returns:
Set[str] a set of fields that have a true value.
"""
return {field for field, is_editable in editable_fields.items() if is_editable}
def get_editable_fields(cc_content: Union[Thread, Comment], context: Dict) -> Set[str]:
"""
Return the set of fields that the requester can edit on the given content
"""
# For closed thread:
# no edits, except 'abuse_flagged' and 'read' are allowed for thread
# no edits, except 'abuse_flagged' is allowed for comment
is_thread = cc_content["type"] == "thread"
is_comment = cc_content["type"] == "comment"
has_moderation_privilege = context["has_moderation_privilege"]
is_staff_or_admin = context["is_staff_or_admin"]
if is_thread:
is_thread_closed = cc_content["closed"]
elif context.get("thread"):
is_thread_closed = context["thread"]["closed"]
else:
# Flagging/un-flagging is always available.
return {"abuse_flagged"}
# Map each field to the condition in which it's editable.
editable_fields = {
"abuse_flagged": True,
"closed": is_thread and has_moderation_privilege,
"close_reason_code": is_thread and has_moderation_privilege,
"pinned": is_thread and (has_moderation_privilege or is_staff_or_admin),
"read": is_thread,
}
if is_thread:
editable_fields.update({"copy_link": True})
if is_thread_closed:
# Return only editable fields
return _filter_fields(editable_fields)
is_author = _is_author(cc_content, context)
editable_fields.update({
"voted": has_moderation_privilege or not is_author or is_staff_or_admin,
"raw_body": has_moderation_privilege or is_author,
"edit_reason_code": has_moderation_privilege and not is_author,
"following": is_thread,
"topic_id": is_thread and (is_author or has_moderation_privilege),
"type": is_thread and (is_author or has_moderation_privilege),
"title": is_thread and (is_author or has_moderation_privilege),
"group_id": is_thread and has_moderation_privilege and context["discussion_division_enabled"],
"endorsed": (
(is_comment and cc_content.get("parent_id", None) is None) and
(has_moderation_privilege or
(_is_author(context["thread"], context) and context["thread"]["thread_type"] == "question"))
),
"anonymous": is_author and context["course"].allow_anonymous,
"anonymous_to_peers": is_author and context["course"].allow_anonymous_to_peers,
})
# Return only editable fields
return _filter_fields(editable_fields)
def can_delete(cc_content, context):
"""
Return True if the requester can delete the given content, False otherwise
"""
return _is_author_or_privileged(cc_content, context)
class IsStaffOrCourseTeamOrEnrolled(permissions.BasePermission):
"""
Permission that checks to see if the user is allowed to post or
comment in the course.
"""
def has_permission(self, request, view):
"""Returns true if the user is enrolled or is staff."""
course_key = CourseKey.from_string(view.kwargs.get('course_id'))
return (
GlobalStaff().has_user(request.user) or
CourseStaffRole(course_key).has_user(request.user) or
CourseInstructorRole(course_key).has_user(request.user) or
CourseEnrollment.is_enrolled(request.user, course_key) or
has_discussion_privileges(request.user, course_key)
)
class IsStaffOrAdmin(permissions.BasePermission):
"""
Permission that checks if the user is staff or an admin.
"""
def has_permission(self, request, view):
"""Returns true if the user is admin or staff and request method is GET."""
course_key = CourseKey.from_string(view.kwargs.get('course_id'))
user_roles = get_user_role_names(request.user, course_key)
is_user_staff = bool(user_roles & {
FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA,
})
return (
GlobalStaff().has_user(request.user) or
request.user.is_staff or
is_user_staff and request.method == "GET"
)