CSRF rejection (403) on trying to using Editor upload

CSRF rejection (403) on trying to using Editor upload

ahousetovahousetov Posts: 3Questions: 1Answers: 0
            var editor;
            var table;
            function getCookie(name) {
                var cookieValue = null;
                console.log('document.cookie : '+document.cookie)
                if (document.cookie && document.cookie !== '') {

                    var cookies = document.cookie.split(';');
                    console.log('cookies : '+cookies);
                    for (var i = 0; i < cookies.length; i++) {
                        var cookie = cookies[i].trim();
                        console.log('cookie : '+cookie);
                        //Does this cookie string begin with the name we want?
                        if (cookie.substring(0, name.length + 1) === (name + '=')) {
                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                            break;
                        }
                    }
                }
                console.log('cookieValue : '+cookieValue)
                return cookieValue;
            }
//             var csrftoken = Cookies.get('csrftoken');
            var csrftoken = getCookie('csrftoken');
            $(document).ready(function() {
                editor = new $.fn.dataTable.Editor({
                    table: "#bill",
                    idSrc: 'pk',
                    i18n: {
                                create: {
                                    button: "Создать",
                                    title:  "Создать новую запись",
                                    submit: "Создать"
                                },
                                edit: {
                                    button: "Изменить",
                                    title:  "Изменить эту запись",
                                    submit: "Изменить"
                                },
                                remove: {
                                    button: "Удалить",
                                    title:  "Удалить эту запись",
                                    submit: "Удалить",
                                    confirm: {
                                                        _: "Вы уверены что хотите удалить %d записей?",
                                                        1: "Вы уверены что хотите удалить эту запись?"
                                    }
                                },
                                error: {
                                        system: "Ошибка. Обратитесь к администратору."
                                }
                    },
                    ajax: {
                        create: {
                            type: 'POST',
                            url: '/api/bill/',
                            headers: {'X-CSRFToken': '{{ csrf_token }}'},
                            data: function(d) {
                                return JSON.stringify(d);
                            }
                        },
                        edit: {
                            type: 'PUT',
                            url: '/api/bill/_id_/',
                            headers: {'X-CSRFToken': '{{ csrf_token }}'},
                            data: function(d) {
                                return JSON.stringify(d);
                            }
                        },
                        remove: {
                            type: 'DELETE',
                            url: '/api/bill/_id_/',
                            headers: {'X-CSRFToken': '{{ csrf_token }}'}
                        },
                        data: function ( d ) {
                          console.log("'{{ csrf_token }}' ");
                          d.CSRFToken = '{{ csrf_token }}';
                        }
                    },
                    fields: [
                               ....
                     {
                        label: "Скан-копия счёта:",
                        name: "scan",
                        type: "upload",
                        display: function ( id ) {
                              return '<img src="'+editor.file( 'images', id ).webPath+'"/>';
                          },
                        noImageText: 'Пока нет изображений.'
                    }, 
                    .....
                    {
                        label: "Примечания:",
                        name: "notes",
                        type: "textarea"
                    }]
                });

My debug configuration: https://debug.datatables.net/ecohok

Error message: A server error occurred while uploading the file

CSRF token missing or incorrect.

I'm trying to upload files to my server, but I constantly get a ban on this and a 403 error. I've read a lot of topics on this forum and the main idea that I get was that the upload field either sends its own CSRF token or needs a new token that is different from the session token.

At the moment, I can say that the function returns an empty value, however, "{{csrf-token}}" really produces a unique value for each session.

Any requests to the server, with the exception of the request to download the file through the download, are performed correctly.

What confuses me is that the csrf token from "{{csrf token}}" and the token from the request cookie are different. Maybe I just did not understand how it works to the end.

This question has accepted answers - jump to:

Answers

  • colincolin Posts: 15,144Questions: 1Answers: 2,586
    Answer ✓

    The section of the manual should help, it's discussing that. There are also a few thread in the forum, such as here and here, that should get you going.

    Colin

  • ahousetovahousetov Posts: 3Questions: 1Answers: 0

    Ok, this is solve a problem with CSRF.

    {
      label: "Скан-копия счёта:",
      name: "scan",
      type: "upload",
      display: function ( id ) {
           return '<img src="'+editor.file( 'images', id ).webPath+'"/>';
      },
      noImageText: 'Пока нет изображений.',
      ajax: {
               type: "POST",
               url: '/api/bill/',
               headers: {'X-CSRFToken': '{{ csrf_token }}'},
               done: function (json) {
               //Make your callback here.
               console.log(json);
               }
       }
     }
    

    But it makes another. If i understand correctly, "upload" field in configuration above, makes it's own ajax POST request to server.
    I have POST request for table with a dozen other fields + "upload", but not for single "upload" field.

    I've tried this:

       {
         label: "Скан-копия счёта:",
         name: "scan",
         type: "upload",
         display: function ( id ) {
             return '<img src="'+editor.file( 'images', id ).webPath+'"/>';
         },
         noImageText: 'Пока нет изображений.',
         ajax: {
                  type: "PUT",
                  url: '/api/bill/_id_',
                  headers: {'X-CSRFToken': '{{ csrf_token }}'},
                  done: function (json) {
                          //Make your callback here.
                          console.log(json);
                   }
          }
        }
    

    But it's still trying make a POST request, which is forbidden in this case.

    I'm confusing. Why it should make it's own independent request? I need a make a separated serializer for processing it?

  • allanallan Posts: 61,722Questions: 1Answers: 10,108 Site admin
    Answer ✓

    If you remove the ajax option from the upload configuration object, it will use Editor's default ajax option.

    Allan

  • ahousetovahousetov Posts: 3Questions: 1Answers: 0

    Thanks Allan.

    I've already try this option, and it wasn;t a good solution for my case.

    Still, i've found my own.

    Using Django framework, it is simple add some extra action to your API. That's what i did:

     @action(detail=False, methods=['get', 'post', 'put'])
        def image(self, request):
    
            pk = request.data["DT_RowID"]
            if pk:
                bill = Bill.objects.get(id=pk)
                upload = request.FILES["upload"]
                web_path = f"{MEDIA_URL}bill/{pk}/"
                try:
                    bill.scan.save(upload.name, upload.file, save=True)
                    respond = {
                        "files": {
                            "bill": {
                                f"{pk}": {
                                    "id": f"{pk}",
                                    "filename": upload.name,
                                    "web_path": f"{web_path}{upload.name}",
                                    "filesize": upload.size
                                }
                            }
                        },
                        "upload": {"id": f"{pk}"}
                    }
                    return Response(respond, status=status.HTTP_200_OK)
                except Exception as e:
                    print(f"e : {e}")
                    return Response(e, status=status.HTTP_400_BAD_REQUEST)
    

    Extra action 'image' for 'api/bill' is a excellent solver for asynchronous work with files.
    It took some experiments with respond, but it works! In documentation says, that respond should be a JSON. When I turned a 'respond' to JSON string, upload gives me an error.

    When i put a simple python dictionary, it started to work fine.

    In my JS code it looks like this:

    {
       label: "Скан-копия счёта:",
       name: "scan",
       type: "upload",
       noImageText: 'Пока нет изображений.',
       display: function ( id ) {
                    // Now this is odd. In all examples i've saw something like:
                    // "editor.file( 'bill', id ).web_path", and it should give a webpath to image
                    // but in my case, "id" - is WebURL already, and It does not require any calls 
                    // to the structures inside the editor. That's why i'm using it directly.
                    return id ? '<img src="'+id+'"style="width:70px" />' : "Нет изображения"
       },
       ajax: {
                 data: function(d) {  d.DT_RowID = editor.ids();  },
                 type: "PUT",
                 url: '/api/bill/image/', // This is API for asynchronic 
                                                 // "upload" field
                 headers: { 'X-CSRFToken': '{{ csrf_token }}' }, // this is a token, for 
                                                                                          // "upload" session.
       }
    },
    

    Couple words about this:

    data: function(d) { d.DT_RowID = editor.ids(); },

    Since "upload" field doen't support "id" expression, and put "pk" of my entry in url became a problem, i've used "editor.ids()" to get a value of "pk" from table. It saves in "DT_RowID" filed of request to server.

    "id" expression would be a sipler and nicer, but this is also work.

    I hope, it help someday someone.

This discussion has been closed.