diff --git a/Makefile b/Makefile index ad2b87fb9c9a..956d007459a6 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,10 @@ compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile * mv requirements/common_constraints.tmp requirements/common_constraints.txt sed 's/pip<24.3//g' requirements/common_constraints.txt > requirements/common_constraints.tmp mv requirements/common_constraints.tmp requirements/common_constraints.txt + sed '/social-auth-app-django>=5.9.0,<6.0.0/d' requirements/common_constraints.txt > requirements/common_constraints.tmp + mv requirements/common_constraints.tmp requirements/common_constraints.txt + sed '/social-auth-core>=4.9.1,<5.0.0/d' requirements/common_constraints.txt > requirements/common_constraints.tmp + mv requirements/common_constraints.tmp requirements/common_constraints.txt pip-compile -v --allow-unsafe ${COMPILE_OPTS} -o requirements/pip.txt requirements/pip.in pip install -r requirements/pip.txt diff --git a/docs/decisions/0027-retirement-of-pending-secondary-email.rst b/docs/decisions/0027-retirement-of-pending-secondary-email.rst deleted file mode 100644 index 21bac56d3ffb..000000000000 --- a/docs/decisions/0027-retirement-of-pending-secondary-email.rst +++ /dev/null @@ -1,107 +0,0 @@ -Retirement of Pending Secondary Email (``new_secondary_email``) -################################################################ - -**Status**: Accepted - -**Date**: 2026-06-10 - ------ - -Context -******* - -Open edX supports an account recovery email feature that allows users to -configure a secondary email address. - -When a user updates their recovery email, the new address is temporarily stored -in the ``student_pendingsecondaryemailchange`` table until the user confirms -ownership of the email address. - -This table contains: - -* ``new_secondary_email`` -* ``activation_key`` - -Once the confirmation link is used: - -1. ``activate_secondary_email()`` is executed. -2. The confirmed email is stored in the account recovery model. -3. The pending record is redacted and deleted. - -During a review of Personally Identifiable Information (PII) handling in the -user retirement workflow, it was discovered that ``new_secondary_email`` was -annotated as:: - - .. pii: Contains new_secondary_email - .. pii_types: email_address - .. pii_retirement: retained - -Although the field contains an email address, it was configured to be retained -after user retirement. - -No documented architectural decision or business requirement explaining this -behavior could be found. - ------ - -Problem -******* - -Retaining ``new_secondary_email`` after a user has requested retirement is -inconsistent with the retirement model, whose purpose is to remove or anonymize -user PII. - -Because ``new_secondary_email`` is only temporary state used while confirming a -recovery email address, retaining it indefinitely provides no clear functional -benefit while unnecessarily preserving personal data. - -During investigation, it was also questioned whether this field was required to -support Enterprise account recovery during the retirement cooling-off period. - ------ - -Decision -******** - -* ``new_secondary_email`` **SHALL** be treated as temporary PII. - -* The retirement process **SHALL** redact and remove ``new_secondary_email`` in - the same manner as other email-address PII. - -* Enterprise account recoverability **SHALL NOT** depend on the retention of - ``new_secondary_email``. - -* The ability to cancel a retirement request during the cooling-off period is - provided through existing account recovery mechanisms, including retained - authentication state where appropriate (such as Social Auth links during the - cooling-off period), and not through pending secondary email records. - ------ - -Consequences -************ - -Positive -======== - -* Eliminates unnecessary retention of personal data. -* Makes retirement behavior consistent across email-related PII. -* Simplifies the retirement model by treating temporary email state the same as - other transient user data. -* Improves compliance with privacy requirements by ensuring temporary email - addresses are not retained after retirement. - -Negative -======== - -* None identified. - ------ - -References -********** - -* Review of ``new_secondary_email`` PII annotations. -* User retirement implementation discussion. -* Enterprise account recovery investigation. -* Pull request implementing retirement handling for ``new_secondary_email``. diff --git a/lms/djangoapps/course_home_api/outline/views.py b/lms/djangoapps/course_home_api/outline/views.py index 0779c8a46515..78d5767ffeed 100644 --- a/lms/djangoapps/course_home_api/outline/views.py +++ b/lms/djangoapps/course_home_api/outline/views.py @@ -9,7 +9,6 @@ from completion.utilities import get_key_to_last_completed_block # lint-amnesty, pylint: disable=wrong-import-order from django.conf import settings # lint-amnesty, pylint: disable=wrong-import-order from django.core.cache import cache -from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 # lint-amnesty, pylint: disable=wrong-import-order from django.urls import reverse # lint-amnesty, pylint: disable=wrong-import-order from django.utils.translation import gettext as _ # lint-amnesty, pylint: disable=wrong-import-order @@ -190,38 +189,30 @@ class OutlineTabView(RetrieveAPIView): def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) - user = request.user + # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) - monitoring_utils.set_custom_attribute('user_id', user.id) - monitoring_utils.set_custom_attribute('is_staff', user.is_staff) + monitoring_utils.set_custom_attribute('user_id', request.user.id) + monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) + + course = get_course_or_403(request.user, 'load', course_key, check_if_enrolled=False) + masquerade_object, request.user = setup_masquerade( request, course_key, - staff_access=has_access(user, 'staff', course_key), + staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) - user_is_masquerading = is_masquerading(user, course_key, course_masquerade=masquerade_object) + user_is_masquerading = is_masquerading(request.user, course_key, course_masquerade=masquerade_object) - # Check if the user is masquerading as a student and get the masqueraded user object - if user_is_masquerading and masquerade_object.role == 'student': - try: - User = get_user_model() - # If the masqueraded user does not exist, we will continue with the original user object. - username = masquerade_object.user_name or 'audit' - user = User.objects.get(username=username) - except User.DoesNotExist: - pass - - course = get_course_or_403(user, 'load', course_key, check_if_enrolled=False) course_overview = get_course_overview_or_404(course_key) - enrollment = CourseEnrollment.get_enrollment(user, course_key) + enrollment = CourseEnrollment.get_enrollment(request.user, course_key) enrollment_mode = getattr(enrollment, 'mode', None) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE - allow_preview_of_verified_content = learner_can_preview_verified_content(course_key, user) + allow_preview_of_verified_content = learner_can_preview_verified_content(course_key, request.user) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) @@ -260,22 +251,22 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements show_enrolled = is_enrolled or is_staff enable_proctored_exams = False if show_enrolled: - course_blocks = get_course_outline_block_tree(request, course_key_string, user) - date_blocks = get_course_date_blocks(course, user, request, num_assignments=1) + course_blocks = get_course_outline_block_tree(request, course_key_string, request.user) + date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) dates_widget['course_date_blocks'] = [block for block in date_blocks if not isinstance(block, TodaysDate)] - handouts_html = get_course_info_section(request, user, course, 'handouts') + handouts_html = get_course_info_section(request, request.user, course, 'handouts') welcome_message_html = get_current_update_for_user(request, course) - offer_data = generate_offer_data(user, course_overview) - access_expiration = get_access_expiration_data(user, course_overview) - cert_data = get_cert_data(user, course, enrollment.mode) if is_enrolled else None + offer_data = generate_offer_data(request.user, course_overview) + access_expiration = get_access_expiration_data(request.user, course_overview) + cert_data = get_cert_data(request.user, course, enrollment.mode) if is_enrolled else None enable_proctored_exams = course_overview.enable_proctored_exams if (is_enrolled and ENABLE_COURSE_GOALS.is_enabled(course_key)): course_goals['weekly_learning_goal_enabled'] = True - selected_goal = get_course_goal(user, course_key) + selected_goal = get_course_goal(request.user, course_key) if selected_goal: course_goals['selected_goal'] = { 'days_per_week': selected_goal.days_per_week, @@ -283,7 +274,7 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements } try: - resume_block = get_key_to_last_completed_block(user, course.id) + resume_block = get_key_to_last_completed_block(request.user, course.id) resume_course['has_visited_course'] = True resume_path = reverse('jump_to', kwargs={ 'course_id': course_key_string, @@ -297,7 +288,7 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements elif allow_public_outline or allow_public or user_is_masquerading: course_blocks = get_course_outline_block_tree(request, course_key_string, None) if allow_public or user_is_masquerading: - handouts_html = get_course_info_section(request, user, course, 'handouts') + handouts_html = get_course_info_section(request, request.user, course, 'handouts') if not is_enrolled: if CourseMode.is_masters_only(course_key): @@ -322,7 +313,7 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements # so this is a tiny first step in that migration. if course_blocks: user_course_outline = get_user_course_outline( - course_key, user, datetime.now(tz=timezone.utc), + course_key, request.user, datetime.now(tz=timezone.utc), preview_verified_content=allow_preview_of_verified_content ) available_seq_ids = {str(usage_key) for usage_key in user_course_outline.sequences} @@ -367,8 +358,8 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements seq_data['is_preview'] = True user_has_passing_grade = False - if not user.is_anonymous: - user_grade = CourseGradeFactory().read(user, course) + if not request.user.is_anonymous: + user_grade = CourseGradeFactory().read(request.user, course) if user_grade: user_has_passing_grade = user_grade.passed diff --git a/openedx/core/djangoapps/enrollments/tests/test_filters.py b/openedx/core/djangoapps/enrollments/tests/test_filters.py new file mode 100644 index 000000000000..d956583057c5 --- /dev/null +++ b/openedx/core/djangoapps/enrollments/tests/test_filters.py @@ -0,0 +1,168 @@ +""" +Test for CourseEnrollmentViewStarted filter in enrollment views. +""" +import json + +from django.test import override_settings +from django.urls import reverse +from openedx_filters import PipelineStep +from openedx_filters.learning.filters import CourseEnrollmentViewStarted +from rest_framework import status +from rest_framework.test import APITestCase + +from common.djangoapps.course_modes.tests.factories import CourseModeFactory +from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory +from openedx.core.djangolib.testing.utils import skip_unless_lms +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + +PATH = "openedx.core.djangoapps.enrollments.tests.test_filters.TestCourseEnrollmentViewStartedPipelineStep" + + +class TestCourseEnrollmentViewStartedPipelineStep(PipelineStep): + """ + Utility pipeline step that prevents enrollment via CourseEnrollmentViewStarted filter. + """ + + def run_filter(self, user, course_key, requester_is_backend_service): # pylint: disable=arguments-differ + """Pipeline step that prevents enrollment for specific users or conditions.""" + if user.username == "blocked_user": + raise CourseEnrollmentViewStarted.PreventEnrollment( + "User is not allowed to enroll in this course." + ) + # Set a side effect on the user to verify the filter was executed + user.profile.set_meta({"enrollment_filter_executed": True}) + user.profile.save() + return { + "user": user, + "course_key": course_key, + "requester_is_backend_service": requester_is_backend_service, + } + + +@skip_unless_lms +class CourseEnrollmentViewStartedFilterTest(APITestCase, ModuleStoreTestCase): + """ + Tests for the CourseEnrollmentViewStarted filter in the enrollment view. + + This class guarantees that the following filters are triggered when attempting + to enroll a user through the enrollment API endpoint: + + - CourseEnrollmentViewStarted + """ + + def setUp(self): # pylint: disable=arguments-differ + super().setUp() + self.course = CourseFactory.create(emit_signals=True) + self.user = UserFactory.create( + username="test_user", + email="test@example.com", + password="password", + ) + self.blocked_user = UserFactory.create( + username="blocked_user", + email="blocked@example.com", + password="password", + ) + UserProfileFactory.create(user=self.user, name="Test User") + UserProfileFactory.create(user=self.blocked_user, name="Blocked User") + + # Create course modes + CourseModeFactory.create( + course_id=self.course.id, + mode_slug='audit', + mode_display_name='Audit' + ) + + @override_settings( + OPEN_EDX_FILTERS_CONFIG={ + "org.openedx.learning.course.enrollment.view.started.v1": { + "pipeline": [ + PATH, + ], + "fail_silently": False, + }, + }, + ) + def test_course_enrollment_view_started_filter_executed(self): + """ + Test that the CourseEnrollmentViewStarted filter is executed when enrolling through the view. + + Expected result: + - CourseEnrollmentViewStarted is triggered and executes the pipeline step. + - The enrollment is created successfully if the filter allows it. + - The filter pipeline step sets a side effect on the user's profile. + """ + self.client.login(username=self.user.username, password="password") + + response = self.client.post( + reverse('courseenrollments'), + { + "course_details": {"course_id": str(self.course.id)}, + "mode": "audit", + }, + format="json", + ) + + assert response.status_code == status.HTTP_200_OK + # Verify the filter was executed by checking the side effect + self.user.profile.refresh_from_db() + meta = self.user.profile.get_meta() + assert meta.get("enrollment_filter_executed") is True + + @override_settings( + OPEN_EDX_FILTERS_CONFIG={ + "org.openedx.learning.course.enrollment.view.started.v1": { + "pipeline": [ + PATH, + ], + "fail_silently": False, + }, + }, + ) + def test_course_enrollment_view_started_filter_prevent_enrollment(self): + """ + Test that enrollment is prevented when filter raises PreventEnrollment. + + Expected result: + - CourseEnrollmentViewStarted is triggered and executes the pipeline step. + - The pipeline step raises PreventEnrollment for blocked_user. + - The endpoint returns HTTP 403 Forbidden. + """ + self.client.login(username=self.blocked_user.username, password="password") + + response = self.client.post( + reverse('courseenrollments'), + { + "course_details": {"course_id": str(self.course.id)}, + "mode": "audit", + }, + format="json", + ) + + assert response.status_code == status.HTTP_403_FORBIDDEN + response_data = json.loads(response.content.decode('utf-8')) + assert "message" in response_data + assert "User is not allowed to enroll" in response_data["message"] + + @override_settings(OPEN_EDX_FILTERS_CONFIG={}) + def test_course_enrollment_view_started_without_filter_configuration(self): + """ + Test enrollment without filter configuration (no filter interference). + + Expected result: + - CourseEnrollmentViewStarted does not have any effect. + - The enrollment process succeeds without filter intervention. + """ + self.client.login(username=self.user.username, password="password") + + response = self.client.post( + reverse('courseenrollments'), + { + "course_details": {"course_id": str(self.course.id)}, + "mode": "audit", + }, + format="json", + ) + + assert response.status_code == status.HTTP_200_OK diff --git a/openedx/core/djangoapps/enrollments/tests/test_views.py b/openedx/core/djangoapps/enrollments/tests/test_views.py index a6b34cbfc60b..137c3a600295 100644 --- a/openedx/core/djangoapps/enrollments/tests/test_views.py +++ b/openedx/core/djangoapps/enrollments/tests/test_views.py @@ -10,7 +10,6 @@ from urllib.parse import quote import ddt -import httpretty import pytest import pytz from django.conf import settings @@ -45,8 +44,6 @@ from openedx.core.djangoapps.user_api.models import RetirementState, UserOrgTag, UserRetirementStatus from openedx.core.djangolib.testing.utils import skip_unless_lms from openedx.core.lib.django_test_client_utils import get_absolute_url -from openedx.features.enterprise_support.tests import FAKE_ENTERPRISE_CUSTOMER -from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls_range @@ -159,7 +156,7 @@ def _get_enrollments(self): @override_waffle_flag(ENABLE_NOTIFICATIONS, True) @ddt.ddt @skip_unless_lms -class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, EnterpriseServiceMockMixin): +class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase): """ Test user enrollment, especially with different course modes. """ @@ -1243,40 +1240,6 @@ def test_enrollment_with_global_staff_permissions(self, using_global_staff_user, assert course_mode == CourseMode.VERIFIED self.client.logout() - @httpretty.activate - @override_settings(ENTERPRISE_SERVICE_WORKER_USERNAME='enterprise_worker', - FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) - @patch('openedx.features.enterprise_support.api.enterprise_customer_from_api') - def test_enterprise_course_enrollment_with_ec_uuid(self, mock_enterprise_customer_from_api): - """Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """ - UserFactory.create( - username='enterprise_worker', - email=self.EMAIL, - password=self.PASSWORD, - ) - CourseModeFactory.create( - course_id=self.course.id, - mode_slug=CourseMode.DEFAULT_MODE_SLUG, - mode_display_name=CourseMode.DEFAULT_MODE_SLUG, - ) - consent_kwargs = { - 'username': self.user.username, - 'course_id': str(self.course.id), - 'ec_uuid': 'this-is-a-real-uuid' - } - mock_enterprise_customer_from_api.return_value = FAKE_ENTERPRISE_CUSTOMER - self.mock_enterprise_course_enrollment_post_api() - self.mock_consent_missing(**consent_kwargs) - self.mock_consent_post(**consent_kwargs) - self.assert_enrollment_status( - expected_status=status.HTTP_200_OK, - as_server=True, - username='enterprise_worker', - linked_enterprise_customer='this-is-a-real-uuid', - ) - assert httpretty.last_request().path == '/consent/api/v1/data_sharing_consent' # pylint: disable=no-member - assert httpretty.last_request().method == httpretty.POST - def test_enrollment_attributes_always_written(self): """ Enrollment attributes should always be written, regardless of whether the enrollment is being created or updated. diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py index dc3423245e9b..c35335c7880c 100644 --- a/openedx/core/djangoapps/enrollments/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -13,12 +13,12 @@ from django.db import IntegrityError # lint-amnesty, pylint: disable=wrong-import-order from django.db.models import Q # lint-amnesty, pylint: disable=wrong-import-order from django.utils.decorators import method_decorator # lint-amnesty, pylint: disable=wrong-import-order -from edx_rest_framework_extensions.auth.jwt.authentication import ( +from edx_rest_framework_extensions.auth.jwt.authentication import ( # lint-amnesty, pylint: disable=wrong-import-order JwtAuthentication, -) # lint-amnesty, pylint: disable=wrong-import-order -from edx_rest_framework_extensions.auth.session.authentication import ( +) +from edx_rest_framework_extensions.auth.session.authentication import ( # lint-amnesty, pylint: disable=wrong-import-order SessionAuthenticationAllowInactiveUser, -) # lint-amnesty, pylint: disable=wrong-import-order +) from opaque_keys import InvalidKeyError # lint-amnesty, pylint: disable=wrong-import-order from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order from rest_framework import permissions, status # lint-amnesty, pylint: disable=wrong-import-order @@ -57,12 +57,7 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.lib.exceptions import CourseNotFoundError from openedx.core.lib.log_utils import audit_log -from openedx.features.enterprise_support.api import ( - ConsentApiServiceClient, - EnterpriseApiException, - EnterpriseApiServiceClient, - enterprise_enabled, -) +from openedx_filters.learning.filters import CourseEnrollmentViewStarted log = logging.getLogger(__name__) REQUIRED_ATTRIBUTES = { @@ -774,26 +769,18 @@ def post(self, request): data={"message": ("'{value}' is an invalid enrollment activation status.").format(value=is_active)}, ) - explicit_linked_enterprise = request.data.get("linked_enterprise_customer") - if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled(): - enterprise_api_client = EnterpriseApiServiceClient() - consent_client = ConsentApiServiceClient() - try: - enterprise_api_client.post_enterprise_course_enrollment(username, str(course_id)) - except EnterpriseApiException as error: - log.exception( - "An unexpected error occurred while creating the new EnterpriseCourseEnrollment " - "for user [%s] in course run [%s]", - username, - course_id, - ) - raise CourseEnrollmentError(str(error)) # lint-amnesty, pylint: disable=raise-missing-from - kwargs = { - "username": username, - "course_id": str(course_id), - "enterprise_customer_uuid": explicit_linked_enterprise, - } - consent_client.provide_consent(**kwargs) + # Filter hook that allows plugins (e.g., Enterprise) to run enrollment-related logic + # and optionally prevent enrollment via CourseEnrollmentViewStarted.PreventEnrollment. + try: + # .. filter_implemented_name: CourseEnrollmentViewStarted + # .. filter_type: org.openedx.learning.course.enrollment.view.started.v1 + CourseEnrollmentViewStarted.run_filter( + user=user, + course_key=course_id, + requester_is_backend_service=has_api_key_permissions, + ) + except CourseEnrollmentViewStarted.PreventEnrollment as exc: + raise EnrollmentNotAllowed(str(exc)) from exc enrollment_attributes = request.data.get("enrollment_attributes") force_enrollment = request.data.get("force_enrollment") diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 748858b7015a..41ee84efcee7 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -22,3 +22,7 @@ Django<6.0 # elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html # See https://github.com/openedx/edx-platform/issues/35126 for more info elasticsearch<7.14.0 + +# 2026-07-06: Constrain social-auth-* packages to the latest version compatible with +# edx-drf-extensions<=10.6.0. (Newer versions require a POST instead of a GET.) +# Tracking issue: https://github.com/openedx/edx-drf-extensions/issues/561 diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 1bb69b0fb57f..1f1147bba8f9 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -42,7 +42,7 @@ django-stubs<6 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==8.3.0 +edx-enterprise==8.4.0 # Date: 2023-07-26 # Our legacy Sass code is incompatible with anything except this ancient libsass version. @@ -92,12 +92,13 @@ py2neo<2022 # Issue for unpinning: https://github.com/openedx/edx-platform/issues/35265 pymongo<4.4.1 -# Date: 2024-08-06 -# social-auth-app-django 5.4.2 introduces a new migration that will not play nicely with large installations. This will touch -# user tables, which are quite large, especially on instances like edx.org. -# We are pinning this until after all the smaller migrations get handled and then we can migrate this all at once. -# Issue for unpinning: https://github.com/edx/edx-arch-experiments/issues/760 +# Date: 2026-07-13 +# social-auth-app-django>=5.9.0 requires Django>=5.2, but edx-platform is still on Django<5.0. +# social-auth-core>=4.9.1 requires cryptography>=46.0.7 which conflicts with other deps at older versions. +# The corresponding lines are stripped from common_constraints.txt via sed in the Makefile. +# Remove these overrides after the Django 5.2 upgrade. social-auth-app-django<=5.4.1 +social-auth-core<4.9.1 # # Date: 2024-10-14 # # The edx-enterprise is currently using edx-rest-api-client==5.7.1, which needs to be updated first. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index e1bbf5b75ca2..21814d2c03ea 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -473,7 +473,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==8.3.0 +edx-enterprise==8.4.0 # via # -c requirements/constraints.txt # -r requirements/edx/kernel.in @@ -1126,6 +1126,7 @@ social-auth-app-django==5.4.1 # edx-enterprise social-auth-core==4.7.0 # via + # -c requirements/constraints.txt # -r requirements/edx/kernel.in # edx-auth-backends # social-auth-app-django diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index bca03a007cba..4c4c7d9edc1c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -747,7 +747,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==8.3.0 +edx-enterprise==8.4.0 # via # -c requirements/constraints.txt # -r requirements/edx/doc.txt @@ -1947,6 +1947,7 @@ social-auth-app-django==5.4.1 # edx-enterprise social-auth-core==4.7.0 # via + # -c requirements/constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-auth-backends diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 1afd665d300d..c75b28fc8838 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -557,7 +557,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==8.3.0 +edx-enterprise==8.4.0 # via # -c requirements/constraints.txt # -r requirements/edx/base.txt @@ -1377,6 +1377,7 @@ social-auth-app-django==5.4.1 # edx-enterprise social-auth-core==4.7.0 # via + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 5ac6f2663600..35fc5d76d58e 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -578,7 +578,7 @@ edx-drf-extensions==10.6.0 # edxval # enterprise-integrated-channels # openedx-learning -edx-enterprise==8.3.0 +edx-enterprise==8.4.0 # via # -c requirements/constraints.txt # -r requirements/edx/base.txt @@ -1485,6 +1485,7 @@ social-auth-app-django==5.4.1 # edx-enterprise social-auth-core==4.7.0 # via + # -c requirements/constraints.txt # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django