feat: Adding new CookieNameChange middleware (#28404)

Description: Adds a new middleware to help with cookie name changes. It uses the idea of expand and contract, where after we've changed the name, the middleware allows up to accept either a cookie with new name (given higher priority when both are present) or cookie with old name.

This is also helpful when changing domain of a cookie.

impacts: developers, users(anyone that has cookies)
Change depends on django setting changes. See CookieNameChange middleware for more info.
This commit is contained in:
Manjinder Singh
2021-08-25 12:43:21 -04:00
committed by GitHub
parent f1a603edcd
commit 5d6f163a0b
4 changed files with 167 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
"""Middleware to change name of an incoming cookie"""
from django.conf import settings
class CookieNameChange:
"""Changes name of an incoming cookie"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
"""
Changes the names of a cookie in request.COOKIES
For this middleware to run:
- set COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE = True
- COOKIE_NAME_CHANGE_EXPAND_INFO is a dict and has following info:
"old_name": Previous name of cookie
"new_name": New name of cookie
Actions taken by middleware:
- will delete any cookie with "old_name"
- if create a new cookie with "new_name" and set its value to value of "old_name" cookie
+ it will not modify cookie with "new_name" if it already exists in request.COOKIES
"""
# .. toggle_name: COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Used to enable CookieNameChange middleware which changes a cookie name in request.COOKIES
# .. toggle_warnings: This should be set at the same time you set COOKIE_NAME_CHANGE_EXPAND_INFO setting
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2021-08-04
# .. toggle_target_removal_date: 2021-10-01
# .. toggle_tickets: https://openedx.atlassian.net/browse/ARCHBOM-1872
if getattr(settings, "COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE", False):
old_cookie_in_request = False
expand_settings = getattr(settings, "COOKIE_NAME_CHANGE_EXPAND_INFO", None)
if (
expand_settings is not None
and isinstance(expand_settings, dict)
and "new_name" in expand_settings
and "old_name" in expand_settings
):
if expand_settings["old_name"] in request.COOKIES:
old_cookie_in_request = True
old_cookie_value = request.COOKIES[expand_settings["old_name"]]
del request.COOKIES[expand_settings["old_name"]]
if (
expand_settings["new_name"] not in request.COOKIES
and old_cookie_in_request
):
request.COOKIES[expand_settings["new_name"]] = old_cookie_value
response = self.get_response(request)
return response

View File

@@ -0,0 +1,109 @@
"""
Test Module to test CookieNameChange class
"""
from unittest.mock import Mock
from django.test import TestCase
from ..middleware import CookieNameChange
class TestCookieNameChange(TestCase):
"""
Test class for CookieNameChange
"""
def setUp(self):
super().setUp()
self.mock_response = Mock()
self.cookie_name_change_middleware = CookieNameChange(self.mock_response)
self.mock_request = Mock()
self.old_value = "." * 100
self.old_key = 'a'
self.extra_cookies = {
"_b": "." * 13,
"_c_": "." * 13,
"a.b": "." * 10,
}
self.old_dict = {
self.old_key: self.old_value,
}
self.expand_settings = {
"old_name": self.old_key,
"new_name": "b",
}
def test_cookie_swap(self):
"""Check to make sure self.Middleware correctly swaps keys"""
self.old_dict.update(self.extra_cookies)
self.mock_request.COOKIES = self.old_dict.copy()
with self.settings(
COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE=True
), self.settings(COOKIE_NAME_CHANGE_EXPAND_INFO=self.expand_settings):
self.cookie_name_change_middleware(self.mock_request)
assert self.expand_settings["old_name"] not in self.mock_request.COOKIES.keys()
assert self.expand_settings["new_name"] in self.mock_request.COOKIES.keys()
assert self.mock_request.COOKIES[self.expand_settings["new_name"]] == self.old_value
test_dict = self.extra_cookies.copy()
test_dict[self.expand_settings['new_name']] = self.old_value
assert self.mock_request.COOKIES == test_dict
# make sure response function is called once
self.mock_response.assert_called_once()
def test_cookie_no_swap(self):
"""Make sure self.cookie_name_change_middleware does not change cookie if new_name cookie is already present"""
new_value = "." * 13
no_change_cookies = {
self.expand_settings['new_name']: new_value,
"_c_": "." * 13,
"a.b": "." * 10,
}
self.old_dict.update(no_change_cookies)
self.mock_request.COOKIES = self.old_dict.copy()
with self.settings(
COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE=True
), self.settings(COOKIE_NAME_CHANGE_EXPAND_INFO=self.expand_settings):
self.cookie_name_change_middleware(self.mock_request)
assert self.expand_settings["old_name"] not in self.mock_request.COOKIES.keys()
assert self.expand_settings["new_name"] in self.mock_request.COOKIES.keys()
assert self.mock_request.COOKIES[self.expand_settings["new_name"]] == new_value
assert self.mock_request.COOKIES == no_change_cookies
# make sure response function is called once
self.mock_response.assert_called_once()
def test_does_nothing(self):
"""Make sure turning off toggle turns off self.cookie_name_change_middleware"""
new_value = "." * 13
no_change_cookies = {
self.expand_settings['new_name']: new_value,
"_c_": "." * 13,
"a.b": "." * 10,
}
self.old_dict.update(no_change_cookies)
self.mock_request.COOKIES = self.old_dict.copy()
with self.settings(
COOKIE_NAME_CHANGE_ACTIVATE_EXPAND_PHASE=False
), self.settings(COOKIE_NAME_CHANGE_EXPAND_INFO=self.expand_settings):
self.cookie_name_change_middleware(self.mock_request)
assert self.mock_request.COOKIES == self.old_dict
# make sure response function is called once
self.mock_response.assert_called_once()