From 67f8b49056e2c8831650988bdee3570d55f1c146 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 8 Feb 2016 17:24:24 -0500 Subject: [PATCH] Fixes for program certificate generation ECOM-3528 --- lms/envs/common.py | 6 ++++++ openedx/core/djangoapps/programs/__init__.py | 3 +++ openedx/core/djangoapps/programs/tasks/v1/tasks.py | 8 ++++++-- .../programs/tasks/v1/tests/test_tasks.py | 12 ++++++++++-- openedx/core/lib/tests/test_token_utils.py | 14 +++++++++----- openedx/core/lib/token_utils.py | 9 +++++++-- 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index be786340a2..c59332e1f1 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1625,6 +1625,12 @@ REQUIRE_JS_PATH_OVERRIDES = [ ] ################################# CELERY ###################################### +# Celery's task autodiscovery won't find tasks nested in a tasks package. +# Tasks are only registered when the module they are defined in is imported. +CELERY_IMPORTS = ( + 'openedx.core.djangoapps.programs.tasks.v1.tasks', +) + # Message configuration CELERY_TASK_SERIALIZER = 'json' diff --git a/openedx/core/djangoapps/programs/__init__.py b/openedx/core/djangoapps/programs/__init__.py index a6287c38bf..3e2626d0bf 100644 --- a/openedx/core/djangoapps/programs/__init__.py +++ b/openedx/core/djangoapps/programs/__init__.py @@ -8,3 +8,6 @@ if and only if the service is deployed in the Open edX installation. To ensure maximum separation of concerns, and a minimum of interdependencies, this package should be kept small, thin, and stateless. """ + +# Register signal handlers +from . import signals diff --git a/openedx/core/djangoapps/programs/tasks/v1/tasks.py b/openedx/core/djangoapps/programs/tasks/v1/tasks.py index 75cc391745..a7781bf79b 100644 --- a/openedx/core/djangoapps/programs/tasks/v1/tasks.py +++ b/openedx/core/djangoapps/programs/tasks/v1/tasks.py @@ -49,7 +49,7 @@ def get_completed_courses(student): """ all_certs = get_certificates_for_user(student.username) return [ - {'course_id': cert['course_key'], 'mode': cert['type']} + {'course_id': unicode(cert['course_key']), 'mode': cert['type']} for cert in all_certs if is_passing_status(cert['status']) ] @@ -108,7 +108,11 @@ def award_program_certificate(client, username, program_id): None """ - client.user_credentials.post({'program_id': program_id, 'username': username}) + client.user_credentials.post({ + 'username': username, + 'credential': {'program_id': program_id}, + 'attributes': [] + }) @task(bind=True, ignore_result=True) diff --git a/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py b/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py index fea3c6b01a..a1ca459791 100644 --- a/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py +++ b/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py @@ -109,7 +109,7 @@ class GetCompletedProgramsTestCase(TestCase): {'course_id': 'test-course-2', 'mode': 'prof-ed'}, ] result = tasks.get_completed_programs(test_client, payload) - self.assertEqual(httpretty.last_request().body, json.dumps({'completed_courses': payload})) + self.assertEqual(json.loads(httpretty.last_request().body), {'completed_courses': payload}) self.assertEqual(result, [1, 2, 3]) @@ -165,12 +165,20 @@ class AwardProgramCertificateTestCase(TestCase): """ test_username = 'test-username' test_client = EdxRestApiClient('http://test-server', jwt='test-token') + httpretty.register_uri( httpretty.POST, 'http://test-server/user_credentials/', ) + tasks.award_program_certificate(test_client, test_username, 123) - self.assertEqual(httpretty.last_request().body, json.dumps({'program_id': 123, 'username': test_username})) + + expected_body = { + 'username': test_username, + 'credential': {'program_id': 123}, + 'attributes': [] + } + self.assertEqual(json.loads(httpretty.last_request().body), expected_body) @ddt.ddt diff --git a/openedx/core/lib/tests/test_token_utils.py b/openedx/core/lib/tests/test_token_utils.py index c2f5ce77f7..662d5fc5dc 100644 --- a/openedx/core/lib/tests/test_token_utils.py +++ b/openedx/core/lib/tests/test_token_utils.py @@ -2,6 +2,7 @@ import calendar import datetime +import ddt from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.test import TestCase @@ -16,6 +17,7 @@ from student.models import anonymous_id_for_user from student.tests.factories import UserFactory, UserProfileFactory +@ddt.ddt class TestIdTokenGeneration(TestCase): """Tests covering ID token generation.""" client_name = 'edx-dummy-client' @@ -24,15 +26,17 @@ class TestIdTokenGeneration(TestCase): super(TestIdTokenGeneration, self).setUp() self.oauth2_client = ClientFactory(name=self.client_name, client_type=CONFIDENTIAL) - self.user = UserFactory() - # Create a profile for the user - self.user_profile = UserProfileFactory(user=self.user) + self.user = UserFactory.build() + self.user.save() @override_settings(OAUTH_OIDC_ISSUER='test-issuer', OAUTH_ID_TOKEN_EXPIRATION=1) @freezegun.freeze_time('2015-01-01 12:00:00') - def test_get_id_token(self): + @ddt.data(True, False) + def test_get_id_token(self, has_profile): """Verify that ID tokens are signed with the correct secret and generated with the correct claims.""" + full_name = UserProfileFactory(user=self.user).name if has_profile else None + token = get_id_token(self.user, self.client_name) payload = jwt.decode( @@ -47,7 +51,7 @@ class TestIdTokenGeneration(TestCase): expected_payload = { 'preferred_username': self.user.username, - 'name': self.user_profile.name, + 'name': full_name, 'email': self.user.email, 'administrator': self.user.is_staff, 'iss': settings.OAUTH_OIDC_ISSUER, diff --git a/openedx/core/lib/token_utils.py b/openedx/core/lib/token_utils.py index 2e845c61fd..4f9f00f406 100644 --- a/openedx/core/lib/token_utils.py +++ b/openedx/core/lib/token_utils.py @@ -41,13 +41,18 @@ def get_id_token(user, client_name): except Client.DoesNotExist: raise ImproperlyConfigured('OAuth2 Client with name [%s] does not exist' % client_name) - user_profile = UserProfile.objects.get(user=user) + try: + # Service users may not have user profiles. + full_name = UserProfile.objects.get(user=user).name + except UserProfile.DoesNotExist: + full_name = None + now = datetime.datetime.utcnow() expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30) payload = { 'preferred_username': user.username, - 'name': user_profile.name, + 'name': full_name, 'email': user.email, 'administrator': user.is_staff, 'iss': settings.OAUTH_OIDC_ISSUER,