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/models.py b/integrated_channels/moodle/models.py index 7e96a6ca8d..05a59ddae7 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 ( @@ -64,6 +66,38 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) + decrypted_username = EncryptedCharField( + max_length=255, + verbose_name="Encrypted Webservice Username", + blank=True, + help_text=_( + "The encrypted API user's username used to obtain new tokens."), + null=True, + ) + + @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 + password = models.CharField( max_length=255, blank=True, @@ -73,6 +107,38 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) + decrypted_password = EncryptedCharField( + max_length=255, + verbose_name="Encrypted Webservice Password", + blank=True, + help_text=_( + "The encrypted API user's password used to obtain new tokens."), + null=True, + ) + + @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 + token = models.CharField( max_length=255, blank=True, @@ -82,6 +148,38 @@ class MoodleEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfiguratio ) ) + decrypted_token = EncryptedCharField( + max_length=255, + verbose_name="Encrypted Webservice Token", + blank=True, + help_text=_( + "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.")