Commit 81fbe35e by Mike Chen

use permissions to check if one can edit or relp

parent 9db1cbba
...@@ -18,62 +18,27 @@ from django.conf import settings ...@@ -18,62 +18,27 @@ from django.conf import settings
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from django_comment_client.utils import JsonResponse, JsonError, extract from django_comment_client.utils import JsonResponse, JsonError, extract
from django_comment_client.permissions import has_permission, has_permission from django_comment_client.permissions import check_permissions_by_view
import functools import functools
#
def permitted(*per): def permitted(fn):
"""
Accepts a list of permissions and proceed if any of the permission is valid.
Note that @permitted("can_view", "can_edit") will proceed if the user has either
"can_view" or "can_edit" permission. To use AND operator in between, wrap them in
a list:
@permitted(["can_view", "can_edit"])
Special conditions can be used like permissions, e.g.
@permitted(["can_vote", "open"]) # where open is True if not content['closed']
"""
def decorator(fn):
@functools.wraps(fn) @functools.wraps(fn)
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
permissions = filter(lambda x: len(x), list(per))
user = request.user
import pdb; pdb.set_trace()
def fetch_content(): def fetch_content():
if "thread_id" in kwargs: if "thread_id" in kwargs:
content = comment_client.get_thread(kwargs["thread_id"]) content = comment_client.get_thread(kwargs["thread_id"])
elif "comment_id" in kwargs: elif "comment_id" in kwargs:
content = comment_client.get_comment(kwargs["comment_id"]) content = comment_client.get_comment(kwargs["comment_id"])
else: else:
logging.warning("missing thread_id or comment_id") content = None
return None
return content return content
def test_permission(user, permission, operator="or"): if check_permissions_by_view(request.user, fetch_content(), request.view_name):
if isinstance(permission, basestring):
if permission == "":
return True
elif permission == "author":
return fetch_content()["user_id"] == request.user.id
elif permission == "open":
return not fetch_content()["closed"]
return has_permission(user, permission)
elif isinstance(permission, list) and operator in ["and", "or"]:
results = [test_permission(user, x, operator="and") for x in permission]
if operator == "or":
return True in results
elif operator == "and":
return not False in results
if test_permission(user, permissions, operator="or"):
return fn(request, *args, **kwargs) return fn(request, *args, **kwargs)
else: else:
return JsonError("unauthorized") return JsonError("unauthorized")
return wrapper return wrapper
return decorator
def thread_author_only(fn): def thread_author_only(fn):
...@@ -106,7 +71,7 @@ def instructor_only(fn): ...@@ -106,7 +71,7 @@ def instructor_only(fn):
@require_POST @require_POST
@login_required @login_required
@permitted("create_thread") @permitted
def create_thread(request, course_id, commentable_id): def create_thread(request, course_id, commentable_id):
attributes = extract(request.POST, ['body', 'title', 'tags']) attributes = extract(request.POST, ['body', 'title', 'tags'])
attributes['user_id'] = request.user.id attributes['user_id'] = request.user.id
...@@ -131,7 +96,7 @@ def create_thread(request, course_id, commentable_id): ...@@ -131,7 +96,7 @@ def create_thread(request, course_id, commentable_id):
@require_POST @require_POST
@login_required @login_required
@permitted("edit_content", ["update_thread", "open", "author"]) @permitted
def update_thread(request, course_id, thread_id): def update_thread(request, course_id, thread_id):
attributes = extract(request.POST, ['body', 'title', 'tags']) attributes = extract(request.POST, ['body', 'title', 'tags'])
response = comment_client.update_thread(thread_id, attributes) response = comment_client.update_thread(thread_id, attributes)
...@@ -171,7 +136,7 @@ def _create_comment(request, course_id, _response_from_attributes): ...@@ -171,7 +136,7 @@ def _create_comment(request, course_id, _response_from_attributes):
@require_POST @require_POST
@login_required @login_required
@permitted(["create_comment", "open"]) @permitted
def create_comment(request, course_id, thread_id): def create_comment(request, course_id, thread_id):
def _response_from_attributes(attributes): def _response_from_attributes(attributes):
return comment_client.create_comment(thread_id, attributes) return comment_client.create_comment(thread_id, attributes)
...@@ -179,14 +144,14 @@ def create_comment(request, course_id, thread_id): ...@@ -179,14 +144,14 @@ def create_comment(request, course_id, thread_id):
@require_POST @require_POST
@login_required @login_required
@permitted("delete_thread") @permitted
def delete_thread(request, course_id, thread_id): def delete_thread(request, course_id, thread_id):
response = comment_client.delete_thread(thread_id) response = comment_client.delete_thread(thread_id)
return JsonResponse(response) return JsonResponse(response)
@require_POST @require_POST
@login_required @login_required
@permitted("update_comment", ["update_comment", "open", "author"]) @permitted
def update_comment(request, course_id, comment_id): def update_comment(request, course_id, comment_id):
attributes = extract(request.POST, ['body']) attributes = extract(request.POST, ['body'])
response = comment_client.update_comment(comment_id, attributes) response = comment_client.update_comment(comment_id, attributes)
...@@ -205,7 +170,7 @@ def update_comment(request, course_id, comment_id): ...@@ -205,7 +170,7 @@ def update_comment(request, course_id, comment_id):
@require_POST @require_POST
@login_required @login_required
@permitted("endorse_comment") @permitted
def endorse_comment(request, course_id, comment_id): def endorse_comment(request, course_id, comment_id):
attributes = extract(request.POST, ['endorsed']) attributes = extract(request.POST, ['endorsed'])
response = comment_client.update_comment(comment_id, attributes) response = comment_client.update_comment(comment_id, attributes)
...@@ -213,7 +178,7 @@ def endorse_comment(request, course_id, comment_id): ...@@ -213,7 +178,7 @@ def endorse_comment(request, course_id, comment_id):
@require_POST @require_POST
@login_required @login_required
@permitted("openclose_thread") @permitted
def openclose_thread(request, course_id, thread_id): def openclose_thread(request, course_id, thread_id):
attributes = extract(request.POST, ['closed']) attributes = extract(request.POST, ['closed'])
response = comment_client.update_thread(thread_id, attributes) response = comment_client.update_thread(thread_id, attributes)
...@@ -221,7 +186,7 @@ def openclose_thread(request, course_id, thread_id): ...@@ -221,7 +186,7 @@ def openclose_thread(request, course_id, thread_id):
@require_POST @require_POST
@login_required @login_required
@permitted(["create_sub_comment", "open"]) @permitted
def create_sub_comment(request, course_id, comment_id): def create_sub_comment(request, course_id, comment_id):
def _response_from_attributes(attributes): def _response_from_attributes(attributes):
return comment_client.create_sub_comment(comment_id, attributes) return comment_client.create_sub_comment(comment_id, attributes)
...@@ -229,14 +194,14 @@ def create_sub_comment(request, course_id, comment_id): ...@@ -229,14 +194,14 @@ def create_sub_comment(request, course_id, comment_id):
@require_POST @require_POST
@login_required @login_required
@permitted("delete_comment") @permitted
def delete_comment(request, course_id, comment_id): def delete_comment(request, course_id, comment_id):
response = comment_client.delete_comment(comment_id) response = comment_client.delete_comment(comment_id)
return JsonResponse(response) return JsonResponse(response)
@require_POST @require_POST
@login_required @login_required
@permitted(["vote", "open"]) @permitted
def vote_for_comment(request, course_id, comment_id, value): def vote_for_comment(request, course_id, comment_id, value):
user_id = request.user.id user_id = request.user.id
response = comment_client.vote_for_comment(comment_id, user_id, value) response = comment_client.vote_for_comment(comment_id, user_id, value)
...@@ -244,7 +209,7 @@ def vote_for_comment(request, course_id, comment_id, value): ...@@ -244,7 +209,7 @@ def vote_for_comment(request, course_id, comment_id, value):
@require_POST @require_POST
@login_required @login_required
@permitted(["unvote", "open"]) @permitted
def undo_vote_for_comment(request, course_id, comment_id): def undo_vote_for_comment(request, course_id, comment_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.undo_vote_for_comment(comment_id, user_id) response = comment_client.undo_vote_for_comment(comment_id, user_id)
...@@ -252,7 +217,7 @@ def undo_vote_for_comment(request, course_id, comment_id): ...@@ -252,7 +217,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
@require_POST @require_POST
@login_required @login_required
@permitted(["vote", "open"]) @permitted
def vote_for_thread(request, course_id, thread_id, value): def vote_for_thread(request, course_id, thread_id, value):
user_id = request.user.id user_id = request.user.id
response = comment_client.vote_for_thread(thread_id, user_id, value) response = comment_client.vote_for_thread(thread_id, user_id, value)
...@@ -260,7 +225,7 @@ def vote_for_thread(request, course_id, thread_id, value): ...@@ -260,7 +225,7 @@ def vote_for_thread(request, course_id, thread_id, value):
@require_POST @require_POST
@login_required @login_required
@permitted(["unvote", "open"]) @permitted
def undo_vote_for_thread(request, course_id, thread_id): def undo_vote_for_thread(request, course_id, thread_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.undo_vote_for_thread(thread_id, user_id) response = comment_client.undo_vote_for_thread(thread_id, user_id)
...@@ -268,7 +233,7 @@ def undo_vote_for_thread(request, course_id, thread_id): ...@@ -268,7 +233,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
@require_POST @require_POST
@login_required @login_required
@permitted("follow_thread") @permitted
def follow_thread(request, course_id, thread_id): def follow_thread(request, course_id, thread_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.subscribe_thread(user_id, thread_id) response = comment_client.subscribe_thread(user_id, thread_id)
...@@ -276,7 +241,7 @@ def follow_thread(request, course_id, thread_id): ...@@ -276,7 +241,7 @@ def follow_thread(request, course_id, thread_id):
@require_POST @require_POST
@login_required @login_required
@permitted("follow_commentable") @permitted
def follow_commentable(request, course_id, commentable_id): def follow_commentable(request, course_id, commentable_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.subscribe_commentable(user_id, commentable_id) response = comment_client.subscribe_commentable(user_id, commentable_id)
...@@ -284,7 +249,7 @@ def follow_commentable(request, course_id, commentable_id): ...@@ -284,7 +249,7 @@ def follow_commentable(request, course_id, commentable_id):
@require_POST @require_POST
@login_required @login_required
@permitted("follow_user") @permitted
def follow_user(request, course_id, followed_user_id): def follow_user(request, course_id, followed_user_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.follow(user_id, followed_user_id) response = comment_client.follow(user_id, followed_user_id)
...@@ -292,7 +257,7 @@ def follow_user(request, course_id, followed_user_id): ...@@ -292,7 +257,7 @@ def follow_user(request, course_id, followed_user_id):
@require_POST @require_POST
@login_required @login_required
@permitted("unfollow_thread") @permitted
def unfollow_thread(request, course_id, thread_id): def unfollow_thread(request, course_id, thread_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.unsubscribe_thread(user_id, thread_id) response = comment_client.unsubscribe_thread(user_id, thread_id)
...@@ -300,7 +265,7 @@ def unfollow_thread(request, course_id, thread_id): ...@@ -300,7 +265,7 @@ def unfollow_thread(request, course_id, thread_id):
@require_POST @require_POST
@login_required @login_required
@permitted("unfollow_commentable") @permitted
def unfollow_commentable(request, course_id, commentable_id): def unfollow_commentable(request, course_id, commentable_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.unsubscribe_commentable(user_id, commentable_id) response = comment_client.unsubscribe_commentable(user_id, commentable_id)
...@@ -308,7 +273,7 @@ def unfollow_commentable(request, course_id, commentable_id): ...@@ -308,7 +273,7 @@ def unfollow_commentable(request, course_id, commentable_id):
@require_POST @require_POST
@login_required @login_required
@permitted("unfollow_user") @permitted
def unfollow_user(request, course_id, followed_user_id): def unfollow_user(request, course_id, followed_user_id):
user_id = request.user.id user_id = request.user.id
response = comment_client.unfollow(user_id, followed_user_id) response = comment_client.unfollow(user_id, followed_user_id)
......
...@@ -18,6 +18,7 @@ import json ...@@ -18,6 +18,7 @@ import json
import comment_client import comment_client
import dateutil import dateutil
from django_comment_client.permissions import check_permissions_by_view
THREADS_PER_PAGE = 5 THREADS_PER_PAGE = 5
PAGES_NEARBY_DELTA = 2 PAGES_NEARBY_DELTA = 2
...@@ -48,7 +49,7 @@ def render_discussion(request, course_id, threads, discussion_id=None, \ ...@@ -48,7 +49,7 @@ def render_discussion(request, course_id, threads, discussion_id=None, \
'forum': (lambda: reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id, discussion_id])), 'forum': (lambda: reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id, discussion_id])),
}[discussion_type]() }[discussion_type]()
annotated_content_info = {thread['id']: get_annotated_content_info(thread, request.user.id) for thread in threads} annotated_content_info = {thread['id']: get_annotated_content_info(thread, request.user, is_thread=True) for thread in threads}
context = { context = {
'threads': threads, 'threads': threads,
...@@ -127,17 +128,18 @@ def forum_form_discussion(request, course_id, discussion_id): ...@@ -127,17 +128,18 @@ def forum_form_discussion(request, course_id, discussion_id):
return render_to_response('discussion/index.html', context) return render_to_response('discussion/index.html', context)
def get_annotated_content_info(content, user_id): def get_annotated_content_info(content, user, is_thread):
return { return {
'editable': str(content['user_id']) == str(user_id), # TODO may relax this to instructors 'editable': check_permissions_by_view(user, content, "update_thread" if is_thread else "update_comment"),
'can_reply': check_permissions_by_view(user, content, "create_comment" if is_thread else "create_sub_comment"),
} }
def get_annotated_content_infos(thread, user_id): def get_annotated_content_infos(thread, user):
infos = {} infos = {}
def _annotate(content): def _annotate(content, is_thread=True):
infos[str(content['id'])] = get_annotated_content_info(content, user_id) infos[str(content['id'])] = get_annotated_content_info(content, user, is_thread)
for child in content.get('children', []): for child in content.get('children', []):
_annotate(child) _annotate(child, is_thread=False)
_annotate(thread) _annotate(thread)
return infos return infos
...@@ -146,7 +148,7 @@ def render_single_thread(request, course_id, thread_id): ...@@ -146,7 +148,7 @@ def render_single_thread(request, course_id, thread_id):
thread = comment_client.get_thread(thread_id, recursive=True) thread = comment_client.get_thread(thread_id, recursive=True)
annotated_content_info = get_annotated_content_infos(thread=thread, \ annotated_content_info = get_annotated_content_infos(thread=thread, \
user_id=request.user.id) user=request.user, is_thread=True)
context = { context = {
'thread': thread, 'thread': thread,
...@@ -162,8 +164,7 @@ def single_thread(request, course_id, discussion_id, thread_id): ...@@ -162,8 +164,7 @@ def single_thread(request, course_id, discussion_id, thread_id):
if request.is_ajax(): if request.is_ajax():
thread = comment_client.get_thread(thread_id, recursive=True) thread = comment_client.get_thread(thread_id, recursive=True)
annotated_content_info = get_annotated_content_infos(thread=thread, \ annotated_content_info = get_annotated_content_infos(thread, request.user)
user_id=request.user.id)
context = {'thread': thread} context = {'thread': thread}
html = render_to_string('discussion/_ajax_single_thread.html', context) html = render_to_string('discussion/_ajax_single_thread.html', context)
......
...@@ -34,11 +34,76 @@ def assign_default_role(sender, instance, **kwargs): ...@@ -34,11 +34,76 @@ def assign_default_role(sender, instance, **kwargs):
logging.info("assign_default_role: adding %s as %s" % (instance, role)) logging.info("assign_default_role: adding %s as %s" % (instance, role))
instance.roles.add(role) instance.roles.add(role)
def check_permissions(user, content, per):
"""
Accepts a list of permissions and proceed if any of the permission is valid.
Note that check_permissions("can_view", "can_edit") will proceed if the user has either
"can_view" or "can_edit" permission. To use AND operator in between, wrap them in
a list:
check_permissions(["can_view", "can_edit"])
Special conditions can be used like permissions, e.g.
(["can_vote", "open"]) # where open is True if not content['closed']
"""
permissions = filter(lambda x: len(x), list(per))
def test_permission(user, permission, operator="or"):
if isinstance(permission, basestring):
# import pdb; pdb.set_trace()
if permission == "":
return True
elif permission == "author":
return content["user_id"] == str(user.id)
elif permission == "open":
return not content["closed"]
return has_permission(user, permission)
elif isinstance(permission, list) and operator in ["and", "or"]:
results = [test_permission(user, x, operator="and") for x in permission]
if operator == "or":
return True in results
elif operator == "and":
return not False in results
return test_permission(user, permissions, operator="or")
VIEW_PERMISSIONS = {
'update_thread' : ('edit_content', ['update_thread', 'open', 'author']),
'create_comment' : (["create_comment", "open"]),
'delete_thread' : ('delete_thread'),
'update_comment' : ('edit_content', ['update_comment', 'open', 'author']),
'endorse_comment' : ('endorse_comment'),
'openclose_thread' : ('openclose_thread'),
'create_sub_comment': (['create_sub_comment', 'open']),
'delete_comment' : ('delete_comment'),
'vote_for_commend' : (['vote', 'open']),
'undo_vote_for_comment': (['unvote', 'open']),
'vote_for_thread' : (['vote', 'open']),
'undo_vote_for_thread': (['unvote', 'open']),
'follow_thread' : ('follow_thread'),
'follow_commentable': ('follow_commentable'),
'follow_user' : ('follow_user'),
'unfollow_thread' : ('unfollow_thread'),
'unfollow_commentable': ('unfollow_commentable'),
'unfollow_user' : ('unfollow_user'),
'create_thread' : ('create_thread'),
}
def check_permissions_by_view(user, content, name):
try:
p = VIEW_PERMISSIONS[name]
except KeyError:
logging.warning("Permission for view named %s does not exist in permissions.py" % name)
permissions = list((p, ) if isinstance(p, basestring) else p)
return check_permissions(user, content, permissions)
moderator_role = Role.register("Moderator") moderator_role = Role.register("Moderator")
student_role = Role.register("Student") student_role = Role.register("Student")
moderator_role.register_permissions(["edit_content", "delete_thread", "openclose_thread", moderator_role.register_permissions(["edit_content", "delete_thread", "openclose_thread",
"update_thread", "endorse_comment", "delete_comment"]) "endorse_comment", "delete_comment"])
student_role.register_permissions(["vote", "update_thread", "follow_thread", "unfollow_thread", student_role.register_permissions(["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote" , "create_thread", "update_comment", "create_sub_comment", "unvote" , "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]) "follow_commentable", "unfollow_commentable", "create_comment", ])
......
...@@ -120,3 +120,7 @@ class JsonError(HttpResponse): ...@@ -120,3 +120,7 @@ class JsonError(HttpResponse):
class HtmlResponse(HttpResponse): class HtmlResponse(HttpResponse):
def __init__(self, html=''): def __init__(self, html=''):
super(HtmlResponse, self).__init__(html, content_type='text/plain') super(HtmlResponse, self).__init__(html, content_type='text/plain')
class ViewNameMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
request.view_name = view_func.__name__
...@@ -294,6 +294,8 @@ MIDDLEWARE_CLASSES = ( ...@@ -294,6 +294,8 @@ MIDDLEWARE_CLASSES = (
'askbot.middleware.spaceless.SpacelessMiddleware', 'askbot.middleware.spaceless.SpacelessMiddleware',
# 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware', # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
# 'debug_toolbar.middleware.DebugToolbarMiddleware', # 'debug_toolbar.middleware.DebugToolbarMiddleware',
'django_comment_client.utils.ViewNameMiddleware',
) )
############################### Pipeline ####################################### ############################### Pipeline #######################################
......
...@@ -78,6 +78,7 @@ initializeFollowThread = (thread) -> ...@@ -78,6 +78,7 @@ initializeFollowThread = (thread) ->
$comment = $(response.html) $comment = $(response.html)
$content.children(".comments").prepend($comment) $content.children(".comments").prepend($comment)
Discussion.setWmdContent $content, $local, "reply-body", "" Discussion.setWmdContent $content, $local, "reply-body", ""
Discussion.setContentInfo response.content['id'], 'can_reply', true
Discussion.setContentInfo response.content['id'], 'editable', true Discussion.setContentInfo response.content['id'], 'editable', true
Discussion.initializeContent($comment) Discussion.initializeContent($comment)
Discussion.bindContentEvents($comment) Discussion.bindContentEvents($comment)
...@@ -321,3 +322,5 @@ initializeFollowThread = (thread) -> ...@@ -321,3 +322,5 @@ initializeFollowThread = (thread) ->
id = $content.attr("_id") id = $content.attr("_id")
if not Discussion.getContentInfo id, 'editable' if not Discussion.getContentInfo id, 'editable'
$local(".discussion-edit").remove() $local(".discussion-edit").remove()
if not Discussion.getContentInfo id, 'can_reply'
$local(".discussion-reply").remove()
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