feat: add API to return course live iframe (#30179)

feat: add API to return course live iframe
This commit is contained in:
Abdurrahman Asad
2022-04-11 23:20:41 +05:00
committed by GitHub
parent 05f2be21a9
commit 4ad1dfcd5d
5 changed files with 184 additions and 3 deletions

View File

@@ -3,6 +3,7 @@ API library for Django REST Framework permissions-oriented workflows
"""
from rest_framework.permissions import BasePermission
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff
from openedx.core.lib.api.view_utils import validate_course_key
@@ -26,3 +27,22 @@ class IsStaffOrInstructor(BasePermission):
CourseInstructorRole(course_key).has_user(request.user) or
CourseStaffRole(course_key).has_user(request.user)
)
class IsEnrolledOrStaff(BasePermission):
"""
Check if user is enrolled in the course or staff
"""
def has_permission(self, request, view):
course_key_string = view.kwargs.get('course_id')
course_key = validate_course_key(course_key_string)
if GlobalStaff().has_user(request.user):
return True
return (
CourseInstructorRole(course_key).has_user(request.user) or
CourseStaffRole(course_key).has_user(request.user) or
CourseEnrollment.is_enrolled(request.user, course_key)
)

View File

@@ -22,6 +22,11 @@ class CourseLiveTab(LtiCourseLaunchMixin, TabFragmentViewMixin, EnrolledTab):
allow_multiple = False
is_dynamic = True
title = gettext_lazy("Live")
ROLE_MAP = {
'student': 'Student',
'staff': 'Administrator',
'instructor': 'Administrator',
}
@request_cached()
def _get_lti_config(self, course: CourseBlock) -> LtiConfiguration:

View File

@@ -3,9 +3,11 @@ Test for course live app views
"""
import json
from django.test import RequestFactory
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from lti_consumer.models import CourseAllowPIISharingInLTIFlag
from lti_consumer.models import CourseAllowPIISharingInLTIFlag, LtiConfiguration
from markupsafe import Markup
from rest_framework.test import APITestCase
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase
@@ -266,3 +268,80 @@ class TestCourseLiveProvidersView(ModuleStoreTestCase, APITestCase):
response = self.client.get(self.url)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected_data)
class TestCourseLiveIFrameView(ModuleStoreTestCase, APITestCase):
"""
Unit tests for course live iframe view
"""
def setUp(self):
super().setUp()
store = ModuleStoreEnum.Type.split
self.course = CourseFactory.create(default_store=store)
self.user = self.create_user_for_course(self.course, user_type=CourseUserType.GLOBAL_STAFF)
@property
def url(self):
"""
Returns the course live iframe API url.
"""
return reverse(
'live_iframe', kwargs={'course_id': str(self.course.id)}
)
def test_api_returns_live_iframe(self):
request = RequestFactory().get(self.url)
request.user = self.user
live_config = CourseLiveConfiguration.objects.create(
course_key=self.course.id,
enabled=True,
provider_type="zoom",
)
live_config.lti_configuration = LtiConfiguration.objects.create(
config_store=LtiConfiguration.CONFIG_ON_DB,
lti_config={
"pii_share_username": 'true',
"pii_share_email": 'true',
"additional_parameters": {
"custom_instructor_email": "test@gmail.com"
}
},
lti_1p1_launch_url='http://test.url',
lti_1p1_client_key='test_client_key',
lti_1p1_client_secret='test_client_secret',
)
live_config.save()
with override_waffle_flag(ENABLE_COURSE_LIVE, True):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertIsInstance(response.data['iframe'], Markup)
self.assertIn('iframe', str(response.data['iframe']))
def test_non_authenticated_user(self):
"""
Verify that 401 is returned if user is not authenticated.
"""
self.client.logout()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 401)
def test_not_enrolled_user(self):
"""
Verify that 403 is returned if user is not enrolled.
"""
self.user = self.create_user_for_course(self.course, user_type=CourseUserType.UNENROLLED)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
def test_live_configuration_disabled(self):
"""
Verify that proper error message is returned if live configuration is disabled.
"""
CourseLiveConfiguration.objects.create(
course_key=self.course.id,
enabled=False,
provider_type="zoom",
)
response = self.client.get(self.url)
self.assertEqual(response.data['developer_message'], 'Course live is not enabled for this course.')

View File

@@ -6,11 +6,17 @@ course live API URLs.
from django.conf import settings
from django.urls import re_path
from openedx.core.djangoapps.course_live.views import CourseLiveConfigurationView, CourseLiveProvidersView
from openedx.core.djangoapps.course_live.views import (
CourseLiveConfigurationView,
CourseLiveIframeView,
CourseLiveProvidersView
)
urlpatterns = [
re_path(fr'^course/{settings.COURSE_ID_PATTERN}/?$',
CourseLiveConfigurationView.as_view(), name='course_live'),
re_path(fr'^providers/{settings.COURSE_ID_PATTERN}/?$',
CourseLiveProvidersView.as_view(), name='live_providers'),
re_path(fr'^iframe/{settings.COURSE_ID_PATTERN}/?$',
CourseLiveIframeView.as_view(), name='live_iframe'),
]

View File

@@ -7,13 +7,17 @@ import edx_api_doc_tools as apidocs
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from lti_consumer.api import get_lti_pii_sharing_state_for_course
from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions, status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from common.djangoapps.util.views import ensure_valid_course_key
from openedx.core.djangoapps.course_live.permissions import IsStaffOrInstructor
from lms.djangoapps.courseware.courses import get_course_with_access
from openedx.core.djangoapps.course_live.permissions import IsEnrolledOrStaff, IsStaffOrInstructor
from openedx.core.djangoapps.course_live.tab import CourseLiveTab
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from ...lib.api.view_utils import verify_course_exists
@@ -207,3 +211,70 @@ class CourseLiveProvidersView(APIView):
"available": AVAILABLE_PROVIDERS
}
}
class CourseLiveIframeView(APIView):
"""
A view for retrieving course live iFrame.
Path: ``api/course_live/iframe/{course_id}/``
Accepts: [GET]
------------------------------------------------------------------------------------
GET
------------------------------------------------------------------------------------
**Returns**
* 200: OK - Contains a course live zoom iframe.
* 401: The requesting user is not authenticated.
* 403: The requesting user lacks access to the course.
* 404: The requested course does not exist.
**Response**
In the case of a 200 response code, the response will be iframe HTML.
**Example**
{
"iframe": "
<iframe
id='lti-tab-embed'
style='width: 100%; min-height: 800px; border: none'
srcdoc='{srcdoc}'
>
</iframe>
",
}
"""
authentication_classes = (
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser
)
permission_classes = (permissions.IsAuthenticated, IsEnrolledOrStaff)
@ensure_valid_course_key
@verify_course_exists()
def get(self, request, course_id: str, **_kwargs) -> Response:
"""
Handle HTTP/GET requests
"""
course_key = CourseKey.from_string(course_id)
course_live_tab = CourseLiveTab({})
course = get_course_with_access(request.user, 'load', course_key)
if not course_live_tab.is_enabled(course, request.user):
error_data = {
"developer_message": "Course live is not enabled for this course."
}
return Response(error_data, status=status.HTTP_200_OK)
iframe = course_live_tab.render_to_fragment(request, course)
data = {
"iframe": iframe.content
}
return Response(data, status=status.HTTP_200_OK)