9

I have encountered a problem when trying to upload files with bootsy gem, but the problem is not related to bootsy itself. Bootsy generates a form with a following definition:

<form class="bootsy-upload-form form-inline" id="new_image" data-type="json" enctype="multipart/form-data" action="/bootsy/image_galleries/67/images" accept-charset="UTF-8" data-remote="true" method="post">
<input name="utf8" value="✓" type="hidden">
<input name="authenticity_token" id="authenticity_token" value="1pQMR5j33OKupMg4TSnQafLQx1BxzWjkP1KqTfZafqDRfGqwB8r2J4FzRE+dyuoHVOw/W0qd1FZ1JoJsJFThDQ==" type="hidden">
...

When I try to upload a file, this line is executed:

this.uploadInput.closest('form').submit();

I have added alert before it so I would see how the serialized data of the form looks like and all the fields are shown as expected (including authenticity token etc.):

alert(this.uploadInput.closest('form').serialize());

When the form is submitted, no data is send during POST request, only headers, nothing is seen in browser inspector, nothing can be seen in log files of rails, it just looks like this:

Started POST "/bootsy/image_galleries/47/images" for ::1 at 2016-02-05 11:20:04 +0100
Processing by Bootsy::ImagesController#create as HTML
Parameters: {"image_gallery_id"=>"47"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 0ms (ActiveRecord: 0.0ms)
FATAL -- : 
ActionController::InvalidAuthenticityToken - ActionController::InvalidAuthenticityToken:
_ actionpack (4.2.4) lib/action_controller/metal/request_forgery_protection.rb:181:in `handle_unverified_request'_

I have authenticity token generated in the form, I have the token in meta tag, everything looks fine, no error is thrown anywhere. I also tried to create sample app which is similar to my real project and it worked as expected, form normally submitted data - when I tried to compare HTML code and javascript events attached to both, they were almost similar expect a few parts because of other gems like ajax_pagination etc., but there were no parts, which should cause such a behavior.

I am using Rails 4.2.4, turbolinks are disabled using the attribute data-no-turbolink on body element, project uses bootstrap and contains JS libraries like jQuery, underscore, parsley, momentjs.

I would appreciate any thoughts, what could went wrong, why the form should not submit any data, where could be a problem. Thanks in advance for any tip.

UPDATE: Just to clarify things, I have taken a picture of a state before sending using AJAX - this javascript is part of jquery_ujs = RoR adapter for jQuery. You can see, that data contains all the form fields before send: enter image description here

But data is NOT being send to the server: enter image description here

On the other hand in my second working project, data is being sent: enter image description here

UPDATE 2: Just a few more information, bootsy gem, which is responsible for creation of the form uses remotipart to attach files to the request. Still...I was debugging the javascript and was unable to identify the problem. Both projects have the same version of jquery and remotipart, also the same version of rails. Looks like this will stay a mystery.

UPDATE 3: So I have almost resolved the issue - uploading is now working, it looks like it was a problem with order of javascript libraries. I'll post the result as soon as I pinpoint the exact issue - I'll reverse the changes and try to fix it again.

Giron
  • 350
  • 3
  • 16
  • Try this, in your contrloller skip_before_action :verify_authenticity_token (or) comment 'protect_from_forgery with: :exception' in application controller. – Sravan Feb 12 '16 at 14:06
  • Check markup issues such as a form nested within a form - these can change what data is submitted – Frederick Cheung Feb 12 '16 at 14:14
  • In my two projects (main - contains this bug, second test project - works as expected), those two forms are syntactically the same. And also I am not responsible for their creation, the form which should send the data is created by bootsy gem and looks ok. – Giron Feb 12 '16 at 21:24
  • This is a long shot, but are you using `CanCan` gem or something similar for user authorization? Maybe the problem is because the user is not authorized to perform that action. If that is not the problem, check out [this answer](http://stackoverflow.com/a/30607146/4084391), maybe it would help you. – Daniel Batalla Feb 15 '16 at 15:28
  • Thank you for your suggestion, it is true that I use CanCan - I have tried to disable the authorization all together with no effect. Like I wrote, the main problem is, that no data is being sent to the server - content-length of request is 0 = no input values are sent (form has several other fields as well), this is not a problem of only authentification token. – Giron Feb 17 '16 at 05:09
  • Any chance you can post your code? I assume you already have permitted params in your model? – lacostenycoder Feb 19 '16 at 12:03
  • I have added third update, I have almost resolved it. – Giron Feb 19 '16 at 13:27

6 Answers6

2

The Issue was with an order of javascript libraries included in application.js. Bootsy was defined after jquery-fileupload. It seems, that Bootsy must be defined before it, with this setup, both works well. So state before:

//= require jquery-fileupload
//= require bootsy
//= require bootsy/locales/cs.js

after (working):

//= require bootsy
//= require bootsy/locales/cs.js
//= require jquery-fileupload
Giron
  • 350
  • 3
  • 16
1

It seems that the Authenticity Token is not being sent to the server as expected.

I would look at the Network requests tab in the browser and inspect the body of the post.

I think setting processData to false is correct. According to the JQuery docs,

By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string

Also, you can add this in the controller before the check for the token:

before_filter { puts params }

I would not skip the Authenticity Token before filter.

Also, in your image "But data is NOT being send to the server:", you are looking at the headers. The Authenticity Token is sent in the body.

I think the issue is the way the form is being encoded. You should not encode it. Set the Content Type to multipart/form-data. The Content Type is currently set to 'application/x-www-form-urlencoded'.

The server is URL decoding the form, which is combining all the fields into one. That's why it doesn't find the Authenticity Token.

According to Sending multipart/formdata with jQuery.ajax, you should set contentType to false in the ajax request. Alternately, you can set it to multipart/form-data.

Community
  • 1
  • 1
B Seven
  • 44,484
  • 66
  • 240
  • 385
  • I know the picture looks like I have no params listed, but this is how chrome shows them. In the second project, you can see the part "Request payload" with params and this part is not present in the problematic project = no params are sent, I have tried in Firefox too, which has special tab for params and there are none. Also your suggested filter doesn't execute, error is thrown before it, which is a bit interesting, because I have deactivated csrf protection. I'll look look into it more later, but now I'll verify Daniels suggestion. Also I agree, skipping token is not the right approach. – Giron Feb 18 '16 at 05:35
0

By default the form only sends normal data.If you need to send image along with the post request you can use remotipart gem. It works great .I have used it myself.

ajayg
  • 44
  • 5
  • Unfortunately I cannot use the gem in this case, because the form I have problems with is part of the other gem (bootsy) and I don't want to modify it's inner parts. But thank you, remotipart looks like and interesting gem. – Giron Feb 18 '16 at 05:43
  • Oh and now I see that bootsy gem has remotipart gem as it's dependency, so it is being used in this case. – Giron Feb 18 '16 at 06:43
0

I have two theories, none of them has nothing to do with your form:

First theory:

With the update of your question, if you look closely, cookies doesn't contain _csrf_token. If _csrf_token is not passed then Rails will raise that error message.

I can't see if your form is generating their own hidden field with this value. It may be that one of your projects do so and the other does't. You should check it out.

If that was the problem, there are various solutions that you can find. One of it is to include [csfr_meta_tag] in HTML head of the view. Check this out for more details.

Second theory:

If you are performing a CORS request, your browser makes an additional OPTIONS request first just to check that you do have permission to access this resource.

Apparently, _session_id cannot be sent in CORS, unless you enable it. To do so, you need to handle the OPTIONS request. This is done by modifying Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers headers on controller before_filter and after_filter methods.

Here lives a gist that shows an example of this (Forget the skip_before_filter line!). If you want to get more details of appointed headers values, check this resource out.

These are my theories, I hope that help you!

UPDATE

If both of your projects are using Rails v4.2, maybe the one that works has set protect_from_forgery like this:

protect_from_forgery
# OR maybe
protect_from_forgery with: :null_session

And the one that doesn't work has set protect_from_forgery like this:

protect_from_forgery with: :exception

The difference between them is the application behavior when a request is found to be unverified. See more here.

Daniel Batalla
  • 1,184
  • 1
  • 11
  • 22
  • Token is presented in both, cookies are just in different order in both pictures. But there are differences, problematic project contains following cookies: _utma, _ga, _gat, _session_id, _token_session. Working project contains only: _utma, _ga, _token_session. By the way I have <%= csrf_meta_tags %> in my HEAD tag. I will look into cookie more. Second theory about CORS should not apply here, request is being sent to the same domain. Thank you for your suggestion, cookie looks interesting. – Giron Feb 18 '16 at 05:41
  • So, I have made a few changes to my second working project (added redis cache store and google analytics) and cookies are now almost the same in both projects, the second still works, first does not. But there is still one cookie "request_method" which second in second project, but not in the first one...I'll look into that. – Giron Feb 18 '16 at 05:52
  • @Giron Are both of your projects using Rails 4 ? I ask you this because there is a significant difference when Rails perform forgery protection between Rails 3 and 4. By default, Rails 3 just [reset the session](https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L84), whereas Rails 4 [raise an exception](https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L172-L174)... Just to be sure ;) – Daniel Batalla Feb 18 '16 at 14:14
  • @Giron I have another theory, I will update my answer to deepen on it a little – Daniel Batalla Feb 18 '16 at 14:22
  • Both projects have protect_from_forgery with: :exception. Also I think that the problem is empty request from the front-end side, so such a backend-side configuration unfortunately cannot help. Anyway, thank you for your time, I really appreciate it, but it looks like front-end side problem with jQuery/remotipart maybe. – Giron Feb 19 '16 at 05:07
0

Without seeing your actual code, we are left at guesses and shooting in the dark, however as obvious as this may seem, I assume you've followed all gem instructions? The install generator uses asset pipeline and sets up initializers etc. This seems rather obvious but did you make sure to do whitelist bootsy param on the model in the app that's not working? (as per https://github.com/volmer/bootsy )

private

def post_params
  params.require(:post).permit(:title, :content, :bootsy_image_gallery_id)
end
lacostenycoder
  • 10,623
  • 4
  • 31
  • 48
  • Both projects have those params permitted so this is not a problem, but anyway thanks - I know it's shooting in the dark. By the way I don't mind looking at obvious things, it's almost always something obvious :-) I'll try to "reinstall" bootsy and we'll see. – Giron Feb 19 '16 at 13:12
  • 1
    Well, your answer made me look again at application.js which lead to solution, so bounty is yours - maybe it's a bit unfair against others, but fair would be to give nobody a bounty and that would be a waste. Enjoy. – Giron Feb 19 '16 at 13:43
  • out of curiosity I ask; would you care to share solution? oops, I see you already posted. Thanks! – lacostenycoder Feb 19 '16 at 15:02
-1

the CRSF authentication issues are the cross site scripting issues.To overcome this issue, do either,

skip_before_action :verify_authenticity_token in your controller

or you can comment this line in application controller.

protect_from_forgery with: :exception  

Visit this link for more description on this

Sravan
  • 18,467
  • 3
  • 30
  • 54
  • This is not the issue - I have been already giving this a thought. My second test project which is very similar has protection up and image upload works as expected (token is send and everything works). The main problem is, that the form doesn't send any data to the server, only headers are being sent. So I don't want to disable this protection, because it should work with it. – Giron Feb 12 '16 at 21:22