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 @@
Utility functions for setting "logged in" cookies used by subdomains.
"""
import time
import json
import time
from django.dispatch import Signal
from django.utils.http import cookie_date
import six
import urllib
from django.conf import settings
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"])
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):
"""
Set cookies indicating that the user is logged in.
......@@ -49,21 +74,7 @@ def set_logged_in_cookies(request, response, user):
HttpResponse
"""
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,
}
cookie_settings = _get_cookie_settings(request)
# Backwards compatibility: set the cookie indicating that the user
# 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):
**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
# to customize content based on user information. Currently,
# we include information that's used to customize the "account"
......@@ -94,38 +140,24 @@ def set_logged_in_cookies(request, response, user):
pass
# 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)
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 = {
'version': settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
'username': user.username,
'email': user.email,
'header_urls': header_urls,
'enrollments': enrollments,
}
# 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()
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
return user_info
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
import json
import unittest
import urllib
from django.test import TestCase
from django.test.client import Client
from django.test.utils import override_settings
......@@ -169,14 +170,10 @@ class LoginTest(CacheIsolationTestCase):
# Verify the format of the "user info" cookie set on login
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)
# Check that the username and email are set
self.assertEqual(user_info["username"], self.user.username)
self.assertEqual(user_info["email"], self.user.email)
# Check that the URLs are absolute
for url in user_info["header_urls"].values():
......
......@@ -94,6 +94,7 @@ requests-oauthlib==0.4.1
scipy==0.14.0
Shapely==1.2.16
singledispatch==3.4.0.2
six>=1.10.0,<2.0.0
sorl-thumbnail==12.3
sortedcontainers==0.9.2
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