Commit 01c22531 by cahrens

Add team permission check on commentable_id.

TNL-2864
parent 7a32fbc9
...@@ -51,6 +51,8 @@ def permitted(fn): ...@@ -51,6 +51,8 @@ def permitted(fn):
content = cc.Thread.find(kwargs["thread_id"]).to_dict() content = cc.Thread.find(kwargs["thread_id"]).to_dict()
elif "comment_id" in kwargs: elif "comment_id" in kwargs:
content = cc.Comment.find(kwargs["comment_id"]).to_dict() content = cc.Comment.find(kwargs["comment_id"]).to_dict()
elif "commentable_id" in kwargs:
content = cc.Commentable.find(kwargs["commentable_id"]).to_dict()
else: else:
content = None content = None
return content return content
...@@ -608,16 +610,6 @@ def follow_commentable(request, course_id, commentable_id): ...@@ -608,16 +610,6 @@ def follow_commentable(request, course_id, commentable_id):
@require_POST @require_POST
@login_required @login_required
@permitted @permitted
def follow_user(request, course_id, followed_user_id):
user = cc.User.from_django_user(request.user)
followed_user = cc.User.find(followed_user_id)
user.follow(followed_user)
return JsonResponse({})
@require_POST
@login_required
@permitted
def unfollow_thread(request, course_id, thread_id): def unfollow_thread(request, course_id, thread_id):
""" """
given a course id and thread id, stop following this thread given a course id and thread id, stop following this thread
...@@ -645,20 +637,6 @@ def unfollow_commentable(request, course_id, commentable_id): ...@@ -645,20 +637,6 @@ def unfollow_commentable(request, course_id, commentable_id):
@require_POST @require_POST
@login_required @login_required
@permitted
def unfollow_user(request, course_id, followed_user_id):
"""
given a course id and user id, stop following this user
ajax only
"""
user = cc.User.from_django_user(request.user)
followed_user = cc.User.find(followed_user_id)
user.unfollow(followed_user)
return JsonResponse({})
@require_POST
@login_required
@csrf.csrf_exempt @csrf.csrf_exempt
def upload(request, course_id): # ajax upload file to a question or answer def upload(request, course_id): # ajax upload file to a question or answer
"""view that handles file upload via Ajax """view that handles file upload via Ajax
......
...@@ -333,11 +333,11 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase): ...@@ -333,11 +333,11 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase):
@ddt.data( @ddt.data(
# old mongo with cache # old mongo with cache
(ModuleStoreEnum.Type.mongo, 1, 7, 5, 13, 8), (ModuleStoreEnum.Type.mongo, 1, 7, 5, 14, 8),
(ModuleStoreEnum.Type.mongo, 50, 7, 5, 13, 8), (ModuleStoreEnum.Type.mongo, 50, 7, 5, 14, 8),
# split mongo: 3 queries, regardless of thread response size. # split mongo: 3 queries, regardless of thread response size.
(ModuleStoreEnum.Type.split, 1, 3, 3, 13, 8), (ModuleStoreEnum.Type.split, 1, 3, 3, 14, 8),
(ModuleStoreEnum.Type.split, 50, 3, 3, 13, 8), (ModuleStoreEnum.Type.split, 50, 3, 3, 14, 8),
) )
@ddt.unpack @ddt.unpack
def test_number_of_mongo_queries( def test_number_of_mongo_queries(
......
...@@ -10,6 +10,7 @@ from lms.lib.comment_client import Thread ...@@ -10,6 +10,7 @@ from lms.lib.comment_client import Thread
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from django_comment_common.models import all_permissions_for_user_in_course from django_comment_common.models import all_permissions_for_user_in_course
from teams.models import CourseTeam
def has_permission(user, permission, course_id=None): def has_permission(user, permission, course_id=None):
...@@ -27,7 +28,7 @@ def has_permission(user, permission, course_id=None): ...@@ -27,7 +28,7 @@ def has_permission(user, permission, course_id=None):
return permission in all_permissions return permission in all_permissions
CONDITIONS = ['is_open', 'is_author', 'is_question_author'] CONDITIONS = ['is_open', 'is_author', 'is_question_author', 'is_team_member_if_applicable']
def _check_condition(user, condition, content): def _check_condition(user, condition, content):
...@@ -55,10 +56,37 @@ def _check_condition(user, condition, content): ...@@ -55,10 +56,37 @@ def _check_condition(user, condition, content):
except KeyError: except KeyError:
return False return False
def check_team_member(user, content):
"""
If the content has a commentable_id, verifies that either it is not associated with a team,
or if it is, that the user is a member of that team.
"""
if not content:
return False
try:
commentable_id = content['commentable_id']
request_cache_dict = RequestCache.get_request_cache().data
cache_key = "django_comment_client.check_team_member.{}.{}".format(user.id, commentable_id)
if cache_key in request_cache_dict:
return request_cache_dict[cache_key]
team = CourseTeam.objects.get(discussion_topic_id=commentable_id)
passes_condition = team.users.filter(id=user.id).count() > 0
request_cache_dict[cache_key] = passes_condition
except KeyError:
# We do not expect KeyError in production-- it usually indicates an improper test mock.
logging.warning("Did not find key commentable_id in content.")
passes_condition = False
except CourseTeam.DoesNotExist:
passes_condition = True
request_cache_dict[cache_key] = passes_condition
return passes_condition
handlers = { handlers = {
'is_open': check_open, 'is_open': check_open,
'is_author': check_author, 'is_author': check_author,
'is_question_author': check_question_author, 'is_question_author': check_question_author,
'is_team_member_if_applicable': check_team_member
} }
return handlers[condition](user, content) return handlers[condition](user, content)
...@@ -88,30 +116,28 @@ def _check_conditions_permissions(user, permissions, course_id, content): ...@@ -88,30 +116,28 @@ def _check_conditions_permissions(user, permissions, course_id, content):
VIEW_PERMISSIONS = { VIEW_PERMISSIONS = {
'update_thread': ['edit_content', ['update_thread', 'is_open', 'is_author']], 'update_thread': ['edit_content', ['update_thread', 'is_open', 'is_author']],
'create_comment': [["create_comment", "is_open"]], 'create_comment': [["create_comment", "is_open", "is_team_member_if_applicable"]],
'delete_thread': ['delete_thread', ['update_thread', 'is_author']], 'delete_thread': ['delete_thread', ['update_thread', 'is_author']],
'update_comment': ['edit_content', ['update_comment', 'is_open', 'is_author']], 'update_comment': ['edit_content', ['update_comment', 'is_open', 'is_author']],
'endorse_comment': ['endorse_comment', 'is_question_author'], 'endorse_comment': ['endorse_comment', 'is_question_author'],
'openclose_thread': ['openclose_thread'], 'openclose_thread': ['openclose_thread'],
'create_sub_comment': [['create_sub_comment', 'is_open']], 'create_sub_comment': [['create_sub_comment', 'is_open', 'is_team_member_if_applicable']],
'delete_comment': ['delete_comment', ['update_comment', 'is_open', 'is_author']], 'delete_comment': ['delete_comment', ['update_comment', 'is_open', 'is_author']],
'vote_for_comment': [['vote', 'is_open']], 'vote_for_comment': [['vote', 'is_open', 'is_team_member_if_applicable']],
'undo_vote_for_comment': [['unvote', 'is_open']], 'undo_vote_for_comment': [['unvote', 'is_open', 'is_team_member_if_applicable']],
'vote_for_thread': [['vote', 'is_open']], 'vote_for_thread': [['vote', 'is_open', 'is_team_member_if_applicable']],
'flag_abuse_for_thread': ['vote'], 'flag_abuse_for_thread': [['vote', 'is_team_member_if_applicable']],
'un_flag_abuse_for_thread': ['vote'], 'un_flag_abuse_for_thread': [['vote', 'is_team_member_if_applicable']],
'flag_abuse_for_comment': ['vote'], 'flag_abuse_for_comment': [['vote', 'is_team_member_if_applicable']],
'un_flag_abuse_for_comment': ['vote'], 'un_flag_abuse_for_comment': [['vote', 'is_team_member_if_applicable']],
'undo_vote_for_thread': [['unvote', 'is_open']], 'undo_vote_for_thread': [['unvote', 'is_open', 'is_team_member_if_applicable']],
'pin_thread': ['openclose_thread'], 'pin_thread': ['openclose_thread'],
'un_pin_thread': ['openclose_thread'], 'un_pin_thread': ['openclose_thread'],
'follow_thread': ['follow_thread'], 'follow_thread': [['follow_thread', 'is_team_member_if_applicable']],
'follow_commentable': ['follow_commentable'], 'follow_commentable': [['follow_commentable', 'is_team_member_if_applicable']],
'follow_user': ['follow_user'], 'unfollow_thread': [['unfollow_thread', 'is_team_member_if_applicable']],
'unfollow_thread': ['unfollow_thread'], 'unfollow_commentable': [['unfollow_commentable', 'is_team_member_if_applicable']],
'unfollow_commentable': ['unfollow_commentable'], 'create_thread': [['create_thread', 'is_team_member_if_applicable']],
'unfollow_user': ['unfollow_user'],
'create_thread': ['create_thread'],
} }
......
...@@ -5,5 +5,15 @@ from lms.lib.comment_client import settings ...@@ -5,5 +5,15 @@ from lms.lib.comment_client import settings
class Commentable(models.Model): class Commentable(models.Model):
accessible_fields = ['id', 'commentable_id']
base_url = "{prefix}/commentables".format(prefix=settings.PREFIX) base_url = "{prefix}/commentables".format(prefix=settings.PREFIX)
type = 'commentable' type = 'commentable'
def retrieve(self, *args, **kwargs):
"""
Override default behavior because commentables don't actually exist in the comment service.
"""
self.attributes["commentable_id"] = self.attributes["id"]
self.retrieved = True
return self
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment