Commit 09f7b614 by Clinton Blackburn

Updated logon user info cookie

- Cookie now includes users' enrollment info
- Removed user email address from cookie
- Refactored functions so that we can update this cookie on other pages
- Added six to ensure new code is compatible with Python 3.x

ECOM-4896
parent 2159b718
...@@ -2,18 +2,43 @@ ...@@ -2,18 +2,43 @@
Utility functions for setting "logged in" cookies used by subdomains. Utility functions for setting "logged in" cookies used by subdomains.
""" """
import time
import json import json
import time
from django.dispatch import Signal import six
import urllib
from django.utils.http import cookie_date
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from django.dispatch import Signal
from django.utils.http import cookie_date
from student.models import CourseEnrollment
CREATE_LOGON_COOKIE = Signal(providing_args=["user", "response"]) CREATE_LOGON_COOKIE = Signal(providing_args=["user", "response"])
def _get_cookie_settings(request):
""" Returns the common cookie settings (e.g. expiration time). """
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
cookie_settings = {
'max_age': max_age,
'expires': expires,
'domain': settings.SESSION_COOKIE_DOMAIN,
'path': '/',
'httponly': None,
}
return cookie_settings
def set_logged_in_cookies(request, response, user): def set_logged_in_cookies(request, response, user):
""" """
Set cookies indicating that the user is logged in. Set cookies indicating that the user is logged in.
...@@ -49,21 +74,7 @@ def set_logged_in_cookies(request, response, user): ...@@ -49,21 +74,7 @@ def set_logged_in_cookies(request, response, user):
HttpResponse HttpResponse
""" """
if request.session.get_expire_at_browser_close(): cookie_settings = _get_cookie_settings(request)
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
cookie_settings = {
'max_age': max_age,
'expires': expires,
'domain': settings.SESSION_COOKIE_DOMAIN,
'path': '/',
'httponly': None,
}
# Backwards compatibility: set the cookie indicating that the user # Backwards compatibility: set the cookie indicating that the user
# is logged in. This is just a boolean value, so it's not very useful. # is logged in. This is just a boolean value, so it's not very useful.
...@@ -76,6 +87,41 @@ def set_logged_in_cookies(request, response, user): ...@@ -76,6 +87,41 @@ def set_logged_in_cookies(request, response, user):
**cookie_settings **cookie_settings
) )
set_user_info_cookie(response, request, user)
# give signal receivers a chance to add cookies
CREATE_LOGON_COOKIE.send(sender=None, user=user, response=response)
return response
def set_user_info_cookie(response, request, user):
""" Sets the user info cookie on the response. """
cookie_settings = _get_cookie_settings(request)
# In production, TLS should be enabled so that this cookie is encrypted
# when we send it. We also need to set "secure" to True so that the browser
# will transmit it only over secure connections.
#
# In non-production environments (acceptance tests, devstack, and sandboxes),
# we still want to set this cookie. However, we do NOT want to set it to "secure"
# because the browser won't send it back to us. This can cause an infinite redirect
# loop in the third-party auth flow, which calls `is_logged_in_cookie_set` to determine
# whether it needs to set the cookie or continue to the next pipeline stage.
user_info_cookie_is_secure = request.is_secure()
user_info = get_user_info_cookie_data(request, user)
response.set_cookie(
settings.EDXMKTG_USER_INFO_COOKIE_NAME.encode('utf-8'),
urllib.quote(json.dumps(user_info)),
secure=user_info_cookie_is_secure,
**cookie_settings
)
def get_user_info_cookie_data(request, user):
""" Returns information that wil populate the user info cookie. """
# Set a cookie with user info. This can be used by external sites # Set a cookie with user info. This can be used by external sites
# to customize content based on user information. Currently, # to customize content based on user information. Currently,
# we include information that's used to customize the "account" # we include information that's used to customize the "account"
...@@ -94,38 +140,24 @@ def set_logged_in_cookies(request, response, user): ...@@ -94,38 +140,24 @@ def set_logged_in_cookies(request, response, user):
pass pass
# Convert relative URL paths to absolute URIs # Convert relative URL paths to absolute URIs
for url_name, url_path in header_urls.iteritems(): for url_name, url_path in six.iteritems(header_urls):
header_urls[url_name] = request.build_absolute_uri(url_path) header_urls[url_name] = request.build_absolute_uri(url_path)
enrollments = []
for enrollment in CourseEnrollment.enrollments_for_user(user):
enrollments.append({
'course_run_id': six.text_type(enrollment.course_id),
'seat_type': enrollment.mode
})
user_info = { user_info = {
'version': settings.EDXMKTG_USER_INFO_COOKIE_VERSION, 'version': settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
'username': user.username, 'username': user.username,
'email': user.email,
'header_urls': header_urls, 'header_urls': header_urls,
'enrollments': enrollments,
} }
# In production, TLS should be enabled so that this cookie is encrypted return user_info
# when we send it. We also need to set "secure" to True so that the browser
# will transmit it only over secure connections.
#
# In non-production environments (acceptance tests, devstack, and sandboxes),
# we still want to set this cookie. However, we do NOT want to set it to "secure"
# because the browser won't send it back to us. This can cause an infinite redirect
# loop in the third-party auth flow, which calls `is_logged_in_cookie_set` to determine
# whether it needs to set the cookie or continue to the next pipeline stage.
user_info_cookie_is_secure = request.is_secure()
response.set_cookie(
settings.EDXMKTG_USER_INFO_COOKIE_NAME.encode('utf-8'),
json.dumps(user_info),
secure=user_info_cookie_is_secure,
**cookie_settings
)
# give signal receivers a chance to add cookies
CREATE_LOGON_COOKIE.send(sender=None, user=user, response=response)
return response
def delete_logged_in_cookies(response): def delete_logged_in_cookies(response):
......
# pylint: disable=missing-docstring
from __future__ import unicode_literals
import six
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import RequestFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.cookies import get_user_info_cookie_data
from student.tests.factories import UserFactory, CourseEnrollmentFactory
class CookieTests(SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(CookieTests, cls).setUpClass()
cls.course = CourseFactory()
def setUp(self):
super(CookieTests, self).setUp()
self.user = UserFactory.create()
def _get_expected_header_urls(self, request):
expected_header_urls = {
'logout': reverse('logout'),
}
# Studio (CMS) does not have the URLs below
if settings.ROOT_URLCONF == 'lms.urls':
expected_header_urls.update({
'account_settings': reverse('account_settings'),
'learner_profile': reverse('learner_profile', kwargs={'username': self.user.username}),
})
# Convert relative URL paths to absolute URIs
for url_name, url_path in six.iteritems(expected_header_urls):
expected_header_urls[url_name] = request.build_absolute_uri(url_path)
return expected_header_urls
def test_get_user_info_cookie_data(self):
""" Verify the function returns data that """
request = RequestFactory().get('/')
request.user = self.user
enrollment_mode = 'verified'
course_id = self.course.id # pylint: disable=no-member
CourseEnrollmentFactory.create(user=self.user, course_id=course_id, mode=enrollment_mode)
actual = get_user_info_cookie_data(request, self.user)
expected_enrollments = [{
'course_run_id': six.text_type(course_id),
'seat_type': enrollment_mode,
}]
expected = {
'version': settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
'username': self.user.username,
'header_urls': self._get_expected_header_urls(request),
'enrollments': expected_enrollments,
}
self.assertDictEqual(actual, expected)
...@@ -4,6 +4,7 @@ Tests for student activation and login ...@@ -4,6 +4,7 @@ Tests for student activation and login
import json import json
import unittest import unittest
import urllib
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -169,14 +170,10 @@ class LoginTest(CacheIsolationTestCase): ...@@ -169,14 +170,10 @@ class LoginTest(CacheIsolationTestCase):
# Verify the format of the "user info" cookie set on login # Verify the format of the "user info" cookie set on login
cookie = self.client.cookies[settings.EDXMKTG_USER_INFO_COOKIE_NAME] cookie = self.client.cookies[settings.EDXMKTG_USER_INFO_COOKIE_NAME]
user_info = json.loads(cookie.value) user_info = json.loads(urllib.unquote(cookie.value))
# Check that the version is set
self.assertEqual(user_info["version"], settings.EDXMKTG_USER_INFO_COOKIE_VERSION) self.assertEqual(user_info["version"], settings.EDXMKTG_USER_INFO_COOKIE_VERSION)
# Check that the username and email are set
self.assertEqual(user_info["username"], self.user.username) self.assertEqual(user_info["username"], self.user.username)
self.assertEqual(user_info["email"], self.user.email)
# Check that the URLs are absolute # Check that the URLs are absolute
for url in user_info["header_urls"].values(): for url in user_info["header_urls"].values():
......
...@@ -94,6 +94,7 @@ requests-oauthlib==0.4.1 ...@@ -94,6 +94,7 @@ requests-oauthlib==0.4.1
scipy==0.14.0 scipy==0.14.0
Shapely==1.2.16 Shapely==1.2.16
singledispatch==3.4.0.2 singledispatch==3.4.0.2
six>=1.10.0,<2.0.0
sorl-thumbnail==12.3 sorl-thumbnail==12.3
sortedcontainers==0.9.2 sortedcontainers==0.9.2
stevedore==1.10.0 stevedore==1.10.0
......
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