简体   繁体   中英

Getting CSRF token missing or incorrect for one route but not the other

Working on React/Django and have run into an issue I can't resolve.

On the front end, there is the following JS sending data to the Django API. In this case, rejectedDocuemts() is sending an array of filenames to the backend so eventually an email can be created and sent to the admin to review. Files that don't match an approved list of extensions make it on this list. This is where the error occurs.

The submitDocuments is where files that meet the approved list of file extensions are submitted. The entire files object is sent to the server to be saved. This one is working perfectly fine.

export function rejectedDocuments(filenames, id) {
    return function (dispatch) {
        axios.post(
            `${URL}/api/${(id)}/documents/upload/error`,
            filenames,
            { 
                headers: {
                    'content-type': 'text/plain',
                    'Authorization': 'JWT ' + sessionStorage.getItem('token')
                }
            }  
        )
    }
}

// Creates the error state to be used in components
export function submitDocuments(files, id) {
    return function (dispatch) {
        const uploaders = _.map(files, f => {
            const formData = new FormData();

            formData.append('file', f);
            return axios.post(
                `${URL}/api/${(id)}/documents/upload/success`,
                formData,
                { 
                    headers: { 
                        'content-type': 'multipart/form-data',
                        'Authorization': 'JWT ' +  sessionStorage.getItem('token')
                    }
                }          
            )
        });

        axios.all(uploaders)
        .then(response => {
            dispatch({ 
                type: SUBMIT,
                payload: response[0].status
            });
        })     
    }
}

The rejectedDocuments class is where the error ( Forbidden (CSRF token missing or incorrect.): /api/84/documents/upload/error ) is occurring. Inspecting the network headers they both include Cookie: csrftoken=xyz . They both have the same CSRF token.

So not sure what is up or how to get around it. submitDocuments works perfectly fine and they are not all that different from one another other than one sends a file object ( submitDocuments ) and the other sends an array of strings ( rejectedDocuments ).

At any rate, here is the Django back-end:

# documents/urls.py
from django.conf.urls import url

from .views import (
    GetDocumentsAPIView, 
    RejectedDocumentsView,
    SaveDocumentAPIView
)

app_name = 'Documents'

urlpatterns = [
    url(r'^documents/upload/success', SaveDocumentAPIView.as_view(), name='upload'),
    url(r'^documents/upload/error', RejectedDocumentsView.as_view(), name='rejected'),
    url(r'^documents/', GetDocumentsAPIView.as_view(), name='documents')
]

# documents/views.py
import datetime

from django.contrib.auth import get_user_model
from django.db.models import Max
from django.views import View

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView

from .serializers import GetDocumentsSerializer, SaveDocumentSerializer
from .models import Uploads

User = get_user_model()

# Create your views here.
class GetDocumentsAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):
        data = {'id': request.user.id}
        if kwargs:
            data['sur_id'] = kwargs.get('sur_id')
        serializer = GetDocumentsSerializer(data=data)
        if serializer.is_valid(raise_exception=True):
            return Response(serializer.data, status=HTTP_200_OK)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

class RejectedDocumentsView(View):
    def post(request):
        print(request)

class SaveDocumentAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        max_id = Uploads.objects.all().aggregate(Max('id'))
        if max_id['id__max'] == None:
            max_id = 1
        else:    
            max_id = max_id['id__max'] + 1
        data = {
            'user_id': request.user.id,
            'sur_id': kwargs.get('sur_id'),
            'co': User.objects.get(id=request.user.id).co,
            'date_uploaded': datetime.datetime.now(),
            'size': request.FILES['file'].size
        }
        filename = str(data['co']) + '_' + str(data['sur_id']) + '_' + str(max_id) + '_' + request.FILES['file'].name
        data['doc_path'] = filename
        self.save_file(request.FILES['file'], filename)
        serializer = SaveDocumentSerializer(data=data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=HTTP_200_OK)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

    # Handling the document
    def save_file(self, file, filename):
        with open('flu/' + filename, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

Not posting the serializes because RejectedDocumentsView(View) doesn't need one and the error seems to be occurring before it makes it to the view.

I re-evaluated my approach and found it was flawed. The data is all being submitted through the same <form> so it occurred to me it should all be submitted through the same FormData() .

I got rid of the rejectedFiles function and ended up rewriting the submitDocuments function as this:

export function submitDocuments(accepetedFiles, rejectedFiles, id) {
    return function (dispatch) {
        var formData = new FormData();

        // handle the accepetedfile and append to formData
        _.map(accepetedFiles, f => {
            formData.append('file', f);
        });
        // handle the rejectedfiles and append to formData
        _.map(rejectedFiles, r => {
            formData.append('rejected', r)
        })

        // Send the formDat to the server
        axios.post(
            `${URL}/api/${(id)}/documents/upload`,
            formData,
            {
                headers: {
                    'content-type': 'multipart/form-data',
                    'Authorization': 'JWT ' + sessionStorage.getItem('token')
                }
            }
        )
        // handling of the server response, should enable the modal to render
        .then(response => {
            dispatch({ 
                type: SUBMIT,
                payload: response.status
            });
        })  
    }
}

This creates FormData() that contains all of the files and the array of filenames that have extensions that are not permitted.

On the back-end, I got rid of the following in the urls.py :

url(r'^documents/upload/error', RejectedDocumentsView.as_view(), name='rejected'),

And ended up with this:

from django.conf.urls import url

from . import views

from .views import (
    GetDocumentsAPIView, 
    SaveDocumentAPIView
)

app_name = 'Documents'

urlpatterns = [
    url(r'^documents/upload', SaveDocumentAPIView.as_view(), name='upload'),
    url(r'^documents/', GetDocumentsAPIView.as_view(), name='documents')
]

Lastly, I updated the views.py to handle the array that was coming in with the request.POST :

class SaveDocumentAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):

        # this for handlign the files we DON't want
        # files are not actually just sent to the server
        # just the files names so an email can be sent to the admin
        # to review the file types
        user_obj = User.objects.filter(id=request.user.id).first()

        if len(request.POST.getlist('rejected')) >= 1:
            send_mail(
                'Invalid File Extension',
                'The user %s %s with email %s has submitted the following files with invalid extensions: \n\n %s' % \
                (user_obj.first_name, user_obj.last_name, user_obj.email, request.POST.getlist('rejected')),
                'The Company <no-reply@company.com>',
                ['admin@company.com']
            )

        # this is for handling the files we do want
        # it writes the files to disk and writes them to the database
        for f in request.FILES.getlist('file'):
            max_id = Uploads.objects.all().aggregate(Max('id'))
            if max_id['id__max'] == None:
                max_id = 1
            else:    
                max_id = max_id['id__max'] + 1
            data = {
                'user_id': request.user.id,
                'sur_id': kwargs.get('sur_id'),
                'co': User.objects.get(id=request.user.id).co,
                'date_uploaded': datetime.datetime.now(),
                'size': f.size
            }
            filename = str(data['co']) + '_' + \
                    str(data['sur_id']) + '_' + \
                    str(max_id) + '_' + \
                    f.name
            data['doc_path'] = filename
            self.save_file(f, filename)
            serializer = SaveDocumentSerializer(data=data)
            if serializer.is_valid(raise_exception=True):
                serializer.save()
        return Response(status=HTTP_200_OK)

    # Handling the document
    def save_file(self, file, filename):
        with open('fileupload/' + filename, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

Although sloppy, it does what I need it to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM