diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b1fd2258d..3f46aac621 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,26 @@ Unreleased ---------- * nothing unreleased +[4.23.16] +---------- +* feat: remove references to client_id and client_secret from CanvasEnterpriseCustomerConfiguration + +[4.23.15] +---------- +* feat: altered decrypted_secret to be encrypted and made credentials nullable + +[4.23.14] +---------- +* feat: populate encrypted client id and secret for canvas integration and remove references to unencrypted fields + +[4.23.13] +---------- +* feat: added encrypted columns for user credentials for SAP config + +[4.23.12] +---------- +* feat: feat: added encrypted client id and secret for canvas integration + [4.23.11] ---------- * feat: implement back-off and retry for SAP SuccessFactors diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 6e2a0a5430..dfe1f750dc 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.23.11" +__version__ = "4.23.16" diff --git a/enterprise/signals.py b/enterprise/signals.py index d16c4ea4de..8813b95a44 100644 --- a/enterprise/signals.py +++ b/enterprise/signals.py @@ -464,3 +464,14 @@ def generate_default_orchestration_record_display_name(sender, instance, **kwarg if COURSE_ENROLLMENT_CHANGED is not None: COURSE_ENROLLMENT_CHANGED.connect(course_enrollment_changed_receiver) + + +@receiver(pre_save, sender=SAPSuccessFactorsEnterpriseCustomerConfiguration) +def update_decrypted_credentials(sender, instance, **kwargs): # pylint: disable=unused-argument + """ + Ensure that the decrypted credentials have same values as unencrypted credentials. + """ + if instance.key != instance.decrypted_key: + instance.decrypted_key = instance.key + if instance.secret != instance.decrypted_secret: + instance.decrypted_secret = instance.secret diff --git a/integrated_channels/api/v1/canvas/serializers.py b/integrated_channels/api/v1/canvas/serializers.py index 095ca06d52..b261964a13 100644 --- a/integrated_channels/api/v1/canvas/serializers.py +++ b/integrated_channels/api/v1/canvas/serializers.py @@ -1,6 +1,8 @@ """ Serializers for Canvas. """ +from rest_framework import serializers + from integrated_channels.api.serializers import EnterpriseCustomerPluginConfigSerializer from integrated_channels.canvas.models import CanvasEnterpriseCustomerConfiguration @@ -9,8 +11,8 @@ class CanvasEnterpriseCustomerConfigurationSerializer(EnterpriseCustomerPluginCo class Meta: model = CanvasEnterpriseCustomerConfiguration extra_fields = ( - 'client_id', - 'client_secret', + 'encrypted_client_id', + 'encrypted_client_secret', 'canvas_account_id', 'canvas_base_url', 'refresh_token', @@ -18,3 +20,6 @@ class Meta: 'oauth_authorization_url', ) 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) diff --git a/integrated_channels/canvas/admin/__init__.py b/integrated_channels/canvas/admin/__init__.py index fab5df5520..5625b0d61f 100644 --- a/integrated_channels/canvas/admin/__init__.py +++ b/integrated_channels/canvas/admin/__init__.py @@ -19,8 +19,8 @@ class CanvasEnterpriseCustomerConfigurationAdmin(DjangoObjectActions, admin.Mode """ list_display = ( "enterprise_customer_name", - "client_id", - "client_secret", + "decrypted_client_id", + "decrypted_client_secret", "canvas_account_id", "canvas_base_url", ) diff --git a/integrated_channels/canvas/client.py b/integrated_channels/canvas/client.py index fe36683875..67571b5fbb 100644 --- a/integrated_channels/canvas/client.py +++ b/integrated_channels/canvas/client.py @@ -959,8 +959,8 @@ def _get_oauth_access_token(self): HTTPError: If we received a failure response code from Canvas. ClientError: If an unexpected response format was received that we could not parse. """ - client_id = self.enterprise_configuration.client_id - client_secret = self.enterprise_configuration.client_secret + client_id = self.enterprise_configuration.decrypted_client_id + client_secret = self.enterprise_configuration.decrypted_client_secret if not client_id: raise ClientError( diff --git a/integrated_channels/canvas/migrations/0036_canvasenterprisecustomerconfiguration_decrypted_client_id_and_more.py b/integrated_channels/canvas/migrations/0036_canvasenterprisecustomerconfiguration_decrypted_client_id_and_more.py new file mode 100644 index 0000000000..d5957022b3 --- /dev/null +++ b/integrated_channels/canvas/migrations/0036_canvasenterprisecustomerconfiguration_decrypted_client_id_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.15 on 2024-08-26 07:03 + +from django.db import migrations +import fernet_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('canvas', '0035_canvaslearnerassessmentdatatransmissionaudit_is_transmitted_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='canvasenterprisecustomerconfiguration', + name='decrypted_client_id', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The encrypted API Client ID provided to edX by the enterprise customer to be used to make API calls to Canvas on behalf of the customer. It will be encrypted when stored in the database.', max_length=255, null=True, verbose_name='Encrypted API Client ID'), + ), + migrations.AddField( + model_name='canvasenterprisecustomerconfiguration', + name='decrypted_client_secret', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The encrypted API Client Secret provided to edX by the enterprise customer to be used to make API calls to Canvas on behalf of the customer. It will be encrypted when stored in the database.', max_length=255, null=True, verbose_name='Encrypted API Client Secret'), + ), + ] diff --git a/integrated_channels/canvas/migrations/0037_canvasenterprisecustomerconfiguration_copy_id_and_secret_and_more.py b/integrated_channels/canvas/migrations/0037_canvasenterprisecustomerconfiguration_copy_id_and_secret_and_more.py new file mode 100644 index 0000000000..9d0c11eca8 --- /dev/null +++ b/integrated_channels/canvas/migrations/0037_canvasenterprisecustomerconfiguration_copy_id_and_secret_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.15 on 2024-08-26 13:57 + +from django.db import migrations + + +def populate_decrypted_client_id_and_secret(apps, schema_editor): # pragma: no cover + """ + Populate the decrypted_client_id and decrypted_client_secret fields for all + existing CanvasEnterpriseCustomerConfiguration + """ + CanvasEnterpriseCustomerConfiguration = apps.get_model('canvas', 'CanvasEnterpriseCustomerConfiguration') + for config in CanvasEnterpriseCustomerConfiguration.objects.all(): + config.decrypted_client_id = config.client_id + config.decrypted_client_secret = config.client_secret + config.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('canvas', '0036_canvasenterprisecustomerconfiguration_decrypted_client_id_and_more'), + ] + + operations = [ + migrations.RunPython(populate_decrypted_client_id_and_secret, reverse_code=migrations.RunPython.noop), + ] diff --git a/integrated_channels/canvas/migrations/0038_alter_canvasenterprisecustomerconfiguration_client_id_and_more.py b/integrated_channels/canvas/migrations/0038_alter_canvasenterprisecustomerconfiguration_client_id_and_more.py new file mode 100644 index 0000000000..93de0269ac --- /dev/null +++ b/integrated_channels/canvas/migrations/0038_alter_canvasenterprisecustomerconfiguration_client_id_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-09-02 08:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('canvas', '0037_canvasenterprisecustomerconfiguration_copy_id_and_secret_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='canvasenterprisecustomerconfiguration', + name='client_id', + field=models.CharField(blank=True, default='', help_text='The API Client ID provided to edX by the enterprise customer to be used to make API calls to Canvas on behalf of the customer.', max_length=255, null=True, verbose_name='API Client ID'), + ), + migrations.AlterField( + model_name='canvasenterprisecustomerconfiguration', + name='client_secret', + field=models.CharField(blank=True, default='', help_text='The API Client Secret provided to edX by the enterprise customer to be used to make API calls to Canvas on behalf of the customer.', max_length=255, null=True, verbose_name='API Client Secret'), + ), + ] diff --git a/integrated_channels/canvas/models.py b/integrated_channels/canvas/models.py index 791e1b6af5..2620803f1f 100644 --- a/integrated_channels/canvas/models.py +++ b/integrated_channels/canvas/models.py @@ -6,10 +6,12 @@ import uuid from logging import getLogger +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 django.utils.translation import gettext_lazy as _ from integrated_channels.canvas.exporters.content_metadata import CanvasContentMetadataExporter @@ -33,28 +35,74 @@ class CanvasEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio Based on: https://canvas.instructure.com/doc/api/file.oauth.html#oauth2-flow-3 """ - client_id = models.CharField( + decrypted_client_id = EncryptedCharField( max_length=255, blank=True, default='', - verbose_name="API Client ID", - help_text=_( - "The API Client ID provided to edX by the enterprise customer to be used to make API " - "calls to Canvas on behalf of the customer." - ) + verbose_name="Encrypted API Client ID", + help_text=( + "The encrypted API Client ID provided to edX by the enterprise customer to be used to make API " + "calls to Canvas on behalf of the customer. It will be encrypted when stored in the database." + ), + null=True, ) - client_secret = models.CharField( + @property + def encrypted_client_id(self): + """ + Returns encrypted client_id as a string. + The data is encrypted in the DB at rest, but unencrypted in the app when retrieved through + `decrypted_client_id`. 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 `decrypted_client_id` from the encrypted value. + """ + self.decrypted_client_id = value + + decrypted_client_secret = EncryptedCharField( max_length=255, blank=True, default='', - verbose_name="API Client Secret", - help_text=_( - "The API Client Secret provided to edX by the enterprise customer to be used to make " - " API calls to Canvas on behalf of the customer." - ) + verbose_name="Encrypted API Client Secret", + help_text=( + "The encrypted API Client Secret provided to edX by the enterprise customer to be used to make " + " API calls to Canvas on behalf of the customer. It will be encrypted when stored in the database." + ), + null=True, ) + @property + def encrypted_client_secret(self): + """ + Returns encrypted client_secret as a string. + The data is encrypted in the DB at rest, but unencrypted in the app when retrieved through + `decrypted_client_secret`. 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 `decrypted_client_secret` from the encrypted value. + """ + self.decrypted_client_secret = value + canvas_account_id = models.BigIntegerField( null=True, blank=True, @@ -107,11 +155,11 @@ def oauth_authorization_url(self): obj: The instance of CanvasEnterpriseCustomerConfiguration being rendered with this admin form. """ - if self.canvas_base_url and self.client_id: + if self.canvas_base_url and self.decrypted_client_id: return (f'{self.canvas_base_url}/login/oauth2/auth' f'?redirect_uri={LMS_OAUTH_REDIRECT_URL}&' f'response_type=code&' - f'client_id={self.client_id}&state={self.uuid}') + f'client_id={self.decrypted_client_id}&state={self.uuid}') else: return None @@ -126,9 +174,9 @@ def is_valid(self): """ missing_items = {'missing': []} incorrect_items = {'incorrect': []} - if not self.client_id: + if not self.decrypted_client_id: missing_items.get('missing').append('client_id') - if not self.client_secret: + if not self.decrypted_client_secret: missing_items.get('missing').append('client_secret') if not self.canvas_base_url: missing_items.get('missing').append('canvas_base_url') diff --git a/integrated_channels/canvas/views.py b/integrated_channels/canvas/views.py index b265209f5d..a9ad68cdd4 100644 --- a/integrated_channels/canvas/views.py +++ b/integrated_channels/canvas/views.py @@ -113,8 +113,8 @@ def get(self, request, *args, **kwargs): access_token_request_params = { 'grant_type': 'authorization_code', - 'client_id': enterprise_config.client_id, - 'client_secret': enterprise_config.client_secret, + 'client_id': enterprise_config.decrypted_client_id, + 'client_secret': enterprise_config.decrypted_client_secret, 'redirect_uri': settings.LMS_INTERNAL_ROOT_URL + "/canvas/oauth-complete", 'code': client_code, } diff --git a/integrated_channels/sap_success_factors/admin/__init__.py b/integrated_channels/sap_success_factors/admin/__init__.py index be8cb305af..7b5d03b440 100644 --- a/integrated_channels/sap_success_factors/admin/__init__.py +++ b/integrated_channels/sap_success_factors/admin/__init__.py @@ -49,7 +49,9 @@ class SAPSuccessFactorsEnterpriseCustomerConfigurationAdmin(DjangoObjectActions, "sapsf_base_url", "sapsf_company_id", "key", + "decrypted_key", "secret", + "decrypted_secret", "sapsf_user_id", "user_type", "has_access_token", diff --git a/integrated_channels/sap_success_factors/migrations/0017_auto_20240823_0936.py b/integrated_channels/sap_success_factors/migrations/0017_auto_20240823_0936.py new file mode 100644 index 0000000000..09c0e2f620 --- /dev/null +++ b/integrated_channels/sap_success_factors/migrations/0017_auto_20240823_0936.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.23 on 2024-08-23 09:36 + +from django.db import migrations, models +import fernet_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('sap_success_factors', '0016_sapsuccessfactorslearnerdatatransmissionaudit_is_transmitted'), + ] + + operations = [ + migrations.AddField( + model_name='sapsuccessfactorsenterprisecustomerconfiguration', + name='decrypted_key', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The encrypted OAuth client identifier. It will be encrypted when stored in the database.', max_length=255, null=True, verbose_name='Encrypted Client ID'), + ), + migrations.AddField( + model_name='sapsuccessfactorsenterprisecustomerconfiguration', + name='decrypted_secret', + field=models.CharField(blank=True, default='', help_text='The encrypted OAuth client secret. It will be encrypted when stored in the database.', max_length=255, null=True, verbose_name='Encrypted Client Secret'), + ), + ] diff --git a/integrated_channels/sap_success_factors/migrations/0018_auto_20240829_0910.py b/integrated_channels/sap_success_factors/migrations/0018_auto_20240829_0910.py new file mode 100644 index 0000000000..2545b2eef1 --- /dev/null +++ b/integrated_channels/sap_success_factors/migrations/0018_auto_20240829_0910.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.23 on 2024-08-29 09:10 + +from django.db import migrations, models +import fernet_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('sap_success_factors', '0017_auto_20240823_0936'), + ] + + operations = [ + migrations.AlterField( + model_name='sapsuccessfactorsenterprisecustomerconfiguration', + name='decrypted_secret', + field=fernet_fields.fields.EncryptedCharField(blank=True, default='', help_text='The encrypted OAuth client secret. It will be encrypted when stored in the database.', max_length=255, null=True, verbose_name='Encrypted Client Secret'), + ), + migrations.AlterField( + model_name='sapsuccessfactorsenterprisecustomerconfiguration', + name='key', + field=models.CharField(blank=True, default='', help_text='OAuth client identifier.', max_length=255, null=True, verbose_name='Client ID'), + ), + migrations.AlterField( + model_name='sapsuccessfactorsenterprisecustomerconfiguration', + name='secret', + field=models.CharField(blank=True, default='', help_text='OAuth client secret.', max_length=255, null=True, verbose_name='Client Secret'), + ), + ] diff --git a/integrated_channels/sap_success_factors/models.py b/integrated_channels/sap_success_factors/models.py index 79d49d584b..6ed9257446 100644 --- a/integrated_channels/sap_success_factors/models.py +++ b/integrated_channels/sap_success_factors/models.py @@ -6,6 +6,7 @@ from logging import getLogger from config_models.models import ConfigurationModel +from fernet_fields import EncryptedCharField from django.db import models from django.utils.translation import gettext_lazy as _ @@ -78,8 +79,22 @@ class SAPSuccessFactorsEnterpriseCustomerConfiguration(EnterpriseCustomerPluginC blank=True, default='', verbose_name="Client ID", - help_text=_("OAuth client identifier.") + help_text=_("OAuth client identifier."), + null=True ) + + decrypted_key = EncryptedCharField( + max_length=255, + verbose_name="Encrypted Client ID", + blank=True, + default='', + help_text=_( + "The encrypted OAuth client identifier." + " It will be encrypted when stored in the database." + ), + null=True + ) + sapsf_base_url = models.CharField( max_length=255, blank=True, @@ -106,8 +121,22 @@ class SAPSuccessFactorsEnterpriseCustomerConfiguration(EnterpriseCustomerPluginC blank=True, default='', verbose_name="Client Secret", - help_text=_("OAuth client secret.") + help_text=_("OAuth client secret."), + null=True ) + + decrypted_secret = EncryptedCharField( + max_length=255, + blank=True, + default='', + verbose_name="Encrypted Client Secret", + help_text=_( + "The encrypted OAuth client secret." + " It will be encrypted when stored in the database." + ), + null=True + ) + user_type = models.CharField( max_length=20, choices=USER_TYPE_CHOICES, diff --git a/tests/test_integrated_channels/test_api/test_canvas/test_views.py b/tests/test_integrated_channels/test_api/test_canvas/test_views.py index 926295eb95..a5dbefc2d3 100644 --- a/tests/test_integrated_channels/test_api/test_canvas/test_views.py +++ b/tests/test_integrated_channels/test_api/test_canvas/test_views.py @@ -33,8 +33,8 @@ def setUp(self): self.enterprise_customer_conf = factories.CanvasEnterpriseCustomerConfigurationFactory( enterprise_customer=self.enterprise_customer, - client_id='ayy', - client_secret='lmao', + encrypted_client_id='ayy', + encrypted_client_secret='lmao', ) @mock.patch('enterprise.rules.crum.get_current_request') @@ -120,6 +120,8 @@ def test_update(self, mock_current_request): payload = { 'canvas_account_id': 1000, 'enterprise_customer': ENTERPRISE_ID, + 'encrypted_client_id': '', + 'encrypted_client_secret': '', } response = self.client.put(url, payload) self.enterprise_customer_conf.refresh_from_db() @@ -169,8 +171,8 @@ def test_is_valid_field(self, mock_current_request): assert missing.get('missing') == ['refresh_token'] assert incorrect.get('incorrect') == ['canvas_base_url', 'display_name'] - self.enterprise_customer_conf.client_id = '' - self.enterprise_customer_conf.client_secret = '' + self.enterprise_customer_conf.decrypted_client_id = '' + self.enterprise_customer_conf.decrypted_client_secret = '' self.enterprise_customer_conf.canvas_base_url = '' self.enterprise_customer_conf.canvas_account_id = None self.enterprise_customer_conf.save() @@ -184,8 +186,8 @@ def test_is_valid_field(self, mock_current_request): # Add a refresh token and assert that is_valid now passes self.enterprise_customer_conf.refresh_token = 'ayylmao' self.enterprise_customer_conf.canvas_base_url = 'http://lovely.com' - self.enterprise_customer_conf.client_id = '1' - self.enterprise_customer_conf.client_secret = '1' + self.enterprise_customer_conf.decrypted_client_id = '1' + self.enterprise_customer_conf.decrypted_client_secret = '1' self.enterprise_customer_conf.canvas_account_id = 1 self.enterprise_customer_conf.display_name = 'nice<3' self.enterprise_customer_conf.save() diff --git a/tests/test_integrated_channels/test_canvas/test_client.py b/tests/test_integrated_channels/test_canvas/test_client.py index 45ad649df3..c736d2385d 100644 --- a/tests/test_integrated_channels/test_canvas/test_client.py +++ b/tests/test_integrated_channels/test_canvas/test_client.py @@ -96,13 +96,13 @@ def setUp(self): self.course_api_path = "/api/v1/provider/content/course" self.course_url = urljoin(self.url_base, self.course_api_path) - self.client_id = "client_id" - self.client_secret = "client_secret" + self.decrypted_client_id = "client_id" + self.decrypted_client_secret = "client_secret" self.access_token = "access_token" self.refresh_token = "refresh_token" self.enterprise_config = factories.CanvasEnterpriseCustomerConfigurationFactory( - client_id=self.client_id, - client_secret=self.client_secret, + decrypted_client_id=self.decrypted_client_id, + decrypted_client_secret=self.decrypted_client_secret, canvas_account_id=self.account_id, canvas_base_url=self.url_base, refresh_token=self.refresh_token, @@ -426,14 +426,14 @@ def test_create_client_session_with_oauth_access_key(self): def test_client_instantiation_fails_without_client_id(self): with pytest.raises(ClientError) as client_error: - self.enterprise_config.client_id = None + self.enterprise_config.decrypted_client_id = None canvas_api_client = CanvasAPIClient(self.enterprise_config) canvas_api_client._create_session() # pylint: disable=protected-access assert client_error.value.message == "Failed to generate oauth access token: Client ID required." def test_client_instantiation_fails_without_client_secret(self): with pytest.raises(ClientError) as client_error: - self.enterprise_config.client_secret = None + self.enterprise_config.decrypted_client_secret = None canvas_api_client = CanvasAPIClient(self.enterprise_config) canvas_api_client._create_session() # pylint: disable=protected-access assert client_error.value.message == "Failed to generate oauth access token: Client secret required." @@ -1083,8 +1083,8 @@ def test_health_check_invalid_config(self): Test the client health check with invalid config """ bad_enterprise_config = factories.CanvasEnterpriseCustomerConfigurationFactory( - client_id=self.client_id, - client_secret=self.client_secret, + decrypted_client_id=self.decrypted_client_id, + decrypted_client_secret=self.decrypted_client_secret, canvas_account_id=self.account_id, canvas_base_url='Not a valid url', refresh_token=self.refresh_token, diff --git a/tests/test_integrated_channels/test_canvas/test_utils.py b/tests/test_integrated_channels/test_canvas/test_utils.py index 6898747db5..671ec14b9f 100644 --- a/tests/test_integrated_channels/test_canvas/test_utils.py +++ b/tests/test_integrated_channels/test_canvas/test_utils.py @@ -28,13 +28,13 @@ def setUp(self): self.account_id = random.randint(9223372036854775800, 9223372036854775807) self.course_id = "edx+111" self.url_base = "http://betatest.instructure.com" - self.client_id = "client_id" - self.client_secret = "client_secret" + self.decrypted_client_id = "client_id" + self.decrypted_client_secret = "client_secret" self.access_token = "access_token" self.refresh_token = "refresh_token" self.enterprise_config = factories.CanvasEnterpriseCustomerConfigurationFactory( - client_id=self.client_id, - client_secret=self.client_secret, + decrypted_client_id=self.decrypted_client_id, + decrypted_client_secret=self.decrypted_client_secret, canvas_account_id=self.account_id, canvas_base_url=self.url_base, refresh_token=self.refresh_token, diff --git a/tests/test_integrated_channels/test_canvas/test_views.py b/tests/test_integrated_channels/test_canvas/test_views.py index efb1a2e703..447f4a64e6 100644 --- a/tests/test_integrated_channels/test_canvas/test_views.py +++ b/tests/test_integrated_channels/test_canvas/test_views.py @@ -56,27 +56,47 @@ def setUp(self): self.refresh_token = 'test-refresh-token' self.urlbase = reverse('canvas-oauth-complete') - CanvasEnterpriseCustomerConfiguration.objects.get_or_create( - uuid=SINGLE_CANVAS_CONFIG['uuid'], - client_id=SINGLE_CANVAS_CONFIG['client_id'], - client_secret=SINGLE_CANVAS_CONFIG['client_secret'], - canvas_account_id=SINGLE_CANVAS_CONFIG['canvas_account_id'], - canvas_base_url=SINGLE_CANVAS_CONFIG['canvas_base_url'], - enterprise_customer=self.enterprise_customer, - active=True, - enterprise_customer_id=ENTERPRISE_ID, - ) + try: + CanvasEnterpriseCustomerConfiguration.objects.get( + uuid=SINGLE_CANVAS_CONFIG['uuid'], + canvas_account_id=SINGLE_CANVAS_CONFIG['canvas_account_id'], + canvas_base_url=SINGLE_CANVAS_CONFIG['canvas_base_url'], + enterprise_customer=self.enterprise_customer, + active=True, + enterprise_customer_id=ENTERPRISE_ID, + ) + except CanvasEnterpriseCustomerConfiguration.DoesNotExist: + CanvasEnterpriseCustomerConfiguration.objects.create( + uuid=SINGLE_CANVAS_CONFIG['uuid'], + decrypted_client_id=SINGLE_CANVAS_CONFIG['client_id'], + decrypted_client_secret=SINGLE_CANVAS_CONFIG['client_secret'], + canvas_account_id=SINGLE_CANVAS_CONFIG['canvas_account_id'], + canvas_base_url=SINGLE_CANVAS_CONFIG['canvas_base_url'], + enterprise_customer=self.enterprise_customer, + active=True, + enterprise_customer_id=ENTERPRISE_ID, + ) - CanvasEnterpriseCustomerConfiguration.objects.get_or_create( - uuid=SECOND_CANVAS_CONFIG['uuid'], - client_id=SECOND_CANVAS_CONFIG['client_id'], - client_secret=SECOND_CANVAS_CONFIG['client_secret'], - canvas_account_id=SECOND_CANVAS_CONFIG['canvas_account_id'], - canvas_base_url=SECOND_CANVAS_CONFIG['canvas_base_url'], - enterprise_customer=self.enterprise_customer, - active=True, - enterprise_customer_id=ENTERPRISE_ID, - ) + try: + CanvasEnterpriseCustomerConfiguration.objects.get( + uuid=SECOND_CANVAS_CONFIG['uuid'], + canvas_account_id=SECOND_CANVAS_CONFIG['canvas_account_id'], + canvas_base_url=SECOND_CANVAS_CONFIG['canvas_base_url'], + enterprise_customer=self.enterprise_customer, + active=True, + enterprise_customer_id=ENTERPRISE_ID, + ) + except CanvasEnterpriseCustomerConfiguration.DoesNotExist: + CanvasEnterpriseCustomerConfiguration.objects.create( + uuid=SECOND_CANVAS_CONFIG['uuid'], + decrypted_client_id=SECOND_CANVAS_CONFIG['client_id'], + decrypted_client_secret=SECOND_CANVAS_CONFIG['client_secret'], + canvas_account_id=SECOND_CANVAS_CONFIG['canvas_account_id'], + canvas_base_url=SECOND_CANVAS_CONFIG['canvas_base_url'], + enterprise_customer=self.enterprise_customer, + active=True, + enterprise_customer_id=ENTERPRISE_ID, + ) def test_successful_refresh_token_by_uuid_request(self): """