From aa4d2a124676114a5f1f0560603ef8c7ef3bf42e Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 5 Oct 2023 14:10:49 +0500 Subject: [PATCH 01/15] feat: replacing non encrypted fields of moodle config model with encrypted ones (ENT 5613) --- .../api/v1/moodle/serializers.py | 12 ++- integrated_channels/moodle/admin/__init__.py | 19 ++++ integrated_channels/moodle/client.py | 6 +- .../migrations/0028_auto_20230928_1530.py | 58 +++++++++++ .../migrations/0029_auto_20231002_0752.py | 37 +++++++ integrated_channels/moodle/models.py | 97 ++++++++++++++++--- test_utils/factories.py | 2 +- .../test_api/test_moodle/test_views.py | 16 +-- .../test_moodle/test_client.py | 12 +-- .../test_content_metadata.py | 6 +- .../test_transmitters/test_learner_data.py | 6 +- 11 files changed, 231 insertions(+), 40 deletions(-) create mode 100644 integrated_channels/moodle/migrations/0028_auto_20230928_1530.py create mode 100644 integrated_channels/moodle/migrations/0029_auto_20231002_0752.py diff --git a/integrated_channels/api/v1/moodle/serializers.py b/integrated_channels/api/v1/moodle/serializers.py index fcef70f5a5..9668dcdb43 100644 --- a/integrated_channels/api/v1/moodle/serializers.py +++ b/integrated_channels/api/v1/moodle/serializers.py @@ -1,6 +1,8 @@ """ Serializer for Moodle configuration. """ +from rest_framework import serializers + from integrated_channels.api.serializers import EnterpriseCustomerPluginConfigSerializer from integrated_channels.moodle.models import MoodleEnterpriseCustomerConfiguration @@ -12,8 +14,12 @@ class Meta: 'moodle_base_url', 'service_short_name', 'category_id', - 'username', - 'password', - 'token', + 'encrypted_username', + 'encrypted_password', + 'encrypted_token', ) fields = EnterpriseCustomerPluginConfigSerializer.Meta.fields + extra_fields + + encrypted_password = serializers.CharField(required=False, allow_blank=False, read_only=False) + encrypted_username = serializers.CharField(required=False, allow_blank=False, read_only=False) + encrypted_token = serializers.CharField(required=False, allow_blank=False, read_only=False) diff --git a/integrated_channels/moodle/admin/__init__.py b/integrated_channels/moodle/admin/__init__.py index 81156462a5..dbef0530c4 100644 --- a/integrated_channels/moodle/admin/__init__.py +++ b/integrated_channels/moodle/admin/__init__.py @@ -21,6 +21,11 @@ class MoodleEnterpriseCustomerConfigurationForm(forms.ModelForm): class Meta: model = MoodleEnterpriseCustomerConfiguration fields = '__all__' + widgets = { + 'decrypted_password': forms.widgets.PasswordInput(), + 'decrypted_username': forms.widgets.PasswordInput(), + 'decrypted_token': forms.widgets.PasswordInput(), + } def clean(self): cleaned_data = super().clean() @@ -46,6 +51,20 @@ class MoodleEnterpriseCustomerConfigurationAdmin(DjangoObjectActions, admin.Mode form = MoodleEnterpriseCustomerConfigurationForm change_actions = ('force_content_metadata_transmission',) + class Meta: + model = MoodleEnterpriseCustomerConfiguration + + def get_fields(self, request, obj=None): + """ + Return the fields that should be displayed on the admin form. + """ + fields = list(super().get_fields(request, obj)) + if obj: + # Exclude password fields when we are editing an existing model. + return [f for f in fields if f not in {'decrypted_username', 'decrypted_token', 'decrypted_password'}] + + return fields + @admin.action( description="Force content metadata transmission for this Enterprise Customer" ) diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index ef62edcb9e..8b3bfc0317 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -121,7 +121,7 @@ def __init__(self, enterprise_configuration): """ super().__init__(enterprise_configuration) self.config = apps.get_app_config('moodle') - self.token = enterprise_configuration.token or self._get_access_token() + self.token = enterprise_configuration.decrypted_token or self._get_access_token() self.api_url = urljoin(self.enterprise_configuration.moodle_base_url, self.MOODLE_API_PATH) def _post(self, additional_params): @@ -171,8 +171,8 @@ def _get_access_token(self): 'Content-Type': 'application/x-www-form-urlencoded', }, data={ - 'username': self.enterprise_configuration.username, - 'password': self.enterprise_configuration.password, + 'username': self.enterprise_configuration.decrypted_username, + 'password': self.enterprise_configuration.decrypted_password, }, ) diff --git a/integrated_channels/moodle/migrations/0028_auto_20230928_1530.py b/integrated_channels/moodle/migrations/0028_auto_20230928_1530.py new file mode 100644 index 0000000000..7c1ed9bd1e --- /dev/null +++ b/integrated_channels/moodle/migrations/0028_auto_20230928_1530.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.20 on 2023-09-28 15:30 + +from django.db import migrations +import fernet_fields.fields + + +def populate_decrypted_fields(apps, schema_editor): + """ + Populates the encryption fields with the data previously stored in database. + """ + MoodleEnterpriseCustomerConfiguration = apps.get_model('moodle', 'MoodleEnterpriseCustomerConfiguration') + + for moodle_enterprise_configuration in MoodleEnterpriseCustomerConfiguration.objects.all(): + moodle_enterprise_configuration.decrypted_username = moodle_enterprise_configuration.username + moodle_enterprise_configuration.decrypted_password = moodle_enterprise_configuration.password + moodle_enterprise_configuration.decrypted_token = moodle_enterprise_configuration.token + moodle_enterprise_configuration.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('moodle', '0027_alter_historicalmoodleenterprisecustomerconfiguration_options'), + ] + + operations = [ + migrations.AddField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='decrypted_password', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's password used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Password'), + ), + migrations.AddField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='decrypted_token', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's token used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Token'), + ), + migrations.AddField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='decrypted_username', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's username used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Username'), + ), + migrations.AddField( + model_name='moodleenterprisecustomerconfiguration', + name='decrypted_password', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's password used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Password'), + ), + migrations.AddField( + model_name='moodleenterprisecustomerconfiguration', + name='decrypted_token', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's token used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Token'), + ), + migrations.AddField( + model_name='moodleenterprisecustomerconfiguration', + name='decrypted_username', + field=fernet_fields.fields.EncryptedCharField(blank=True, help_text="The encrypted API user's username used to obtain new tokens.", max_length=255, null=True, verbose_name='Encrypted Webservice Username'), + ), + migrations.RunPython(populate_decrypted_fields), + ] diff --git a/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py b/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py new file mode 100644 index 0000000000..1e30243f7c --- /dev/null +++ b/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.20 on 2023-10-02 07:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('moodle', '0028_auto_20230928_1530'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='password', + ), + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='token', + ), + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='username', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='password', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='token', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='username', + ), + ] diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index 7e96a6ca8d..745eda58d4 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -5,9 +5,11 @@ import json from logging import getLogger +from fernet_fields import EncryptedCharField from simple_history.models import HistoricalRecords 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.integrated_channel.models import ( @@ -55,33 +57,102 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) - username = models.CharField( + decrypted_username = EncryptedCharField( max_length=255, - verbose_name="Webservice Username", + verbose_name="Encrypted Webservice Username", blank=True, help_text=_( - "The API user's username used to obtain new tokens." - ) + "The encrypted API user's username used to obtain new tokens."), + null=True, ) - password = models.CharField( + @property + def encrypted_username(self): + """ + Return encrypted username as a string. + + The data is encrypted in the DB at rest, but is unencrypted in the app when retrieved through the + decrypted_username field. This method will encrypt the username again before sending. + """ + if self.decrypted_username: + return force_str( + self._meta.get_field('decrypted_username').fernet.encrypt( + force_bytes(self.decrypted_username) + ) + ) + return self.decrypted_username + + @encrypted_username.setter + def encrypted_username(self, value): + """ + Set the encrypted username. + """ + self.decrypted_username = value + + decrypted_password = EncryptedCharField( max_length=255, + verbose_name="Encrypted Webservice Password", blank=True, - verbose_name="Webservice Password", help_text=_( - "The API user's password used to obtain new tokens." - ) + "The encrypted API user's password used to obtain new tokens."), + null=True, ) - token = models.CharField( + @property + def encrypted_password(self): + """ + Return encrypted password as a string. + + The data is encrypted in the DB at rest, but is unencrypted in the app when retrieved through the + decrypted_password field. This method will encrypt the password again before sending. + """ + if self.decrypted_password: + return force_str( + self._meta.get_field('decrypted_password').fernet.encrypt( + force_bytes(self.decrypted_password) + ) + ) + return self.decrypted_password + + @encrypted_password.setter + def encrypted_password(self, value): + """ + Set the encrypted password. + """ + self.decrypted_password = value + + decrypted_token = EncryptedCharField( max_length=255, + verbose_name="Encrypted Webservice Token", blank=True, - verbose_name="Webservice User Token", help_text=_( - "The user's token for the Moodle webservice." - ) + "The encrypted API user's token used to obtain new tokens."), + null=True, ) + @property + def encrypted_token(self): + """ + Return encrypted token as a string. + + The data is encrypted in the DB at rest, but is unencrypted in the app when retrieved through the + decrypted_token field. This method will encrypt the token again before sending. + """ + if self.decrypted_token: + return force_str( + self._meta.get_field('decrypted_token').fernet.encrypt( + force_bytes(self.decrypted_token) + ) + ) + return self.decrypted_token + + @encrypted_token.setter + def encrypted_token(self, value): + """ + Set the encrypted token. + """ + self.decrypted_token = value + transmission_chunk_size = models.IntegerField( default=1, help_text=_("The maximum number of data items to transmit to the integrated channel with each request.") @@ -120,7 +191,7 @@ def is_valid(self): incorrect_items = {'incorrect': []} if not self.moodle_base_url: missing_items.get('missing').append('moodle_base_url') - if not self.token and not (self.username and self.password): + if not self.decrypted_token and not (self.decrypted_username and self.decrypted_password): missing_items.get('missing').append('token OR username and password') if not self.service_short_name: missing_items.get('missing').append('service_short_name') diff --git a/test_utils/factories.py b/test_utils/factories.py index 1d6998d92c..3d87059754 100644 --- a/test_utils/factories.py +++ b/test_utils/factories.py @@ -894,7 +894,7 @@ class Meta: enterprise_customer = factory.SubFactory(EnterpriseCustomerFactory) moodle_base_url = factory.LazyAttribute(lambda x: FAKER.url()) service_short_name = factory.LazyAttribute(lambda x: FAKER.slug()) - token = factory.LazyAttribute(lambda x: FAKER.slug()) + decrypted_token = factory.LazyAttribute(lambda x: FAKER.slug()) class AdminNotificationFactory(factory.django.DjangoModelFactory): diff --git a/tests/test_integrated_channels/test_api/test_moodle/test_views.py b/tests/test_integrated_channels/test_api/test_moodle/test_views.py index 23fe53db18..7deaac0de3 100644 --- a/tests/test_integrated_channels/test_api/test_moodle/test_views.py +++ b/tests/test_integrated_channels/test_api/test_moodle/test_views.py @@ -82,13 +82,13 @@ def test_update(self, mock_current_request): 'moodle_base_url': 'http://testing2', 'service_short_name': 'test', 'enterprise_customer': ENTERPRISE_ID, - 'token': 'testing' + 'encrypted_token': 'testing' } response = self.client.put(url, payload) self.moodle_config.refresh_from_db() self.assertEqual(self.moodle_config.moodle_base_url, 'http://testing2') self.assertEqual(self.moodle_config.service_short_name, 'test') - self.assertEqual(self.moodle_config.token, 'testing') + self.assertEqual(self.moodle_config.decrypted_token, 'testing') self.assertEqual(response.status_code, 200) @mock.patch('enterprise.rules.crum.get_current_request') @@ -139,9 +139,9 @@ def test_is_valid_field(self, mock_current_request): _, incorrect = data[0].get('is_valid') assert incorrect.get('incorrect') == ['moodle_base_url', 'display_name'] - self.moodle_config.token = '' - self.moodle_config.username = '' - self.moodle_config.password = '' + self.moodle_config.decrypted_token = '' + self.moodle_config.decrypted_username = '' + self.moodle_config.decrypted_password = '' self.moodle_config.moodle_base_url = '' self.moodle_config.service_short_name = '' self.moodle_config.save() @@ -152,9 +152,9 @@ def test_is_valid_field(self, mock_current_request): assert missing.get('missing') == ['moodle_base_url', 'token OR username and password', 'service_short_name'] self.moodle_config.category_id = 10 - self.moodle_config.username = 'lmao' - self.moodle_config.password = 'foobar' - self.moodle_config.token = 'baa' + self.moodle_config.decrypted_username = 'lmao' + self.moodle_config.decrypted_password = 'foobar' + self.moodle_config.decrypted_token = 'baa' self.moodle_config.moodle_base_url = 'http://lovely.com' self.moodle_config.service_short_name = 'short' self.moodle_config.display_name = '1234!@#$' diff --git a/tests/test_integrated_channels/test_moodle/test_client.py b/tests/test_integrated_channels/test_moodle/test_client.py index c9fd66389e..dd0f770b99 100644 --- a/tests/test_integrated_channels/test_moodle/test_client.py +++ b/tests/test_integrated_channels/test_moodle/test_client.py @@ -79,15 +79,15 @@ def setUp(self): self.learner_data_payload = '{{"courseID": {}, "grade": {}}}'.format(self.moodle_course_id, self.grade) self.enterprise_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.moodle_base_url, - username=self.user, - password=self.password, - token=self.token, + decrypted_username=self.user, + decrypted_password=self.password, + decrypted_token=self.token, ) self.enterprise_custom_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.custom_moodle_base_url, - username=self.user, - password=self.password, - token=self.token, + decrypted_username=self.user, + decrypted_password=self.password, + decrypted_token=self.token, grade_scale=10, grade_assignment_name='edX Grade Test' ) diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py index bd3fb0bf7f..9a5311a1ec 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py @@ -33,9 +33,9 @@ def setUp(self): self.enterprise_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.moodle_base_url, enterprise_customer=enterprise_customer, - username=self.user, - password=self.password, - token=self.api_token, + decrypted_username=self.user, + decrypted_password=self.password, + decrypted_token=self.api_token, ) def test_prepare_items_for_transmission(self): diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py index f94498fcc5..59d2396458 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py @@ -33,9 +33,9 @@ def setUp(self): moodle_base_url='foobar', service_short_name='shortname', category_id=1, - username='username', - password='password', - token='token', + decrypted_username='username', + decrypted_password='password', + decrypted_token='token', ) self.payload = MoodleLearnerDataTransmissionAudit( moodle_user_email=self.enterprise_customer.contact_email, From 29b0a6ca06b3c9343618ce19fab41ec2e607dc64 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Sat, 2 Dec 2023 02:19:19 +0500 Subject: [PATCH 02/15] test: added feature flag to allow data transmission through encrypted user data columns --- integrated_channels/moodle/client.py | 14 +++++-- .../migrations/0029_auto_20231002_0752.py | 37 ------------------- integrated_channels/moodle/models.py | 6 +++ 3 files changed, 17 insertions(+), 40 deletions(-) delete mode 100644 integrated_channels/moodle/migrations/0029_auto_20231002_0752.py diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index 00141c82cc..5ac6bdb446 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -121,7 +121,11 @@ def __init__(self, enterprise_configuration): """ super().__init__(enterprise_configuration) self.config = apps.get_app_config('moodle') - self.token = enterprise_configuration.decrypted_token or self._get_access_token() + self.token = ( + enterprise_configuration.decrypted_token + if enterprise_configuration.use_encrypted_user_data + else enterprise_configuration.token + ) or self._get_access_token() self.api_url = urljoin(self.enterprise_configuration.moodle_base_url, self.MOODLE_API_PATH) def _post(self, additional_params): @@ -171,8 +175,12 @@ def _get_access_token(self): 'Content-Type': 'application/x-www-form-urlencoded', }, data={ - 'username': self.enterprise_configuration.decrypted_username, - 'password': self.enterprise_configuration.decrypted_password, + "username": self.enterprise_configuration.decrypted_username + if self.enterprise_configuration.use_encrypted_user_data + else self.enterprise_configuration.username, + "password": self.enterprise_configuration.decrypted_password + if self.enterprise_configuration.use_encrypted_user_data + else self.enterprise_configuration.password, }, ) diff --git a/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py b/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py deleted file mode 100644 index 1e30243f7c..0000000000 --- a/integrated_channels/moodle/migrations/0029_auto_20231002_0752.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.20 on 2023-10-02 07:52 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('moodle', '0028_auto_20230928_1530'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='password', - ), - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='token', - ), - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='username', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='password', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='token', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='username', - ), - ] diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index 3c9732b95b..4694fd834c 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -212,6 +212,12 @@ def encrypted_token(self, value): default=False, ) + use_encrypted_user_data = models.BooleanField( + help_text=_("When set to True, the configured customer will use encrypted columns data to make client" + " requests, this a boolean flag for testing purpose"), + default=False, + ) + history = HistoricalRecords() class Meta: From 49be74d983dddd0580f7adf40bceb7a1098fddb0 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Mon, 4 Dec 2023 10:56:08 +0500 Subject: [PATCH 03/15] refactor: removing trailing whitespace --- integrated_channels/moodle/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index 4694fd834c..bb2a5c069f 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -99,7 +99,7 @@ def encrypted_username(self, value): Set the encrypted username. """ self.decrypted_username = value - + password = models.CharField( max_length=255, blank=True, From 64f00ac153cabcb3dfd8fd72d288618d81a350e7 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Mon, 4 Dec 2023 12:39:21 +0500 Subject: [PATCH 04/15] feat: added feature flag to test client calls using encrypted columns --- .../migrations/0031_auto_20231204_0737.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 integrated_channels/moodle/migrations/0031_auto_20231204_0737.py diff --git a/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py b/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py new file mode 100644 index 0000000000..184e0a42c6 --- /dev/null +++ b/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2023-12-04 07:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('moodle', '0030_merge_0028_auto_20231116_1826_0029_auto_20231106_1233'), + ] + + operations = [ + migrations.AddField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='use_encrypted_user_data', + field=models.BooleanField(default=False, help_text='When set to True, the configured customer will use encrypted columns data to make client requests, this a boolean flag for testing purpose'), + ), + migrations.AddField( + model_name='moodleenterprisecustomerconfiguration', + name='use_encrypted_user_data', + field=models.BooleanField(default=False, help_text='When set to True, the configured customer will use encrypted columns data to make client requests, this a boolean flag for testing purpose'), + ), + ] From 97575026e4e67e67f5cfc334ae031553b86035a8 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Fri, 8 Dec 2023 17:57:32 +0500 Subject: [PATCH 05/15] test: updated tests for use_encrypted_user_data flag --- tests/test_integrated_channels/test_moodle/test_client.py | 6 ++++++ .../test_moodle/test_transmitters/test_content_metadata.py | 3 +++ .../test_moodle/test_transmitters/test_learner_data.py | 3 +++ 3 files changed, 12 insertions(+) diff --git a/tests/test_integrated_channels/test_moodle/test_client.py b/tests/test_integrated_channels/test_moodle/test_client.py index dd0f770b99..c0f07a2eda 100644 --- a/tests/test_integrated_channels/test_moodle/test_client.py +++ b/tests/test_integrated_channels/test_moodle/test_client.py @@ -82,12 +82,18 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.token, + username=self.user, + password=self.password, + token=self.token, ) self.enterprise_custom_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.custom_moodle_base_url, decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.token, + username=self.user, + password=self.password, + token=self.token, grade_scale=10, grade_assignment_name='edX Grade Test' ) diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py index 9a5311a1ec..46f0a8d21f 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py @@ -36,6 +36,9 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.api_token, + username=self.user, + password=self.password, + token=self.api_token, ) def test_prepare_items_for_transmission(self): diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py index 4928860943..c55e0b6960 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py @@ -39,6 +39,9 @@ def setUp(self): decrypted_username='username', decrypted_password='password', decrypted_token='token', + username='username', + password='password', + token='token', ) self.payload = MoodleLearnerDataTransmissionAudit( moodle_user_email=self.enterprise_customer.contact_email, From cb05574d207d76f50e1eea900f8940cd23f28be6 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Sat, 9 Dec 2023 05:19:16 +0500 Subject: [PATCH 06/15] feat: removed un encrypted user credentials data and testing feature flag --- integrated_channels/moodle/client.py | 14 ++---- .../migrations/0032_auto_20231208_2345.py | 45 +++++++++++++++++++ integrated_channels/moodle/models.py | 33 -------------- .../test_moodle/test_client.py | 6 --- .../test_content_metadata.py | 3 -- .../test_transmitters/test_learner_data.py | 3 -- 6 files changed, 48 insertions(+), 56 deletions(-) create mode 100644 integrated_channels/moodle/migrations/0032_auto_20231208_2345.py diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index 7d2e6c06e8..bb0614054f 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -121,11 +121,7 @@ def __init__(self, enterprise_configuration): """ super().__init__(enterprise_configuration) self.config = apps.get_app_config('moodle') - self.token = ( - enterprise_configuration.decrypted_token - if enterprise_configuration.use_encrypted_user_data - else enterprise_configuration.token - ) or self._get_access_token() + self.token = enterprise_configuration.decrypted_token or self._get_access_token() self.api_url = urljoin(self.enterprise_configuration.moodle_base_url, self.MOODLE_API_PATH) def _post(self, additional_params): @@ -175,12 +171,8 @@ def _get_access_token(self): 'Content-Type': 'application/x-www-form-urlencoded', }, data={ - "username": self.enterprise_configuration.decrypted_username - if self.enterprise_configuration.use_encrypted_user_data - else self.enterprise_configuration.username, - "password": self.enterprise_configuration.decrypted_password - if self.enterprise_configuration.use_encrypted_user_data - else self.enterprise_configuration.password, + "username": self.enterprise_configuration.decrypted_username, + "password": self.enterprise_configuration.decrypted_password, }, ) diff --git a/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py b/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py new file mode 100644 index 0000000000..c280a1a17d --- /dev/null +++ b/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.23 on 2023-12-08 23:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('moodle', '0031_auto_20231204_0737'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='password', + ), + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='token', + ), + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='use_encrypted_user_data', + ), + migrations.RemoveField( + model_name='historicalmoodleenterprisecustomerconfiguration', + name='username', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='password', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='token', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='use_encrypted_user_data', + ), + migrations.RemoveField( + model_name='moodleenterprisecustomerconfiguration', + name='username', + ), + ] diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index bb2a5c069f..61c15b360a 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -57,15 +57,6 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) - username = models.CharField( - max_length=255, - verbose_name="Webservice Username", - blank=True, - help_text=_( - "The API user's username used to obtain new tokens." - ) - ) - decrypted_username = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Username", @@ -100,15 +91,6 @@ def encrypted_username(self, value): """ self.decrypted_username = value - password = models.CharField( - max_length=255, - blank=True, - verbose_name="Webservice Password", - help_text=_( - "The API user's password used to obtain new tokens." - ) - ) - decrypted_password = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Password", @@ -143,15 +125,6 @@ def encrypted_password(self, value): """ self.decrypted_password = value - token = models.CharField( - max_length=255, - blank=True, - verbose_name="Webservice User Token", - help_text=_( - "The user's token for the Moodle webservice." - ) - ) - decrypted_token = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Token", @@ -212,12 +185,6 @@ def encrypted_token(self, value): default=False, ) - use_encrypted_user_data = models.BooleanField( - help_text=_("When set to True, the configured customer will use encrypted columns data to make client" - " requests, this a boolean flag for testing purpose"), - default=False, - ) - history = HistoricalRecords() class Meta: diff --git a/tests/test_integrated_channels/test_moodle/test_client.py b/tests/test_integrated_channels/test_moodle/test_client.py index c0f07a2eda..dd0f770b99 100644 --- a/tests/test_integrated_channels/test_moodle/test_client.py +++ b/tests/test_integrated_channels/test_moodle/test_client.py @@ -82,18 +82,12 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.token, - username=self.user, - password=self.password, - token=self.token, ) self.enterprise_custom_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.custom_moodle_base_url, decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.token, - username=self.user, - password=self.password, - token=self.token, grade_scale=10, grade_assignment_name='edX Grade Test' ) diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py index 46f0a8d21f..9a5311a1ec 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py @@ -36,9 +36,6 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.api_token, - username=self.user, - password=self.password, - token=self.api_token, ) def test_prepare_items_for_transmission(self): diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py index c55e0b6960..4928860943 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py @@ -39,9 +39,6 @@ def setUp(self): decrypted_username='username', decrypted_password='password', decrypted_token='token', - username='username', - password='password', - token='token', ) self.payload = MoodleLearnerDataTransmissionAudit( moodle_user_email=self.enterprise_customer.contact_email, From 00d648ac559a3071f3e233741410bee613e4f812 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 21 Dec 2023 23:43:53 +0500 Subject: [PATCH 07/15] refactor: made admin level validation now use encrypted data --- integrated_channels/moodle/admin/__init__.py | 25 +++----------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/integrated_channels/moodle/admin/__init__.py b/integrated_channels/moodle/admin/__init__.py index dbef0530c4..44c11cb02c 100644 --- a/integrated_channels/moodle/admin/__init__.py +++ b/integrated_channels/moodle/admin/__init__.py @@ -21,17 +21,12 @@ class MoodleEnterpriseCustomerConfigurationForm(forms.ModelForm): class Meta: model = MoodleEnterpriseCustomerConfiguration fields = '__all__' - widgets = { - 'decrypted_password': forms.widgets.PasswordInput(), - 'decrypted_username': forms.widgets.PasswordInput(), - 'decrypted_token': forms.widgets.PasswordInput(), - } def clean(self): cleaned_data = super().clean() - cleaned_username = cleaned_data.get('username') - cleaned_password = cleaned_data.get('password') - cleaned_token = cleaned_data.get('token') + cleaned_username = cleaned_data.get('decrypted_username') + cleaned_password = cleaned_data.get('decrypted_password') + cleaned_token = cleaned_data.get('decrypted_token') if cleaned_token and (cleaned_username or cleaned_password): raise ValidationError(_('Cannot set both a Username/Password and Token')) if (cleaned_username and not cleaned_password) or (cleaned_password and not cleaned_username): @@ -51,20 +46,6 @@ class MoodleEnterpriseCustomerConfigurationAdmin(DjangoObjectActions, admin.Mode form = MoodleEnterpriseCustomerConfigurationForm change_actions = ('force_content_metadata_transmission',) - class Meta: - model = MoodleEnterpriseCustomerConfiguration - - def get_fields(self, request, obj=None): - """ - Return the fields that should be displayed on the admin form. - """ - fields = list(super().get_fields(request, obj)) - if obj: - # Exclude password fields when we are editing an existing model. - return [f for f in fields if f not in {'decrypted_username', 'decrypted_token', 'decrypted_password'}] - - return fields - @admin.action( description="Force content metadata transmission for this Enterprise Customer" ) From c0f5873031551f2a4b2ad256b20b85a0173c689e Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Fri, 22 Dec 2023 05:06:11 +0500 Subject: [PATCH 08/15] refactor: removing unused import and variables --- integrated_channels/moodle/client.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index f3b1ba29ad..0c9c25918f 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -10,7 +10,6 @@ import requests from django.apps import apps -from django.conf import settings from integrated_channels.exceptions import ClientError from integrated_channels.integrated_channel.client import IntegratedChannelApiClient @@ -183,11 +182,6 @@ def _get_access_token(self): 'service': self.enterprise_configuration.service_short_name } - decrypted_username = self.enterprise_configuration.decrypted_username - username = self.enterprise_configuration.username - decrypted_password = self.enterprise_configuration.decrypted_password - password = self.enterprise_configuration.password - response = requests.post( urljoin( self.enterprise_configuration.moodle_base_url, From eeb3abf395749426c6e4f17d4a3b32437c2d0b5e Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 28 Dec 2023 00:03:54 +0500 Subject: [PATCH 09/15] refactor: reverted removal of unencrypted user data columns --- .../migrations/0031_auto_20231204_0737.py | 23 ---------- .../migrations/0032_auto_20231208_2345.py | 45 ------------------- integrated_channels/moodle/models.py | 32 ++++++++++++- 3 files changed, 31 insertions(+), 69 deletions(-) delete mode 100644 integrated_channels/moodle/migrations/0031_auto_20231204_0737.py delete mode 100644 integrated_channels/moodle/migrations/0032_auto_20231208_2345.py diff --git a/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py b/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py deleted file mode 100644 index 184e0a42c6..0000000000 --- a/integrated_channels/moodle/migrations/0031_auto_20231204_0737.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.20 on 2023-12-04 07:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('moodle', '0030_merge_0028_auto_20231116_1826_0029_auto_20231106_1233'), - ] - - operations = [ - migrations.AddField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='use_encrypted_user_data', - field=models.BooleanField(default=False, help_text='When set to True, the configured customer will use encrypted columns data to make client requests, this a boolean flag for testing purpose'), - ), - migrations.AddField( - model_name='moodleenterprisecustomerconfiguration', - name='use_encrypted_user_data', - field=models.BooleanField(default=False, help_text='When set to True, the configured customer will use encrypted columns data to make client requests, this a boolean flag for testing purpose'), - ), - ] diff --git a/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py b/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py deleted file mode 100644 index c280a1a17d..0000000000 --- a/integrated_channels/moodle/migrations/0032_auto_20231208_2345.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-08 23:45 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('moodle', '0031_auto_20231204_0737'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='password', - ), - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='token', - ), - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='use_encrypted_user_data', - ), - migrations.RemoveField( - model_name='historicalmoodleenterprisecustomerconfiguration', - name='username', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='password', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='token', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='use_encrypted_user_data', - ), - migrations.RemoveField( - model_name='moodleenterprisecustomerconfiguration', - name='username', - ), - ] diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index 61c15b360a..213c300f90 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -57,6 +57,15 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) + username = models.CharField( + max_length=255, + verbose_name="Webservice Username", + blank=True, + help_text=_( + "The API user's username used to obtain new tokens." + ) + ) + decrypted_username = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Username", @@ -91,6 +100,15 @@ def encrypted_username(self, value): """ self.decrypted_username = value + password = models.CharField( + max_length=255, + blank=True, + verbose_name="Webservice Password", + help_text=_( + "The API user's password used to obtain new tokens." + ) + ) + decrypted_password = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Password", @@ -125,6 +143,15 @@ def encrypted_password(self, value): """ self.decrypted_password = value + token = models.CharField( + max_length=255, + blank=True, + verbose_name="Webservice User Token", + help_text=_( + "The user's token for the Moodle webservice." + ) + ) + decrypted_token = EncryptedCharField( max_length=255, verbose_name="Encrypted Webservice Token", @@ -203,7 +230,10 @@ def is_valid(self): incorrect_items = {'incorrect': []} if not self.moodle_base_url: missing_items.get('missing').append('moodle_base_url') - if not self.decrypted_token and not (self.decrypted_username and self.decrypted_password): + if (not self.token and not (self.username and self.password)) or ( + not self.decrypted_token + and not (self.decrypted_username and self.decrypted_password) + ): missing_items.get('missing').append('token OR username and password') if not self.service_short_name: missing_items.get('missing').append('service_short_name') From d5bb0b69d84bf89c40e19b9f15a0dc0d9370b8e9 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 28 Dec 2023 00:17:30 +0500 Subject: [PATCH 10/15] refactor: reverted removal of feature flag for testing --- integrated_channels/moodle/client.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index 0c9c25918f..619db70f60 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -10,6 +10,7 @@ import requests from django.apps import apps +from django.conf import settings from integrated_channels.exceptions import ClientError from integrated_channels.integrated_channel.client import IntegratedChannelApiClient @@ -142,7 +143,12 @@ def __init__(self, enterprise_configuration): """ super().__init__(enterprise_configuration) self.config = apps.get_app_config('moodle') - self.token = enterprise_configuration.decrypted_token or self._get_access_token() + token = ( + enterprise_configuration.decrypted_token + if getattr(settings, 'FEATURES', {}).get('USE_ENCRYPTED_USER_DATA', False) + else enterprise_configuration.token + ) + self.token = token or self._get_access_token() self.api_url = urljoin(self.enterprise_configuration.moodle_base_url, self.MOODLE_API_PATH) def _post(self, additional_params): @@ -182,6 +188,11 @@ def _get_access_token(self): 'service': self.enterprise_configuration.service_short_name } + decrypted_username = self.enterprise_configuration.decrypted_username + username = self.enterprise_configuration.username + decrypted_password = self.enterprise_configuration.decrypted_password + password = self.enterprise_configuration.password + response = requests.post( urljoin( self.enterprise_configuration.moodle_base_url, @@ -192,8 +203,8 @@ def _get_access_token(self): 'Content-Type': 'application/x-www-form-urlencoded', }, data={ - "username": self.enterprise_configuration.decrypted_username, - "password": self.enterprise_configuration.decrypted_password, + "username": decrypted_username if settings.FEATURES.get('USE_ENCRYPTED_USER_DATA', False) else username, + "password": decrypted_password if settings.FEATURES.get('USE_ENCRYPTED_USER_DATA', False) else password, }, ) From effae7e5d1e9c45fb299893e2e01bc16ad168d3e Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 28 Dec 2023 03:33:58 +0500 Subject: [PATCH 11/15] refactor: revert removal of unencrypted user data from test cases --- integrated_channels/moodle/client.py | 5 +++-- tests/test_integrated_channels/test_moodle/test_client.py | 8 +++++++- .../test_transmitters/test_content_metadata.py | 3 +++ .../test_moodle/test_transmitters/test_learner_data.py | 3 +++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index 619db70f60..5f41e00bce 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -192,6 +192,7 @@ def _get_access_token(self): username = self.enterprise_configuration.username decrypted_password = self.enterprise_configuration.decrypted_password password = self.enterprise_configuration.password + use_encrypted_user_data = getattr(settings, 'FEATURES', {}).get('USE_ENCRYPTED_USER_DATA', False) response = requests.post( urljoin( @@ -203,8 +204,8 @@ def _get_access_token(self): 'Content-Type': 'application/x-www-form-urlencoded', }, data={ - "username": decrypted_username if settings.FEATURES.get('USE_ENCRYPTED_USER_DATA', False) else username, - "password": decrypted_password if settings.FEATURES.get('USE_ENCRYPTED_USER_DATA', False) else password, + "username": decrypted_username if use_encrypted_user_data else username, + "password": decrypted_password if use_encrypted_user_data else password, }, ) diff --git a/tests/test_integrated_channels/test_moodle/test_client.py b/tests/test_integrated_channels/test_moodle/test_client.py index dd0f770b99..029fac06bf 100644 --- a/tests/test_integrated_channels/test_moodle/test_client.py +++ b/tests/test_integrated_channels/test_moodle/test_client.py @@ -82,12 +82,18 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.token, + username=self.user, + password=self.password, + token=self.token, ) self.enterprise_custom_config = factories.MoodleEnterpriseCustomerConfigurationFactory( moodle_base_url=self.custom_moodle_base_url, decrypted_username=self.user, decrypted_password=self.password, - decrypted_token=self.token, + decrypted_token=self.token + username=self.user, + password=self.password, + token=self.token, grade_scale=10, grade_assignment_name='edX Grade Test' ) diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py index 9a5311a1ec..46f0a8d21f 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_content_metadata.py @@ -36,6 +36,9 @@ def setUp(self): decrypted_username=self.user, decrypted_password=self.password, decrypted_token=self.api_token, + username=self.user, + password=self.password, + token=self.api_token, ) def test_prepare_items_for_transmission(self): diff --git a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py index 4928860943..c55e0b6960 100644 --- a/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py +++ b/tests/test_integrated_channels/test_moodle/test_transmitters/test_learner_data.py @@ -39,6 +39,9 @@ def setUp(self): decrypted_username='username', decrypted_password='password', decrypted_token='token', + username='username', + password='password', + token='token', ) self.payload = MoodleLearnerDataTransmissionAudit( moodle_user_email=self.enterprise_customer.contact_email, From 06a3f180a940dca88b243ac82e136600c363c812 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 28 Dec 2023 03:38:13 +0500 Subject: [PATCH 12/15] refactor: fixied parsing failed due to syntax error --- tests/test_integrated_channels/test_moodle/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integrated_channels/test_moodle/test_client.py b/tests/test_integrated_channels/test_moodle/test_client.py index 029fac06bf..c0f07a2eda 100644 --- a/tests/test_integrated_channels/test_moodle/test_client.py +++ b/tests/test_integrated_channels/test_moodle/test_client.py @@ -90,7 +90,7 @@ def setUp(self): moodle_base_url=self.custom_moodle_base_url, decrypted_username=self.user, decrypted_password=self.password, - decrypted_token=self.token + decrypted_token=self.token, username=self.user, password=self.password, token=self.token, From fb73371fb9be39798b02cf39355b325056ff269d Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Thu, 28 Dec 2023 04:02:50 +0500 Subject: [PATCH 13/15] refactor: revert removal of unencrypted user data from test test_is_valid_field --- .../test_api/test_moodle/test_views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_integrated_channels/test_api/test_moodle/test_views.py b/tests/test_integrated_channels/test_api/test_moodle/test_views.py index 7deaac0de3..ef420739e9 100644 --- a/tests/test_integrated_channels/test_api/test_moodle/test_views.py +++ b/tests/test_integrated_channels/test_api/test_moodle/test_views.py @@ -142,6 +142,9 @@ def test_is_valid_field(self, mock_current_request): self.moodle_config.decrypted_token = '' self.moodle_config.decrypted_username = '' self.moodle_config.decrypted_password = '' + self.moodle_config.token = '' + self.moodle_config.username = '' + self.moodle_config.password = '' self.moodle_config.moodle_base_url = '' self.moodle_config.service_short_name = '' self.moodle_config.save() @@ -155,6 +158,9 @@ def test_is_valid_field(self, mock_current_request): self.moodle_config.decrypted_username = 'lmao' self.moodle_config.decrypted_password = 'foobar' self.moodle_config.decrypted_token = 'baa' + self.moodle_config.username = 'lmao' + self.moodle_config.password = 'foobar' + self.moodle_config.token = 'baa' self.moodle_config.moodle_base_url = 'http://lovely.com' self.moodle_config.service_short_name = 'short' self.moodle_config.display_name = '1234!@#$' From 5453b23e80839df154bc6ba74e2278e6883df992 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Tue, 9 Jan 2024 18:47:32 +0500 Subject: [PATCH 14/15] refactor: updated model level validation check --- integrated_channels/moodle/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/integrated_channels/moodle/models.py b/integrated_channels/moodle/models.py index 213c300f90..83442f4d44 100644 --- a/integrated_channels/moodle/models.py +++ b/integrated_channels/moodle/models.py @@ -230,10 +230,7 @@ def is_valid(self): incorrect_items = {'incorrect': []} if not self.moodle_base_url: missing_items.get('missing').append('moodle_base_url') - if (not self.token and not (self.username and self.password)) or ( - not self.decrypted_token - and not (self.decrypted_username and self.decrypted_password) - ): + if not self.decrypted_token and not (self.decrypted_username and self.decrypted_password): missing_items.get('missing').append('token OR username and password') if not self.service_short_name: missing_items.get('missing').append('service_short_name') From 0f524b8f5dd4371b3087f4c3f9dd9732d6df4a99 Mon Sep 17 00:00:00 2001 From: "mueez.khan" Date: Mon, 15 Jan 2024 14:59:52 +0500 Subject: [PATCH 15/15] refactor: updated build version --- CHANGELOG.rst | 5 +++++ enterprise/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60b2dd8d3e..59e4197236 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,11 @@ Change Log Unreleased ---------- +[4.9.1] +-------- + +feat: replacing non encrypted fields of moodle config model with encrypted ones + [4.9.0] -------- diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 901407901d..a3dceeccd6 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.9.0" +__version__ = "4.9.1"