-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: logout other sessions on email change
- Loading branch information
1 parent
129940b
commit a86486b
Showing
10 changed files
with
264 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,14 +2,15 @@ | |
|
||
from unittest import skipUnless | ||
from unittest.mock import patch | ||
from django.test.utils import override_settings | ||
|
||
from edx_toggles.toggles.testutils import override_waffle_flag | ||
|
||
from common.djangoapps.student.models import CourseEnrollmentCelebration, PendingNameChange, UserProfile | ||
from common.djangoapps.student.signals.signals import USER_EMAIL_CHANGED | ||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory, UserProfileFactory | ||
from lms.djangoapps.courseware.toggles import COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES | ||
from openedx.core.djangolib.testing.utils import skip_unless_lms | ||
from openedx.core.djangolib.testing.utils import skip_unless_lms, get_mock_request | ||
from openedx.features.name_affirmation_api.utils import is_name_affirmation_installed | ||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order | ||
|
||
|
@@ -75,10 +76,26 @@ def test_listen_for_verified_name_approved(self): | |
@patch('common.djangoapps.student.signals.receivers.get_braze_client') | ||
def test_listen_for_user_email_changed(self, mock_get_braze_client): | ||
""" | ||
Ensure that USER_EMAIL_CHANGED signal triggers correct calls to get_braze_client. | ||
Ensure that USER_EMAIL_CHANGED signal triggers correct calls to | ||
get_braze_client and update email in session if ENFORCE_SESSION_EMAIL_MATCH | ||
is enabled. | ||
""" | ||
user = UserFactory(email='[email protected]', username='jdoe') | ||
request = get_mock_request(user=user) | ||
request.session = self.client.session | ||
|
||
USER_EMAIL_CHANGED.send(sender=None, user=user) | ||
# simulating email change | ||
user.email = '[email protected]' | ||
user.save() | ||
|
||
assert mock_get_braze_client.called | ||
with override_settings(ENFORCE_SESSION_EMAIL_MATCH=False): | ||
USER_EMAIL_CHANGED.send(sender=None, user=user, request=request) | ||
|
||
assert mock_get_braze_client.called | ||
assert request.session.get('email', None) is None | ||
|
||
with override_settings(ENFORCE_SESSION_EMAIL_MATCH=True): | ||
USER_EMAIL_CHANGED.send(sender=None, user=user, request=request) | ||
|
||
assert mock_get_braze_client.called | ||
assert request.session.get('email', None) == user.email |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
from common.djangoapps.student.tests.factories import UserFactory | ||
|
||
from ..middleware import ( | ||
EmailChangeSessionInvalidationMiddleware, | ||
SafeCookieData, | ||
SafeSessionMiddleware, | ||
mark_user_change_as_expected, | ||
|
@@ -615,3 +616,126 @@ def test_user_change_with_no_ids(self): | |
request.user = object() | ||
assert len(request.debug_user_changes) == 2 | ||
assert "Changing request user but user has no id." in request.debug_user_changes[1] | ||
|
||
|
||
class TestEmailChangeSessionInvalidationMiddleware(TestSafeSessionsLogMixin, TestCase): | ||
""" | ||
Test class for EmailChangeSessionInvalidationMiddleware | ||
""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.TEST_PASSWORD = 'Password1234' | ||
self.user = UserFactory.create(email='[email protected]', password=self.TEST_PASSWORD) | ||
self.addCleanup(set_current_request, None) | ||
self.request = get_mock_request(self.user) | ||
self.client.response = HttpResponse() | ||
self.client.response.cookies = SimpleCookie() | ||
|
||
@override_settings(ENFORCE_SESSION_EMAIL_MATCH=False) | ||
@patch('openedx.core.djangoapps.safe_sessions.middleware._mark_cookie_for_deletion') | ||
def test_process_request_settings_disabled(self, mock_mark_cookie_for_deletion): | ||
""" | ||
Calls EmailChangeSessionInvalidationMiddleware.process_request when no user is authenticated. | ||
Verifies that session and cookies are not affected. | ||
""" | ||
# Log in the user | ||
self.client.login(email=self.user.email, password=self.TEST_PASSWORD) | ||
self.request.session = self.client.session | ||
|
||
# Ensure no email is stored in the session | ||
self.assertIsNone(self.request.session.get('email')) | ||
|
||
# Call process_request without authenticating a user | ||
EmailChangeSessionInvalidationMiddleware(get_response=lambda request: None).process_request(self.request) | ||
|
||
# Assert that session and cookies are not affected | ||
# Assert that _mark_cookie_for_deletion not called | ||
mock_mark_cookie_for_deletion.assert_not_called() | ||
|
||
@override_settings(ENFORCE_SESSION_EMAIL_MATCH=True) | ||
@patch('openedx.core.djangoapps.safe_sessions.middleware._mark_cookie_for_deletion') | ||
def test_process_request_not_authenticated(self, mock_mark_cookie_for_deletion): | ||
""" | ||
Calls EmailChangeSessionInvalidationMiddleware.process_request when no user is authenticated. | ||
Verifies that session and cookies are not affected. | ||
""" | ||
# Unauthenticated User | ||
self.request.user = AnonymousUser() | ||
|
||
# Call process_request without authenticating a user | ||
EmailChangeSessionInvalidationMiddleware(get_response=lambda request: None).process_request(self.request) | ||
|
||
# Assert that session and cookies are not affected | ||
# Assert that _mark_cookie_for_deletion not called | ||
mock_mark_cookie_for_deletion.assert_not_called() | ||
|
||
@override_settings(ENFORCE_SESSION_EMAIL_MATCH=True) | ||
def test_process_request_authenticated_no_session_email(self): | ||
""" | ||
Calls EmailChangeSessionInvalidationMiddleware.process_request when a user is authenticated | ||
but there is no email in the session. Verifies that session and cookies are not affected. | ||
""" | ||
# Log in the user | ||
self.client.login(email=self.user.email, password=self.TEST_PASSWORD) | ||
self.request.session = self.client.session | ||
|
||
# Ensure no email is stored in the session | ||
self.assertIsNone(self.request.session.get('email')) | ||
|
||
# Call process_request | ||
EmailChangeSessionInvalidationMiddleware(get_response=lambda request: None).process_request(self.request) | ||
|
||
# Assert that the session and cookies are not affected | ||
self.assertIsNone(self.request.session.get('email')) | ||
self.assertEqual(len(self.client.response.cookies), 0) | ||
|
||
@override_settings(ENFORCE_SESSION_EMAIL_MATCH=True) | ||
def test_process_request_authenticated_matching_session_email(self): | ||
""" | ||
Calls EmailChangeSessionInvalidationMiddleware.process_request when a user is authenticated | ||
and the session email matches request.user.email. Verifies that session and cookies are not affected. | ||
""" | ||
# Log in the user | ||
self.client.login(email=self.user.email, password=self.TEST_PASSWORD) | ||
self.request.session = self.client.session | ||
self.request.session['email'] = self.user.email # Set the session email to match request.user.email | ||
self.client.response.set_cookie(settings.SESSION_COOKIE_NAME, 'authenticated') # Add some logged-in cookie | ||
|
||
# Ensure email matches in the session | ||
self.assertEqual(self.request.session.get('email'), self.user.email) | ||
# Ensure session cookie exist | ||
self.assertEqual(len(self.client.response.cookies), 1) | ||
|
||
# Call process_request | ||
EmailChangeSessionInvalidationMiddleware(get_response=lambda request: None).process_request(self.request) | ||
|
||
# Assert that the session and cookies are not affected | ||
self.assertEqual(self.request.session.get('email'), self.user.email) | ||
self.assertEqual(len(self.client.response.cookies), 1) | ||
self.assertEqual(self.client.response.cookies[settings.SESSION_COOKIE_NAME].value, 'authenticated') | ||
|
||
@override_settings(ENFORCE_SESSION_EMAIL_MATCH=True) | ||
@patch('openedx.core.djangoapps.safe_sessions.middleware._mark_cookie_for_deletion') | ||
def test_process_request_email_mismatch(self, mock_mark_cookie_for_deletion): | ||
""" | ||
Calls EmailChangeSessionInvalidationMiddleware.process_request with | ||
a mismatch between session email and user email. Verifies that session | ||
is flushed and cookies are marked for deletion. | ||
""" | ||
# Log in the user | ||
self.client.login(email=self.user.email, password=self.TEST_PASSWORD) | ||
self.request.session = self.client.session | ||
self.request.session['email'] = self.user.email # Set the session email to match request.user.email | ||
self.client.response.set_cookie(settings.SESSION_COOKIE_NAME, 'authenticated') # Add some logged-in cookie | ||
|
||
# Modify session email to create a mismatch (Email changed) | ||
self.request.session['email'] = '[email protected]' | ||
|
||
# Call process_request | ||
EmailChangeSessionInvalidationMiddleware(get_response=lambda request: None).process_request(self.request) | ||
|
||
# Assert that the session is flushed and cookies marked for deletion | ||
mock_mark_cookie_for_deletion.assert_called() | ||
assert self.request.session.get(SESSION_KEY) is None | ||
assert self.request.user == AnonymousUser() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.