django-cookie-consent icon indicating copy to clipboard operation
django-cookie-consent copied to clipboard

CleanCookiesMiddleware does not work

Open vocationeers opened this issue 4 years ago • 4 comments

It seems that at the current state the CleanCookiesMiddleware does not work in Django 2.x as is:

TypeError: 'CleanCookiesMiddleware' object is not callable

This error is solved by:

   def __call__(self, request):
        response = self.get_response(request)
        self.process_response(request, response)
        return response

However, then a new error appears: cookie_consent\middleware.py", line 43, in process_response if group_version < cookie.get_version() and not settings.COOKIE_CONSENT_OPT_OUT: TypeError: '<' not supported between instances of 'NoneType' and 'str'

I think this one can be fixed by modifying the code as below, but I might have missed something as I am not understanding the code to the last bit

    def process_response(self, request, response):
        if not is_cookie_consent_enabled(request):
            return response
        cookie_dic = get_cookie_dict_from_request(request)
        for cookie_group in all_cookie_groups().values():
            if not cookie_group.is_deletable:
                continue
            group_version = cookie_dic.get(cookie_group.varname, None)
            for cookie in cookie_group.cookie_set.all():
                if cookie.name not in request.COOKIES:
                    continue
                if group_version == None:
                    if not settings.COOKIE_CONSENT_OPT_OUT:
                        response.delete_cookie(
                            smart_str(cookie.name),
                            cookie.path, cookie.domain
                        )
                    continue
                    
                if group_version == settings.COOKIE_CONSENT_DECLINE:
                    response.delete_cookie(smart_str(cookie.name),
                                           cookie.path, cookie.domain)

                if group_version < cookie.get_version() and not settings.COOKIE_CONSENT_OPT_OUT:
                    response.delete_cookie(
                        smart_str(cookie.name),
                        cookie.path, cookie.domain
                    )
        return response

vocationeers avatar Apr 16 '20 12:04 vocationeers

Is there a work in progress regarding this issue ?

ThomSawyer avatar Jan 22 '21 13:01 ThomSawyer

@ThomSawyer No there is not. If you could fix it that would be great. It looks like your recent PR attempts to fix it. Just making sure does your PR officially fix this?

9mido avatar Feb 04 '21 02:02 9mido

I was able to reproduce the first part of this issue.

Add to your settings.py MIDDLEWARE setting:

'cookie_consent.middleware.CleanCookiesMiddleware',

A guide to setting the middleware should really be added to the docs.

   def __call__(self, request):
        response = self.get_response(request)
        self.process_response(request, response)
        return response

^That code is definitely needed. self.process_response(request, response) is needed to call the process_response function in the middleware.

if cookie.name not in str(request.COOKIES) ^We need to make request.COOKIES a string because it is a dictionary. The loop will continue to the next iteration if we do not change this. The if statement will always be true if we compare cookie.name string to request.COOKIES dictionary.

group_version = cookie_dic.get(cookie_group.varname, None) ^This is returning None in the case of vocationeers code. It is not finding the timestamp version for a group because neither cookies are accepted or declined. See models.py to see the version functions. We can use vocationeers code for if group_version == None: if we would like or use if group_version == None and not settings.COOKIE_CONSENT_OPT_OUT:

To actually test if the middleware works and the cookies are deleted automatically when declined or not accepted/declined, in your database cookies table you must provide all cookies for that group with the exact cookie name, path, and domain.

For example, for a stripe cookie group I have a cookie called __stripe_sid. I accepted the stripe cookies so I could get that __stripe_sid cookie to show up in my browser. Then as soon as I hit decline, the __stripe_sid was automatically deleted thanks to the middleware.

The only problem though is if these third party websites add more cookies without you knowing and they do not match what you have in your database, there will be some extra cookies still remaining in the browser I imagine.

These are some comments I made in the current code to help people understand what is going on (use print statements whenever possible):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        self.process_response(request, response)
        return response

    def process_response(self, request, response):
        #if COOKIE_CONSENT_ENABLED setting is false then return a response
        if not is_cookie_consent_enabled(request):
            return response
        #returns a dictionary of cookie groups and their -1 (declined) or accepted timestamp values
        cookie_dic = get_cookie_dict_from_request(request)
        #from the cache, loop through all cookie groups
        for cookie_group in all_cookie_groups().values():
            #if a specific cookie group database value is not deleteable (false), go to the next loop iteration
            if not cookie_group.is_deletable:
                continue
            #returns timestamps or -1s from the browser cookies
            group_version = cookie_dic.get(cookie_group.varname, None)
            #loop through all database cookie groups cookies
            for cookie in cookie_group.cookie_set.all():
                #if the cookie name from the database is not in the browser cookie_consent cookie group, go to the next loop iteration
                if cookie.name not in str(request.COOKIES):  #Need to change the request.COOKIES dictionary to a string for the if statement to work. 
                    continue
                #if the cookie group version of -1 equals the COOKIE_CONSENT_DECLINE value of -1
                if group_version == settings.COOKIE_CONSENT_DECLINE:
                    #delete the declined cookie with django built in response.delete_cookie
                    response.delete_cookie(smart_str(cookie.name),
                                           cookie.path, cookie.domain)
                #if the cookie group version timestamp or -1 is less than the cookie version timestamp or -1 and the settings.COOKIE_CONSENT_OPT_OUT setting is false
                if group_version < cookie.get_version() and not settings.COOKIE_CONSENT_OPT_OUT:
                    #delete the declined cookie with django built in response.delete_cookie
                    response.delete_cookie(
                        smart_str(cookie.name),
                        cookie.path, cookie.domain
                    )
        return response

some1ataplace avatar Mar 04 '22 21:03 some1ataplace

The quickest way to fix this without waiting for the PR is to create your own Middleware. Create a middleware.py file in your project, like this:

from cookie_consent.middleware import CleanCookiesMiddleware
from django.utils.deprecation import MiddlewareMixin


class CleanCookiesFixMiddleware(CleanCookiesMiddleware, MiddlewareMixin):
    pass

Then use this Middleware in your settings.py, instead of the one from the package:

MIDDLEWARE = [
    ...,
    'yourpackagename.middleware.CleanCookiesFixMiddleware',
]

That's it! The problem is that the CleanCookiesMiddleware included in the package is a plain object lacking some needed methods. Compositing that object (to get the process_response method) with the MiddlewareMixin from Django itself (to get the needed methods) does the trick with very little code.

xbello avatar May 25 '22 08:05 xbello

Fixed via #80

sergei-maertens avatar Sep 24 '22 14:09 sergei-maertens