From 43ac63e977b732f1ee03d7f8ae283336d69f6b30 Mon Sep 17 00:00:00 2001 From: MueezKhan246 <93375917+MueezKhan246@users.noreply.github.com> Date: Wed, 17 Apr 2024 06:56:57 +0500 Subject: [PATCH] Degreed replacing encrypted fields with non encrypted (#2063) * feat: replacing non encrypted fields of degreed config model with encrypted ones --- CHANGELOG.rst | 4 ++ enterprise/__init__.py | 2 +- .../api/v1/degreed2/serializers.py | 6 ++ integrated_channels/degreed2/client.py | 5 +- integrated_channels/degreed2/models.py | 69 +++++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2335dddf9d..6723a4619d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,10 @@ Change Log Unreleased ---------- +[4.15.3] +-------- +* feat: replacing non encrypted fields of degreed config model with encrypted ones + [4.15.2] -------- * feat: save cornerstone learner's information received from frontend. diff --git a/enterprise/__init__.py b/enterprise/__init__.py index b2368fa707..0612e907e0 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.15.2" +__version__ = "4.15.3" diff --git a/integrated_channels/api/v1/degreed2/serializers.py b/integrated_channels/api/v1/degreed2/serializers.py index 914a2c8a40..d93213793a 100644 --- a/integrated_channels/api/v1/degreed2/serializers.py +++ b/integrated_channels/api/v1/degreed2/serializers.py @@ -1,6 +1,8 @@ """ Serializer for Degreed2 configuration. """ +from rest_framework import serializers + from integrated_channels.api.serializers import EnterpriseCustomerPluginConfigSerializer from integrated_channels.degreed2.models import Degreed2EnterpriseCustomerConfiguration @@ -11,7 +13,11 @@ class Meta: extra_fields = ( 'client_id', 'client_secret', + 'encrypted_client_id', + 'encrypted_client_secret', 'degreed_base_url', 'degreed_token_fetch_base_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/degreed2/client.py b/integrated_channels/degreed2/client.py index b495f5e447..768bc00d10 100644 --- a/integrated_channels/degreed2/client.py +++ b/integrated_channels/degreed2/client.py @@ -674,11 +674,12 @@ def _get_oauth_access_token(self, scope): """ config = self.enterprise_configuration url = self.get_oauth_url() + use_encrypted_user_data = getattr(settings, 'FEATURES', {}).get('USE_ENCRYPTED_USER_DATA', False) data = { 'grant_type': 'client_credentials', 'scope': scope, - 'client_id': config.client_id, - 'client_secret': config.client_secret, + 'client_id': config.decrypted_client_id if use_encrypted_user_data else config.client_id, + 'client_secret': config.decrypted_client_secret if use_encrypted_user_data else config.client_secret, } start_time = time.time() response = requests.post( diff --git a/integrated_channels/degreed2/models.py b/integrated_channels/degreed2/models.py index 41367b74c1..b148b18f8e 100644 --- a/integrated_channels/degreed2/models.py +++ b/integrated_channels/degreed2/models.py @@ -9,6 +9,7 @@ from fernet_fields import EncryptedCharField from django.db import models +from django.utils.encoding import force_bytes, force_str from integrated_channels.degreed2.exporters.content_metadata import Degreed2ContentMetadataExporter from integrated_channels.degreed2.exporters.learner_data import Degreed2LearnerExporter @@ -53,6 +54,40 @@ class Degreed2EnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigurat null=True ) + decrypted_client_id = EncryptedCharField( + max_length=255, + blank=True, + default='', + 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 Degreed on behalf of the customer." + ), + null=True + ) + + @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, @@ -76,6 +111,40 @@ class Degreed2EnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigurat null=True ) + decrypted_client_secret = EncryptedCharField( + max_length=255, + blank=True, + default='', + 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 Degreed on behalf of the customer." + ), + null=True + ) + + @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 + degreed_base_url = models.CharField( max_length=255, blank=True,