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:
0
openedx/core/djangoapps/cookie_metadata/__init__.py
Normal file
0
openedx/core/djangoapps/cookie_metadata/__init__.py
Normal file
58
openedx/core/djangoapps/cookie_metadata/middleware.py
Normal file
58
openedx/core/djangoapps/cookie_metadata/middleware.py
Normal 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
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user