diff --git a/cms/djangoapps/contentstore/views/program.py b/cms/djangoapps/contentstore/views/program.py index 8983798612..aa7df2d16a 100644 --- a/cms/djangoapps/contentstore/views/program.py +++ b/cms/djangoapps/contentstore/views/program.py @@ -1,12 +1,13 @@ """Programs views for use with Studio.""" from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse -from django.http import Http404 +from django.http import Http404, JsonResponse from django.utils.decorators import method_decorator from django.views.generic import View from edxmako.shortcuts import render_to_response from openedx.core.djangoapps.programs.models import ProgramsApiConfig +from openedx.core.lib.token_utils import get_id_token class ProgramAuthoringView(View): @@ -18,13 +19,6 @@ class ProgramAuthoringView(View): """ @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - """Relays requests to matching methods. - - Decorated to require login before accessing the authoring app. - """ - return super(ProgramAuthoringView, self).dispatch(*args, **kwargs) - def get(self, request, *args, **kwargs): """Populate the template context with values required for the authoring app to run.""" programs_config = ProgramsApiConfig.current() @@ -38,3 +32,16 @@ class ProgramAuthoringView(View): }) else: raise Http404 + + +class ProgramsIdTokenView(View): + """Provides id tokens to JavaScript clients for use with the Programs API.""" + + @method_decorator(login_required) + def get(self, request, *args, **kwargs): + """Generate and return a token, if the integration is enabled.""" + if ProgramsApiConfig.current().is_studio_tab_enabled: + id_token = get_id_token(request.user, 'programs') + return JsonResponse({'id_token': id_token}) + else: + raise Http404 diff --git a/cms/djangoapps/contentstore/views/tests/test_programs.py b/cms/djangoapps/contentstore/views/tests/test_programs.py index e148109c54..480a9cab01 100644 --- a/cms/djangoapps/contentstore/views/tests/test_programs.py +++ b/cms/djangoapps/contentstore/views/tests/test_programs.py @@ -1,17 +1,20 @@ """Tests covering the Programs listing on the Studio home.""" +import json + from django.conf import settings from django.core.urlresolvers import reverse import httpretty +import mock from oauth2_provider.tests.factories import ClientFactory from provider.constants import CONFIDENTIAL from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin from student.tests.factories import UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase -class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, ModuleStoreTestCase): +class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModuleStoreTestCase): """Verify Program listing behavior.""" def setUp(self): super(TestProgramListing, self).setUp() @@ -70,7 +73,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, ModuleStoreT self.assertIn(program_name, response.content) -class TestProgramAuthoringView(ProgramsApiConfigMixin, ModuleStoreTestCase): +class TestProgramAuthoringView(ProgramsApiConfigMixin, SharedModuleStoreTestCase): """Verify the behavior of the program authoring app's host view.""" def setUp(self): super(TestProgramAuthoringView, self).setUp() @@ -118,3 +121,43 @@ class TestProgramAuthoringView(ProgramsApiConfigMixin, ModuleStoreTestCase): student = UserFactory(is_staff=False) self.client.login(username=student.username, password='test') self._assert_status(404) + + +class TestProgramsIdTokenView(ProgramsApiConfigMixin, SharedModuleStoreTestCase): + """Tests for the programs id_token endpoint.""" + + def setUp(self): + super(TestProgramsIdTokenView, self).setUp() + self.user = UserFactory() + self.client.login(username=self.user.username, password='test') + self.path = reverse('programs_id_token') + + def test_config_disabled(self): + """Ensure the endpoint returns 404 when Programs authoring is disabled.""" + self.create_config(enable_studio_tab=False) + response = self.client.get(self.path) + self.assertEqual(response.status_code, 404) + + def test_not_logged_in(self): + """Ensure the endpoint denies access to unauthenticated users.""" + self.create_config() + self.client.logout() + response = self.client.get(self.path) + self.assertEqual(response.status_code, 302) + self.assertIn(settings.LOGIN_URL, response['Location']) + + @mock.patch('cms.djangoapps.contentstore.views.program.get_id_token', return_value='test-id-token') + def test_config_enabled(self, mock_get_id_token): + """ + Ensure the endpoint responds with a valid JSON payload when authoring + is enabled. + """ + self.create_config() + response = self.client.get(self.path) + self.assertEqual(response.status_code, 200) + payload = json.loads(response.content) + self.assertEqual(payload, {"id_token": "test-id-token"}) + # this comparison is a little long-handed because we need to compare user instances directly + user, client_name = mock_get_id_token.call_args[0] + self.assertEqual(user, self.user) + self.assertEqual(client_name, "programs") diff --git a/cms/urls.py b/cms/urls.py index 2f955e8414..2439987c54 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -3,7 +3,7 @@ from django.conf.urls import patterns, include, url # There is a course creators admin table. from ratelimitbackend import admin -from cms.djangoapps.contentstore.views.program import ProgramAuthoringView +from cms.djangoapps.contentstore.views.program import ProgramAuthoringView, ProgramsIdTokenView admin.autodiscover() @@ -185,9 +185,10 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'): ) urlpatterns += ( + # These views use a configuration model to determine whether or not to + # display the Programs authoring app. If disabled, a 404 is returned. + url(r'^programs/id_token/$', ProgramsIdTokenView.as_view(), name='programs_id_token'), # Drops into the Programs authoring app, which handles its own routing. - # The view uses a configuration model to determine whether or not to - # display the authoring app. If disabled, a 404 is returned. url(r'^program/', ProgramAuthoringView.as_view(), name='programs'), )