Skip to content

Commit fedda7e

Browse files
committed
feat: Add configurable registration field overrides for SAML providers
1 parent 4369055 commit fedda7e

4 files changed

Lines changed: 421 additions & 8 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.13 on 2026-02-18 00:00
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('third_party_auth', '0013_default_site_id_wrapper_function'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='samlproviderconfig',
15+
name='other_settings',
16+
field=models.TextField(blank=True, help_text='For advanced use cases, enter a JSON object with addtional configuration. The tpa-saml backend supports {"requiredEntitlements": ["urn:..."]}, which can be used to require the presence of a specific eduPersonEntitlement, and {"extra_field_definitions": [{"name": "...", "urn": "..."},...]}, which can be used to define registration form fields and the URNs that can be used to retrieve the relevant values from the SAML response, and {"registration_field_overrides": {"field_name": "required|optional|hidden"}}, which can be used to control the visibility and requirement status of specific registration form fields (e.g., marketing_emails_opt_in, research). Custom provider types, as selected in the "Identity Provider Type" field, may make use of the information stored in this field for additional configuration.', verbose_name='Advanced settings'),
17+
),
18+
]

common/djangoapps/third_party_auth/models.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -753,9 +753,12 @@ class SAMLProviderConfig(ProviderConfig):
753753
'which can be used to require the presence of a specific eduPersonEntitlement, '
754754
'and {"extra_field_definitions": [{"name": "...", "urn": "..."},...]}, which can be '
755755
'used to define registration form fields and the URNs that can be used to retrieve '
756-
'the relevant values from the SAML response. Custom provider types, as selected '
757-
'in the "Identity Provider Type" field, may make use of the information stored '
758-
'in this field for additional configuration.'
756+
'the relevant values from the SAML response, '
757+
'and {"registration_field_overrides": {"field_name": "required|optional|hidden"}}, '
758+
'which can be used to control the visibility and requirement status of specific '
759+
'registration form fields (e.g., marketing_emails_opt_in, research). '
760+
'Custom provider types, as selected in the "Identity Provider Type" field, may make '
761+
'use of the information stored in this field for additional configuration.'
759762
)
760763
)
761764
archived = models.BooleanField(default=False)
@@ -838,7 +841,39 @@ def get_setting(self, name):
838841
return other_settings[name]
839842
raise KeyError
840843

841-
def get_config(self, backend):
844+
def get_registration_field_overrides(self):
845+
"""
846+
Get registration field visibility/requirement overrides for this provider.
847+
848+
This allows SAML providers to configure whether certain fields
849+
(like marketing_emails_opt_in or research) should be required,
850+
optional, or hidden on the registration form.
851+
852+
Returns:
853+
dict: Mapping of field names to settings ("required", "optional", "hidden")
854+
Returns empty dict if no overrides are configured.
855+
856+
Example:
857+
{
858+
"marketing_emails_opt_in": "optional",
859+
"research": "hidden"
860+
}
861+
"""
862+
try:
863+
overrides = self.get_setting('registration_field_overrides')
864+
# Validate override values
865+
valid_values = {'required', 'optional', 'hidden'}
866+
if isinstance(overrides, dict):
867+
return {
868+
field: value
869+
for field, value in overrides.items()
870+
if value in valid_values
871+
}
872+
return {}
873+
except KeyError:
874+
return {}
875+
876+
def get_config(self):
842877
"""
843878
Return a SAMLIdentityProvider instance for use by SAMLAuthBackend.
844879
@@ -898,7 +933,7 @@ def get_config(self, backend):
898933
SAMLConfiguration.current(self.site.id, 'default')
899934
)
900935
idp_class = get_saml_idp_class(self.identity_provider_type)
901-
return idp_class(backend, self.slug, **conf)
936+
return idp_class(self.slug, **conf)
902937

903938

904939
class SAMLProviderData(models.Model):

openedx/core/djangoapps/user_authn/views/registration_form.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ def __init__(self):
401401

402402
field_order = configuration_helpers.get_value('REGISTRATION_FIELD_ORDER')
403403
if not field_order:
404-
field_order = settings.REGISTRATION_FIELD_ORDER or valid_fields
404+
field_order = getattr(settings, 'REGISTRATION_FIELD_ORDER', None) or valid_fields
405405
# Check that all of the valid_fields are in the field order and vice versa,
406406
# if not append missing fields at end of field order
407407
if set(valid_fields) != set(field_order):
@@ -1111,6 +1111,57 @@ def _add_terms_of_service_field(self, form_desc, required=True):
11111111
},
11121112
)
11131113

1114+
def _apply_provider_field_overrides(self, form_desc, provider_overrides, field_name):
1115+
"""
1116+
Apply SAML provider-specific overrides to a registration form field.
1117+
1118+
This allows SAML providers to configure whether certain fields
1119+
(like marketing_emails_opt_in or research) should be required,
1120+
optional, or hidden.
1121+
1122+
Arguments:
1123+
form_desc (FormDescription): The registration form description to modify
1124+
provider_overrides (dict): Field override configuration from provider
1125+
field_name (str): Name of the field to potentially override
1126+
1127+
Returns:
1128+
bool: True if the field was overridden, False otherwise
1129+
"""
1130+
if field_name not in provider_overrides:
1131+
return False
1132+
1133+
override_value = provider_overrides[field_name]
1134+
1135+
if override_value == "hidden":
1136+
# Hide the field completely
1137+
form_desc.override_field_properties(
1138+
field_name,
1139+
field_type="hidden",
1140+
required=False,
1141+
label="",
1142+
instructions="",
1143+
default=""
1144+
)
1145+
return True
1146+
1147+
elif override_value == "required":
1148+
# Make the field required
1149+
form_desc.override_field_properties(
1150+
field_name,
1151+
required=True
1152+
)
1153+
return True
1154+
1155+
elif override_value == "optional":
1156+
# Make the field optional (ensure it's not required)
1157+
form_desc.override_field_properties(
1158+
field_name,
1159+
required=False
1160+
)
1161+
return True
1162+
1163+
return False
1164+
11141165
def _apply_third_party_auth_overrides(self, request, form_desc):
11151166
"""Modify the registration form if the user has authenticated with a third-party provider.
11161167
If a user has successfully authenticated with a third-party provider,
@@ -1195,3 +1246,26 @@ def _apply_third_party_auth_overrides(self, request, form_desc):
11951246
default=current_provider.name if current_provider.name else "Third Party",
11961247
required=False,
11971248
)
1249+
1250+
# Apply provider-specific field visibility overrides
1251+
# This allows SAML providers to configure whether fields like
1252+
# marketing_emails_opt_in or research should be shown/required
1253+
provider_field_overrides = current_provider.get_registration_field_overrides()
1254+
1255+
if provider_field_overrides:
1256+
# List of fields that can be overridden by the provider
1257+
overrideable_fields = [
1258+
'marketing_emails_opt_in',
1259+
'research',
1260+
# Add more fields here as needed
1261+
]
1262+
1263+
for field_name in overrideable_fields:
1264+
# Only apply overrides for fields that are actually in the form
1265+
# This check prevents errors if a field isn't enabled platform-wide
1266+
if any(f['name'] == field_name for f in form_desc.fields):
1267+
self._apply_provider_field_overrides(
1268+
form_desc,
1269+
provider_field_overrides,
1270+
field_name
1271+
)

0 commit comments

Comments
 (0)