Commit fc814aee by Jesse Shapiro

Add data sharing consent features for EnterpriseCustomer

parent 388ed865
......@@ -188,6 +188,19 @@ class AccountCreationForm(forms.Form):
"required": _("To enroll, you must follow the honor code.")
}
)
elif field_name == 'data_sharing_consent':
if field_value == "required":
self.fields[field_name] = TrueField(
error_messages={
"required": _(
"You must consent to data sharing to register."
)
}
)
elif field_value == 'optional':
self.fields[field_name] = forms.BooleanField(
required=False
)
else:
required = field_value == "required"
min_length = 1 if field_name in ("gender", "level_of_education") else 2
......
......@@ -46,6 +46,7 @@ from social.exceptions import AuthException, AuthAlreadyAssociated
from edxmako.shortcuts import render_to_response, render_to_string
from util.enterprise_helpers import data_sharing_consent_requirement_at_login
from course_modes.models import CourseMode
from shoppingcart.api import order_history
from student.models import (
......@@ -1644,6 +1645,10 @@ def create_account_with_params(request, params):
if should_link_with_social_auth or (third_party_auth.is_enabled() and pipeline.running(request)):
params["password"] = pipeline.make_random_password()
# Add a form requirement for data sharing consent if the EnterpriseCustomer
# for the request requires it at login
extra_fields['data_sharing_consent'] = data_sharing_consent_requirement_at_login(request)
# if doing signup for an external authorization, then get email, password, name from the eamap
# don't use the ones from the form, since the user could have hacked those
# unless originally we didn't get a valid email or name from the external auth
......@@ -1740,6 +1745,9 @@ def create_account_with_params(request, params):
if third_party_auth.is_enabled() and pipeline.running(request):
running_pipeline = pipeline.get(request)
third_party_provider = provider.Registry.get_from_pipeline(running_pipeline)
# Store received data sharing consent field values in the pipeline for use
# by any downstream pipeline elements which require them.
running_pipeline['kwargs']['data_sharing_consent'] = form.cleaned_data.get('data_sharing_consent', None)
# Track the user's registration
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
......
......@@ -23,3 +23,34 @@ class ExceptionMiddleware(SocialAuthExceptionMiddleware):
redirect_uri = pipeline.AUTH_DISPATCH_URLS[auth_entry]
return redirect_uri
class PipelineQuarantineMiddleware(object):
"""
Middleware flushes the session if a user agent with a quarantined session
attempts to leave the quarantined set of views.
"""
def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument
"""
Check the session to see if we've quarantined the user to a particular
step of the authentication pipeline; if so, look up which modules the
user is allowed to browse to without breaking the pipeline. If the view
that's been requested is outside those modules, then flush the session.
In general, this middleware should be used in cases where allowing the
user to exit the running pipeline would be undesirable, and where it'd
be better to flush the session state rather than allow it. Pipeline
quarantining is utilized by the Enterprise application to enforce
collection of user consent for sharing data with a linked third-party
authentication provider.
"""
running_pipeline = request.session.get('partial_pipeline')
if not running_pipeline:
return
view_module = view_func.__module__
quarantined_modules = request.session.get('third_party_auth_quarantined_modules', None)
if quarantined_modules is not None and not any(view_module.startswith(mod) for mod in quarantined_modules):
request.session.flush()
......@@ -74,6 +74,7 @@ from django.core.urlresolvers import reverse
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
from social.apps.django_app.default import models
from social.apps.django_app.default.models import UserSocialAuth
from social.exceptions import AuthException
from social.pipeline import partial
from social.pipeline.social_auth import associate_by_email
......@@ -200,6 +201,38 @@ def get(request):
return request.session.get('partial_pipeline')
def get_real_social_auth_object(request):
"""
At times, the pipeline will have a "social" kwarg that contains a dictionary
rather than an actual DB-backed UserSocialAuth object. We need the real thing,
so this method allows us to get that by passing in the relevant request.
"""
running_pipeline = get(request)
if running_pipeline and 'social' in running_pipeline['kwargs']:
social = running_pipeline['kwargs']['social']
if isinstance(social, dict):
social = UserSocialAuth.objects.get(uid=social.get('uid', ''))
return social
def quarantine_session(request, locations):
"""
Set a session variable indicating that the session is restricted
to being used in views contained in the modules listed by string
in the `locations` argument.
Example: ``quarantine_session(request, ('enterprise.views',))``
"""
request.session['third_party_auth_quarantined_modules'] = locations
def lift_quarantine(request):
"""
Remove the session quarantine variable.
"""
request.session.pop('third_party_auth_quarantined_modules', None)
def get_authenticated_user(auth_provider, username, uid):
"""Gets a saved user authenticated by a particular backend.
......
......@@ -10,9 +10,12 @@ If true, it:
b) calls apply_settings(), passing in the Django settings
"""
from util.enterprise_helpers import insert_enterprise_pipeline_elements
_FIELDS_STORED_IN_SESSION = ['auth_entry', 'next']
_MIDDLEWARE_CLASSES = (
'third_party_auth.middleware.ExceptionMiddleware',
'third_party_auth.middleware.PipelineQuarantineMiddleware',
)
_SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/dashboard'
......@@ -37,7 +40,7 @@ def apply_settings(django_settings):
# Inject our customized auth pipeline. All auth backends must work with
# this pipeline.
django_settings.SOCIAL_AUTH_PIPELINE = (
django_settings.SOCIAL_AUTH_PIPELINE = [
'third_party_auth.pipeline.parse_query_params',
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
......@@ -53,7 +56,10 @@ def apply_settings(django_settings):
'social.pipeline.user.user_details',
'third_party_auth.pipeline.set_logged_in_cookies',
'third_party_auth.pipeline.login_analytics',
)
]
# Add enterprise pipeline elements if the enterprise app is installed
insert_enterprise_pipeline_elements(django_settings.SOCIAL_AUTH_PIPELINE)
# Required so that we can use unmodified PSA OAuth2 backends:
django_settings.SOCIAL_AUTH_STRATEGY = 'third_party_auth.strategy.ConfigurationModelStrategy'
......
......@@ -76,15 +76,16 @@ class IntegrationTestMixin(object):
self.assertEqual(form_fields['email']['defaultValue'], self.USER_EMAIL)
self.assertEqual(form_fields['name']['defaultValue'], self.USER_NAME)
self.assertEqual(form_fields['username']['defaultValue'], self.USER_USERNAME)
registration_values = {
'email': 'email-edited@tpa-test.none',
'name': 'My Customized Name',
'username': 'new_username',
'honor_code': True,
}
# Now complete the form:
ajax_register_response = self.client.post(
reverse('user_api_registration'),
{
'email': 'email-edited@tpa-test.none',
'name': 'My Customized Name',
'username': 'new_username',
'honor_code': True,
}
registration_values
)
self.assertEqual(ajax_register_response.status_code, 200)
# Then the AJAX will finish the third party auth:
......
......@@ -163,6 +163,7 @@ class TestShibIntegrationTest(IntegrationTestMixin, testutil.SAMLTestCase):
def _configure_testshib_provider(self, **kwargs):
""" Enable and configure the TestShib SAML IdP as a third_party_auth provider """
fetch_metadata = kwargs.pop('fetch_metadata', True)
assert_metadata_updates = kwargs.pop('assert_metadata_updates', True)
kwargs.setdefault('name', self.PROVIDER_NAME)
kwargs.setdefault('enabled', True)
kwargs.setdefault('visible', True)
......@@ -176,9 +177,10 @@ class TestShibIntegrationTest(IntegrationTestMixin, testutil.SAMLTestCase):
if fetch_metadata:
self.assertTrue(httpretty.is_enabled())
num_changed, num_failed, num_total = fetch_saml_metadata()
self.assertEqual(num_failed, 0)
self.assertEqual(num_changed, 1)
self.assertEqual(num_total, 1)
if assert_metadata_updates:
self.assertEqual(num_failed, 0)
self.assertEqual(num_changed, 1)
self.assertEqual(num_total, 1)
def do_provider_login(self, provider_redirect_url):
""" Mocked: the user logs in to TestShib and then gets redirected back """
......
"""
Test the session-flushing middleware
"""
import unittest
from django.conf import settings
from django.test import Client
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestSessionFlushMiddleware(unittest.TestCase):
"""
Ensure that if the pipeline is exited when it's been quarantined,
the entire session is flushed.
"""
def test_session_flush(self):
"""
Test that a quarantined session is flushed when navigating elsewhere
"""
client = Client()
session = client.session
session['fancy_variable'] = 13025
session['partial_pipeline'] = 'pipeline_running'
session['third_party_auth_quarantined_modules'] = ('fake_quarantined_module',)
session.save()
client.get('/')
self.assertEqual(client.session.get('fancy_variable', None), None)
def test_session_no_running_pipeline(self):
"""
Test that a quarantined session without a running pipeline is not flushed
"""
client = Client()
session = client.session
session['fancy_variable'] = 13025
session['third_party_auth_quarantined_modules'] = ('fake_quarantined_module',)
session.save()
client.get('/')
self.assertEqual(client.session.get('fancy_variable', None), 13025)
def test_session_no_quarantine(self):
"""
Test that a session with a running pipeline but no quarantine is not flushed
"""
client = Client()
session = client.session
session['fancy_variable'] = 13025
session['partial_pipeline'] = 'pipeline_running'
session.save()
client.get('/')
self.assertEqual(client.session.get('fancy_variable', None), 13025)
......@@ -5,6 +5,7 @@ import unittest
from django.conf import settings
from django import test
from django.contrib.auth import models
import mock
from third_party_auth import pipeline, provider
from third_party_auth.tests import testutil
......@@ -208,3 +209,95 @@ class UrlFormationTestCase(TestCase):
with self.assertRaises(ValueError):
pipeline.get_complete_url(provider_id)
@unittest.skipUnless(
testutil.AUTH_FEATURES_KEY in settings.FEATURES, testutil.AUTH_FEATURES_KEY + ' not in settings.FEATURES')
class TestPipelineUtilityFunctions(TestCase, test.TestCase):
"""
Test some of the isolated utility functions in the pipeline
"""
def setUp(self):
super(TestPipelineUtilityFunctions, self).setUp()
self.user = social_models.DjangoStorage.user.create_user(username='username', password='password')
self.social_auth = social_models.UserSocialAuth.objects.create(
user=self.user,
uid='fake uid',
provider='fake provider'
)
def test_get_real_social_auth_from_dict(self):
"""
Test that we can use a dictionary with a UID entry to retrieve a
database-backed UserSocialAuth object.
"""
request = mock.MagicMock(
session={
'partial_pipeline': {
'kwargs': {
'social': {
'uid': 'fake uid'
}
}
}
}
)
real_social = pipeline.get_real_social_auth_object(request)
self.assertEqual(real_social, self.social_auth)
def test_get_real_social_auth(self):
"""
Test that trying to get a database-backed UserSocialAuth from an existing
instance returns correctly.
"""
request = mock.MagicMock(
session={
'partial_pipeline': {
'kwargs': {
'social': self.social_auth
}
}
}
)
real_social = pipeline.get_real_social_auth_object(request)
self.assertEqual(real_social, self.social_auth)
def test_get_real_social_auth_no_pipeline(self):
"""
Test that if there's no running pipeline, we return None when looking
for a database-backed UserSocialAuth object.
"""
request = mock.MagicMock(session={})
real_social = pipeline.get_real_social_auth_object(request)
self.assertEqual(real_social, None)
def test_get_real_social_auth_no_social(self):
"""
Test that if a UserSocialAuth object hasn't been attached to the pipeline as
`social`, we return none
"""
request = mock.MagicMock(
session={
'running_pipeline': {
'kwargs': {}
}
}
)
real_social = pipeline.get_real_social_auth_object(request)
self.assertEqual(real_social, None)
def test_quarantine(self):
"""
Test that quarantining a session adds the correct flags, and that
lifting the quarantine similarly removes those flags.
"""
request = mock.MagicMock(
session={}
)
pipeline.quarantine_session(request, locations=('my_totally_real_module', 'other_real_module',))
self.assertEqual(
request.session['third_party_auth_quarantined_modules'],
('my_totally_real_module', 'other_real_module',),
)
pipeline.lift_quarantine(request)
self.assertNotIn('third_party_auth_quarantined_modules', request.session)
......@@ -2,6 +2,7 @@
from third_party_auth import provider, settings
from third_party_auth.tests import testutil
from util.enterprise_helpers import enterprise_enabled
import unittest
......@@ -55,3 +56,9 @@ class SettingsUnitTest(testutil.TestCase):
# bad in prod.
settings.apply_settings(self.settings)
self.assertFalse(self.settings.SOCIAL_AUTH_RAISE_EXCEPTIONS)
@unittest.skipUnless(enterprise_enabled(), 'enterprise not enabled')
def test_enterprise_elements_inserted(self):
settings.apply_settings(self.settings)
self.assertIn('enterprise.tpa_pipeline.set_data_sharing_consent_record', self.settings.SOCIAL_AUTH_PIPELINE)
self.assertIn('enterprise.tpa_pipeline.verify_data_sharing_consent', self.settings.SOCIAL_AUTH_PIPELINE)
"""
Helpers to access the enterprise app
"""
from django.conf import settings
from django.utils.translation import ugettext as _
try:
from enterprise.models import EnterpriseCustomer
from enterprise.tpa_pipeline import (
active_provider_requests_data_sharing,
active_provider_enforces_data_sharing,
get_enterprise_customer_for_request,
)
except ImportError:
pass
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
def enterprise_enabled():
"""
Determines whether the Enterprise app is installed
"""
return 'enterprise' in settings.INSTALLED_APPS
def data_sharing_consent_requested(request):
"""
Determine if the EnterpriseCustomer for a given HTTP request
requests data sharing consent
"""
if not enterprise_enabled():
return False
return active_provider_requests_data_sharing(request)
def data_sharing_consent_required_at_login(request):
"""
Determines if data sharing consent is required at
a given location
"""
if not enterprise_enabled():
return False
return active_provider_enforces_data_sharing(request, EnterpriseCustomer.AT_LOGIN)
def data_sharing_consent_requirement_at_login(request):
"""
Returns either 'optional' or 'required' based on where we are.
"""
if not enterprise_enabled():
return None
if data_sharing_consent_required_at_login(request):
return 'required'
if data_sharing_consent_requested(request):
return 'optional'
return None
def insert_enterprise_fields(request, form_desc):
"""
Enterprise methods which modify the logistration form are called from this method.
"""
if not enterprise_enabled():
return
add_data_sharing_consent_field(request, form_desc)
def add_data_sharing_consent_field(request, form_desc):
"""
Adds a checkbox field to be selected if the user consents to share data with
the EnterpriseCustomer attached to the SSO provider with which they're authenticating.
"""
enterprise_customer = get_enterprise_customer_for_request(request)
required = data_sharing_consent_required_at_login(request)
if not data_sharing_consent_requested(request):
return
label = _(
"I agree to allow {platform_name} to share data about my enrollment, "
"completion and performance in all {platform_name} courses and programs "
"where my enrollment is sponsored by {ec_name}."
).format(
platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME),
ec_name=enterprise_customer.name
)
error_msg = _(
"To link your account with {ec_name}, you are required to consent to data sharing."
).format(
ec_name=enterprise_customer.name
)
form_desc.add_field(
"data_sharing_consent",
label=label,
field_type="checkbox",
default=False,
required=required,
error_messages={"required": error_msg},
)
def insert_enterprise_pipeline_elements(pipeline):
"""
If the enterprise app is enabled, insert additional elements into the
pipeline so that data sharing consent views are used.
"""
if not enterprise_enabled():
return
additional_elements = (
'enterprise.tpa_pipeline.set_data_sharing_consent_record',
'enterprise.tpa_pipeline.verify_data_sharing_consent',
)
# Find the item we need to insert the data sharing consent elements before
insert_point = pipeline.index('social.pipeline.social_auth.load_extra_data')
for index, element in enumerate(additional_elements):
pipeline.insert(insert_point + index, element)
"""
Test the enterprise app helpers
"""
import unittest
from django.conf import settings
import mock
from util.enterprise_helpers import (
enterprise_enabled,
data_sharing_consent_requested,
data_sharing_consent_required_at_login,
data_sharing_consent_requirement_at_login,
insert_enterprise_fields,
insert_enterprise_pipeline_elements
)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestEnterpriseHelpers(unittest.TestCase):
"""
Test enterprise app helpers
"""
@mock.patch('util.enterprise_helpers.enterprise_enabled')
def test_utils_with_enterprise_disabled(self, mock_enterprise_enabled):
"""
Test that the enterprise app not being available causes
the utilities to return the expected default values.
"""
mock_enterprise_enabled.return_value = False
self.assertFalse(data_sharing_consent_requested(None))
self.assertFalse(data_sharing_consent_required_at_login(None))
self.assertEqual(data_sharing_consent_requirement_at_login(None), None)
self.assertEqual(insert_enterprise_fields(None, None), None)
self.assertEqual(insert_enterprise_pipeline_elements(None), None)
def test_enterprise_enabled(self):
"""
The test settings inherit from common, which have the enterprise
app installed; therefore, it should appear installed here.
"""
self.assertTrue(enterprise_enabled())
@mock.patch('enterprise.tpa_pipeline.get_enterprise_customer_for_request')
def test_data_sharing_consent_requested(self, mock_get_ec):
"""
Test that we correctly check whether data sharing consent is requested.
"""
request = mock.MagicMock(session={'partial_pipeline': 'thing'})
mock_get_ec.return_value = mock.MagicMock(requests_data_sharing_consent=True)
self.assertTrue(data_sharing_consent_requested(request))
mock_get_ec.return_value = mock.MagicMock(requests_data_sharing_consent=False)
self.assertFalse(data_sharing_consent_requested(request))
mock_get_ec.return_value = None
self.assertFalse(data_sharing_consent_requested(request))
request = mock.MagicMock(session={})
self.assertFalse(data_sharing_consent_requested(request))
@mock.patch('enterprise.tpa_pipeline.get_enterprise_customer_for_request')
def test_data_sharing_consent_required(self, mock_get_ec):
"""
Test that we correctly check whether data sharing consent is required at login.
"""
check_method = mock.MagicMock(return_value=True)
request = mock.MagicMock(session={'partial_pipeline': 'thing'})
mock_get_ec.return_value = mock.MagicMock(enforces_data_sharing_consent=check_method)
self.assertTrue(data_sharing_consent_required_at_login(request))
check_method.return_value = False
mock_get_ec.return_value = mock.MagicMock(enforces_data_sharing_consent=check_method)
self.assertFalse(data_sharing_consent_required_at_login(request))
mock_get_ec.return_value = None
self.assertFalse(data_sharing_consent_required_at_login(request))
request = mock.MagicMock(session={})
self.assertFalse(data_sharing_consent_required_at_login(request))
@mock.patch('enterprise.tpa_pipeline.get_enterprise_customer_for_request')
def test_data_sharing_consent_requirement(self, mock_get_ec):
"""
Test that we get the correct requirement string for the current consent statae.
"""
request = mock.MagicMock(session={'partial_pipeline': 'thing'})
mock_ec = mock.MagicMock(
enforces_data_sharing_consent=mock.MagicMock(return_value=True),
requests_data_sharing_consent=True,
)
mock_get_ec.return_value = mock_ec
self.assertEqual(data_sharing_consent_requirement_at_login(request), 'required')
mock_ec.enforces_data_sharing_consent.return_value = False
self.assertEqual(data_sharing_consent_requirement_at_login(request), 'optional')
mock_ec.requests_data_sharing_consent = False
self.assertEqual(data_sharing_consent_requirement_at_login(request), None)
@mock.patch('util.enterprise_helpers.get_enterprise_customer_for_request')
@mock.patch('enterprise.tpa_pipeline.get_enterprise_customer_for_request')
@mock.patch('util.enterprise_helpers.configuration_helpers')
def test_insert_enterprise_fields(self, mock_config_helpers, mock_get_ec, mock_get_ec2):
"""
Test that the insertion of the enterprise fields is processed as expected.
"""
request = mock.MagicMock(session={'partial_pipeline': 'thing'})
mock_ec = mock.MagicMock(
enforces_data_sharing_consent=mock.MagicMock(return_value=True),
requests_data_sharing_consent=True,
)
# Name values in a MagicMock constructor don't fill a `name` attribute
mock_ec.name = 'MassiveCorp'
mock_get_ec.return_value = mock_ec
mock_get_ec2.return_value = mock_ec
mock_config_helpers.get_value.return_value = 'OpenEdX'
form_desc = mock.MagicMock()
form_desc.add_field.return_value = None
expected_label = (
"I agree to allow OpenEdX to share data about my enrollment, "
"completion and performance in all OpenEdX courses and programs "
"where my enrollment is sponsored by MassiveCorp."
)
expected_err_msg = (
"To link your account with MassiveCorp, you are required to consent to data sharing."
)
insert_enterprise_fields(request, form_desc)
mock_ec.enforces_data_sharing_consent.return_value = False
insert_enterprise_fields(request, form_desc)
calls = [
mock.call(
'data_sharing_consent',
label=expected_label,
field_type='checkbox',
default=False,
required=True,
error_messages={'required': expected_err_msg}
),
mock.call(
'data_sharing_consent',
label=expected_label,
field_type='checkbox',
default=False,
required=False,
error_messages={'required': expected_err_msg}
)
]
form_desc.add_field.assert_has_calls(calls)
form_desc.add_field.reset_mock()
mock_ec.requests_data_sharing_consent = False
insert_enterprise_fields(request, form_desc)
form_desc.add_field.assert_not_called()
......@@ -2662,7 +2662,6 @@ OPTIONAL_APPS = (
# Enterprise App (http://github.com/edx/edx-enterprise)
'enterprise',
# Required by the Enterprise App
'django_object_actions', # https://github.com/crccheck/django-object-actions
)
......
......@@ -245,6 +245,7 @@
color: $red;
}
&[for="register-data_sharing_consent"],
&[for="register-honor_code"],
&[for="register-terms_of_service"] {
display: inline-block;
......
......@@ -17,6 +17,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from django_comment_common.models import ForumsConfig
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from util.enterprise_helpers import enterprise_enabled
# Uncomment the next two lines to enable the admin:
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
......@@ -859,6 +860,12 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
url(r'^login_oauth_token/(?P<backend>[^/]+)/$', 'student.views.login_oauth_token'),
)
# Enterprise
if enterprise_enabled():
urlpatterns += (
url(r'', include('enterprise.urls')),
)
# OAuth token exchange
if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'):
urlpatterns += (
......
......@@ -1026,6 +1026,43 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
}
)
@mock.patch('util.enterprise_helpers.active_provider_requests_data_sharing')
@mock.patch('util.enterprise_helpers.active_provider_enforces_data_sharing')
@mock.patch('util.enterprise_helpers.get_enterprise_customer_for_request')
@mock.patch('util.enterprise_helpers.configuration_helpers')
def test_register_form_consent_field(self, config_helper, get_ec, mock_enforce, mock_request):
"""
Test that if we have an EnterpriseCustomer active for the request, and that
EnterpriseCustomer is set to require data sharing consent, the correct
field is added to the form descriptor.
"""
fake_ec = mock.MagicMock(
enforces_data_sharing_consent=mock.MagicMock(return_value=True),
requests_data_sharing_consent=True,
)
fake_ec.name = 'MegaCorp'
get_ec.return_value = fake_ec
config_helper.get_value.return_value = 'OpenEdX'
mock_request.return_value = True
mock_enforce.return_value = True
self._assert_reg_field(
dict(),
{
u"name": u"data_sharing_consent",
u"type": u"checkbox",
u"required": True,
u"label": (
"I agree to allow OpenEdX to share data about my enrollment, "
"completion and performance in all OpenEdX courses and programs "
"where my enrollment is sponsored by MegaCorp."
),
u"defaultValue": False,
u"errorMessages": {
u'required': u'To link your account with MegaCorp, you are required to consent to data sharing.',
}
}
)
@mock.patch('openedx.core.djangoapps.user_api.views._')
def test_register_form_level_of_education_translations(self, fake_gettext):
fake_gettext.side_effect = lambda text: text + ' TRANSLATED'
......
......@@ -31,6 +31,7 @@ from student.cookies import set_logged_in_cookies
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser
from util.json_request import JsonResponse
from util.enterprise_helpers import insert_enterprise_fields
from .preferences.api import get_country_time_zones, update_email_opt_in
from .helpers import FormDescription, shim_student_view, require_post_params
from .models import UserPreference, UserProfile
......@@ -279,6 +280,9 @@ class RegistrationView(APIView):
required=self._is_field_required(field_name)
)
# Add any Enterprise fields if the app is enabled
insert_enterprise_fields(request, form_desc)
return HttpResponse(form_desc.to_json(), content_type="application/json")
@method_decorator(csrf_exempt)
......
......@@ -46,7 +46,7 @@ edx-drf-extensions==1.2.1
edx-lint==0.4.3
edx-django-oauth2-provider==1.1.4
edx-django-sites-extensions==2.1.1
edx-enterprise==0.1.0
edx-enterprise==0.6.0
edx-oauth2-provider==1.2.0
edx-opaque-keys==0.4.0
edx-organizations==0.4.1
......
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