Skip to content

Commit ac4a589

Browse files
committed
✨(backend) add associate_by_email step to social auth pipeline
Add social auth pipeline step `associate_by_email` when RENATER_FER_SAML switch is active.
1 parent 91647e6 commit ac4a589

File tree

4 files changed

+118
-5
lines changed

4 files changed

+118
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
1313
- Remove all legacy Cloudfront code and mentions
1414
- Remove all legacy Lambdas
1515

16+
### Changed
17+
18+
- Use associate_by_email when RENATER_FER_SAML switch is enabled
19+
1620
## [5.11.1] - 2025-07-17
1721

1822
### Fixed

src/backend/marsha/account/social_pipeline/__init__.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,20 @@
2424
set(_steps_mapping.keys()) & set(DEFAULT_EDU_FED_AUTH_PIPELINE)
2525
) == len(_steps_mapping)
2626

27+
MARSHA_DEFAULT_AUTH_PIPELINE = []
28+
for step in DEFAULT_EDU_FED_AUTH_PIPELINE:
29+
if step == "social_edu_federation.pipeline.user.create_user":
30+
# Associate by email must be before user creation
31+
MARSHA_DEFAULT_AUTH_PIPELINE.append(
32+
"marsha.account.social_pipeline.social_auth.associate_by_email"
33+
)
2734

28-
MARSHA_DEFAULT_AUTH_PIPELINE = tuple(
29-
_steps_mapping.get(step, step) for step in DEFAULT_EDU_FED_AUTH_PIPELINE
30-
) + (
35+
if step in _steps_mapping:
36+
MARSHA_DEFAULT_AUTH_PIPELINE.append(_steps_mapping[step])
37+
else:
38+
MARSHA_DEFAULT_AUTH_PIPELINE.append(step)
39+
40+
MARSHA_DEFAULT_AUTH_PIPELINE += [
3141
"marsha.account.social_pipeline.organization.create_organization_from_saml",
3242
"marsha.account.social_pipeline.playlist.create_playlist_from_saml",
33-
)
43+
]

src/backend/marsha/account/social_pipeline/social_auth.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Marsha's specific Python Social Auth pipeline steps for authentication."""
22

33
from social_core.pipeline.social_auth import (
4+
associate_by_email as social_associate_by_email,
45
auth_allowed as social_auth_allowed,
56
social_details as social_social_details,
67
)
@@ -47,3 +48,13 @@ def social_details(backend, details, response, *args, **kwargs):
4748
details["last_name"] = fullname
4849

4950
return results_dict
51+
52+
53+
def associate_by_email(backend, details, *args, user=None, **kwargs):
54+
"""
55+
Associates user by email when the RENATER_FER_SAML feature is enabled.
56+
"""
57+
if not waffle.switch_is_active(RENATER_FER_SAML):
58+
return None
59+
60+
return social_associate_by_email(backend, details, user, *args, **kwargs)

src/backend/marsha/account/tests/test_social_pipeline_social_auth.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
from waffle.testutils import override_switch
1010

1111
from marsha.account.social_pipeline import MARSHA_DEFAULT_AUTH_PIPELINE
12-
from marsha.account.social_pipeline.social_auth import auth_allowed, social_details
12+
from marsha.account.social_pipeline.social_auth import (
13+
associate_by_email,
14+
auth_allowed,
15+
social_details,
16+
)
1317
from marsha.core.defaults import RENATER_FER_SAML
1418

1519

@@ -188,3 +192,87 @@ def test_social_details_step_without_names(self):
188192
social_details(backend, details, response=None),
189193
{"details": {"first_name": "", "last_name": ""}},
190194
)
195+
196+
197+
class AssociateByEmailPipelineTestCase(TestCase):
198+
"""Test case for the `associate_by_email` pipeline step."""
199+
200+
maxDiff = None
201+
202+
def test_marsha_pipeline_includes_associate_by_email_step(self):
203+
"""Asserts the `associate_by_email` step is in Marsha's default pipeline."""
204+
self.assertListEqual(
205+
[
206+
"marsha.account.social_pipeline.social_auth.social_details",
207+
"social_core.pipeline.social_auth.social_uid",
208+
"marsha.account.social_pipeline.social_auth.auth_allowed",
209+
"social_core.pipeline.social_auth.social_user",
210+
"social_core.pipeline.user.get_username",
211+
"marsha.account.social_pipeline.social_auth.associate_by_email",
212+
"social_edu_federation.pipeline.user.create_user",
213+
"social_core.pipeline.social_auth.associate_user",
214+
"social_core.pipeline.social_auth.load_extra_data",
215+
"social_core.pipeline.user.user_details",
216+
"marsha.account.social_pipeline.organization.create_organization_from_saml",
217+
"marsha.account.social_pipeline.playlist.create_playlist_from_saml",
218+
],
219+
MARSHA_DEFAULT_AUTH_PIPELINE,
220+
msg="MARSHA_DEFAULT_AUTH_PIPELINE must be fixed to include `associate_by_email`",
221+
)
222+
223+
self.assertListEqual(
224+
[
225+
"marsha.account.social_pipeline.social_auth.social_details",
226+
"social_core.pipeline.social_auth.social_uid",
227+
"marsha.account.social_pipeline.social_auth.auth_allowed",
228+
"social_core.pipeline.social_auth.social_user",
229+
"social_core.pipeline.user.get_username",
230+
"marsha.account.social_pipeline.social_auth.associate_by_email",
231+
"social_edu_federation.pipeline.user.create_user",
232+
"social_core.pipeline.social_auth.associate_user",
233+
"social_core.pipeline.social_auth.load_extra_data",
234+
"social_core.pipeline.user.user_details",
235+
"marsha.account.social_pipeline.organization.create_organization_from_saml",
236+
"marsha.account.social_pipeline.playlist.create_playlist_from_saml",
237+
],
238+
settings.SOCIAL_AUTH_SAML_FER_PIPELINE,
239+
msg="SOCIAL_AUTH_SAML_FER_PIPELINE must be fixed to include `associate_by_email`",
240+
)
241+
242+
@override_switch(RENATER_FER_SAML, active=True)
243+
def test_associate_by_email_step_enabled(self):
244+
"""Asserts the email is used to associate the user when the waffle switch is enabled."""
245+
strategy = load_strategy()
246+
backend = load_backend(strategy, "saml_fer", None)
247+
with mock.patch(
248+
"marsha.account.social_pipeline.social_auth.social_associate_by_email"
249+
) as social_associate_by_email_mock:
250+
details = {"not": "relevant"}
251+
252+
associate_by_email(backend, details, strategy, 42, some_kwargs=18)
253+
254+
social_associate_by_email_mock.assert_called_once_with(
255+
backend,
256+
details,
257+
None,
258+
strategy,
259+
42,
260+
some_kwargs=18,
261+
)
262+
263+
@override_switch(RENATER_FER_SAML, active=False)
264+
def test_associate_by_email_step_disabled(self):
265+
"""
266+
Asserts the email is not used to associate the user when the waffle switch is disabled.
267+
"""
268+
strategy = load_strategy()
269+
backend = load_backend(strategy, "saml_fer", None)
270+
with mock.patch(
271+
"marsha.account.social_pipeline.social_auth.social_associate_by_email"
272+
) as social_associate_by_email_mock:
273+
details = {"not": "relevant"}
274+
275+
self.assertIsNone(
276+
associate_by_email(backend, details, strategy, 42, some_kwargs=18)
277+
)
278+
self.assertFalse(social_associate_by_email_mock.called)

0 commit comments

Comments
 (0)