Commit 2fbfcfd2 by jsa

Implement an endpoint to get id_tokens for programs.

ECOM-2440
parent 2cfeb34f
"""Programs views for use with Studio.""" """Programs views for use with Studio."""
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse 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.utils.decorators import method_decorator
from django.views.generic import View from django.views.generic import View
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.lib.token_utils import get_id_token
class ProgramAuthoringView(View): class ProgramAuthoringView(View):
...@@ -18,13 +19,6 @@ class ProgramAuthoringView(View): ...@@ -18,13 +19,6 @@ class ProgramAuthoringView(View):
""" """
@method_decorator(login_required) @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): def get(self, request, *args, **kwargs):
"""Populate the template context with values required for the authoring app to run.""" """Populate the template context with values required for the authoring app to run."""
programs_config = ProgramsApiConfig.current() programs_config = ProgramsApiConfig.current()
...@@ -38,3 +32,16 @@ class ProgramAuthoringView(View): ...@@ -38,3 +32,16 @@ class ProgramAuthoringView(View):
}) })
else: else:
raise Http404 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
"""Tests covering the Programs listing on the Studio home.""" """Tests covering the Programs listing on the Studio home."""
import json
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
import httpretty import httpretty
import mock
from oauth2_provider.tests.factories import ClientFactory from oauth2_provider.tests.factories import ClientFactory
from provider.constants import CONFIDENTIAL from provider.constants import CONFIDENTIAL
from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from student.tests.factories import UserFactory 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.""" """Verify Program listing behavior."""
def setUp(self): def setUp(self):
super(TestProgramListing, self).setUp() super(TestProgramListing, self).setUp()
...@@ -70,7 +73,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, ModuleStoreT ...@@ -70,7 +73,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, ModuleStoreT
self.assertIn(program_name, response.content) 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.""" """Verify the behavior of the program authoring app's host view."""
def setUp(self): def setUp(self):
super(TestProgramAuthoringView, self).setUp() super(TestProgramAuthoringView, self).setUp()
...@@ -118,3 +121,43 @@ class TestProgramAuthoringView(ProgramsApiConfigMixin, ModuleStoreTestCase): ...@@ -118,3 +121,43 @@ class TestProgramAuthoringView(ProgramsApiConfigMixin, ModuleStoreTestCase):
student = UserFactory(is_staff=False) student = UserFactory(is_staff=False)
self.client.login(username=student.username, password='test') self.client.login(username=student.username, password='test')
self._assert_status(404) 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")
...@@ -3,7 +3,7 @@ from django.conf.urls import patterns, include, url ...@@ -3,7 +3,7 @@ from django.conf.urls import patterns, include, url
# There is a course creators admin table. # There is a course creators admin table.
from ratelimitbackend import admin from ratelimitbackend import admin
from cms.djangoapps.contentstore.views.program import ProgramAuthoringView from cms.djangoapps.contentstore.views.program import ProgramAuthoringView, ProgramsIdTokenView
admin.autodiscover() admin.autodiscover()
...@@ -185,9 +185,10 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'): ...@@ -185,9 +185,10 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW'):
) )
urlpatterns += ( 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. # 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'), url(r'^program/', ProgramAuthoringView.as_view(), name='programs'),
) )
......
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