diff --git a/openedx/core/djangoapps/user_api/accounts/settings_views.py b/openedx/core/djangoapps/user_api/accounts/settings_views.py index 2471631072..c71f16f8cb 100644 --- a/openedx/core/djangoapps/user_api/accounts/settings_views.py +++ b/openedx/core/djangoapps/user_api/accounts/settings_views.py @@ -148,7 +148,7 @@ def account_settings_context(request): } enterprise_customer = get_enterprise_customer_for_learner(user=request.user) - update_account_settings_context_for_enterprise(context, enterprise_customer) + update_account_settings_context_for_enterprise(context, enterprise_customer, user) if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py index 9f6a1e9dc9..e33868966f 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -21,6 +21,7 @@ from django.test import TestCase from django.test.client import RequestFactory from mock import Mock, patch from six import iteritems +from social_django.models import UserSocialAuth from openedx.core.djangoapps.ace_common.tests.mixins import EmailTemplateTagMixin from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory @@ -89,6 +90,11 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, Retireme self.staff_user = UserFactory(is_staff=True, password=self.password) self.reset_tracker() + enterprise_patcher = patch('openedx.features.enterprise_support.api.get_enterprise_customer_for_learner') + enterprise_learner_patcher = enterprise_patcher.start() + enterprise_learner_patcher.return_value = {} + self.addCleanup(enterprise_learner_patcher.stop) + def test_get_username_provided(self): """Test the difference in behavior when a username is supplied to get_account_settings.""" account_settings = get_account_settings(self.default_request)[0] @@ -240,6 +246,8 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, Retireme (True, False), # is_synch_learner_profile_data (True, False), + # has `UserSocialAuth` record + (True, False), ) ) @ddt.unpack @@ -248,9 +256,11 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, Retireme field_name_value, is_enterprise_user, is_synch_learner_profile_data, + has_user_social_auth_record, mock_auth_provider, mock_customer, ): + idp_backend_name = 'tpa-saml' mock_customer.return_value = {} if is_enterprise_user: mock_customer.return_value.update({ @@ -259,13 +269,25 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, Retireme 'identity_provider': 'saml-ubc' }) mock_auth_provider.return_value.sync_learner_profile_data = is_synch_learner_profile_data + mock_auth_provider.return_value.backend_name = idp_backend_name update_data = {field_name_value[0]: field_name_value[1]} + user_fullname_editable = False + if has_user_social_auth_record: + UserSocialAuth.objects.create( + provider=idp_backend_name, + user=self.user + ) + else: + UserSocialAuth.objects.all().delete() + # user's fullname is editable if no `UserSocialAuth` record exists + user_fullname_editable = field_name_value[0] == 'name' + # prevent actual email change requests with patch('openedx.core.djangoapps.user_api.accounts.api.student_views.do_email_change_request'): - # expect field un-editability only when both of the following conditions are met - if is_enterprise_user and is_synch_learner_profile_data: + # expect field un-editability only when all of the following conditions are met + if is_enterprise_user and is_synch_learner_profile_data and not user_fullname_editable: with self.assertRaises(AccountValidationError) as validation_error: update_account_settings(self.user, update_data) field_errors = validation_error.exception.field_errors diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py index 7923fd5118..78c1a44860 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py @@ -27,6 +27,7 @@ from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.djangolib.testing.utils import skip_unless_lms from student.tests.factories import UserFactory from third_party_auth.tests.testutil import ThirdPartyAuthTestMixin +from openedx.features.enterprise_support.utils import get_enterprise_readonly_account_fields @skip_unless_lms @@ -106,7 +107,8 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, SiteMixin, ProgramsApiCon self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK) self.assertEqual(context['enterprise_name'], None) self.assertEqual( - context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS} + context['enterprise_readonly_account_fields'], + {'fields': list(get_enterprise_readonly_account_fields(self.user))} ) expected_beta_language = {'code': 'lt-lt', 'name': settings.LANGUAGE_DICT.get('lt-lt')} self.assertEqual(context['beta_language'], expected_beta_language) @@ -152,7 +154,8 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, SiteMixin, ProgramsApiCon self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK) self.assertEqual(context['enterprise_name'], dummy_enterprise_customer['name']) self.assertEqual( - context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS} + context['enterprise_readonly_account_fields'], + {'fields': list(get_enterprise_readonly_account_fields(self.user))} ) def test_view(self): diff --git a/openedx/features/enterprise_support/utils.py b/openedx/features/enterprise_support/utils.py index a5a05e402c..40cdc6bc8f 100644 --- a/openedx/features/enterprise_support/utils.py +++ b/openedx/features/enterprise_support/utils.py @@ -16,6 +16,7 @@ from enterprise.models import EnterpriseCustomerUser from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_authn.cookies import standard_cookie_settings from openedx.core.djangolib.markup import HTML, Text +from social_django.models import UserSocialAuth def get_cache_key(**kwargs): @@ -232,21 +233,21 @@ def _set_experiments_is_enterprise_cookie(request, response, experiments_is_ente ) -def update_account_settings_context_for_enterprise(context, enterprise_customer): +def update_account_settings_context_for_enterprise(context, enterprise_customer, user): """ Take processed context for account settings page and update it taking enterprise customer into account. Arguments: - context (dict): Context for account settings page. - enterprise_customer (dict): data for enterprise customer - + context (dict): Context for account settings page. + enterprise_customer (dict): data for enterprise customer + user (User): request user """ enterprise_context = { 'enterprise_name': enterprise_customer['name'] if enterprise_customer else None, 'sync_learner_profile_data': _get_sync_learner_profile_data(enterprise_customer), 'edx_support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'enterprise_readonly_account_fields': { - 'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS + 'fields': list(get_enterprise_readonly_account_fields(user)) } } context.update(enterprise_context) @@ -260,8 +261,33 @@ def get_enterprise_readonly_account_fields(user): from openedx.features.enterprise_support.api import get_enterprise_customer_for_learner enterprise_customer = get_enterprise_customer_for_learner(user) + enterprise_readonly_account_fields = list(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS) + + # if user has no `UserSocialAuth` record then allow to edit `fullname` + # whether the `sync_learner_profile_data` is enabled or disabled + user_social_auth_record = _user_has_social_auth_record(user, enterprise_customer) + if not user_social_auth_record: + enterprise_readonly_account_fields.remove('name') + sync_learner_profile_data = _get_sync_learner_profile_data(enterprise_customer) - return set(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS) if sync_learner_profile_data else set() + return set(enterprise_readonly_account_fields) if sync_learner_profile_data else set() + + +def _user_has_social_auth_record(user, enterprise_customer): + """ + Return True if a `UserSocialAuth` record exists for `user` False otherwise. + """ + if enterprise_customer: + identity_provider = third_party_auth.provider.Registry.get( + provider_id=enterprise_customer['identity_provider'], + ) + if identity_provider: + return UserSocialAuth.objects.select_related('user').filter( + provider=identity_provider.backend_name, + user=user + ).exists() + + return False def _get_sync_learner_profile_data(enterprise_customer):