From 01425c0baf8167afeda39d8ddb0844f390086417 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 21 Aug 2015 16:55:37 -0400 Subject: [PATCH 1/6] bump version of edx-proctoring --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index dd67c1b95d..907a747c70 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -57,7 +57,7 @@ git+https://github.com/edx/ecommerce-api-client.git@1.1.0#egg=ecommerce-api-clie -e git+https://github.com/edx/edx-user-state-client.git@30c0ad4b9f57f8d48d6943eb585ec8a9205f4469#egg=edx-user-state-client -e git+https://github.com/edx/edx-organizations.git@release-2015-08-03#egg=edx-organizations -git+https://github.com/edx/edx-proctoring.git@0.7.1#egg=edx-proctoring==0.7.1 +git+https://github.com/edx/edx-proctoring.git@0.7.2#egg=edx-proctoring==0.7.2 # Third Party XBlocks -e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga From 88a038aa1ee8f7115f1da9d38034b098a7ec8863 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 21 Aug 2015 18:08:13 +0500 Subject: [PATCH 2/6] Fixed email parm function for CCX email modified: lms/djangoapps/instructor/tests/test_enrollment.py --- lms/djangoapps/ccx/views.py | 2 +- lms/djangoapps/instructor/enrollment.py | 14 ++- .../instructor/tests/test_enrollment.py | 109 +++++++++++++++++- .../emails/enroll_email_enrolledmessage.txt | 2 +- .../emails/enroll_email_enrolledsubject.txt | 2 +- 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/ccx/views.py b/lms/djangoapps/ccx/views.py index 931ba33b08..f34bd8acb4 100644 --- a/lms/djangoapps/ccx/views.py +++ b/lms/djangoapps/ccx/views.py @@ -410,7 +410,7 @@ def ccx_invite(request, course, ccx=None): try: validate_email(email) course_key = CCXLocator.from_course_locator(course.id, ccx.id) - email_params = get_email_params(course, auto_enroll) + email_params = get_email_params(course, auto_enroll, course_key=course_key, display_name=ccx.display_name) if action == 'Enroll': enroll_email( course_key, diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index ebfea8437a..305ee8b636 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -261,7 +261,7 @@ def _reset_module_attempts(studentmodule): studentmodule.save() -def get_email_params(course, auto_enroll, secure=True): +def get_email_params(course, auto_enroll, secure=True, course_key=None, display_name=None): """ Generate parameters used when parsing email templates. @@ -270,6 +270,8 @@ def get_email_params(course, auto_enroll, secure=True): """ protocol = 'https' if secure else 'http' + course_key = course_key or course.id.to_deprecated_string() + display_name = display_name or course.display_name_with_default stripped_site_name = microsite.get_value( 'SITE_NAME', @@ -285,7 +287,7 @@ def get_email_params(course, auto_enroll, secure=True): course_url = u'{proto}://{site}{path}'.format( proto=protocol, site=stripped_site_name, - path=reverse('course_root', kwargs={'course_id': course.id.to_deprecated_string()}) + path=reverse('course_root', kwargs={'course_id': course_key}) ) # We can't get the url to the course's About page if the marketing site is enabled. @@ -294,7 +296,7 @@ def get_email_params(course, auto_enroll, secure=True): course_about_url = u'{proto}://{site}{path}'.format( proto=protocol, site=stripped_site_name, - path=reverse('about_course', kwargs={'course_id': course.id.to_deprecated_string()}) + path=reverse('about_course', kwargs={'course_id': course_key}) ) is_shib_course = uses_shib(course) @@ -304,6 +306,7 @@ def get_email_params(course, auto_enroll, secure=True): 'site_name': stripped_site_name, 'registration_url': registration_url, 'course': course, + 'display_name': display_name, 'auto_enroll': auto_enroll, 'course_url': course_url, 'course_about_url': course_about_url, @@ -321,6 +324,7 @@ def send_mail_to_student(student, param_dict, language=None): [ `site_name`: name given to edX instance (a `str`) `registration_url`: url for registration (a `str`) + `display_name` : display name of a course (a `str`) `course_id`: id of course (a `str`) `auto_enroll`: user input option (a `str`) `course_url`: url of course (a `str`) @@ -338,8 +342,8 @@ def send_mail_to_student(student, param_dict, language=None): """ # add some helpers and microconfig subsitutions - if 'course' in param_dict: - param_dict['course_name'] = param_dict['course'].display_name_with_default + if 'display_name' in param_dict: + param_dict['course_name'] = param_dict['display_name'] param_dict['site_name'] = microsite.get_value( 'SITE_NAME', diff --git a/lms/djangoapps/instructor/tests/test_enrollment.py b/lms/djangoapps/instructor/tests/test_enrollment.py index 1a6e3d268e..02b8dca271 100644 --- a/lms/djangoapps/instructor/tests/test_enrollment.py +++ b/lms/djangoapps/instructor/tests/test_enrollment.py @@ -5,6 +5,7 @@ Unit tests for instructor.enrollment methods. import json import mock +from mock import patch from abc import ABCMeta from courseware.models import StudentModule from django.conf import settings @@ -12,11 +13,17 @@ from django.test import TestCase from django.utils.translation import get_language from django.utils.translation import override as override_language from nose.plugins.attrib import attr +from ccx_keys.locator import CCXLocator from student.tests.factories import UserFactory from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from ccx.tests.factories import CcxFactory from student.models import CourseEnrollment, CourseEnrollmentAllowed +from student.roles import CourseCcxCoachRole # pylint: disable=import-error +from student.tests.factories import ( # pylint: disable=import-error + AdminFactory +) from instructor.enrollment import ( EmailEnrollmentState, enroll_email, @@ -30,7 +37,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey from submissions import api as sub_api from student.models import anonymous_id_for_user -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE @attr('shard_1') @@ -566,6 +573,53 @@ class TestSendBetaRoleEmail(TestCase): send_beta_role_email(bad_action, self.user, self.email_params) +@attr('shard_1') +class TestGetEmailParamsCCX(ModuleStoreTestCase): + """ + Test what URLs the function get_email_params for CCX student enrollment. + """ + + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + + @patch.dict('django.conf.settings.FEATURES', {'CUSTOM_COURSES_EDX': True}) + def setUp(self): + super(TestGetEmailParamsCCX, self).setUp() + + self.course = CourseFactory.create() + self.coach = AdminFactory.create() + role = CourseCcxCoachRole(self.course.id) + role.add_users(self.coach) + self.ccx = CcxFactory(course_id=self.course.id, coach=self.coach) + self.course_key = CCXLocator.from_course_locator(self.course.id, self.ccx.id) + # Explicitly construct what we expect the course URLs to be + site = settings.SITE_NAME + self.course_url = u'https://{}/courses/{}/'.format( + site, + self.course_key + ) + self.course_about_url = self.course_url + 'about' + self.registration_url = u'https://{}/register'.format( + site, + ) + + @patch.dict('django.conf.settings.FEATURES', {'CUSTOM_COURSES_EDX': True}) + def test_ccx_enrollment_email_params(self): + # For a CCX, what do we expect to get for the URLs? + # Also make sure `auto_enroll` is properly passed through. + result = get_email_params( + self.course, + True, + course_key=self.course_key, + display_name=self.ccx.display_name + ) + + self.assertEqual(result['display_name'], self.ccx.display_name) + self.assertEqual(result['auto_enroll'], True) + self.assertEqual(result['course_about_url'], self.course_about_url) + self.assertEqual(result['registration_url'], self.registration_url) + self.assertEqual(result['course_url'], self.course_url) + + @attr('shard_1') class TestGetEmailParams(ModuleStoreTestCase): """ @@ -615,13 +669,18 @@ class TestGetEmailParams(ModuleStoreTestCase): class TestRenderMessageToString(ModuleStoreTestCase): """ Test that email templates can be rendered in a language chosen manually. + Test CCX enrollmet email. """ + MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + def setUp(self): super(TestRenderMessageToString, self).setUp() self.subject_template = 'emails/enroll_email_allowedsubject.txt' self.message_template = 'emails/enroll_email_allowedmessage.txt' self.course = CourseFactory.create() + self.course_key = None + self.ccx = None def get_email_params(self): """ @@ -633,6 +692,27 @@ class TestRenderMessageToString(ModuleStoreTestCase): return email_params + def get_email_params_ccx(self): + """ + Returns a dictionary of parameters used to render an email for CCX. + """ + coach = AdminFactory.create() + role = CourseCcxCoachRole(self.course.id) + role.add_users(coach) + self.ccx = CcxFactory(course_id=self.course.id, coach=coach) + self.course_key = CCXLocator.from_course_locator(self.course.id, self.ccx.id) + + email_params = get_email_params( + self.course, + True, + course_key=self.course_key, + display_name=self.ccx.display_name + ) + email_params["email_address"] = "user@example.com" + email_params["full_name"] = "Jean Reno" + + return email_params + def get_subject_and_message(self, language): """ Returns the subject and message rendered in the specified language. @@ -644,6 +724,18 @@ class TestRenderMessageToString(ModuleStoreTestCase): language=language ) + def get_subject_and_message_ccx(self): + """ + Returns the subject and message rendered in the specified language for CCX. + """ + subject_template = 'emails/enroll_email_enrolledsubject.txt' + message_template = 'emails/enroll_email_enrolledmessage.txt' + return render_message_to_string( + subject_template, + message_template, + self.get_email_params_ccx() + ) + def test_subject_and_message_translation(self): subject, message = self.get_subject_and_message('fr') language_after_rendering = get_language() @@ -658,3 +750,18 @@ class TestRenderMessageToString(ModuleStoreTestCase): subject, message = self.get_subject_and_message(None) self.assertIn("You have been", subject) self.assertIn("You have been", message) + + @patch.dict('django.conf.settings.FEATURES', {'CUSTOM_COURSES_EDX': True}) + def test_render_message_ccx(self): + """ + Test email template renders for CCX. + """ + subject, message = self.get_subject_and_message_ccx() + self.assertIn(self.ccx.display_name, subject) + self.assertIn(self.ccx.display_name, message) + site = settings.SITE_NAME + course_url = u'https://{}/courses/{}/'.format( + site, + self.course_key + ) + self.assertIn(course_url, message) diff --git a/lms/templates/emails/enroll_email_enrolledmessage.txt b/lms/templates/emails/enroll_email_enrolledmessage.txt index a0a817473e..2fe1bcb236 100644 --- a/lms/templates/emails/enroll_email_enrolledmessage.txt +++ b/lms/templates/emails/enroll_email_enrolledmessage.txt @@ -5,7 +5,7 @@ ${_("Dear {full_name}").format(full_name=full_name)} ${_("You have been enrolled in {course_name} at {site_name} by a member " "of the course staff. The course should now appear on your {site_name} " "dashboard.").format( - course_name=course.display_name_with_default, + course_name=display_name or course.display_name_with_default, site_name=site_name )} diff --git a/lms/templates/emails/enroll_email_enrolledsubject.txt b/lms/templates/emails/enroll_email_enrolledsubject.txt index f13675f99a..accb21886e 100644 --- a/lms/templates/emails/enroll_email_enrolledsubject.txt +++ b/lms/templates/emails/enroll_email_enrolledsubject.txt @@ -1,5 +1,5 @@ <%! from django.utils.translation import ugettext as _ %> ${_("You have been enrolled in {course_name}").format( - course_name=course.display_name_with_default + course_name=display_name or course.display_name_with_default )} \ No newline at end of file From 28368a0d71f699bb201df19714739ce439bd0d1f Mon Sep 17 00:00:00 2001 From: Fred Smith Date: Tue, 25 Aug 2015 09:29:17 -0400 Subject: [PATCH 3/6] DEVOPS-2620 - get setting from environment for DISPLAY_ANALYTICS_ENROLLMENTS --- lms/envs/aws.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index ffc1311480..f1311f6248 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -463,6 +463,7 @@ ANALYTICS_DATA_TOKEN = AUTH_TOKENS.get("ANALYTICS_DATA_TOKEN", ANALYTICS_DATA_TO # Analytics Dashboard ANALYTICS_DASHBOARD_URL = ENV_TOKENS.get("ANALYTICS_DASHBOARD_URL", ANALYTICS_DASHBOARD_URL) ANALYTICS_DASHBOARD_NAME = ENV_TOKENS.get("ANALYTICS_DASHBOARD_NAME", PLATFORM_NAME + " Insights") +DISPLAY_ANALYTICS_ENROLLMENTS = ENV_TOKENS.get("DISPLAY_ANALYTICS_ENROLLMENTS", True) # Zendesk ZENDESK_USER = AUTH_TOKENS.get("ZENDESK_USER") From 79a075328fd1048525cd2205a6049969fd3ba921 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 13 Aug 2015 11:17:54 -0700 Subject: [PATCH 4/6] Allow configuring sensitive third_party_auth settings via lms.auth.json --- common/djangoapps/third_party_auth/admin.py | 20 ++++++-- common/djangoapps/third_party_auth/models.py | 48 +++++++++++++++---- .../third_party_auth/tests/test_provider.py | 14 +++++- .../third_party_auth/tests/test_views.py | 37 ++++++++++++-- lms/envs/aws.py | 9 ++++ 5 files changed, 111 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/third_party_auth/admin.py b/common/djangoapps/third_party_auth/admin.py index 8495ef3a2b..a949f3fcb0 100644 --- a/common/djangoapps/third_party_auth/admin.py +++ b/common/djangoapps/third_party_auth/admin.py @@ -9,7 +9,17 @@ from config_models.admin import ConfigurationModelAdmin, KeyedConfigurationModel from .models import OAuth2ProviderConfig, SAMLProviderConfig, SAMLConfiguration, SAMLProviderData from .tasks import fetch_saml_metadata -admin.site.register(OAuth2ProviderConfig, KeyedConfigurationModelAdmin) + +class OAuth2ProviderConfigAdmin(KeyedConfigurationModelAdmin): + """ Django Admin class for OAuth2ProviderConfig """ + def get_list_display(self, request): + """ Don't show every single field in the admin change list """ + return ( + 'name', 'enabled', 'backend_name', 'secondary', 'skip_registration_form', + 'skip_email_verification', 'change_date', 'changed_by', 'edit_link', + ) + +admin.site.register(OAuth2ProviderConfig, OAuth2ProviderConfigAdmin) class SAMLProviderConfigAdmin(KeyedConfigurationModelAdmin): @@ -55,10 +65,12 @@ class SAMLConfigurationAdmin(ConfigurationModelAdmin): def key_summary(self, inst): """ Short summary of the key pairs configured """ - if not inst.public_key or not inst.private_key: + public_key = inst.get_setting('SP_PUBLIC_CERT') + private_key = inst.get_setting('SP_PRIVATE_KEY') + if not public_key or not private_key: return u'Key pair incomplete/missing' - pub1, pub2 = inst.public_key[0:10], inst.public_key[-10:] - priv1, priv2 = inst.private_key[0:10], inst.private_key[-10:] + pub1, pub2 = public_key[0:10], public_key[-10:] + priv1, priv2 = private_key[0:10], private_key[-10:] return u'Public: {}…{}
Private: {}…{}'.format(pub1, pub2, priv1, priv2) key_summary.allow_tags = True diff --git a/common/djangoapps/third_party_auth/models.py b/common/djangoapps/third_party_auth/models.py index 12224c7bbf..fcabea6c89 100644 --- a/common/djangoapps/third_party_auth/models.py +++ b/common/djangoapps/third_party_auth/models.py @@ -178,7 +178,16 @@ class OAuth2ProviderConfig(ProviderConfig): ) ) key = models.TextField(blank=True, verbose_name="Client ID") - secret = models.TextField(blank=True, verbose_name="Client Secret") + secret = models.TextField( + blank=True, + verbose_name="Client Secret", + help_text=( + 'For increased security, you can avoid storing this in your database by leaving ' + ' this field blank and setting ' + 'SOCIAL_AUTH_OAUTH_SECRETS = {"(backend name)": "secret", ...} ' + 'in your instance\'s Django settings (or lms.auth.json)' + ) + ) other_settings = models.TextField(blank=True, help_text="Optional JSON object with advanced settings, if any.") class Meta(object): # pylint: disable=missing-docstring @@ -192,8 +201,13 @@ class OAuth2ProviderConfig(ProviderConfig): def get_setting(self, name): """ Get the value of a setting, or raise KeyError """ - if name in ("KEY", "SECRET"): - return getattr(self, name.lower()) + if name == "KEY": + return self.key + if name == "SECRET": + if self.secret: + return self.secret + # To allow instances to avoid storing secrets in the DB, the secret can also be set via Django: + return getattr(settings, 'SOCIAL_AUTH_OAUTH_SECRETS', {}).get(self.backend_name, '') if self.other_settings: other_settings = json.loads(self.other_settings) assert isinstance(other_settings, dict), "other_settings should be a JSON object (dictionary)" @@ -310,10 +324,22 @@ class SAMLConfiguration(ConfigurationModel): help_text=( 'To generate a key pair as two files, run ' '"openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key". ' - 'Paste the contents of saml.key here.' - ) + 'Paste the contents of saml.key here. ' + 'For increased security, you can avoid storing this in your database by leaving ' + 'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PRIVATE_KEY setting ' + 'in your instance\'s Django settings (or lms.auth.json).' + ), + blank=True, + ) + public_key = models.TextField( + help_text=( + 'Public key certificate. ' + 'For increased security, you can avoid storing this in your database by leaving ' + 'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PUBLIC_CERT setting ' + 'in your instance\'s Django settings (or lms.auth.json).' + ), + blank=True, ) - public_key = models.TextField(help_text="Public key certificate.") entity_id = models.CharField(max_length=255, default="http://saml.example.com", verbose_name="Entity ID") org_info_str = models.TextField( verbose_name="Organization Info", @@ -360,9 +386,15 @@ class SAMLConfiguration(ConfigurationModel): if name == "SP_ENTITY_ID": return self.entity_id if name == "SP_PUBLIC_CERT": - return self.public_key + if self.public_key: + return self.public_key + # To allow instances to avoid storing keys in the DB, the key pair can also be set via Django: + return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '') if name == "SP_PRIVATE_KEY": - return self.private_key + if self.private_key: + return self.private_key + # To allow instances to avoid storing keys in the DB, the private key can also be set via Django: + return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '') other_config = json.loads(self.other_config_str) if name in ("TECHNICAL_CONTACT", "SUPPORT_CONTACT"): contact = { diff --git a/common/djangoapps/third_party_auth/tests/test_provider.py b/common/djangoapps/third_party_auth/tests/test_provider.py index 06a09b7b4f..ff67df5d0d 100644 --- a/common/djangoapps/third_party_auth/tests/test_provider.py +++ b/common/djangoapps/third_party_auth/tests/test_provider.py @@ -23,7 +23,7 @@ class RegistryTest(testutil.TestCase): enabled_providers = provider.Registry.enabled() self.assertEqual(len(enabled_providers), 1) self.assertEqual(enabled_providers[0].name, "Google") - self.assertEqual(enabled_providers[0].secret, "opensesame") + self.assertEqual(enabled_providers[0].get_setting("SECRET"), "opensesame") self.configure_google_provider(enabled=False) enabled_providers = provider.Registry.enabled() @@ -32,7 +32,17 @@ class RegistryTest(testutil.TestCase): self.configure_google_provider(enabled=True, secret="alohomora") enabled_providers = provider.Registry.enabled() self.assertEqual(len(enabled_providers), 1) - self.assertEqual(enabled_providers[0].secret, "alohomora") + self.assertEqual(enabled_providers[0].get_setting("SECRET"), "alohomora") + + def test_secure_configuration(self): + """ Test that some sensitive values can be configured via Django settings """ + self.configure_google_provider(enabled=True, secret="") + enabled_providers = provider.Registry.enabled() + self.assertEqual(len(enabled_providers), 1) + self.assertEqual(enabled_providers[0].name, "Google") + self.assertEqual(enabled_providers[0].get_setting("SECRET"), "") + with self.settings(SOCIAL_AUTH_OAUTH_SECRETS={'google-oauth2': 'secret42'}): + self.assertEqual(enabled_providers[0].get_setting("SECRET"), "secret42") def test_cannot_load_arbitrary_backends(self): """ Test that only backend_names listed in settings.AUTHENTICATION_BACKENDS can be used """ diff --git a/common/djangoapps/third_party_auth/tests/test_views.py b/common/djangoapps/third_party_auth/tests/test_views.py index 583efddb99..3b9122c1a7 100644 --- a/common/djangoapps/third_party_auth/tests/test_views.py +++ b/common/djangoapps/third_party_auth/tests/test_views.py @@ -4,6 +4,7 @@ Test the views served by third_party_auth. # pylint: disable=no-member import ddt from lxml import etree +from onelogin.saml2.errors import OneLogin_Saml2_Error import unittest from .testutil import AUTH_FEATURE_ENABLED, SAMLTestCase @@ -26,8 +27,7 @@ class SAMLMetadataTest(SAMLTestCase): response = self.client.get(self.METADATA_URL) self.assertEqual(response.status_code, 404) - @ddt.data('saml_key', 'saml_key_alt') # Test two slightly different key pair export formats - def test_metadata(self, key_name): + def test_metadata(self): self.enable_saml() doc = self._fetch_metadata() # Check the ACS URL: @@ -62,13 +62,44 @@ class SAMLMetadataTest(SAMLTestCase): support_email="joe@example.com" ) - def test_signed_metadata(self): + @ddt.data( + # Test two slightly different key pair export formats + ('saml_key', 'MIICsDCCAhmgAw'), + ('saml_key_alt', 'MIICWDCCAcGgAw'), + ) + @ddt.unpack + def test_signed_metadata(self, key_name, pub_key_starts_with): self.enable_saml( + private_key=self._get_private_key(key_name), + public_key=self._get_public_key(key_name), other_config_str='{"SECURITY_CONFIG": {"signMetadata": true} }', ) + self._validate_signed_metadata(pub_key_starts_with=pub_key_starts_with) + + def test_secure_key_configuration(self): + """ Test that the SAML private key can be stored in Django settings and not the DB """ + self.enable_saml( + public_key='', + private_key='', + other_config_str='{"SECURITY_CONFIG": {"signMetadata": true} }', + ) + with self.assertRaises(OneLogin_Saml2_Error): + self._fetch_metadata() # OneLogin_Saml2_Error: Cannot sign metadata: missing SP private key. + with self.settings( + SOCIAL_AUTH_SAML_SP_PRIVATE_KEY=self._get_private_key('saml_key'), + SOCIAL_AUTH_SAML_SP_PUBLIC_CERT=self._get_public_key('saml_key'), + ): + self._validate_signed_metadata() + + def _validate_signed_metadata(self, pub_key_starts_with='MIICsDCCAhmgAw'): + """ Fetch the SAML metadata and do some validation """ doc = self._fetch_metadata() sig_node = doc.find(".//{}".format(etree.QName(XMLDSIG_XML_NS, 'SignatureValue'))) self.assertIsNotNone(sig_node) + # Check that the right public key was used: + pub_key_node = doc.find(".//{}".format(etree.QName(XMLDSIG_XML_NS, 'X509Certificate'))) + self.assertIsNotNone(pub_key_node) + self.assertIn(pub_key_starts_with, pub_key_node.text) def _fetch_metadata(self): """ Fetch and parse the metadata XML at self.METADATA_URL """ diff --git a/lms/envs/aws.py b/lms/envs/aws.py index f1311f6248..7f2b8cc39f 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -559,6 +559,15 @@ if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): # The reduced session expiry time during the third party login pipeline. (Value in seconds) SOCIAL_AUTH_PIPELINE_TIMEOUT = ENV_TOKENS.get('SOCIAL_AUTH_PIPELINE_TIMEOUT', 600) + # Most provider configuration is done via ConfigurationModels but for a few sensitive values + # we allow configuration via AUTH_TOKENS instead (optionally). + # The SAML private/public key values do not need the delimiter lines (such as + # "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----" etc.) but they may be included + # if you want (though it's easier to format the key values as JSON without the delimiters). + SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '') + SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '') + SOCIAL_AUTH_OAUTH_SECRETS = AUTH_TOKENS.get('SOCIAL_AUTH_OAUTH_SECRETS', {}) + # third_party_auth config moved to ConfigurationModels. This is for data migration only: THIRD_PARTY_AUTH_OLD_CONFIG = AUTH_TOKENS.get('THIRD_PARTY_AUTH', None) From 96fc306337cba13c9e6d8c2300aa03aeacd2779e Mon Sep 17 00:00:00 2001 From: Chris Rodriguez Date: Fri, 21 Aug 2015 10:17:40 -0400 Subject: [PATCH 5/6] Fixes modal color --- lms/static/sass/shared/_modal.scss | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index fd6da29cb5..f3aacb5e5c 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -12,15 +12,15 @@ .modal { @extend %ui-depth1; - background: $gray-d2; - border-radius: 3px; - box-shadow: 0 0px 5px 0 $shadow-d1; - color: $white; display: none; + position: absolute; left: 50%; padding: 8px; - position: absolute; width: grid-width(5); + border-radius: 3px; + box-shadow: 0 0px 5px 0 $shadow-d1; + background: $gray-d2; + color: $base-font-color; &.video-modal { left: 50%; @@ -62,6 +62,11 @@ padding-bottom: ($baseline/2); position: relative; + p { + font-size: .9em; + line-height: 1.4; + } + header { @extend %ui-depth1; margin-bottom: ($baseline*1.5); From 16ac5801c90b50e14b70eda3629499d15893ba85 Mon Sep 17 00:00:00 2001 From: Fred Smith Date: Wed, 26 Aug 2015 11:10:21 -0400 Subject: [PATCH 6/6] =?UTF-8?q?Revert=20"DEVOPS-2620=20-=20get=20setting?= =?UTF-8?q?=20from=20environment=20for=20DISPLAY=5FANALYTICS=5FENRO?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lms/envs/aws.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 7f2b8cc39f..cb0c4965d5 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -463,7 +463,6 @@ ANALYTICS_DATA_TOKEN = AUTH_TOKENS.get("ANALYTICS_DATA_TOKEN", ANALYTICS_DATA_TO # Analytics Dashboard ANALYTICS_DASHBOARD_URL = ENV_TOKENS.get("ANALYTICS_DASHBOARD_URL", ANALYTICS_DASHBOARD_URL) ANALYTICS_DASHBOARD_NAME = ENV_TOKENS.get("ANALYTICS_DASHBOARD_NAME", PLATFORM_NAME + " Insights") -DISPLAY_ANALYTICS_ENROLLMENTS = ENV_TOKENS.get("DISPLAY_ANALYTICS_ENROLLMENTS", True) # Zendesk ZENDESK_USER = AUTH_TOKENS.get("ZENDESK_USER")