OAuth2 API to call XBlock handlers

This commit is contained in:
Chandrakant Gopalan
2017-08-31 16:03:17 -04:00
committed by Daniel Clemente Laboreo
parent 1765c99a5a
commit a3df96bf1c
5 changed files with 199 additions and 102 deletions

View File

@@ -17,11 +17,17 @@ from django.template.context_processors import csrf
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.utils.text import slugify
from django.views.decorators.csrf import csrf_exempt
from edx_proctoring.services import ProctoringService
from edx_rest_framework_extensions.authentication import JwtAuthentication
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from requests.auth import HTTPBasicAuth
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.views import APIView
from rest_framework_oauth.authentication import OAuth2Authentication
from six import text_type
from xblock.core import XBlock
from xblock.django.request import django_to_webob_request, webob_to_django_response
@@ -68,7 +74,6 @@ from student.roles import CourseBetaTesterRole
from track import contexts
from util import milestones_helpers
from util.json_request import JsonResponse
from django.utils.text import slugify
from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
from xblock_django.user_service import DjangoXBlockUserService
from xmodule.contentstore.django import contentstore
@@ -927,39 +932,31 @@ def handle_xblock_callback_noauth(request, course_id, usage_id, handler, suffix=
return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course)
def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
class XblockCallbackView(APIView):
"""
Generic view for extensions. This is where AJAX calls go.
Arguments:
request (Request): Django request.
course_id (str): Course containing the block
usage_id (str)
handler (str)
suffix (str)
Raises:
Http404: If the course is not found in the modulestore.
Class-based view for extensions. This is where AJAX calls go.
Return 403 error if the user is not logged in. Raises Http404 if
the location and course_id do not identify a valid module, the module is
not accessible by the user, or the module raises NotFoundError. If the
module raises any other error, it will escape this function.
"""
# NOTE (CCB): Allow anonymous GET calls (e.g. for transcripts). Modifying this view is simpler than updating
# the XBlocks to use `handle_xblock_callback_noauth`...which is practically identical to this view.
if request.method != 'GET' and not request.user.is_authenticated:
return HttpResponseForbidden()
authentication_classes = (JwtAuthentication, SessionAuthentication, OAuth2Authentication,)
permission_classes = (IsAuthenticatedOrReadOnly,)
request.user.known = request.user.is_authenticated
def get(self, request, course_id, usage_id, handler, suffix=None):
return _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix)
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
raise Http404('{} is not a valid course key'.format(course_id))
def post(self, request, course_id, usage_id, handler, suffix=None):
return _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix)
with modulestore().bulk_operations(course_key):
try:
course = modulestore().get_course(course_key)
except ItemNotFoundError:
raise Http404('{} does not exist in the modulestore'.format(course_id))
def put(self, request, course_id, usage_id, handler, suffix=None):
return _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix)
return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course)
def patch(self, request, course_id, usage_id, handler, suffix=None):
return _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix)
def delete(self, request, course_id, usage_id, handler, suffix=None):
return _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix)
def get_module_by_usage_id(request, course_id, usage_id, disable_staff_debug_info=False, course=None):
@@ -1037,10 +1034,11 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course
"""
# Check submitted files
files = request.FILES or {}
error_msg = _check_files_limits(files)
if error_msg:
return JsonResponse({'success': error_msg}, status=413)
if request.method == 'POST' and request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
files = request.FILES
error_msg = _check_files_limits(files)
if error_msg:
return JsonResponse({'success': error_msg}, status=413)
# Make a CourseKey from the course_id, raising a 404 upon parse error.
try:
@@ -1095,6 +1093,27 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course
return webob_to_django_response(resp)
def _get_course_and_invoke_handler(request, course_id, usage_id, handler, suffix):
"""
Gets the course object to pass as an additional argument to invoke the xblock handler.
"""
request.user.known = request.user.is_authenticated
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
raise Http404('{} is not a valid course key'.format(course_id))
with modulestore().bulk_operations(course_key):
try:
course = modulestore().get_course(course_key)
except ItemNotFoundError:
raise Http404('{} does not exist in the modulestore'.format(course_id))
return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course=course)
def hash_resource(resource):
"""
Hash a :class:`web_fragments.fragment.FragmentResource