permissions.py 4.28 KB
Newer Older
1 2 3 4
"""
Module for checking permissions with the comment_client backend
"""

Mike Chen committed
5
import logging
6
from types import NoneType
7
from django.core import cache
8
from opaque_keys.edx.keys import CourseKey
9 10 11

CACHE = cache.get_cache('default')
CACHE_LIFESPAN = 60
12

Calen Pennington committed
13

14
def cached_has_permission(user, permission, course_id=None):
Mike Chen committed
15 16
    """
    Call has_permission if it's not cached. A change in a user's role or
17
    a role's permissions will only become effective after CACHE_LIFESPAN seconds.
Mike Chen committed
18
    """
19
    assert isinstance(course_id, (NoneType, CourseKey))
20 21
    key = u"permission_{user_id:d}_{course_id}_{permission}".format(
        user_id=user.id, course_id=course_id, permission=permission)
22
    val = CACHE.get(key, None)
23 24
    if val not in [True, False]:
        val = has_permission(user, permission, course_id=course_id)
25
        CACHE.set(key, val, CACHE_LIFESPAN)
26 27
    return val

Calen Pennington committed
28

29
def has_permission(user, permission, course_id=None):
30
    assert isinstance(course_id, (NoneType, CourseKey))
31 32 33
    for role in user.roles.filter(course_id=course_id):
        if role.has_permission(permission):
            return True
Mike Chen committed
34 35 36
    return False


37
CONDITIONS = ['is_open', 'is_author']
Calen Pennington committed
38 39


40
def _check_condition(user, condition, course_id, data):
41
    def check_open(user, condition, course_id, data):
Rocky Duan committed
42 43 44 45
        try:
            return data and not data['content']['closed']
        except KeyError:
            return False
Mike Chen committed
46

47
    def check_author(user, condition, course_id, data):
Rocky Duan committed
48 49 50 51
        try:
            return data and data['content']['user_id'] == str(user.id)
        except KeyError:
            return False
Mike Chen committed
52

53
    handlers = {
Calen Pennington committed
54 55
        'is_open': check_open,
        'is_author': check_author,
56
    }
Mike Chen committed
57

58
    return handlers[condition](user, condition, course_id, data)
59

60

61
def _check_conditions_permissions(user, permissions, course_id, **kwargs):
62 63
    """
    Accepts a list of permissions and proceed if any of the permission is valid.
64
    Note that ["can_view", "can_edit"] will proceed if the user has either
65 66
    "can_view" or "can_edit" permission. To use AND operator in between, wrap them in
    a list.
67
    """
68 69 70 71

    def test(user, per, operator="or"):
        if isinstance(per, basestring):
            if per in CONDITIONS:
72
                return _check_condition(user, per, course_id, kwargs)
73
            return cached_has_permission(user, per, course_id=course_id)
74 75
        elif isinstance(per, list) and operator in ["and", "or"]:
            results = [test(user, x, operator="and") for x in per]
76 77 78 79
            if operator == "or":
                return True in results
            elif operator == "and":
                return not False in results
Kevin Chugh committed
80
    return test(user, permissions, operator="or")
81 82 83


VIEW_PERMISSIONS = {
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    'update_thread': ['edit_content', ['update_thread', 'is_open', 'is_author']],
    'create_comment': [["create_comment", "is_open"]],
    'delete_thread': ['delete_thread', ['update_thread', 'is_author']],
    'update_comment': ['edit_content', ['update_comment', 'is_open', 'is_author']],
    'endorse_comment': ['endorse_comment'],
    'openclose_thread': ['openclose_thread'],
    'create_sub_comment': [['create_sub_comment', 'is_open']],
    'delete_comment': ['delete_comment', ['update_comment', 'is_open', 'is_author']],
    'vote_for_comment': [['vote', 'is_open']],
    'undo_vote_for_comment': [['unvote', 'is_open']],
    'vote_for_thread': [['vote', 'is_open']],
    'flag_abuse_for_thread': [['vote', 'is_open']],
    'un_flag_abuse_for_thread': [['vote', 'is_open']],
    'flag_abuse_for_comment': [['vote', 'is_open']],
    'un_flag_abuse_for_comment': [['vote', 'is_open']],
    'undo_vote_for_thread': [['unvote', 'is_open']],
100 101
    'pin_thread': ['openclose_thread'],
    'un_pin_thread': ['openclose_thread'],
102 103 104 105 106 107 108
    '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'],
109 110
}

111 112

def check_permissions_by_view(user, course_id, content, name):
113
    assert isinstance(course_id, CourseKey)
114 115 116 117
    try:
        p = VIEW_PERMISSIONS[name]
    except KeyError:
        logging.warning("Permission for view named %s does not exist in permissions.py" % name)
118
    return _check_conditions_permissions(user, p, course_id, content=content)