diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 57157ef161..1b463dc00c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,7 +15,9 @@ Change Log Unreleased ---------- -Nothing unreleased. +[4.7.3] +-------- +feat: added management command to re-encrypt enterprise customer reporting configs [4.7.2] -------- diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 90cefe02c9..8269609df6 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.7.2" +__version__ = "4.7.3" diff --git a/enterprise/management/commands/reencrypt_enterprise_customer_reporting_config_passwords.py b/enterprise/management/commands/reencrypt_enterprise_customer_reporting_config_passwords.py new file mode 100644 index 0000000000..3b0bec0456 --- /dev/null +++ b/enterprise/management/commands/reencrypt_enterprise_customer_reporting_config_passwords.py @@ -0,0 +1,30 @@ +""" +Django management command to reencrypt passwords in enterprise custom reporting configs. +""" +import logging + +from django.core.management import BaseCommand + +from enterprise.models import EnterpriseCustomerReportingConfiguration + +LOGGER = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Django management command to reencrypt passwords in enterprise custom reporting configs + It's useful when following encryption keys are rotated + - FERNET_KEYS + - LMS_FERNET_KEY + + Example usage: + ./manage.py lms reencrypt_enterprise_customer_reporting_config_passwords + + """ + def handle(self, *args, **options): + try: + for config in EnterpriseCustomerReportingConfiguration.objects.all(): + config.save() # resaving reencrypts all the encrypted columns + LOGGER.info('Enterprise customer reporting configuration passwords reencrypted succesfully!') + except Exception as e: # pylint: disable=broad-except + LOGGER.exception(f'Failed to reencrypt customer reporting configuration passwords. Error: {e}') diff --git a/tests/test_enterprise/management/test_reencrypt_enterprise_customer_reporting_config_passwords.py b/tests/test_enterprise/management/test_reencrypt_enterprise_customer_reporting_config_passwords.py new file mode 100644 index 0000000000..2df2fe1c3e --- /dev/null +++ b/tests/test_enterprise/management/test_reencrypt_enterprise_customer_reporting_config_passwords.py @@ -0,0 +1,43 @@ +""" +Tests for the djagno management command `reencrypt_enterprise_customer_reporting_config_passwords`. +""" +from testfixtures import LogCapture + +from django.core.management import call_command +from django.test import TestCase + +from enterprise.models import EnterpriseCustomerReportingConfiguration +from test_utils import factories + +LOGGER_NAME = 'enterprise.management.commands.reencrypt_enterprise_customer_reporting_config_passwords' + + +class ReencryptPasswordsTest(TestCase): + """ + Test command `reencrypt_enterprise_customer_reporting_config_passwords`. + """ + def test_reencrypt_command(self): + # Create an instance of EnterpriseCustomerReportingConfiguration + enterprise_customer = factories.EnterpriseCustomerFactory(name="GriffCo") + original_config = EnterpriseCustomerReportingConfiguration.objects.create( + enterprise_customer=enterprise_customer, + active=True, + delivery_method=EnterpriseCustomerReportingConfiguration.DELIVERY_METHOD_EMAIL, + email='test@edx.org', + decrypted_password='test_password', + day_of_month=1, + hour_of_day=1, + ) + + # Assert that the password has been encrypted + self.assertNotEqual(original_config.encrypted_password, 'test_password') + + with LogCapture(LOGGER_NAME) as log: + call_command('reencrypt_enterprise_customer_reporting_config_passwords') + self.assertEqual( + 'Enterprise customer reporting configuration passwords reencrypted succesfully!', + log.records[0].message + ) + updated_config = EnterpriseCustomerReportingConfiguration.objects.get(pk=original_config.pk) + # Assert that the password has been reencrypted + self.assertNotEqual(updated_config.encrypted_password, updated_config.encrypted_password)