Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: configure zoom live conference calls settings if exists at organization level #415

Merged
merged 2 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions openedx/core/djangoapps/course_live/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,15 @@ class CourseLiveConfig(AppConfig):
PluginURLs.REGEX: r'^api/course_live/',
PluginURLs.RELATIVE_PATH: 'urls',
},
'settings_config': {
'lms.djangoapp': {
'common': {'relative_path': 'settings.common'},
'production': {'relative_path': 'settings.production'},
},
'cms.djangoapp': {
'common': {'relative_path': 'settings.common'},
'production': {'relative_path': 'settings.production'},
},
},
}
}
38 changes: 36 additions & 2 deletions openedx/core/djangoapps/course_live/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def has_valid_global_keys(self) -> bool:
raise NotImplementedError()


class Zoom(LiveProvider):
class Zoom(LiveProvider, HasGlobalCredentials):
"""
Zoom LTI PRO live provider
"""
Expand All @@ -92,9 +92,43 @@ class Zoom(LiveProvider):
]

@property
def is_enabled(self):
def has_free_tier(self) -> bool:
"""
Check if free tier is enabled by checking for valid keys
"""
return self.has_valid_global_keys()

@property
def is_enabled(self) -> bool:
return True

@staticmethod
def get_global_keys() -> Dict:
"""
Get keys from settings
"""
try:
COURSE_LIVE_GLOBAL_CREDENTIALS = {
Faraz32123 marked this conversation as resolved.
Show resolved Hide resolved
"KEY": settings.ZOOM_BUTTON_GLOBAL_KEY,
"SECRET": settings.ZOOM_BUTTON_GLOBAL_SECRET,
"URL": settings.ZOOM_BUTTON_GLOBAL_URL,
}
return COURSE_LIVE_GLOBAL_CREDENTIALS
except AttributeError:
return {}

def has_valid_global_keys(self) -> bool:
"""
Check if keys are valid and not None
"""
credentials = self.get_global_keys()
if credentials:
self.key = credentials.get('KEY')
self.secret = credentials.get('SECRET')
self.url = credentials.get('URL')
return bool(self.key and self.secret and self.url)
return False


class BigBlueButton(LiveProvider, HasGlobalCredentials):
"""
Expand Down
Empty file.
19 changes: 19 additions & 0 deletions openedx/core/djangoapps/course_live/settings/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Common settings for Live Video Conferencing Tools.
"""


def plugin_settings(settings):
"""
Common settings for Live Video Conferencing Tools
Set these variables in the Tutor Config or lms.yml for local testing
ZOOM_BUTTON_GLOBAL_KEY: <ZOOM_BUTTON_GLOBAL_KEY>
ZOOM_BUTTON_GLOBAL_SECRET: <ZOOM_BUTTON_GLOBAL_SECRET>
ZOOM_BUTTON_GLOBAL_URL: <ZOOM_BUTTON_GLOBAL_URL>
ZOOM_INSTRUCTOR_EMAIL: <ZOOM_INSTRUCTOR_EMAIL>
"""
# zoom settings
settings.ZOOM_BUTTON_GLOBAL_KEY = ""
settings.ZOOM_BUTTON_GLOBAL_SECRET = ""
settings.ZOOM_BUTTON_GLOBAL_URL = ""
settings.ZOOM_INSTRUCTOR_EMAIL = ""
23 changes: 23 additions & 0 deletions openedx/core/djangoapps/course_live/settings/production.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Production settings for Live Video Conferencing Tools.
"""


def plugin_settings(settings):
"""
Production settings for Live Video Conferencing Tools
"""

# zoom settings
settings.ZOOM_BUTTON_GLOBAL_KEY = settings.ENV_TOKENS.get(
"ZOOM_BUTTON_GLOBAL_KEY", settings.ZOOM_BUTTON_GLOBAL_KEY
)
settings.ZOOM_BUTTON_GLOBAL_SECRET = settings.ENV_TOKENS.get(
"ZOOM_BUTTON_GLOBAL_SECRET", settings.ZOOM_BUTTON_GLOBAL_SECRET
)
settings.ZOOM_BUTTON_GLOBAL_URL = settings.ENV_TOKENS.get(
"ZOOM_BUTTON_GLOBAL_URL", settings.ZOOM_BUTTON_GLOBAL_URL
)
settings.ZOOM_INSTRUCTOR_EMAIL = settings.ENV_TOKENS.get(
"ZOOM_INSTRUCTOR_EMAIL", settings.ZOOM_INSTRUCTOR_EMAIL
)
5 changes: 4 additions & 1 deletion openedx/core/djangoapps/course_live/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from openedx.core.djangoapps.course_live.views import (
CourseLiveConfigurationView,
CourseLiveIframeView,
CourseLiveProvidersView
CourseLiveProvidersView,
CourseLiveZoomView,
)

urlpatterns = [
Expand All @@ -19,4 +20,6 @@
CourseLiveProvidersView.as_view(), name='live_providers'),
re_path(fr'^iframe/{settings.COURSE_ID_PATTERN}/?$',
CourseLiveIframeView.as_view(), name='live_iframe'),
re_path(rf"^configure_zoom/{settings.COURSE_ID_PATTERN}/?$",
CourseLiveZoomView.as_view(), name="zoom"),
]
108 changes: 105 additions & 3 deletions openedx/core/djangoapps/course_live/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
"""
from typing import Dict

from django.conf import settings
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 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
Expand All @@ -25,6 +28,27 @@
from .serializers import CourseLiveConfigurationSerializer


def is_zoom_creds_global(serialized_data):
"""
returns True or False if zoom creds are global or not respoectively
"""
if serialized_data.get("provider_type", "") == "zoom":
key = serialized_data.get("lti_configuration", {}).get("lti_1p1_client_key", "")
url = serialized_data.get("lti_configuration", {}).get("lti_1p1_launch_url", "")
email = (
serialized_data.get("lti_configuration", {})
.get("lti_config", "")
.get("additional_parameters", {})
.get("custom_instructor_email", "")
)
global_key = settings.ZOOM_BUTTON_GLOBAL_KEY
global_url = settings.ZOOM_BUTTON_GLOBAL_URL
global_email = settings.ZOOM_INSTRUCTOR_EMAIL
if key == global_key and url == global_url and email == global_email:
return True
return False


class CourseLiveConfigurationView(APIView):
"""
View for configuring CourseLive settings.
Expand Down Expand Up @@ -62,8 +86,12 @@ def get(self, request: Request, course_id: str) -> Response:
"pii_sharing_allowed": get_lti_pii_sharing_state_for_course(course_id),
"course_id": course_id
})
serialized_data = serializer.data
serialized_data["global_zoom_creds_enabled"] = is_zoom_creds_global(
serialized_data
)

return Response(serializer.data)
return Response(serialized_data)

@apidocs.schema(
parameters=[
Expand Down Expand Up @@ -136,7 +164,12 @@ def post(self, request, course_id: str) -> Response:
if not serializer.is_valid():
raise ValidationError(serializer.errors)
serializer.save()
return Response(serializer.data)
serialized_data = serializer.data
serialized_data["global_zoom_creds_enabled"] = is_zoom_creds_global(
serialized_data
)

return Response(serialized_data)


class CourseLiveProvidersView(APIView):
Expand Down Expand Up @@ -277,3 +310,72 @@ def get(self, request, course_id: str, **_kwargs) -> Response:
"iframe": iframe.content
}
return Response(data, status=status.HTTP_200_OK)


class CourseLiveZoomView(APIView):
Faraz32123 marked this conversation as resolved.
Show resolved Hide resolved
"""
View for configuring Zoom Global Credentials settings.
"""
@ensure_valid_course_key
@verify_course_exists()
def post(self, request, course_id: str) -> Response:
"""
Handle HTTP/POST requests
"""
request.data["enabled"] = True
request.data["lti_configuration"] = {
"lti_1p1_client_key": settings.ZOOM_BUTTON_GLOBAL_KEY,
"lti_1p1_client_secret": settings.ZOOM_BUTTON_GLOBAL_SECRET,
"lti_1p1_launch_url": settings.ZOOM_BUTTON_GLOBAL_URL,
"lti_config": {
"additional_parameters": {
"custom_instructor_email": settings.ZOOM_INSTRUCTOR_EMAIL,
}
},
"version": "lti_1p1",
}
request.data["provider_type"] = "zoom"
request.data["pii_sharing_allowed"] = True
request.data["free_tier"] = False

pii_sharing_allowed = get_lti_pii_sharing_state_for_course(course_id)
provider = (
ProviderManager()
.get_enabled_providers()
.get(request.data.get("provider_type"))
)

if not pii_sharing_allowed and provider.requires_pii_sharing():
return Response(
{
"pii_sharing_allowed": pii_sharing_allowed,
"message": "PII sharing is not allowed on this course",
}
)
if (
provider
and not provider.additional_parameters
and request.data.get("lti_configuration", False)
):
# Add empty lti config if none is provided in case additional params are not required
request.data["lti_configuration"]["lti_config"] = {
"additional_parameters": {}
}
configuration = CourseLiveConfiguration.objects.filter(
course_key=course_id
).last()
serializer = CourseLiveConfigurationSerializer(
configuration,
data=request.data,
context={
"pii_sharing_allowed": pii_sharing_allowed,
"course_id": course_id,
"provider": provider,
},
)
if not serializer.is_valid():
raise ValidationError(serializer.errors)
serializer.save()
serialized_data = serializer.data
serialized_data["global_zoom_creds_enabled"] = True
return Response(serialized_data)