user.py 7.73 KB
Newer Older
Steve Strassmann committed
1
from django.core.exceptions import PermissionDenied
2
from django.contrib.auth.models import User
3
from django.contrib.auth.decorators import login_required
4
from django.views.decorators.http import require_http_methods
David Baumgold committed
5
from django.utils.translation import ugettext as _
6
from django.views.decorators.http import require_POST
7
from django.views.decorators.csrf import ensure_csrf_cookie
David Baumgold committed
8
from edxmako.shortcuts import render_to_response
9

10
from xmodule.modulestore.django import modulestore
11
from opaque_keys.edx.keys import CourseKey
12
from opaque_keys.edx.locator import LibraryLocator
13
from util.json_request import JsonResponse, expect_json
14
from student.roles import CourseInstructorRole, CourseStaffRole, LibraryUserRole
15
from course_creators.views import user_requested_access
Steve Strassmann committed
16

17
from student.auth import STUDIO_EDIT_ROLES, STUDIO_VIEW_USERS, get_user_permissions
Steve Strassmann committed
18

19
from student.models import CourseEnrollment
20
from django.http import HttpResponseNotFound
21
from student import auth
22 23


24
__all__ = ['request_course_creator', 'course_team_handler']
Steve Strassmann committed
25

26

27 28 29
@require_POST
@login_required
def request_course_creator(request):
30 31 32
    """
    User has requested course creation access.
    """
33 34 35 36
    user_requested_access(request.user)
    return JsonResponse({"Status": "OK"})


37
# pylint: disable=unused-argument
Steve Strassmann committed
38 39
@login_required
@ensure_csrf_cookie
40
@require_http_methods(("GET", "POST", "PUT", "DELETE"))
41
def course_team_handler(request, course_key_string=None, email=None):
42 43 44 45 46 47 48 49 50 51 52
    """
    The restful handler for course team users.

    GET
        html: return html page for managing course team
        json: return json representation of a particular course team member (email is required).
    POST or PUT
        json: modify the permissions for a particular course team member (email is required, as well as role in the payload).
    DELETE:
        json: remove a particular course team member from the course team (email is required).
    """
53
    course_key = CourseKey.from_string(course_key_string) if course_key_string else None
54
    # No permissions check here - each helper method does its own check.
55 56

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
57
        return _course_team_user(request, course_key, email)
58
    elif request.method == 'GET':  # assume html
59
        return _manage_users(request, course_key)
60 61 62 63
    else:
        return HttpResponseNotFound()


64 65 66 67 68 69 70 71 72 73
def user_with_role(user, role):
    """ Build user representation with attached role """
    return {
        'id': user.id,
        'username': user.username,
        'email': user.email,
        'role': role
    }


74
def _manage_users(request, course_key):
75
    """
76
    This view will return all CMS users who are editors for the specified course
77
    """
78
    # check that logged in user has permissions to this item
79
    user_perms = get_user_permissions(request.user, course_key)
E. Kolpakov committed
80
    if not user_perms & STUDIO_VIEW_USERS:
81 82
        raise PermissionDenied()

83
    course_module = modulestore().get_course(course_key)
84
    instructors = set(CourseInstructorRole(course_key).users_with_role())
85
    # the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
86
    staff = set(CourseStaffRole(course_key).users_with_role()).union(instructors)
87

88 89 90 91 92 93
    formatted_users = []
    for user in instructors:
        formatted_users.append(user_with_role(user, 'instructor'))
    for user in staff - instructors:
        formatted_users.append(user_with_role(user, 'staff'))

94 95
    return render_to_response('manage_users.html', {
        'context_course': course_module,
96 97
        'show_transfer_ownership_hint': request.user in instructors and len(instructors) == 1,
        'users': formatted_users,
98
        'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
99 100 101
    })


102
@expect_json
103
def _course_team_user(request, course_key, email):
104 105 106
    """
    Handle the add, remove, promote, demote requests ensuring the requester has authority
    """
107
    # check that logged in user has permissions to this item
108 109 110 111
    requester_perms = get_user_permissions(request.user, course_key)
    permissions_error_response = JsonResponse({"error": _("Insufficient permissions")}, 403)
    if (requester_perms & STUDIO_VIEW_USERS) or (email == request.user.email):
        # This user has permissions to at least view the list of users or is editing themself
112 113
        pass
    else:
114 115
        # This user is not even allowed to know who the authorized users are.
        return permissions_error_response
116

117 118
    try:
        user = User.objects.get(email=email)
119
    except Exception:
120
        msg = {
121
            "error": _("Could not find user by email address '{email}'.").format(email=email),
122 123
        }
        return JsonResponse(msg, 404)
124

125 126 127 128 129 130 131
    is_library = isinstance(course_key, LibraryLocator)
    # Ordered list of roles: can always move self to the right, but need STUDIO_EDIT_ROLES to move any user left
    if is_library:
        role_hierarchy = (CourseInstructorRole, CourseStaffRole, LibraryUserRole)
    else:
        role_hierarchy = (CourseInstructorRole, CourseStaffRole)

132 133
    if request.method == "GET":
        # just return info about the user
134
        msg = {
135 136
            "email": user.email,
            "active": user.is_active,
137
            "role": None,
138
        }
139
        # what's the highest role that this user has? (How should this report global staff?)
140 141
        for role in role_hierarchy:
            if role(course_key).has_user(user):
142
                msg["role"] = role.ROLE
143
                break
144
        return JsonResponse(msg)
145

146 147 148 149 150
    # All of the following code is for editing/promoting/deleting users.
    # Check that the user has STUDIO_EDIT_ROLES permission or is editing themselves:
    if not ((requester_perms & STUDIO_EDIT_ROLES) or (user.id == request.user.id)):
        return permissions_error_response

151 152
    # can't modify an inactive user
    if not user.is_active:
153
        msg = {
154
            "error": _('User {email} has registered but has not yet activated his/her account.').format(email=email),
155
        }
156
        return JsonResponse(msg, 400)
157

158
    if request.method == "DELETE":
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
        new_role = None
    else:
        # only other operation supported is to promote/demote a user by changing their role:
        # role may be None or "" (equivalent to a DELETE request) but must be set.
        # Check that the new role was specified:
        if "role" in request.json or "role" in request.POST:
            new_role = request.json.get("role", request.POST.get("role"))
        else:
            return JsonResponse({"error": _("No `role` specified.")}, 400)

    old_roles = set()
    role_added = False
    for role_type in role_hierarchy:
        role = role_type(course_key)
        if role_type.ROLE == new_role:
            if (requester_perms & STUDIO_EDIT_ROLES) or (user.id == request.user.id and old_roles):
E. Kolpakov committed
175 176
                # User has STUDIO_EDIT_ROLES permission or
                # is currently a member of a higher role, and is thus demoting themself
177 178 179 180 181 182 183 184 185 186 187 188 189 190
                auth.add_users(request.user, role, user)
                role_added = True
            else:
                return permissions_error_response
        elif role.has_user(user):
            # Remove the user from this old role:
            old_roles.add(role)

    if new_role and not role_added:
        return JsonResponse({"error": _("Invalid `role` specified.")}, 400)

    for role in old_roles:
        if isinstance(role, CourseInstructorRole) and role.users_with_role().count() == 1:
            msg = {"error": _("You may not remove the last Admin. Add another Admin first.")}
191
            return JsonResponse(msg, 400)
192 193 194 195 196
        auth.remove_users(request.user, role, user)

    if new_role and not is_library:
        # The user may be newly added to this course.
        # auto-enroll the user in the course so that "View Live" will work.
197
        CourseEnrollment.enroll(user, course_key)
198

199
    return JsonResponse()