Skip to content

Commit dedbbe7

Browse files
committed
feat: make marketing_emails_opt_in optional
1 parent 4369055 commit dedbbe7

File tree

4 files changed

+355
-15
lines changed

4 files changed

+355
-15
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated migration for adding optional email checkbox configuration fields
2+
3+
from django.db import migrations, models
4+
import django.utils.translation
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('third_party_auth', '0013_default_site_id_wrapper_function'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='samlproviderconfig',
16+
name='marketing_emails_opt_in_optional',
17+
field=models.BooleanField(
18+
default=False,
19+
help_text=django.utils.translation.gettext_lazy(
20+
"If enabled, the marketing emails opt-in checkbox will be optional for users "
21+
"registering via this provider instead of required. When disabled, marketing email opt-in "
22+
"is determined by the global MARKETING_EMAILS_OPT_IN setting."
23+
),
24+
),
25+
),
26+
]

common/djangoapps/third_party_auth/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,14 @@ class SAMLProviderConfig(ProviderConfig):
745745
"immediately after authenticating with the third party instead of the login page."
746746
),
747747
)
748+
marketing_emails_opt_in_optional = models.BooleanField(
749+
default=False,
750+
help_text=_(
751+
"If enabled, the marketing emails opt-in checkbox will be optional for users "
752+
"registering via this provider instead of required. When disabled, marketing email opt-in "
753+
"is determined by the global MARKETING_EMAILS_OPT_IN setting."
754+
),
755+
)
748756
other_settings = models.TextField(
749757
verbose_name="Advanced settings", blank=True,
750758
help_text=(

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

Lines changed: 125 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import copy
6+
import logging
67
import re
78
from importlib import import_module
89

@@ -18,6 +19,7 @@
1819
from eventtracking import tracker
1920

2021
from common.djangoapps import third_party_auth
22+
from common.djangoapps.third_party_auth.models import SAMLProviderConfig
2123
from common.djangoapps.edxmako.shortcuts import marketing_link
2224
from common.djangoapps.student.models import CourseEnrollmentAllowed, UserProfile, email_exists_or_retired
2325
from common.djangoapps.util.password_policy_validators import (
@@ -36,6 +38,9 @@
3638
from openedx.features.enterprise_support.api import enterprise_customer_for_request
3739

3840

41+
log = logging.getLogger(__name__)
42+
43+
3944
class TrueCheckbox(widgets.CheckboxInput):
4045
"""
4146
A checkbox widget that only accepts "true" (case-insensitive) as true.
@@ -410,6 +415,7 @@ def __init__(self):
410415
field_order.extend(sorted(difference))
411416

412417
self.field_order = field_order
418+
self.request = None # Will be set by get_registration_form
413419

414420
def get_registration_form(self, request):
415421
"""Return a description of the registration form.
@@ -426,6 +432,7 @@ def get_registration_form(self, request):
426432
Returns:
427433
HttpResponse
428434
"""
435+
self.request = request
429436
form_desc = FormDescription("post", self._get_registration_submit_url(request))
430437
self._apply_third_party_auth_overrides(request, form_desc)
431438

@@ -703,13 +710,63 @@ def _add_marketing_emails_opt_in_field(self, form_desc, required=False):
703710
platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
704711
)
705712

713+
# Check if marketing opt-in should be optional for this SAML provider
714+
# If the SAML provider config says the field is optional, set default to False
715+
# and clear any existing field overrides that may have been set by the TPA provider
716+
default_value = True # Default: checkbox is checked
717+
field_required = required
718+
719+
if self.request and third_party_auth.is_enabled():
720+
running_pipeline = third_party_auth.pipeline.get(self.request)
721+
if running_pipeline:
722+
try:
723+
from common.djangoapps.third_party_auth.models import SAMLProviderConfig
724+
# idp_name can be in kwargs directly or in kwargs['details']
725+
saml_provider_name = running_pipeline.get('kwargs', {}).get('idp_name')
726+
if not saml_provider_name:
727+
saml_provider_name = running_pipeline.get('kwargs', {}).get('details', {}).get('idp_name')
728+
729+
if saml_provider_name:
730+
try:
731+
# Query the SAML provider config
732+
saml_config = SAMLProviderConfig.objects.get(
733+
slug=saml_provider_name
734+
)
735+
736+
if saml_config.marketing_emails_opt_in_optional:
737+
log.info(
738+
"SAML provider %s has marketing_emails_opt_in_optional=True, "
739+
"setting default to False and required to False",
740+
saml_provider_name
741+
)
742+
default_value = False # When optional, user opts out by default
743+
field_required = False # Make field optional
744+
745+
# Set field override to ensure our SAML-specific values are used
746+
# This will override any values set by the TPA provider, or create
747+
# a new override if one doesn't exist
748+
log.info(
749+
"Setting field override for marketing_emails_opt_in to defaultValue=False, required=False"
750+
)
751+
form_desc._field_overrides['marketing_emails_opt_in'] = {
752+
'defaultValue': False,
753+
'required': False
754+
}
755+
except SAMLProviderConfig.DoesNotExist:
756+
log.debug(
757+
"SAML provider config not found for idp_name: %s",
758+
saml_provider_name
759+
)
760+
except (ImportError, Exception) as e: # pylint: disable=broad-except
761+
log.debug("Error checking SAML provider config in field handler: %s", str(e))
762+
706763
form_desc.add_field(
707764
'marketing_emails_opt_in',
708765
label=opt_in_label,
709766
field_type="checkbox",
710767
exposed=True,
711-
default=True, # the checkbox will automatically be checked; meaning user has opted in
712-
required=required,
768+
default=default_value,
769+
required=field_required,
713770
)
714771

715772
def _add_field_with_configurable_select_options(self, field_name, field_label, form_desc, required=False):
@@ -1150,22 +1207,73 @@ def _apply_third_party_auth_overrides(self, request, form_desc):
11501207

11511208
for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS:
11521209
if field_name in field_overrides:
1153-
form_desc.override_field_properties(
1154-
field_name, default=field_overrides[field_name]
1155-
)
1156-
1157-
if (
1158-
field_name not in ['terms_of_service', 'honor_code'] and
1159-
field_overrides[field_name] and
1160-
hide_registration_fields_except_tos
1161-
):
1210+
# Special handling for marketing_emails_opt_in:
1211+
# If SAML provider config has set marketing_emails_opt_in_optional=True,
1212+
# don't let the provider's get_register_form_data override the default
1213+
skip_override = False
1214+
if field_name == 'marketing_emails_opt_in':
1215+
try:
1216+
from common.djangoapps.third_party_auth.models import SAMLProviderConfig
1217+
# idp_name can be in kwargs directly or in kwargs['details']
1218+
saml_provider_name = running_pipeline.get('kwargs', {}).get('idp_name')
1219+
if not saml_provider_name:
1220+
saml_provider_name = running_pipeline.get('kwargs', {}).get('details', {}).get('idp_name')
1221+
if saml_provider_name:
1222+
try:
1223+
# Try to find the SAML provider config
1224+
# First try with current_set(), then fall back to direct query
1225+
try:
1226+
saml_config = SAMLProviderConfig.objects.current_set().get(
1227+
slug=saml_provider_name
1228+
)
1229+
except Exception: # pylint: disable=broad-except
1230+
# Fallback to direct query without current_set()
1231+
saml_config = SAMLProviderConfig.objects.get(
1232+
slug=saml_provider_name
1233+
)
1234+
1235+
if saml_config.marketing_emails_opt_in_optional:
1236+
log.debug(
1237+
"Skipping provider override for marketing_emails_opt_in "
1238+
"due to SAML config for provider: %s",
1239+
saml_provider_name
1240+
)
1241+
skip_override = True
1242+
except SAMLProviderConfig.DoesNotExist:
1243+
log.debug(
1244+
"SAML provider config not found for idp_name: %s",
1245+
saml_provider_name
1246+
)
1247+
except (ImportError, Exception) as e: # pylint: disable=broad-except
1248+
log.debug("Error checking SAML provider config: %s", str(e))
1249+
1250+
if not skip_override:
11621251
form_desc.override_field_properties(
1163-
field_name,
1164-
field_type="hidden",
1165-
label="",
1166-
instructions="",
1252+
field_name, default=field_overrides[field_name]
11671253
)
11681254

1255+
if (
1256+
field_name not in ['terms_of_service', 'honor_code'] and
1257+
field_overrides[field_name] and
1258+
hide_registration_fields_except_tos
1259+
):
1260+
# When hiding a field, set default to False for checkbox fields
1261+
# like marketing_emails_opt_in to avoid auto-opting users in
1262+
field_default = field_overrides[field_name]
1263+
if field_name == 'marketing_emails_opt_in':
1264+
field_default = False
1265+
log.info(
1266+
"Hiding marketing_emails_opt_in field and setting default to False"
1267+
)
1268+
1269+
form_desc.override_field_properties(
1270+
field_name,
1271+
field_type="hidden",
1272+
default=field_default,
1273+
label="",
1274+
instructions="",
1275+
)
1276+
11691277
# Hide the confirm_email field
11701278
form_desc.override_field_properties(
11711279
"confirm_email",
@@ -1195,3 +1303,5 @@ def _apply_third_party_auth_overrides(self, request, form_desc):
11951303
default=current_provider.name if current_provider.name else "Third Party",
11961304
required=False,
11971305
)
1306+
1307+

0 commit comments

Comments
 (0)