Skip to content

Commit 2440234

Browse files
committed
fix: create profile for single-login-exempt users; add group exemption test
Exempt users returned early before UserProfile.objects.get_or_create, so users without a profile never got one created on login while PREVENT_CONCURRENT_LOGINS was enabled. Move the exemption check to only skip set_login_session, and add coverage for SINGLE_LOGIN_EXEMPT_GROUPS and for client1 remaining authenticated after a concurrent login.
1 parent 4c5fd00 commit 2440234

2 files changed

Lines changed: 42 additions & 5 deletions

File tree

common/djangoapps/student/models/user.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,9 +1371,6 @@ def enforce_single_login(sender, request, user, signal, **kwargs): # pylint: di
13711371
to prevent concurrent logins.
13721372
"""
13731373
if settings.FEATURES.get('PREVENT_CONCURRENT_LOGINS', False):
1374-
if user and _is_single_login_exempt(user):
1375-
# Shared service/automation accounts may hold concurrent sessions.
1376-
return
13771374
if signal == user_logged_in:
13781375
key = request.session.session_key
13791376
else:
@@ -1383,7 +1380,8 @@ def enforce_single_login(sender, request, user, signal, **kwargs): # pylint: di
13831380
user=user,
13841381
defaults={'name': user.username}
13851382
)
1386-
if user_profile:
1383+
if user_profile and not _is_single_login_exempt(user):
1384+
# Shared service/automation accounts may hold concurrent sessions.
13871385
user.profile.set_login_session(key)
13881386

13891387

openedx/core/djangoapps/user_authn/views/tests/test_login.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import ddt
1414
from django.conf import settings
15-
from django.contrib.auth.models import User # pylint: disable=imported-auth-user
15+
from django.contrib.auth.models import Group, User # pylint: disable=imported-auth-user
1616
from django.core import mail
1717
from django.core.cache import cache
1818
from django.http import HttpResponse
@@ -682,6 +682,45 @@ def test_single_session_exempt_user(self):
682682
# session is ever deleted.
683683
assert 'session_id' not in self.user.profile.get_meta()
684684

685+
try:
686+
# this test can be run with either lms or studio settings
687+
# since studio does not have a dashboard url, we should
688+
# look for another url that is login_required, in that case
689+
url = reverse('dashboard')
690+
except NoReverseMatch:
691+
url = reverse('upload_transcripts')
692+
response = client1.get(url)
693+
# client1 remains authenticated; the exempt user's first session
694+
# was not evicted by the second login.
695+
assert response.status_code == 200
696+
697+
@patch.dict("django.conf.settings.FEATURES", {'PREVENT_CONCURRENT_LOGINS': True})
698+
def test_single_session_exempt_group(self):
699+
"""
700+
A user in a group listed in SINGLE_LOGIN_EXEMPT_GROUPS is not subject
701+
to single-login enforcement: a concurrent login does not record the
702+
single-session slot and therefore does not evict the first session.
703+
"""
704+
group = Group.objects.create(name='exempt-service-accounts')
705+
self.user.groups.add(group)
706+
707+
creds = {'email': self.user_email, 'password': self.password}
708+
client1 = Client()
709+
client2 = Client()
710+
711+
with override_settings(SINGLE_LOGIN_EXEMPT_GROUPS=[group.name]):
712+
response = client1.post(self.url, creds)
713+
self._assert_response(response, success=True)
714+
715+
# A second login must NOT evict the exempt user's first session.
716+
response = client2.post(self.url, creds)
717+
self._assert_response(response, success=True)
718+
719+
self.user = User.objects.get(pk=self.user.pk)
720+
# No single-session slot is recorded for exempt users, so neither
721+
# session is ever deleted.
722+
assert 'session_id' not in self.user.profile.get_meta()
723+
685724
@patch.dict("django.conf.settings.FEATURES", {'PREVENT_CONCURRENT_LOGINS': True})
686725
def test_single_session_with_no_user_profile(self):
687726
"""

0 commit comments

Comments
 (0)