Commit aff44b1f by Calen Pennington

Merge remote-tracking branch 'origin/master' into feature/cale/cms-master

Conflicts:
	cms/djangoapps/contentstore/views.py
parents a1b99869 fbba883c
import logging
import sys
from django.contrib.auth.models import User, Group
from django.core.exceptions import PermissionDenied
from xmodule.modulestore import Location
# define a couple of simple roles, we just need ADMIN and EDITOR now for our purposes
ADMIN_ROLE_NAME = 'admin'
EDITOR_ROLE_NAME = 'editor'
# we're just making a Django group for each location/role combo
# to do this we're just creating a Group name which is a formatted string
# of those two variables
def get_course_groupname_for_role(location, role):
loc = Location(location)
groupname = loc.course_id + ':' + role
return groupname
def get_users_in_course_group_by_role(location, role):
groupname = get_course_groupname_for_role(location, role)
group = Group.objects.get(name=groupname)
return group.user_set.all()
'''
Create all permission groups for a new course and subscribe the caller into those roles
'''
def create_all_course_groups(creator, location):
create_new_course_group(creator, location, ADMIN_GROUP_NAME)
create_new_course_group(creator, location, EDITOR_GROUP_NAME)
def create_new_course_group(creator, location, role):
groupname = get_course_groupname_for_role(location, role)
(group, created) =Group.get_or_create(name=groupname)
if created:
group.save()
creator.groups.add(group)
creator.save()
return
def add_user_to_course_group(caller, user, location, role):
# only admins can add/remove other users
if not is_user_in_course_group_role(caller, location, ADMIN_ROLE_NAME):
raise PermissionDenied
if user.is_active and user.is_authenticated:
groupname = get_course_groupname_for_role(location, role)
group = Group.objects.get(name=groupname)
user.groups.add(group)
user.save()
return True
return False
def get_user_by_email(email):
user = None
# try to look up user, return None if not found
try:
user = User.objects.get(email=email)
except:
pass
return user
def remove_user_from_course_group(caller, user, location, role):
# only admins can add/remove other users
if not is_user_in_course_group_role(caller, location, ADMIN_ROLE_NAME):
raise PermissionDenied
# see if the user is actually in that role, if not then we don't have to do anything
if is_user_in_course_group_role(user, location, role) == True:
groupname = get_course_groupname_for_role(location, role)
group = Group.objects.get(name=groupname)
user.groups.remove(group)
user.save()
def is_user_in_course_group_role(user, location, role):
if user.is_active and user.is_authenticated:
return user.groups.filter(name=get_course_groupname_for_role(location,role)).count() > 0
return False
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
'''
cdodge: for a given Xmodule, return the course that it belongs to
NOTE: This makes a lot of assumptions about the format of the course location
Also we have to assert that this module maps to only one course item - it'll throw an
assert if not
'''
def get_course_location_for_item(location):
item_loc = Location(location)
# check to see if item is already a course, if so we can skip this
if item_loc.category != 'course':
# @hack! We need to find the course location however, we don't
# know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
courses = modulestore().get_items(course_search_location)
# make sure we found exactly one match on this above course search
found_cnt = len(courses)
if found_cnt == 0:
raise BaseException('Could not find course at {0}'.format(course_search_location))
if found_cnt > 1:
raise BaseException('Found more than one course at {0}. There should only be one!!!'.format(course_search_location))
location = courses[0].location
return location
...@@ -5,6 +5,7 @@ import logging ...@@ -5,6 +5,7 @@ import logging
import sys import sys
import mimetypes import mimetypes
import StringIO import StringIO
import exceptions
from collections import defaultdict from collections import defaultdict
from uuid import uuid4 from uuid import uuid4
...@@ -13,6 +14,7 @@ from PIL import Image ...@@ -13,6 +14,7 @@ from PIL import Image
from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -37,9 +39,11 @@ from operator import attrgetter ...@@ -37,9 +39,11 @@ from operator import attrgetter
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
#from django.core.cache import cache
from cache_toolbox.core import set_cached_content, get_cached_content, del_cached_content from cache_toolbox.core import set_cached_content, get_cached_content, del_cached_content
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
from auth.authz import ADMIN_ROLE_NAME, EDITOR_ROLE_NAME
from .utils import get_course_location_for_item
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -76,6 +80,10 @@ def index(request): ...@@ -76,6 +80,10 @@ def index(request):
List all courses available to the logged in user List all courses available to the logged in user
""" """
courses = modulestore().get_items(['i4x', None, None, 'course', None]) courses = modulestore().get_items(['i4x', None, None, 'course', None])
# filter out courses that we don't have access to
courses = filter(lambda course: has_access(request.user, course.location), courses)
return render_to_response('index.html', { return render_to_response('index.html', {
'courses': [(course.metadata.get('display_name'), 'courses': [(course.metadata.get('display_name'),
reverse('course_index', args=[ reverse('course_index', args=[
...@@ -88,10 +96,10 @@ def index(request): ...@@ -88,10 +96,10 @@ def index(request):
# ==== Views with per-item permissions================================ # ==== Views with per-item permissions================================
def has_access(user, location): def has_access(user, location, role=EDITOR_ROLE_NAME):
'''Return True if user allowed to access this piece of data''' '''Return True if user allowed to access this piece of data'''
# TODO (vshnayder): actually check perms '''Note that the CMS permissions model is with respect to courses'''
return user.is_active and user.is_authenticated return is_user_in_course_group_role(user, get_course_location_for_item(location), role)
@login_required @login_required
...@@ -103,8 +111,10 @@ def course_index(request, org, course, name): ...@@ -103,8 +111,10 @@ def course_index(request, org, course, name):
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
location = ['i4x', org, course, 'course', name] location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location): if not has_access(request.user, location):
raise Http404 # TODO (vshnayder): better error raise PermissionDenied()
upload_asset_callback_url = reverse('upload_asset', kwargs = { upload_asset_callback_url = reverse('upload_asset', kwargs = {
'org' : org, 'org' : org,
...@@ -130,9 +140,9 @@ def edit_unit(request, location): ...@@ -130,9 +140,9 @@ def edit_unit(request, location):
id: A Location URL id: A Location URL
""" """
# TODO (vshnayder): change name from id to location in coffee+html as well. # check that we have permissions to edit this item
if not has_access(request.user, location): if not has_access(request.user, location):
raise Http404 # TODO (vshnayder): better error raise PermissionDenied()
item = modulestore().get_item(location) item = modulestore().get_item(location)
...@@ -361,8 +371,10 @@ def get_module_previews(request, descriptor): ...@@ -361,8 +371,10 @@ def get_module_previews(request, descriptor):
@expect_json @expect_json
def save_item(request): def save_item(request):
item_location = request.POST['id'] item_location = request.POST['id']
# check permissions for this user within this course
if not has_access(request.user, item_location): if not has_access(request.user, item_location):
raise Http404 # TODO (vshnayder): better error raise PermissionDenied()
if request.POST['data']: if request.POST['data']:
data = request.POST['data'] data = request.POST['data']
...@@ -400,7 +412,7 @@ def clone_item(request): ...@@ -400,7 +412,7 @@ def clone_item(request):
template = Location(request.POST['template']) template = Location(request.POST['template'])
if not has_access(request.user, parent_location): if not has_access(request.user, parent_location):
raise Http404 # TODO (vshnayder): better error raise PermissionDenied()
parent = modulestore().get_item(parent_location) parent = modulestore().get_item(parent_location)
dest_location = parent_location._replace(category=template.category, name=uuid4().hex) dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
...@@ -504,3 +516,84 @@ def upload_asset(request, org, course, coursename): ...@@ -504,3 +516,84 @@ def upload_asset(request, org, course, coursename):
logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name)) logging.error('Failed to generate thumbnail for {0}. Continuing...'.format(name))
return HttpResponse('Upload completed') return HttpResponse('Upload completed')
'''
This view will return all CMS users who are editors for the specified course
'''
@login_required
@ensure_csrf_cookie
def manage_users(request, org, course, name):
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location, role=ADMIN_ROLE_NAME):
raise PermissionDenied()
return render_to_response('manage_users.html', {
'editors': get_users_in_course_group_by_role(location, EDITOR_ROLE_NAME)
})
def create_json_response(errmsg = None):
if errmsg is not None:
resp = HttpResponse(json.dumps({'Status': 'Failed', 'ErrMsg' : errmsg}))
else:
resp = HttpResponse(json.dumps({'Status': 'OK'}))
return resp
'''
This POST-back view will add a user - specified by email - to the list of editors for
the specified course
'''
@login_required
@ensure_csrf_cookie
def add_user(request, org, course, name):
email = request.POST["email"]
if email=='':
return create_json_response('Please specify an email address.')
location = ['i4x', org, course, 'course', name]
# check that logged in user has admin permissions to this course
if not has_access(request.user, location, role=ADMIN_ROLE_NAME):
raise PermissionDenied()
user = get_user_by_email(email)
# user doesn't exist?!? Return error.
if user is None:
return create_json_response('Could not find user by email address \'{0}\'.'.format(email))
# user exists, but hasn't activated account?!?
if not user.is_active:
return create_json_response('User {0} has registered but has not yet activated his/her account.'.format(email))
# ok, we're cool to add to the course group
add_user_to_course_group(request.user, user, location, EDITOR_ROLE_NAME)
return create_json_response()
'''
This POST-back view will remove a user - specified by email - from the list of editors for
the specified course
'''
@login_required
@ensure_csrf_cookie
def remove_user(request, org, course, name):
email = request.POST["email"]
location = ['i4x', org, course, 'course', name]
# check that logged in user has admin permissions on this course
if not has_access(request.user, location, role=ADMIN_ROLE_NAME):
raise PermissionDenied()
user = get_user_by_email(email)
if user is None:
return create_json_response('Could not find user by email address \'{0}\'.'.format(email))
remove_user_from_course_group(request.user, user, location, EDITOR_ROLE_NAME)
return create_json_response()
...@@ -282,6 +282,7 @@ INSTALLED_APPS = ( ...@@ -282,6 +282,7 @@ INSTALLED_APPS = (
# For CMS # For CMS
'contentstore', 'contentstore',
'auth',
'github_sync', 'github_sync',
'student', # misleading name due to sharing with lms 'student', # misleading name due to sharing with lms
......
<%inherit file="base.html" />
<%block name="title">Course Editor Manager</%block>
<%include file="widgets/header.html"/>
<%block name="content">
<section class="main-container">
<h2>Course Editors</h2>
<ul>
% for user in editors:
<li>${user.email} (${user.username})</li>
% endfor
</ul>
<form action="add_user" id="addEditorsForm">
<label>email:&nbsp;</label><input type="text" name="email" placeholder="email@example.com..." />
<input type="submit" value="add editor" />
</form>
<div id="result"></div>
<script>
$("#addEditorsForm").submit(function(event) {
event.preventDefault();
var $form = $(this),
email = $form.find('input[name="email"]').val(),
url = $form.attr('action');
$.post(url, {email:email},
function(data) {
if(data['Status'] != 'OK')
$("#result").empty().append(data['ErrMsg']);
else
location.reload();
});
});
</script>
</section>
</%block>
...@@ -20,7 +20,14 @@ urlpatterns = ('', ...@@ -20,7 +20,14 @@ urlpatterns = ('',
url(r'^preview/modx/(?P<preview_id>[^/]*)/(?P<location>.*?)/(?P<dispatch>[^/]*)$', url(r'^preview/modx/(?P<preview_id>[^/]*)/(?P<location>.*?)/(?P<dispatch>[^/]*)$',
'contentstore.views.preview_dispatch', name='preview_dispatch'), 'contentstore.views.preview_dispatch', name='preview_dispatch'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$',
'contentstore.views.upload_asset', name='upload_asset') 'contentstore.views.upload_asset', name='upload_asset'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/manage_users$',
'contentstore.views.manage_users', name='manage_users'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/add_user$',
'contentstore.views.add_user', name='add_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/remove_user$',
'contentstore.views.remove_user', name='remove_user')
) )
# User creation and updating views # User creation and updating views
......
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