# Cookie library has moved to http in python3 try: import Cookie except ImportError: import http.cookies as Cookie import django from distutils.version import LooseVersion from django.conf import settings try: from django.utils.deprecation import MiddlewareMixin except ImportError: MiddlewareMixin = object from django_cookies_samesite.user_agent_checker import UserAgentChecker Cookie.Morsel._reserved["samesite"] = "SameSite" Cookie.Morsel._reserved.update({"samesite": "SameSite", "secure": "Secure"}) # TODO: change this to 3.1.0 once Django 3.1 is released DJANGO_SUPPORTED_VERSION = "3.0.0" def get_config_setting(setting_name, default_value=None): """Load the Django setting with DCS_ prefix and fallback to the legacy name if not found.""" return getattr( settings, "DCS_{}".format(setting_name), getattr(settings, setting_name, default_value), ) class CookiesSameSite(MiddlewareMixin): """ Support for SameSite attribute in Cookies is fully implemented in Django 3.1 and won't be back-ported to Django 2.x. This middleware will be obsolete when your app will start using Django 3.1. """ def __init__(self, *args, **kwargs): self.protected_cookies = get_config_setting( "SESSION_COOKIE_SAMESITE_KEYS", set() ) if not isinstance(self.protected_cookies, (list, set, tuple)): raise ValueError( "SESSION_COOKIE_SAMESITE_KEYS should be a list, set or tuple." ) self.protected_cookies = set(self.protected_cookies) self.protected_cookies |= { settings.SESSION_COOKIE_NAME, settings.CSRF_COOKIE_NAME, } samesite_flag = get_config_setting("SESSION_COOKIE_SAMESITE", "") self.samesite_flag = ( str(samesite_flag).capitalize() if samesite_flag is not None else "" ) self.samesite_force_all = get_config_setting( "SESSION_COOKIE_SAMESITE_FORCE_ALL" ) return super(CookiesSameSite, self).__init__(*args, **kwargs) def update_cookie(self, cookie, request, response): response.cookies[cookie]["samesite"] = self.samesite_flag if request.is_secure(): response.cookies[cookie]["secure"] = True def process_response(self, request, response): # same-site = None introduced for Chrome 80 breaks for Chrome 51-66 # Refer (https://www.chromium.org/updates/same-site/incompatible-clients) http_user_agent = request.META.get("HTTP_USER_AGENT") or " " user_agent_checker = UserAgentChecker(http_user_agent) if user_agent_checker.do_not_send_same_site_policy: return response if LooseVersion(django.get_version()) >= LooseVersion(DJANGO_SUPPORTED_VERSION): raise DeprecationWarning( "Your version of Django supports SameSite flag in the cookies mechanism. " "You should remove django-cookies-samesite from your project." ) if not self.samesite_flag: return response # TODO: capitalize those values if self.samesite_flag not in {"Lax", "None", "Strict"}: raise ValueError('samesite must be "Lax", "None", or "Strict".') if self.samesite_force_all: for cookie in response.cookies: self.update_cookie(cookie, request, response) else: for cookie in self.protected_cookies: if cookie in response.cookies: self.update_cookie(cookie, request, response) return response