Skip to content

Commit

Permalink
Merge branch 'master' of github.com:openedx/edx-enterprise into hamza…
Browse files Browse the repository at this point in the history
…waleed/ENT-8662-fix-sso-entityId-parse-error
  • Loading branch information
hamzawaleed01 committed Apr 18, 2024
2 parents 3d7484b + e64d1a3 commit 11c9fc0
Show file tree
Hide file tree
Showing 17 changed files with 217 additions and 57 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ Change Log
Unreleased
----------
[4.15.4]
--------
* fix: allowing for existing pecus to be added to enterprise groups

[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.
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.15.2"
__version__ = "4.15.4"
2 changes: 1 addition & 1 deletion enterprise/api/v1/views/enterprise_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def assign_learners(self, request, group_uuid):
]
# According to Django docs, bulk created objects can't be used in future bulk creates as the in memory
# objects returned by bulk_create won't have PK's assigned.
models.PendingEnterpriseCustomerUser.objects.bulk_create(pecu_records)
models.PendingEnterpriseCustomerUser.objects.bulk_create(pecu_records, ignore_conflicts=True)
pecus = models.PendingEnterpriseCustomerUser.objects.filter(
user_email__in=emails_to_create_batch,
enterprise_customer=customer,
Expand Down
6 changes: 6 additions & 0 deletions integrated_channels/api/v1/degreed2/serializers.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
5 changes: 3 additions & 2 deletions integrated_channels/degreed2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
24 changes: 24 additions & 0 deletions integrated_channels/degreed2/migrations/0026_auto_20240329_1537.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.23 on 2024-03-29 15:37

from django.db import migrations
import fernet_fields.fields


class Migration(migrations.Migration):

dependencies = [
('degreed2', '0025_delete_historicaldegreed2enterprisecustomerconfiguration'),
]

operations = [
migrations.AddField(
model_name='degreed2enterprisecustomerconfiguration',
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 Degreed on behalf of the customer.', max_length=255, null=True, verbose_name='Encrypted API Client ID'),
),
migrations.AddField(
model_name='degreed2enterprisecustomerconfiguration',
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 Degreed on behalf of the customer.', max_length=255, null=True, verbose_name='Encrypted API Client Secret'),
),
]
26 changes: 26 additions & 0 deletions integrated_channels/degreed2/migrations/0027_auto_20240329_1537.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 3.2.23 on 2024-03-29 15:37

from django.db import migrations


def populate_decrypted_fields(apps, schema_editor):
"""
Populates the encryption fields with the data previously stored in database.
"""
Degreed2EnterpriseCustomerConfiguration = apps.get_model('degreed2', 'Degreed2EnterpriseCustomerConfiguration')

for degreed2_enterprise_configuration in Degreed2EnterpriseCustomerConfiguration.objects.all():
degreed2_enterprise_configuration.decrypted_client_id = degreed2_enterprise_configuration.client_id
degreed2_enterprise_configuration.decrypted_client_secret = degreed2_enterprise_configuration.client_secret
degreed2_enterprise_configuration.save()


class Migration(migrations.Migration):

dependencies = [
('degreed2', '0026_auto_20240329_1537'),
]

operations = [
migrations.RunPython(populate_decrypted_fields, reverse_code=migrations.RunPython.noop),
]
95 changes: 95 additions & 0 deletions integrated_channels/degreed2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import json
from logging import getLogger

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
Expand Down Expand Up @@ -39,6 +42,52 @@ class Degreed2EnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigurat
)
)

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
)

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,
Expand All @@ -50,6 +99,52 @@ class Degreed2EnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigurat
)
)

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
)

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,
Expand Down
2 changes: 1 addition & 1 deletion requirements/celery53.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ celery==5.3.6
click==8.1.7
click-didyoumean==0.3.1
click-repl==0.3.0
kombu==5.3.6
kombu==5.3.7
prompt-toolkit==3.0.43
vine==5.1.0
2 changes: 1 addition & 1 deletion requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
distlib==0.3.8
# via virtualenv
filelock==3.13.3
filelock==3.13.4
# via
# tox
# virtualenv
Expand Down
18 changes: 7 additions & 11 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ backports-zoneinfo[tzdata]==0.2.1
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
# backports-zoneinfo
# celery
# django
# kombu
Expand Down Expand Up @@ -163,7 +162,7 @@ click-repl==0.3.0
# -r requirements/test-master.txt
# -r requirements/test.txt
# celery
code-annotations==1.6.0
code-annotations==1.8.0
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
Expand All @@ -186,7 +185,6 @@ coreschema==0.0.4
coverage[toml]==7.4.4
# via
# -r requirements/test.txt
# coverage
# pytest-cov
cryptography==38.0.4
# via
Expand All @@ -207,7 +205,7 @@ defusedxml==0.7.1
# -r requirements/test-master.txt
# -r requirements/test.txt
# djangorestframework-xml
diff-cover==8.0.3
diff-cover==9.0.0
# via -r requirements/test.txt
dill==0.3.8
# via pylint
Expand Down Expand Up @@ -372,7 +370,7 @@ edx-drf-extensions==10.2.0
# -r requirements/test-master.txt
# -r requirements/test.txt
# edx-rbac
edx-i18n-tools==1.3.0
edx-i18n-tools==1.5.0
# via -r requirements/dev.in
edx-lint==5.3.6
# via -r requirements/dev.in
Expand All @@ -382,13 +380,12 @@ edx-opaque-keys[django]==2.5.1
# -r requirements/test-master.txt
# -r requirements/test.txt
# edx-drf-extensions
# edx-opaque-keys
edx-rbac==1.8.0
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
edx-rest-api-client==5.6.1
edx-rest-api-client==5.7.0
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
Expand All @@ -398,7 +395,7 @@ edx-tincan-py35==1.0.0
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
edx-toggles==5.1.1
edx-toggles==5.2.0
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
Expand All @@ -408,7 +405,7 @@ factory-boy==3.3.0
# -c requirements/constraints.txt
# -r requirements/doc.txt
# -r requirements/test.txt
faker==24.7.1
faker==24.9.0
# via
# -r requirements/doc.txt
# -r requirements/test.txt
Expand Down Expand Up @@ -496,7 +493,7 @@ jwcrypto==1.5.4
# -r requirements/test-master.txt
# -r requirements/test.txt
# django-oauth-toolkit
kombu==5.3.6
kombu==5.3.7
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
Expand Down Expand Up @@ -661,7 +658,6 @@ pyjwt[crypto]==2.8.0
# drf-jwt
# edx-drf-extensions
# edx-rest-api-client
# pyjwt
# snowflake-connector-python
pylint==3.1.0
# via
Expand Down
Loading

0 comments on commit 11c9fc0

Please sign in to comment.