Commit a820b3f7 by Dave St.Germain

Merge pull request #2793 from edx/dcs/mat-17

Add an option to prevent multiple logins of the same user.
parents 6517b067 0501775d
......@@ -91,6 +91,9 @@ FEATURES = {
# Allow creating courses with non-ascii characters in the course id
'ALLOW_UNICODE_COURSE_ID': False,
# Prevent concurrent logins per user
'PREVENT_CONCURRENT_LOGINS': False,
}
ENABLE_JASMINE = False
......
......@@ -34,6 +34,7 @@ from django_countries import CountryField
from track import contexts
from track.views import server_track
from eventtracking import tracker
from importlib import import_module
from course_modes.models import CourseMode
import lms.lib.comment_client as cc
......@@ -42,6 +43,7 @@ from util.query import use_read_replica_if_available
unenroll_done = Signal(providing_args=["course_enrollment"])
log = logging.getLogger(__name__)
AUDIT_LOG = logging.getLogger("audit")
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
class AnonymousUserId(models.Model):
......@@ -239,6 +241,20 @@ class UserProfile(models.Model):
def set_meta(self, js):
self.meta = json.dumps(js)
def set_login_session(self, session_id=None):
"""
Sets the current session id for the logged-in user.
If session_id doesn't match the existing session,
deletes the old session object.
"""
meta = self.get_meta()
old_login = meta.get('session_id', None)
if old_login:
SessionStore(session_key=old_login).delete()
meta['session_id'] = session_id
self.set_meta(meta)
self.save()
def unique_id_for_user(user):
"""
......@@ -292,7 +308,6 @@ class PendingEmailChange(models.Model):
activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True)
EVENT_NAME_ENROLLMENT_ACTIVATED = 'edx.course.enrollment.activated'
EVENT_NAME_ENROLLMENT_DEACTIVATED = 'edx.course.enrollment.deactivated'
......@@ -383,7 +398,6 @@ class CourseEnrollment(models.Model):
# list of possible values.
mode = models.CharField(default="honor", max_length=100)
class Meta:
unique_together = (('user', 'course_id'),)
ordering = ('user', 'course_id')
......@@ -866,3 +880,18 @@ def log_successful_logout(sender, request, user, **kwargs):
AUDIT_LOG.info(u"Logout - user.id: {0}".format(request.user.id))
else:
AUDIT_LOG.info(u"Logout - {0}".format(request.user))
@receiver(user_logged_in)
@receiver(user_logged_out)
def enforce_single_login(sender, request, user, signal, **kwargs):
"""
Sets the current session id in the user profile,
to prevent concurrent logins.
"""
if settings.FEATURES.get('PREVENT_CONCURRENT_LOGINS', False):
if signal == user_logged_in:
key = request.session.session_key
else:
key = None
user.profile.set_login_session(key)
......@@ -179,6 +179,32 @@ class LoginTest(TestCase):
response, _audit_log = self._login_response('test@edx.org', 'wrong_password')
self._assert_response(response, success=False, value='Too many failed login attempts')
@patch.dict("django.conf.settings.FEATURES", {'PREVENT_CONCURRENT_LOGINS': True})
def test_single_session(self):
creds = {'email': 'test@edx.org', 'password': 'test_password'}
client1 = Client()
client2 = Client()
response = client1.post(self.url, creds)
self._assert_response(response, success=True)
self.assertEqual(self.user.profile.get_meta()['session_id'], self.client.session.session_key)
# second login should log out the first
response = client2.post(self.url, creds)
self._assert_response(response, success=True)
try:
# this test can be run with either lms or studio settings
# since studio does not have a dashboard url, we should
# look for another url that is login_required, in that case
url = reverse('dashboard')
except NoReverseMatch:
url = reverse('upload_transcripts')
response = client1.get(url)
# client1 will be logged out
self.assertEqual(response.status_code, 302)
def _login_response(self, email, password, patched_audit_log='student.views.AUDIT_LOG'):
''' Post the login info '''
post_params = {'email': email, 'password': password}
......
......@@ -239,6 +239,9 @@ FEATURES = {
# Toggle to enable alternate urls for marketing links
'ENABLE_MKTG_SITE': False,
# Prevent concurrent logins per user
'PREVENT_CONCURRENT_LOGINS': False,
}
# Used for A/B testing
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment