cookies.py 6.11 KB
Newer Older
Will Daly committed
1 2 3
"""
Utility functions for setting "logged in" cookies used by subdomains.
"""
4
from __future__ import unicode_literals
Will Daly committed
5

6
import json
7
import time
Will Daly committed
8

9
import six
10
from django.conf import settings
11
from django.core.urlresolvers import NoReverseMatch, reverse
12 13 14 15 16 17 18 19
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'])


20
def standard_cookie_settings(request):
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
    """ 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,
    }
Will Daly committed
38

39
    return cookie_settings
40

Will Daly committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

def set_logged_in_cookies(request, response, user):
    """
    Set cookies indicating that the user is logged in.

    Some installations have an external marketing site configured
    that displays a different UI when the user is logged in
    (e.g. a link to the student dashboard instead of to the login page)

    Currently, two cookies are set:

    * EDXMKTG_LOGGED_IN_COOKIE_NAME: Set to 'true' if the user is logged in.
    * EDXMKTG_USER_INFO_COOKIE_VERSION: JSON-encoded dictionary with user information (see below).

    The user info cookie has the following format:
    {
        "version": 1,
        "username": "test-user",
        "header_urls": {
            "account_settings": "https://example.com/account/settings",
            "learner_profile": "https://example.com/u/test-user",
            "logout": "https://example.com/logout"
        }
    }

    Arguments:
        request (HttpRequest): The request to the view, used to calculate
            the cookie's expiration date based on the session expiration date.
        response (HttpResponse): The response on which the cookie will be set.
        user (User): The currently logged in user.

    Returns:
        HttpResponse

    """
76
    cookie_settings = standard_cookie_settings(request)
Will Daly committed
77 78 79 80 81

    # Backwards compatibility: set the cookie indicating that the user
    # is logged in.  This is just a boolean value, so it's not very useful.
    # In the future, we should be able to replace this with the "user info"
    # cookie set below.
82 83 84 85 86 87
    response.set_cookie(
        settings.EDXMKTG_LOGGED_IN_COOKIE_NAME.encode('utf-8'),
        'true',
        secure=None,
        **cookie_settings
    )
Will Daly committed
88

89 90 91 92 93 94 95 96 97 98
    set_user_info_cookie(response, request)

    # 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):
    """ Sets the user info cookie on the response. """
99
    cookie_settings = standard_cookie_settings(request)
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

    # 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)

    response.set_cookie(
        settings.EDXMKTG_USER_INFO_COOKIE_NAME.encode('utf-8'),
        json.dumps(user_info),
        secure=user_info_cookie_is_secure,
        **cookie_settings
    )


def get_user_info_cookie_data(request):
    """ Returns information that wil populate the user info cookie. """
    user = request.user

Will Daly committed
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    # 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"
    # links in the header of subdomain sites (such as the marketing site).
    header_urls = {'logout': reverse('logout')}

    # Unfortunately, this app is currently used by both the LMS and Studio login pages.
    # If we're in Studio, we won't be able to reverse the account/profile URLs.
    # To handle this, we don't add the URLs if we can't reverse them.
    # External sites will need to have fallback mechanisms to handle this case
    # (most likely just hiding the links).
    try:
        header_urls['account_settings'] = reverse('account_settings')
        header_urls['learner_profile'] = reverse('learner_profile', kwargs={'username': user.username})
    except NoReverseMatch:
        pass

    # Convert relative URL paths to absolute URIs
143
    for url_name, url_path in six.iteritems(header_urls):
Will Daly committed
144 145 146 147 148 149
        header_urls[url_name] = request.build_absolute_uri(url_path)

    user_info = {
        'version': settings.EDXMKTG_USER_INFO_COOKIE_VERSION,
        'username': user.username,
        'header_urls': header_urls,
150
        'enrollmentStatusHash': CourseEnrollment.generate_enrollment_status_hash(user)
Will Daly committed
151 152
    }

153
    return user_info
Will Daly committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167


def delete_logged_in_cookies(response):
    """
    Delete cookies indicating that the user is logged in.

    Arguments:
        response (HttpResponse): The response sent to the client.

    Returns:
        HttpResponse

    """
    for cookie_name in [settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, settings.EDXMKTG_USER_INFO_COOKIE_NAME]:
168 169 170 171 172
        response.delete_cookie(
            cookie_name.encode('utf-8'),
            path='/',
            domain=settings.SESSION_COOKIE_DOMAIN
        )
Will Daly committed
173 174 175 176 177 178 179 180 181 182

    return response


def is_logged_in_cookie_set(request):
    """Check whether the request has logged in cookies set. """
    return (
        settings.EDXMKTG_LOGGED_IN_COOKIE_NAME in request.COOKIES and
        settings.EDXMKTG_USER_INFO_COOKIE_NAME in request.COOKIES
    )