Routing issues with multi-part forms on Heroku - ruby-on-rails

This is a very bizarre problem, and unfortunately I can't provide a ton of information since I don't even know where to begin diagnosing the problem. I'm hoping somebody hear magically knows what to do, and I'm happy to clarify as necessary.
For some reason, anytime I submit a new multi-part form with a file attachment on Heroku using Chrome, I am sent upon submitting to the default "index" action--that is, the page I would be sent to if I had submitted a GET instead of a POST.
I'm using standard RESTful routes in Rails, so I have the following in my routes.rb:
resources :documents do
member do
get :download, :follow
end
end
My forms are pretty standard:
<%= form_for #document, :html => { :multipart => true } do |f| %>
<!-- Form code -->
<% end %>
But again, this is being treated as a GET rather than a POST request, so I am simply redirected to /documents. This is true even if I manually specify :method => :post in the form definition.
The really, really bizarre thing is that this is only happening on Heroku and only happening with Chrome. The forms work fine on my local dev version using Chrome or on Heroku using Safari.
There is no redirect occurring anywhere in the code, and when I check the Heroku logs it's only reporting a GET to /documents, never a POST with subsequent processing that could explain this.
Any help would be greatly appreciated. I really don't know where to begin trying to solve this.
UPDATE:
I am using Chrome for Mac, version 12.0.742.53 beta.
Here is the form output.
<form accept-charset="UTF-8" action="/documents" class="new_document" enctype="multipart/form-data" id="new_document" method="post">
<div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /><input name="authenticity_token" type="hidden" value="q8Qv4q9BXdV4yWzsPx9cgZoFGhJHxj6Nzje/SSnYsYo=" /></div>
....
<input type="file" name="document[file]" />
....
</form>
Nothing seems amiss with the routes, which include the following:
POST /documents(.:format) {:action=>"create", :controller=>"documents"}

Could you confirm that you have <%= csrf_meta_tag %> in your view

Related

rails 6 token authentication still needed?

I take courses on rails 5.x.x and when they used form they add a line for token authentication to protect their site, on the start of the form, like this :
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
But to be on the last version of rails i'm on 6.1.3 version and i don't see anything on the web about that.
So the question is : Did I still need to set this authenticity token anywhere ? if yes, where ? and if no, why ? If you have some links about that for rails 6 I don't say no. Thank's you.
No, you don't need to add it manually, Rails does it for you in each form.
<%= form_with do |form| %>
Form contents
<% end %>
generates
<form accept-charset="UTF-8" action="/" method="post">
<input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
Form contents
</form>
You'll notice that the HTML contains an input element with type
hidden. This input is important, because non-GET forms cannot be
successfully submitted without it. The hidden input element with the
name authenticity_token is a security feature of Rails called
cross-site request forgery protection, and form helpers generate it
for every non-GET form (provided that this security feature is
enabled). You can read more about this in the Securing Rails
Applications guide.
https://guides.rubyonrails.org/form_helpers.html

overriding rails authenticity_token setting

Submitting a form requires a token which the receiving server has named authenticity_token and is expecting a string that is known only to posting and receiving servers:
<input id="authenticity_token" name="authenticity_token" type="hidden" value="ac513de198a9f0536df5003fb5ba3122d1ee53d5" />
that value renders if I call an instance or global variable. However, even though the controller is set with the following filter:
skip_before_action :verify_authenticity_token, only: [:reservation]
whether I try
<%= form_tag('https://test.webten.net/form') do %>
<%= hidden_field_tag :authenticity_token, #ppayment.authenticity_token, id: "authenticity_token" %>
or
<%= tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: #ppayment.authenticity_token) %>
or
<input id="" name="authenticity_token" type="hidden" value="ac513de198a9f0536df5003fb5ba3122d1ee53d5" />
Rails ends up squashing each value with its own session setting value and rendering:
<input type="hidden" name="authenticity_token" id="authenticity_token" value="ydi5En1ywUkN5VsYIBXu6JTbQmXtwxNhpKlyjbbLi3RdvCc+A59EdDZvroGsGFplAAE5ATLcSqw25LVQkyPtKw==">
<input type="hidden" name="authenticity_token" value="ydi5En1ywUkN5VsYIBXu6JTbQmXtwxNhpKlyjbbLi3RdvCc+A59EdDZvroGsGFplAAE5ATLcSqw25LVQkyPtKw==">
<input id="authenticity_token" name="authenticity_token" type="hidden" value="ydi5En1ywUkN5VsYIBXu6JTbQmXtwxNhpKlyjbbLi3RdvCc+A59EdDZvroGsGFplAAE5ATLcSqw25LVQkyPtKw==">
How can rails default behaviour be overridden in this controller action?
You have to override the :authenticity_token argument in the form_tag call to prevent Rails from adding the session's authenticity token by default:
<%= form_tag('https://test.webten.net/form', authenticity_token: 'ac513de198a9f0536df5003fb5ba3122d1ee53d5') do %>
or:
<%= form_tag('https://test.webten.net/form', authenticity_token: false) do %>
<%= hidden_field_tag 'authenticity_token', 'ac513de198a9f0536df5003fb5ba3122d1ee53d5' %>
If that doesn't work, you probably have some JS that overwrites the authenticity token you provided from the one in the META tag. Search for 'csrf-token' in your JS.
See http://guides.rubyonrails.org/form_helpers.html#forms-to-external-resources for more information.
[this answer is complementary to the preceeding one. It solves the issue, but in an inelegant manner]
Various possible avenues of solution appear incomplete / off the mark
form_authenticity_tokenis a view helper that returns the current
session's authenticity token. Too late to act upon that as the
layout may already invoke it
most voted response has a per action method
protect_from_forgery for ignoring it for internal purposes. Still
is too late as session already has issued it
a skip before action is just like the previous solution, too
late, session token is issued
The rails guide is incorrect in stating: authenticity_token:
'external_token'
Aaron Breckenridge's answer provide a clue: "you probably have some JS that overwrites the authenticity token ", supported by this posting. Thus one could search the content of app directory and still not find a JS handler... when jquery-ujs, for example, is gem installed. Based on observed behaviour, it must be getting fired up every time it encounters name='authenticity-token' and sets the value based on the META tag (logical. why have 2 different tokens on same page...) - doing its job properly!
Literally, that token must not be generated upstream. So modifying the layout's header section:
<% unless request.path == "/controller/action" %>
<%= csrf_meta_tags %>
<% end %>
is a working solution. This solution gets pollution-prone for multiple paths. Another requires multiple layout handling... thus, file under 'kludgey'. (willing to entertain better one!)

Why does my Rails form always return a `POST` when I explicitly calling `PUT`?

Here's my examples that produce the same result :
# `enterprise_registration` is an already created/saved object
form_for enterprise_registration, method: :put do |format|; format; end
form_for enterprise_registration, url: logo_url, method: :put do |format|; format; end
form_for enterprise_registration, url: logo_url, html: {method: :put} do |format|; format; end
This returns the form with the method attribute set to POST.
Why is that happening, do you think? And how can I make it a :put request?
Update
I now understand that Rails forms embed a hidden _method and set it to put, but my form is still getting delivered as a POST that is preventing my form from finding my matching PUT URL
form tag has only GET or POST method allowed. See also here for more explanation. Rails, however, has his own method to handle GET/POST/PUT/PATCH requests. If you would examine any of your form defined as either form_for or form_tag in Rails, you will notice that first element of the form is a hidden <div> which contain two hidden fields:
<div style="display:none">
<input name="utf8" type="hidden" value="✓"><input name="_method" type="hidden" value="patch">
<input name="authenticity_token" type="hidden" value="9i5eRhwhx4NhvSxqIJm6cv9x6NSlY82hpNpfrpk/I0c=">
</div>
First field called _method contains the form action which is the request type for controller.
Web browsers are actually only programmed to receive POST and GET requests (I'm not sure why). The way Rails mimics full REST (which includes put and delete) is include those in hidden fields. So technically it's sending a POST, but with the PUT attached sort of awkwardly in that hidden field.
I'm assuming that Rails is still doing that sort of conversion behind the scenes still.

Form submitted twice, due to :remote=>true

My form submitted twice, after double checked, it was cause by ':remote=>true'. I removed it, my project works well. Who can show me why? And how to use ':remote=>true'?
My ruby code:
<%= form_tag(admin_product_group_product_scopes_path(#product_group), :remote => true, :id => 'new_product_group_form') do %>
<%
options =
grouped_options_for_select(
Scopes::Product::SCOPES.map do |group_name, scopes|
[
t(:name, :scope => [:product_scopes, :groups, group_name]),
scopes.keys.map do |scope_name|
[ t(:name, :scope => [:product_scopes, :scopes, scope_name]), scope_name]
end
]
end
)
%>
<p>
<label for="product_scope_name"><%= t('add_scope') %></label>
<%= select_tag("product_scope[name]", options) %>
<input type="submit" value="<%= t('add') %>" />
</p>
<% end %>
The final html code in browser.
<form accept-charset="UTF-8" action="/admin/product_groups/17/product_scopes" data-remote="true" id="new_product_group_form" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓"><input name="authenticity_token" type="hidden" value="GocX/l4ZNgF/feKtzC8FuohebM2k5MuIHtdeGp2Oi0A="></div>
<p>
<label for="product_scope_name">Add a scope</label>
<select id="product_scope_name" name="product_scope[name]"><optgroup label="Taxon"><option value="taxons_name_eq">In Taxon(without descendants)</option>
<option value="in_taxons">In taxons and all their descendants</option></optgroup><optgroup label="Text search"><option value="in_name">Product name have following</option>
<option value="in_name_or_keywords">Product name or meta keywords have following</option>
<option value="in_name_or_description">Product name or description have following</option>
<option value="with_ids">Products with IDs</option></optgroup><optgroup label="Values"><option value="with">With value</option>
<option value="with_property">With property</option>
<option value="with_property_value">With property value</option>
<option value="with_option">With option</option>
<option value="with_option_value">With option and value</option></optgroup><optgroup label="Price"><option value="price_between">Price between</option>
<option value="master_price_lte">Master price lesser or equal to</option>
<option value="master_price_gte">Master price greater or equal to</option></optgroup></select>
<input type="submit" value="Add">
</p>
</form>
In case people are stumbling on this question like I did:
I had the same problem, and sannankhalid's answer didn't fix it, but deleting a locally precompiled application.js in the public/assets directory did -- the ujs is included twice, so it fires twice. Via https://stackoverflow.com/a/9627690/604093
On Rails 5, rails-ujs replaces jquery_ujs. Events will trigger twice if both are required.
// app/assets/javascripts/application.js
//= require jquery_ujs <-- delete this
//= require rails-ujs
I am assuming that you are using jquery. This is usually happened when there is an incomplete call or there is some sort of error and you haven't refresh the page. Try something like this:
<script type="text/javascript">
$('#new_product_group_form').submit(function() {
$(this).unbind('submit').submit();
});
</script>
It seems that Ryan Muller's answer is correct. But removing application.js is not proper way to do as per my view. What I have done is I have opened the developer's tool in chrome and click on network part. Now when I click on submit button then it would show me that who is making request. So I removed that JS and tried it again and it works. So as per Ryan Muller its correct that its problem of JS by including twice. But make sure you maintain the dependency of JS as well.
Try ctrl-c on the server to stop it. Then rm -r public/assets/ to get rid of the assets directory (and the duplicate application.js). Restart the server from the same terminal window and it might work as expected.
Wanted to add another possible cause for this. For me it was using Mixpanel's api. Specifically https://mixpanel.com/docs/integration-libraries/javascript-full-api#track_forms
It seems, that using :remote=> true in conjunction with mixpanel.track_forms will cause the form to submit via normal html after the desired json.
It's a probably rare, but took me a while to track down.
Here is the HAML equivalent to sannankhalid's.
:javascript
= f.submit(function() {
$(this).unbind('submit').submit();
});
I had the double-POST issue with Rails 4 and Bootstrap 3 (w/jQuery), submitting form updates from modals.
in your application template (or wherever you keep the <%= javascript_include_tag "application" %> tag or the haml equivalent, add the "data-turbolinks-track" => true flag, so the tag will now look like this: <%= javascript_include_tag "application", "data-turbolinks-track" => true %>.
It happened to me once the solution was to remove the google tag manager, because I was tracking the form submission with it.
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-PQZJ2T"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-PQZJ2T');</script>
In most cases, this issue is caused by jquery_ujs or rails_ujs being included multiple times.
See discussion on: https://github.com/rails/jquery-ujs/issues/208

Why does the post method seem to be clearing my rails session data?

After the user has logged in and their username authenticated and saved in session[:user_name], when submitting a form with method="post" (using standard html) all session data is cleared and user is routed back to the login page. This does not happen if the method is set to get.
Post works when I use the rails "form_tag" which generates these additional lines:
<div style="margin: 0pt; padding: 0pt; display: inline;">
<input type="hidden" value="✓" name="utf8">
<input type="hidden" value="a_bunch_of_gibberish" name="authenticity_token">
</div>
What is happening?
Are you using Rails 3.0.4? It sounds like it might be related to the CSRF fix.
If you need a fix specific to your needs, you can overwrite #handle_unverified_request in your controller. See https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/request_forgery_protection.rb.
Here's an example where this issue was with OmniAuth and OpenId. (See section 'CSRF Protection In Rails 3.0.4')
This issue can also happen if you're creating your own html form.
If you're getting redirected without knowing why, it's because of "protect_from_forgery" in your ApplicationController
In order to allow your form to post, add the following code to it (or use form_tag, explanation below):
<form ... >
...
<%= hidden_field_tag "authenticity_token", form_authenticity_token %>
...
</form>
The reason the "form_tag" works is because form_tag generates the hidden field for you =)

Resources