Repository: learningequality/kolibri

Issue:
Kolibri should explicitly set cookie name
<!--
Instructions:
 * Fill out the sections below, replace …'s with information about your issue
 * Use the 'preview' function above this text box to verify formatting before submitting
-->

### Observed behavior

When running Kolibri and Studio at the same time in the same browser, Kolibri will immediately sign the user out and require them to sign in again. This process will repeat for as long as both apps are running.

This happens because Kolibri and Studio both currently use the Django default cookie name, 'sessionid'. This causes Kolibri and Studio to set each other's sessionid after various actions.

### Expected behavior
When you sign in, you stay signed in, Studio or not. :)

### User-facing consequences
This is primarily an internal developer issue, though it could happen on devices that deploy other local services in addition to Kolibri that are using Django and keep the default cookie name.

### Steps to reproduce
- Start Studio and sign in
- Start Kolibri and sign in
- Return to Kolibri and try to perform an action, like importing content
Result: It says you are signed out and asks you to sign in again. Once you sign in again, this process will repeat.

### Context
- Latest Kolibri from develop branch as of 6-28-18
- Latest Studio from develop branch as of 6-28-18
- OS X 10.13 (High Sierra)
- Any browser, tested with Safari, Chrome, Chromium, and FIrefox



Discussion:

Resolving patch:
diff --git a/kolibri/deployment/default/settings/base.py b/kolibri/deployment/default/settings/base.py
--- a/kolibri/deployment/default/settings/base.py
+++ b/kolibri/deployment/default/settings/base.py
@@ -339,6 +339,19 @@
 
 ENABLE_DATA_BOOTSTRAPPING = True
 
+# Session configuration
+
+SESSION_ENGINE = "django.contrib.sessions.backends.file"
+
+SESSION_FILE_PATH = os.path.join(conf.KOLIBRI_HOME, "sessions")
+
+if not os.path.exists(SESSION_FILE_PATH):
+    if not os.path.exists(conf.KOLIBRI_HOME):
+        raise RuntimeError("The KOLIBRI_HOME dir does not exist")
+    os.mkdir(SESSION_FILE_PATH)
+
+SESSION_COOKIE_NAME = "kolibri"
+
 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
 SESSION_COOKIE_AGE = 600
diff --git a/kolibri/utils/cli.py b/kolibri/utils/cli.py
--- a/kolibri/utils/cli.py
+++ b/kolibri/utils/cli.py
@@ -641,6 +641,9 @@ def main(args=None):  # noqa: max-complexity=13
         # Check if the content directory exists when Kolibri runs after the first time.
         check_content_directory_exists_and_writable()
 
+        # Clear old sessions up
+        call_command("clearsessions")
+
         daemon = not arguments['--foreground']
         if sys.platform == 'darwin':
             daemon = False


Test patch:
diff --git a/kolibri/core/auth/test/test_api.py b/kolibri/core/auth/test/test_api.py
--- a/kolibri/core/auth/test/test_api.py
+++ b/kolibri/core/auth/test/test_api.py
@@ -4,9 +4,10 @@
 
 import collections
 import sys
+from importlib import import_module
 
 import factory
-from django.contrib.sessions.models import Session
+from django.conf import settings
 from django.core.urlresolvers import reverse
 from rest_framework import status
 from rest_framework.test import APITestCase as BaseTestCase
@@ -302,25 +303,26 @@ def setUp(self):
         self.facility.add_admin(self.admin)
         self.cr = ClassroomFactory.create(parent=self.facility)
         self.cr.add_coach(self.admin)
+        self.session_store = import_module(settings.SESSION_ENGINE).SessionStore()
 
     def test_login_and_logout_superuser(self):
         self.client.post(reverse('session-list'), data={"username": self.superuser.username, "password": DUMMY_PASSWORD})
-        sessions = Session.objects.all()
-        self.assertEqual(len(sessions), 1)
+        session_key = self.client.session.session_key
+        self.assertTrue(self.session_store.exists(session_key))
         self.client.delete(reverse('session-detail', kwargs={'pk': 'current'}))
-        self.assertEqual(len(Session.objects.all()), 0)
+        self.assertFalse(self.session_store.exists(session_key))
 
     def test_login_and_logout_facility_user(self):
         self.client.post(reverse('session-list'), data={"username": self.user.username, "password": DUMMY_PASSWORD, "facility": self.facility.id})
-        sessions = Session.objects.all()
-        self.assertEqual(len(sessions), 1)
+        session_key = self.client.session.session_key
+        self.assertTrue(self.session_store.exists(session_key))
         self.client.delete(reverse('session-detail', kwargs={'pk': 'current'}))
-        self.assertEqual(len(Session.objects.all()), 0)
+        self.assertFalse(self.session_store.exists(session_key))
 
     def test_incorrect_credentials_does_not_log_in_user(self):
+        session_key = self.client.session.session_key
         self.client.post(reverse('session-list'), data={"username": self.user.username, "password": "foo", "facility": self.facility.id})
-        sessions = Session.objects.all()
-        self.assertEqual(len(sessions), 0)
+        self.assertEqual(session_key, self.client.session.session_key)
 
     def test_session_return_admin_and_coach_kind(self):
         self.client.post(reverse('session-list'), data={"username": self.admin.username, "password": "bar", "facility": self.facility.id})
@@ -334,9 +336,9 @@ def test_session_return_anon_kind(self):
 
     def test_session_update_last_active(self):
         self.client.post(reverse('session-list'), data={"username": self.user.username, "password": DUMMY_PASSWORD, "facility": self.facility.id})
-        expire_date = Session.objects.get().expire_date
+        expire_date = self.client.session.get_expiry_date()
         self.client.get(reverse('session-detail', kwargs={'pk': 'current'}))
-        new_expire_date = Session.objects.get().expire_date
+        new_expire_date = self.client.session.get_expiry_date()
         self.assertTrue(expire_date < new_expire_date)
 
 
@@ -369,9 +371,9 @@ def test_create_bad_username_fails(self):
         self.assertFalse(models.FacilityUser.objects.all())
 
     def test_sign_up_also_logs_in_user(self):
-        self.assertFalse(Session.objects.all())
+        session_key = self.client.session.session_key
         self.client.post(reverse('signup-list'), data={"username": "user", "password": DUMMY_PASSWORD})
-        self.assertTrue(Session.objects.all())
+        self.assertNotEqual(session_key, self.client.session.session_key)
 
 
 class FacilityDatasetAPITestCase(APITestCase):