Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Unreleased
----------
* nothing unreleased

[6.8.5] - 2026-03-31
---------------------
* feat: add AccountSettingsEnterpriseReadOnlyFieldsStep pipeline step (ENT-11510)

[6.8.4] - 2026-03-31
--------------------
* fix: hard delete customer admin records from API
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__ = "6.8.4"
__version__ = "6.8.5"
Empty file added enterprise/filters/__init__.py
Empty file.
105 changes: 105 additions & 0 deletions enterprise/filters/accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Pipeline step for determining read-only account settings fields.
"""
from openedx_filters.filters import PipelineStep
from social_django.models import UserSocialAuth

from django.conf import settings

try:
from common.djangoapps import third_party_auth
except ImportError:
third_party_auth = None

from enterprise.models import EnterpriseCustomerIdentityProvider, EnterpriseCustomerUser


class AccountSettingsEnterpriseReadOnlyFieldsStep(PipelineStep):
"""
Adds SSO-managed fields to the read-only account settings fields set.

This step is intended to be registered as a pipeline step for the
``org.openedx.learning.account.settings.read_only_fields.requested.v1`` filter.

When a user is linked to an enterprise customer whose SSO identity provider has
``sync_learner_profile_data`` enabled, the fields listed in
``settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS`` are added to ``readonly_fields``.
The ``"name"`` field is only added when the user has an existing ``UserSocialAuth``
record for the enterprise IdP backend.
"""

def run_filter(self, readonly_fields, user): # pylint: disable=arguments-differ
"""
Add enterprise SSO-managed fields to the read-only fields set.

The original code migrated from openedx-platform can be distilled into 3 logical branches:

1. If NO identify provider (IdP) has sync enabled → no readonly fields added.
2. If one or more IdPs have sync enabled, AND user has social auth → append ALL readonly fields.
3. If one or more IdPs have sync enabled, AND user has NO social auth → append readonly fields MINUS 'name'.

Each return statement below is marked with the corresponding branch number.

Arguments:
readonly_fields (set): current set of read-only account field names.
user (User): the Django User whose account settings are being updated.

Returns:
dict: updated pipeline data with ``readonly_fields`` key.
"""
enterprise_customer_user = (
EnterpriseCustomerUser.objects.filter(user_id=user.id)
.order_by('-active', '-modified')
.select_related('enterprise_customer')
.first()
)
if not enterprise_customer_user:
# Logical branch #1 (early exit)
return {"readonly_fields": readonly_fields, "user": user}

enterprise_customer = enterprise_customer_user.enterprise_customer

idp_records = list(
EnterpriseCustomerIdentityProvider.objects
.filter(enterprise_customer=enterprise_customer)
)

# Track whether any IdP for the customer is configured to sync learner profile data. If none are, then we can
# safely allow all fields to be editable since they won't get overwritten by the sync process
sync_learner_profile_data = False

# Accumulate a list of all identity providers for the customer. If the learner does NOT have any social auth
# account configured with these backends, then we can safely allow them to edit the 'name' field (full name)
provider_backend_names = []

for idp in idp_records:
identity_provider = third_party_auth.provider.Registry.get(
provider_id=idp.provider_id
)
if identity_provider and getattr(identity_provider, 'sync_learner_profile_data', False):
sync_learner_profile_data = True

backend_name = getattr(identity_provider, 'backend_name', None)
if backend_name:
provider_backend_names.append(backend_name)

# If none of the IdPs for the customer are configured to sync, allow the fields to be editable
if not sync_learner_profile_data:
# Logical branch #1
return {"readonly_fields": readonly_fields, "user": user}

# Determine if the learner has social auth configured.
has_social_auth = False
if provider_backend_names:
has_social_auth = UserSocialAuth.objects.filter(
provider__in=provider_backend_names, user=user
).exists()

enterprise_readonly = set(getattr(settings, 'ENTERPRISE_READONLY_ACCOUNT_FIELDS', []))

# If the learner does NOT have social auth configured, then at least allow them to edit their name.
if not has_social_auth:
enterprise_readonly = enterprise_readonly - {'name'}

# Logical branch #2 and #3
return {"readonly_fields": readonly_fields | enterprise_readonly, "user": user}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
import csv
import logging

from social_django.models import UserSocialAuth

from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
from django.db import transaction

try:
from social_django.models import UserSocialAuth
except ImportError:
UserSocialAuth = None

logger = logging.getLogger(__name__)


Expand Down
13 changes: 3 additions & 10 deletions enterprise/tpa_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,14 @@
from datetime import datetime
from logging import getLogger

from social_core.pipeline.partial import partial
from social_django.models import UserSocialAuth

from django.urls import reverse

from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser
from enterprise.utils import get_identity_provider, get_social_auth_from_idp

try:
from social_core.pipeline.partial import partial
except ImportError:
from enterprise.decorators import null_decorator as partial

try:
from social_django.models import UserSocialAuth
except ImportError:
UserSocialAuth = None

try:
from common.djangoapps.third_party_auth.provider import Registry
except ImportError:
Expand Down
6 changes: 1 addition & 5 deletions enterprise/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from edx_django_utils.cache import TieredCache
from edx_django_utils.cache import get_cache_key as get_django_cache_key
from slumber.exceptions import HttpClientError
from social_django.models import UserSocialAuth

from django.apps import apps
from django.conf import settings
Expand Down Expand Up @@ -88,11 +89,6 @@
except ImportError:
get_url = None

try:
from social_django.models import UserSocialAuth
except ImportError:
UserSocialAuth = None

# Only create manual enrollments if running in edx-platform
try:
from common.djangoapps.student.api import (
Expand Down
2 changes: 2 additions & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jsondiff
jsonfield
openedx-atlas
openedx-events
openedx-filters
paramiko
path.py
pillow
Expand All @@ -41,6 +42,7 @@ requests
rules
slumber
snowflake-connector-python
social-auth-app-django
stevedore
testfixtures
unicodecsv
Expand Down
29 changes: 29 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ defusedxml==0.7.1
# -r requirements/test-master.txt
# -r requirements/test.txt
# djangorestframework-xml
# python3-openid
# social-auth-core
diff-cover==10.2.0
# via -r requirements/test.txt
dill==0.4.1
Expand Down Expand Up @@ -239,6 +241,8 @@ django==5.2.12
# edx-toggles
# jsonfield
# openedx-events
# openedx-filters
# social-auth-app-django
django-cache-memoize==0.2.1
# via
# -r requirements/doc.txt
Expand Down Expand Up @@ -413,6 +417,7 @@ edx-opaque-keys[django]==3.1.0
# edx-ccx-keys
# edx-drf-extensions
# openedx-events
# openedx-filters
edx-rbac==3.0.0
# via
# -r requirements/doc.txt
Expand Down Expand Up @@ -671,6 +676,8 @@ oauthlib==3.3.1
# -r requirements/test-master.txt
# -r requirements/test.txt
# django-oauth-toolkit
# requests-oauthlib
# social-auth-core
openedx-atlas==0.7.0
# via
# -r requirements/doc.txt
Expand All @@ -681,6 +688,11 @@ openedx-events==11.1.0
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
openedx-filters==2.1.0
# via
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
packaging==26.0
# via
# -r requirements/doc.txt
Expand Down Expand Up @@ -823,6 +835,7 @@ pyjwt[crypto]==2.12.1
# edx-rest-api-client
# firebase-admin
# snowflake-connector-python
# social-auth-core
pylint==3.3.9
# via
# -c requirements/constraints.txt
Expand Down Expand Up @@ -898,6 +911,10 @@ python-slugify==8.0.4
# -r requirements/test-master.txt
# -r requirements/test.txt
# code-annotations
python3-openid==3.2.0
# via
# -r requirements/test.txt
# social-auth-core
pytz==2026.1.post1
# via
# -r requirements/doc.txt
Expand Down Expand Up @@ -930,13 +947,19 @@ requests==2.32.5
# edx-rest-api-client
# google-api-core
# google-cloud-storage
# requests-oauthlib
# requests-toolbelt
# responses
# sailthru-client
# slumber
# snowflake-connector-python
# social-auth-core
# sphinx
# twine
requests-oauthlib==2.0.0
# via
# -r requirements/test.txt
# social-auth-core
requests-toolbelt==1.0.0
# via twine
responses==0.26.0
Expand Down Expand Up @@ -1007,6 +1030,12 @@ snowflake-connector-python==4.3.0
# -r requirements/doc.txt
# -r requirements/test-master.txt
# -r requirements/test.txt
social-auth-app-django==5.7.0
# via -r requirements/test.txt
social-auth-core==4.8.5
# via
# -r requirements/test.txt
# social-auth-app-django
sortedcontainers==2.4.0
# via
# -r requirements/doc.txt
Expand Down
10 changes: 5 additions & 5 deletions requirements/doc.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
-r test-master.txt

doc8 # reStructuredText style checker
sphinx-book-theme # Common theme for all Open edX projects
readme_renderer # Validates README.rst for usage on PyPI
Sphinx # Documentation builder
docutils
factory-boy
edx-braze-client
pytest
edx-braze-client
factory-boy
readme_renderer # Validates README.rst for usage on PyPI
Sphinx # Documentation builder
sphinx-book-theme # Common theme for all Open edX projects
Loading
Loading