From 8a5ac8c3073e4ca8dbe7aaa4a494e19a93f14206 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Mon, 6 May 2024 15:55:43 +0500 Subject: [PATCH 1/3] revert: revert async task functionality implemented in 4.15.6 fix: update language cookie for an enterprise learner --- CHANGELOG.rst | 5 +++++ enterprise/__init__.py | 2 +- enterprise/middleware.py | 2 ++ enterprise/settings/test.py | 3 ++- enterprise/signals.py | 8 ++++---- enterprise/tasks.py | 18 +----------------- enterprise/utils.py | 9 ++++----- tests/test_models.py | 19 +------------------ 8 files changed, 20 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 873eee070d..15533e97c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,11 @@ Unreleased ---------- * nothing unreleased +[4.17.1] +-------- +* revert: revert async task functionality implemented in 4.15.6 +* fix: update language cookie for an enterprise learner + [4.17.0] -------- * feat: limit the number of resulting learners in Django admin manage learners view diff --git a/enterprise/__init__.py b/enterprise/__init__.py index dc908abd25..35c9801268 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.17.0" +__version__ = "4.17.1" diff --git a/enterprise/middleware.py b/enterprise/middleware.py index 4d4fcbb17f..abf5dc484e 100644 --- a/enterprise/middleware.py +++ b/enterprise/middleware.py @@ -2,6 +2,7 @@ Middleware for enterprise app. """ +from django.conf import settings from django.utils.deprecation import MiddlewareMixin from enterprise.utils import get_enterprise_customer_for_user @@ -71,3 +72,4 @@ def process_request(self, request): if not user_pref and not is_request_from_mobile_app(request): # pylint: disable=protected-access request._anonymous_user_cookie_lang = enterprise_customer.default_language + request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = enterprise_customer.default_language diff --git a/enterprise/settings/test.py b/enterprise/settings/test.py index f6dae73273..c2c9032b14 100644 --- a/enterprise/settings/test.py +++ b/enterprise/settings/test.py @@ -329,7 +329,8 @@ def root(*args): 'SAP': 1, } -LANGUAGE_COOKIE = 'openedx-language-preference' +LANGUAGE_COOKIE_NAME = "openedx-language-preference" +SHARED_COOKIE_DOMAIN = '' ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL = f'{LMS_INTERNAL_ROOT_URL}/oauth2' ENTERPRISE_BACKEND_SERVICE_EDX_OAUTH2_KEY = 'test_backend_oauth2_key' diff --git a/enterprise/signals.py b/enterprise/signals.py index 24f19003c8..52ec0adf72 100644 --- a/enterprise/signals.py +++ b/enterprise/signals.py @@ -11,12 +11,13 @@ from enterprise.api import activate_admin_permissions from enterprise.api_client.enterprise_catalog import EnterpriseCatalogApiClient from enterprise.decorators import disable_for_loaddata -from enterprise.tasks import create_enterprise_enrollment, update_enterprise_learners_user_preference +from enterprise.tasks import create_enterprise_enrollment from enterprise.utils import ( NotConnectedToOpenEdX, get_default_catalog_content_filter, localized_utcnow, unset_enterprise_learner_language, + unset_language_of_all_enterprise_learners, ) from integrated_channels.blackboard.models import BlackboardEnterpriseCustomerConfiguration from integrated_channels.canvas.models import CanvasEnterpriseCustomerConfiguration @@ -103,11 +104,10 @@ def update_lang_pref_of_all_learners(sender, instance, **kwargs): # pylint: dis # The middleware in the enterprise will handle the cases for setting a proper language for the learner. if instance.default_language: prev_state = models.EnterpriseCustomer.objects.filter(uuid=instance.uuid).first() - if prev_state and prev_state.default_language != instance.default_language: + if prev_state is None or prev_state.default_language != instance.default_language: # Unset the language preference of all the learners linked with the enterprise customer. # The middleware in the enterprise will handle the cases for setting a proper language for the learner. - logger.info('Task triggered to update user preference for learners. Enterprise: [%s]', instance.uuid) - update_enterprise_learners_user_preference.delay(instance.uuid) + unset_language_of_all_enterprise_learners(instance) @receiver(pre_save, sender=models.EnterpriseCustomerBrandingConfiguration) diff --git a/enterprise/tasks.py b/enterprise/tasks.py index f6646ad1ec..bfd7328631 100644 --- a/enterprise/tasks.py +++ b/enterprise/tasks.py @@ -16,11 +16,7 @@ from enterprise.api_client.braze import ENTERPRISE_BRAZE_ALIAS_LABEL, BrazeAPIClient from enterprise.api_client.enterprise_catalog import EnterpriseCatalogApiClient from enterprise.constants import SSO_BRAZE_CAMPAIGN_ID -from enterprise.utils import ( - get_enterprise_customer, - send_email_notification_message, - unset_language_of_all_enterprise_learners, -) +from enterprise.utils import get_enterprise_customer, send_email_notification_message LOGGER = getLogger(__name__) @@ -338,15 +334,3 @@ def send_group_membership_removal_notification(enterprise_customer_uuid, members ) LOGGER.exception(message) raise exc - - -@shared_task -@set_code_owner_attribute -def update_enterprise_learners_user_preference(enterprise_customer_uuid): - """ - Update the user preference `pref-lang` attribute for all enterprise learners linked with an enterprise. - - Arguments: - * enterprise_customer_uuid (UUID): uuid of an enterprise customer - """ - unset_language_of_all_enterprise_learners(enterprise_customer_uuid) diff --git a/enterprise/utils.py b/enterprise/utils.py index dae60e6713..435796453b 100644 --- a/enterprise/utils.py +++ b/enterprise/utils.py @@ -2219,18 +2219,17 @@ def get_platform_logo_url(): return urljoin(settings.LMS_ROOT_URL, logo_url) -def unset_language_of_all_enterprise_learners(enterprise_customer_uuid): +def unset_language_of_all_enterprise_learners(enterprise_customer): """ Unset the language preference of all the learners belonging to the given enterprise customer. Arguments: - enterprise_customer_uuid (UUI): uuid of an enterprise customer + enterprise_customer (UUI): Instance of the enterprise customer. """ if UserPreference: - enterprise_customer = get_enterprise_customer(enterprise_customer_uuid) user_ids = list(enterprise_customer.enterprise_customer_users.values_list('user_id', flat=True)) - LOGGER.info('Update user preference started for learners. Enterprise: [%s]', enterprise_customer_uuid) + LOGGER.info('Update user preference started for learners. Enterprise: [%s]', enterprise_customer.uuid) for chunk in batch(user_ids, batch_size=10000): UserPreference.objects.filter( @@ -2241,7 +2240,7 @@ def unset_language_of_all_enterprise_learners(enterprise_customer_uuid): ) LOGGER.info('Updated user preference for learners. Batch Size: [%s]', len(chunk)) - LOGGER.info('Update user preference completed for learners. Enterprise: [%s]', enterprise_customer_uuid) + LOGGER.info('Update user preference completed for learners. Enterprise: [%s]', enterprise_customer.uuid) def unset_enterprise_learner_language(enterprise_customer_user): diff --git a/tests/test_models.py b/tests/test_models.py index 558652b289..705c74081a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -61,7 +61,6 @@ SystemWideEnterpriseUserRoleAssignment, logo_path, ) -from enterprise.signals import update_enterprise_learners_user_preference from enterprise.utils import ( CourseEnrollmentDowngradeError, get_default_catalog_content_filter, @@ -327,12 +326,8 @@ def test_catalog_contains_course_with_enterprise_customer_catalog(self, api_clie api_client_mock.return_value.enterprise_contains_content_items.return_value = False assert enterprise_customer.catalog_contains_course(fake_catalog_api.FAKE_COURSE_RUN['key']) is False - @mock.patch( - 'enterprise.signals.update_enterprise_learners_user_preference.delay', - wraps=update_enterprise_learners_user_preference - ) @mock.patch('enterprise.utils.UserPreference', return_value=mock.MagicMock()) - def test_unset_language_of_all_enterprise_learners(self, user_preference_mock, mock_task): + def test_unset_language_of_all_enterprise_learners(self, user_preference_mock): """ Validate that unset_language_of_all_enterprise_learners is called whenever default_language changes. """ @@ -344,37 +339,25 @@ def test_unset_language_of_all_enterprise_learners(self, user_preference_mock, m ) enterprise_customer.default_language = 'es-419' enterprise_customer.save() - mock_task.assert_called_once() user_preference_mock.objects.filter.assert_called_once() # Make sure `unset_language_of_all_enterprise_learners` is called each time `default_language` changes. enterprise_customer.default_language = 'es-417' enterprise_customer.save() - assert mock_task.call_count == 2 assert user_preference_mock.objects.filter.call_count == 2 # make sure `unset_language_of_all_enterprise_learners` is not called if `default_language` is # not changed. enterprise_customer.default_language = 'es-417' enterprise_customer.save() - assert mock_task.call_count == 2 assert user_preference_mock.objects.filter.call_count == 2 # Make sure `unset_language_of_all_enterprise_learners` is not called if `default_language` is # set to `None`. enterprise_customer.default_language = None enterprise_customer.save() - assert mock_task.call_count == 2 assert user_preference_mock.objects.filter.call_count == 2 - @mock.patch('enterprise.signals.update_enterprise_learners_user_preference.delay') - def test_async_task_not_called_on_enterprise_customer_creation(self, mock_task): - """ - Validate that upon creation of new enterprise customer, async task is not called. - """ - factories.EnterpriseCustomerFactory() - mock_task.assert_not_called() - def test_enterprise_customer_user_toggle_universal_link(self): enterprise_customer = factories.EnterpriseCustomerFactory() # Toggle to True creates with no date, does not create a new link From c8c6c53abbedd9efd30142753c2bd97b323e7e2e Mon Sep 17 00:00:00 2001 From: MueezKhan246 <93375917+MueezKhan246@users.noreply.github.com> Date: Tue, 7 May 2024 19:50:44 +0500 Subject: [PATCH 2/3] Added fields for holding encrypted data in database for blackboard (#1907) * feat: added fields for holding encrypted data in database for blackboard --- CHANGELOG.rst | 4 ++ .../api/v1/blackboard/serializers.py | 5 ++ .../migrations/0020_auto_20240422_1709.py | 24 +++++++ .../migrations/0021_auto_20240423_1057.py | 15 ++++ integrated_channels/blackboard/models.py | 68 +++++++++++++++++++ integrated_channels/blackboard/utils.py | 17 +++++ test_utils/factories.py | 2 + .../test_api/test_blackboard/test_views.py | 25 +++++++ 8 files changed, 160 insertions(+) create mode 100644 integrated_channels/blackboard/migrations/0020_auto_20240422_1709.py create mode 100644 integrated_channels/blackboard/migrations/0021_auto_20240423_1057.py create mode 100644 integrated_channels/blackboard/utils.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 15533e97c3..acbb70f6f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[4.17.2] +-------- +* feat: added fields for holding encrypted data in database for blackboard + [4.17.1] -------- * revert: revert async task functionality implemented in 4.15.6 diff --git a/integrated_channels/api/v1/blackboard/serializers.py b/integrated_channels/api/v1/blackboard/serializers.py index fc40e2dabb..01c13a597c 100644 --- a/integrated_channels/api/v1/blackboard/serializers.py +++ b/integrated_channels/api/v1/blackboard/serializers.py @@ -18,6 +18,8 @@ class Meta: extra_fields = ( 'client_id', 'client_secret', + 'encrypted_client_id', + 'encrypted_client_secret', 'blackboard_base_url', 'refresh_token', 'uuid', @@ -25,6 +27,9 @@ class Meta: ) fields = EnterpriseCustomerPluginConfigSerializer.Meta.fields + extra_fields + encrypted_client_id = serializers.CharField(required=False, allow_blank=False, read_only=False) + encrypted_client_secret = serializers.CharField(required=False, allow_blank=False, read_only=False) + class BlackboardGlobalConfigSerializer(serializers.ModelSerializer): diff --git a/integrated_channels/blackboard/migrations/0020_auto_20240422_1709.py b/integrated_channels/blackboard/migrations/0020_auto_20240422_1709.py new file mode 100644 index 0000000000..49b53579bb --- /dev/null +++ b/integrated_channels/blackboard/migrations/0020_auto_20240422_1709.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.23 on 2024-04-22 17:09 + +from django.db import migrations +import fernet_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('blackboard', '0019_delete_historicalblackboardenterprisecustomerconfiguration'), + ] + + operations = [ + migrations.AddField( + model_name='blackboardenterprisecustomerconfiguration', + name='decrypted_client_id', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The API Client ID (encrypted at db level) provided to edX by the enterprise customer to be used to make API calls to Degreed on behalf of the customer.', max_length=255, verbose_name='API Client ID encrypted at db level'), + ), + migrations.AddField( + model_name='blackboardenterprisecustomerconfiguration', + name='decrypted_client_secret', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The API Client Secret (encrypted at db level) provided to edX by the enterprise customer to be used to make API calls to Degreed on behalf of the customer.', max_length=255, verbose_name='API Client Secret encrypted at db level'), + ), + ] diff --git a/integrated_channels/blackboard/migrations/0021_auto_20240423_1057.py b/integrated_channels/blackboard/migrations/0021_auto_20240423_1057.py new file mode 100644 index 0000000000..c5fa684682 --- /dev/null +++ b/integrated_channels/blackboard/migrations/0021_auto_20240423_1057.py @@ -0,0 +1,15 @@ +# Generated by Django 3.2.23 on 2024-04-23 10:57 + +from django.db import migrations +from integrated_channels.blackboard.utils import populate_decrypted_fields_blackboard + + +class Migration(migrations.Migration): + + dependencies = [ + ('blackboard', '0020_auto_20240422_1709'), + ] + + operations = [ + migrations.RunPython(populate_decrypted_fields_blackboard, reverse_code=migrations.RunPython.noop), + ] diff --git a/integrated_channels/blackboard/models.py b/integrated_channels/blackboard/models.py index b5e1b83778..dfa914b5c5 100644 --- a/integrated_channels/blackboard/models.py +++ b/integrated_channels/blackboard/models.py @@ -7,10 +7,12 @@ from logging import getLogger from config_models.models import ConfigurationModel +from fernet_fields import EncryptedCharField from six.moves.urllib.parse import urljoin from django.conf import settings from django.db import models +from django.utils.encoding import force_bytes, force_str from integrated_channels.blackboard.exporters.content_metadata import BlackboardContentMetadataExporter from integrated_channels.blackboard.exporters.learner_data import BlackboardLearnerExporter @@ -108,6 +110,39 @@ class BlackboardEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigur ) ) + decrypted_client_id = EncryptedCharField( + max_length=255, + blank=True, + default='', + verbose_name="API Client ID encrypted at db level", + help_text=( + "The API Client ID (encrypted at db level) provided to edX by the enterprise customer to be used" + " to make API calls to Degreed on behalf of the customer." + ) + ) + + @property + def encrypted_client_id(self): + """ + Return encrypted client_id as a string. + The data is encrypted in the DB at rest, but is unencrypted in the app when retrieved through the + decrypted_client_id field. This method will encrypt the client_id again before sending. + """ + if self.decrypted_client_id: + return force_str( + self._meta.get_field('decrypted_client_id').fernet.encrypt( + force_bytes(self.decrypted_client_id) + ) + ) + return self.decrypted_client_id + + @encrypted_client_id.setter + def encrypted_client_id(self, value): + """ + Set the encrypted client_id. + """ + self.decrypted_client_id = value + client_secret = models.CharField( max_length=255, blank=True, @@ -119,6 +154,39 @@ class BlackboardEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigur ) ) + decrypted_client_secret = EncryptedCharField( + max_length=255, + blank=True, + default='', + verbose_name="API Client Secret encrypted at db level", + help_text=( + "The API Client Secret (encrypted at db level) provided to edX by the enterprise customer to be " + "used to make API calls to Degreed on behalf of the customer." + ), + ) + + @property + def encrypted_client_secret(self): + """ + Return encrypted client_secret as a string. + The data is encrypted in the DB at rest, but is unencrypted in the app when retrieved through the + decrypted_client_secret field. This method will encrypt the client_secret again before sending. + """ + if self.decrypted_client_secret: + return force_str( + self._meta.get_field('decrypted_client_secret').fernet.encrypt( + force_bytes(self.decrypted_client_secret) + ) + ) + return self.decrypted_client_secret + + @encrypted_client_secret.setter + def encrypted_client_secret(self, value): + """ + Set the encrypted client_secret. + """ + self.decrypted_client_secret = value + blackboard_base_url = models.CharField( max_length=255, blank=True, diff --git a/integrated_channels/blackboard/utils.py b/integrated_channels/blackboard/utils.py new file mode 100644 index 0000000000..7d9ea39cb6 --- /dev/null +++ b/integrated_channels/blackboard/utils.py @@ -0,0 +1,17 @@ +""" +Utilities for Blackboard integrated channels. +""" + + +def populate_decrypted_fields_blackboard(apps, schema_editor=None): # pylint: disable=unused-argument + """ + Populates the encryption fields in Blackboard config with the data previously stored in database. + """ + BlackboardEnterpriseCustomerConfiguration = apps.get_model( + 'blackboard', 'BlackboardEnterpriseCustomerConfiguration' + ) + + for blackboard_enterprise_configuration in BlackboardEnterpriseCustomerConfiguration.objects.all(): + blackboard_enterprise_configuration.decrypted_client_id = blackboard_enterprise_configuration.client_id + blackboard_enterprise_configuration.decrypted_client_secret = blackboard_enterprise_configuration.client_secret + blackboard_enterprise_configuration.save() diff --git a/test_utils/factories.py b/test_utils/factories.py index 16ff12f2d4..c25eb1e6b7 100644 --- a/test_utils/factories.py +++ b/test_utils/factories.py @@ -891,6 +891,8 @@ class Meta: blackboard_base_url = factory.LazyAttribute(lambda x: FAKER.url()) client_id = factory.LazyAttribute(lambda x: FAKER.random_int(min=1)) client_secret = factory.LazyAttribute(lambda x: FAKER.uuid4()) + decrypted_client_id = factory.LazyAttribute(lambda x: FAKER.random_int(min=1)) + decrypted_client_secret = factory.LazyAttribute(lambda x: FAKER.uuid4()) refresh_token = factory.LazyAttribute(lambda x: FAKER.uuid4()) diff --git a/tests/test_integrated_channels/test_api/test_blackboard/test_views.py b/tests/test_integrated_channels/test_api/test_blackboard/test_views.py index 848b73eb64..effdca2004 100644 --- a/tests/test_integrated_channels/test_api/test_blackboard/test_views.py +++ b/tests/test_integrated_channels/test_api/test_blackboard/test_views.py @@ -4,11 +4,13 @@ import json from unittest import mock +from django.apps import apps from django.urls import reverse from enterprise.constants import ENTERPRISE_ADMIN_ROLE from enterprise.utils import localized_utcnow from integrated_channels.blackboard.models import BlackboardEnterpriseCustomerConfiguration +from integrated_channels.blackboard.utils import populate_decrypted_fields_blackboard from test_utils import FAKE_UUIDS, APITest, factories @@ -129,6 +131,8 @@ def test_update(self, mock_current_request): payload = { 'client_secret': 1000, 'client_id': 1001, + 'encrypted_client_secret': 1000, + 'encrypted_client_id': 1001, 'blackboard_base_url': 'http://testing2', 'enterprise_customer': FAKE_UUIDS[0], } @@ -136,9 +140,30 @@ def test_update(self, mock_current_request): self.enterprise_customer_conf.refresh_from_db() self.assertEqual(self.enterprise_customer_conf.client_secret, '1000') self.assertEqual(self.enterprise_customer_conf.client_id, '1001') + self.assertEqual(self.enterprise_customer_conf.decrypted_client_secret, '1000') + self.assertEqual(self.enterprise_customer_conf.decrypted_client_id, '1001') self.assertEqual(self.enterprise_customer_conf.blackboard_base_url, 'http://testing2') self.assertEqual(response.status_code, 200) + @mock.patch('enterprise.rules.crum.get_current_request') + def test_populate_decrypted_fields(self, mock_current_request): + mock_current_request.return_value = self.get_request_with_jwt_cookie( + system_wide_role=ENTERPRISE_ADMIN_ROLE, + context=self.enterprise_customer.uuid, + ) + url = reverse('api:v1:blackboard:configuration-detail', args=[self.enterprise_customer_conf.id]) + client_secret = self.enterprise_customer_conf.client_secret + payload = { + 'encrypted_client_secret': '1000', + 'enterprise_customer': FAKE_UUIDS[0], + } + self.client.put(url, payload) + self.enterprise_customer_conf.refresh_from_db() + self.assertEqual(self.enterprise_customer_conf.decrypted_client_secret, '1000') + populate_decrypted_fields_blackboard(apps) + self.enterprise_customer_conf.refresh_from_db() + self.assertEqual(self.enterprise_customer_conf.decrypted_client_secret, client_secret) + @mock.patch('enterprise.rules.crum.get_current_request') def test_partial_update(self, mock_current_request): mock_current_request.return_value = self.get_request_with_jwt_cookie( From 83f2cff98961ad3f2bafd54da3064f5e38999800 Mon Sep 17 00:00:00 2001 From: MueezKhan246 <93375917+MueezKhan246@users.noreply.github.com> Date: Tue, 7 May 2024 20:10:01 +0500 Subject: [PATCH 3/3] Black Board: replacing encrypted fields with non encrypted (#2091) * feat: replacing non encrypted fields of blackboard config model with encypted ones --- CHANGELOG.rst | 4 ++++ enterprise/__init__.py | 2 +- integrated_channels/blackboard/client.py | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index acbb70f6f8..8d7e366bd5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[4.17.3] +-------- +* feat: replacing non encrypted fields of blackboard config model with encypted ones + [4.17.2] -------- * feat: added fields for holding encrypted data in database for blackboard diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 35c9801268..28330ba3ca 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.17.1" +__version__ = "4.17.3" diff --git a/integrated_channels/blackboard/client.py b/integrated_channels/blackboard/client.py index a4e871138e..fbfe6f0244 100644 --- a/integrated_channels/blackboard/client.py +++ b/integrated_channels/blackboard/client.py @@ -12,6 +12,7 @@ import requests from django.apps import apps +from django.conf import settings from django.db import transaction from integrated_channels.blackboard.exporters.content_metadata import BLACKBOARD_COURSE_CONTENT_NAME @@ -426,7 +427,12 @@ def _create_auth_header(self): """ auth header in oauth2 token format as required by blackboard doc """ - app_key = self.enterprise_configuration.client_id + use_encrypted_user_data = getattr(settings, 'FEATURES', {}).get('USE_ENCRYPTED_USER_DATA', False) + app_key = ( + self.enterprise_configuration.decrypted_client_id + if use_encrypted_user_data + else self.enterprise_configuration.client_id + ) if not app_key: if not self.global_blackboard_config.app_key: raise ClientError( @@ -434,7 +440,12 @@ def _create_auth_header(self): HTTPStatus.INTERNAL_SERVER_ERROR.value ) app_key = self.global_blackboard_config.app_key - app_secret = self.enterprise_configuration.client_secret + + app_secret = ( + self.enterprise_configuration.decrypted_client_secret + if use_encrypted_user_data + else self.enterprise_configuration.client_secret + ) if not app_secret: if not self.global_blackboard_config.app_secret: raise ClientError(