Skip to content

Views mutate user object when using force_request #5016

@Alex3917

Description

@Alex3917

tl;dr: In line 25 and 271 of test.py, request._force_auth_user = user should probably be replaced with request._force_auth_user = copy.deepcopy(user). Otherwise if you make a second request to a view with the same user, bad things can happen.

See the test code and comments below for an example of how this happens:

class UserTestCase(TestCase):
    def setUp(self):
        self.factory = APIRequestFactory()
        self.view = user_views.User.as_view()

    def test_modify_profile_with_invalid_gender(self):
        user1 = UserFactory(email_address='[email protected]', password='hunter2')

        # Try submitting an invalid gender
        put_request_data = { 'gender': 'ABC' }

        request = self.factory.put('/user', put_request_data)
        force_authenticate(request, user=user1)
        resp = self.view(request)

        # At this point in the test code one would expect that user1
        # wouldn't have any 'gender' property, since we never saved the
        # user model in the 'put' method. However, the implementation of 
        # force_authenticate uses the user object that's passed in directly
        # instead of doing `user = copy.deepcopy(user).`

        request2 = self.factory.get('/user')
        force_authenticate(request2, user=user1)
        resp2 = self.view(request2)

        # And now the 'get' view returns { 'gender': 'ABC' } instead of 
        # { 'gender': '' }, so subsequent tests fail. 


# user_views.py
class User(APIView):
    def put(self, request):
        gender = request.data['gender']

        try:
            # Let's assume gender is a choice field where this throws an error
            request.user.gender = gender
            request.user.full_clean()
        except ValidationError as e:
            return Response(data={}, status=422)

        # This code never executes so the user model shouldn't get saved.
        request.user.save()
        return Response(data={}, status=200)


    def get(self, request):
        resp = {'gender': request.user.gender }
        return Response(data=resp, status=200)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions