Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cookies_to_delete is always an empty list #311

Open
kevinrenskers opened this issue May 9, 2023 · 12 comments · Fixed by #316
Open

cookies_to_delete is always an empty list #311

kevinrenskers opened this issue May 9, 2023 · 12 comments · Fixed by #316

Comments

@kevinrenskers
Copy link

kevinrenskers commented May 9, 2023

When BasketMiddleware adds something to request.cookies_to_delete, it is never actually deleted inside of ApiBasketMiddleWare.process_response. This is because ApiBasketMiddleWare.__call__ calls super(ApiBasketMiddleWare, self).__call__(request), which also has the line request.cookies_to_delete = [] (see here).

So the result of this is that cookies that should be getting deleted are not getting deleted. For example, when I am logged in as a user and then log out, the oscar_open_basket cookie still has a basket_id value of the logged in user's basket. And BasketMiddleware doesn't recognize such a basket for anonymous users:

    def get_cookie_basket(self, cookie_key, request, manager):
        """
        Looks for a basket which is referenced by a cookie.

        If a cookie key is found with no matching basket, then we add
        it to the list to be deleted.
        """
        basket = None
        if cookie_key in request.COOKIES:
            basket_hash = request.COOKIES[cookie_key]
            try:
                basket_id = Signer().unsign(basket_hash)
                basket = Basket.objects.get(pk=basket_id, owner=None,
                                            status=Basket.OPEN)
            except (BadSignature, Basket.DoesNotExist):
                request.cookies_to_delete.append(cookie_key)
        return basket

As you can see, it filters on owner=None and since the basket in the cookie still belongs to someone, it doesn't find the basket, creates a brand new basket, and it also want to delete the cookie. But this deletion does not happen.

@kevinrenskers
Copy link
Author

The way cookies are handled are actually a huge problem I think. At the moment when somebody is logged in, and then logs out, and I fetch their baskets using the BasketList view, I get the basket back that belongs to the previously logged in user. You can then proceed to add products to the basket, but it is then impossible to checkout, because the basket that is used by Oscar itself (stored in the request object) is a completely blank basket without an id, and nothing in it.

@kevinrenskers
Copy link
Author

Actually not sure if it's caused by the cookie or not; when I manually remove the oscar_open_basket from my browser's storage, I still get the previous user's basket back from BasketList. Why is that an anonymous user gets a basket with an owner id? Oscar itself doesn't allow this (see that owner=None filter I mentioned above) so now oscar and oscarapi are using two separate baskets.

@kevinrenskers
Copy link
Author

kevinrenskers commented May 11, 2023

The method get_anonymous_basket doesn't do any check to see if the owner is None, and get_basket_id_from_session relies on request.session.get(settings.OSCAR_BASKET_COOKIE_OPEN), which still returns the basket id from the previously logged in user, after you logged out.

So something is not getting cleared when logging out it seems?

@kevinrenskers
Copy link
Author

kevinrenskers commented May 24, 2023

My workaround for now is to use my own LoginView:

from oscarapi.views.login import LoginView as CoreLoginView

class LoginView(CoreLoginView):
    def delete(self, request, *args, **kwargs):
        """
        Destroy the session.

        For anonymous users that means having their basket destroyed as well,
        because there is no way to reach it otherwise.
        """
        request = request._request
        if request.user.is_anonymous:
            basket = operations.get_anonymous_basket(request)
            if basket:
                operations.flush_and_delete_basket(basket)

        request.session.flush()

        # Create a new basket for the anonymous user
        basket = Basket.objects.create()
        basket.save()
        store_basket_in_session(basket, request.session)

        response = Response("")

        response.set_cookie(
            settings.OSCAR_BASKET_COOKIE_OPEN,
            Signer().sign(basket.id),
            max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME,
            domain=settings.SESSION_COOKIE_DOMAIN,
            secure=settings.SESSION_COOKIE_SECURE,
            httponly=settings.SESSION_COOKIE_HTTPONLY,
            samesite=settings.SESSION_COOKIE_SAMESITE,
        )

        return response

So when the user logs out I manually create a new basket, store that in the session, and set a new cookie.

@specialunderwear
Copy link
Member

specialunderwear commented Jul 14, 2023

Hi @kevinrenskers could you write a test that proves your point and create a pull request?
I'm working on the basket middleware right now and I think I solve this based on your description, but it would be awesome to be able to verify with a test.

specialunderwear added a commit that referenced this issue Jul 14, 2023
…eded.

This solves that and also solves a problem with logout being broken because
cookies failed to be deleted.

Fixes #311
@specialunderwear
Copy link
Member

Maybe you can test the pull request and see if it solves your problem @kevinrenskers

@kevinrenskers
Copy link
Author

I'll try, but that won't be before the middle of next week at the earliest.

joeyjurjens pushed a commit that referenced this issue Jul 21, 2023
#316)

* Oscarapi always accessed and prcessed the basket even if it wasn't needed.

This solves that and also solves a problem with logout being broken because
cookies failed to be deleted.

Fixes #311

* Removed print statement
@specialunderwear
Copy link
Member

I had to revert the change that was meant to fix this.

@specialunderwear
Copy link
Member

@kevinrenskers I can't reproduce this problem at all. I can only confirm that the cookie of the anonymous user does not get deleted after login. However the moment I access the loggedin user via the api, the cookie is deleted and the basket is moved to the session. I can not confirm that basket listings contain baskets from other users. Could it be that you are logged in as superuser? Superusers can see baskets that belong to other users.

@kevinrenskers
Copy link
Author

kevinrenskers commented Aug 30, 2023

I'm not seeing a basket that belongs to some random other user, but from the user I was previously logged in as.

So let's say I am logged in as User A, then I log out. This should result in a new basket getting created for the anonymous user, but instead the basket for User A is still being used.

This is the root cause:

When BasketMiddleware adds something to request.cookies_to_delete, it is never actually deleted inside of ApiBasketMiddleWare.process_response. This is because ApiBasketMiddleWare.__call__ calls super(ApiBasketMiddleWare, self).__call__(request), which also has the line request.cookies_to_delete = [] (see here).

The result is that the person who was logged in and then logged out, can not add anything to the basket:

The way cookies are handled are actually a huge problem I think. At the moment when somebody is logged in, and then logs out, and I fetch their baskets using the BasketList view, I get the basket back that belongs to the previously logged in user. You can then proceed to add products to the basket, but it is then impossible to checkout, because the basket that is used by Oscar itself (stored in the request object) is a completely blank basket without an id, and nothing in it.

Basically Oscar and OscarAPI end up with two separate baskets and the user can not checkout at all.

@specialunderwear
Copy link
Member

specialunderwear commented Aug 31, 2023

@kevinrenskers could you modify the test in #319 that I added to make it reproduce what you describe? I tried to reproduce it, but I can't get it to show the problem you are describing, as you can see in the test ...

I never claim I couldn't reproduce seeing random users basket, I can't see any other baskets at all, not from the previously loggedin user, none, only the basket that belongs to the current user. There might be some specific sequence of actions that need to be performed in the api and the normal website to trigger this. I can't seem to find it though.

@kevinrenskers
Copy link
Author

I'm not sure how to add unit tests for this situation. I've encountered it in real world use of OscarAPI but it'll take quite a lot of time to replicate that in a unit test, I'd first have to get much more familiar with your code. Time I sadly don't have, especially since we as a company have decided to move away from Oscar / OscarAPI, so for us it doesn't make a lot of sense to put more time into this issue. Sorry :/

I did notice there's no unit tests for ApiBasketMiddleWare, maybe that would be a good start?

Otherwise feel free to close this issue if nobody else is running into it and it can't be reproduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants