I'm looking for a solution that will allow my Rails app to render a user-friendly maintenance page when there is no MySQL server available to connect to.
Normally a Mysql::Error is thrown from the MySQL connection adapter in active_record Something like:
/!\ FAILSAFE /!\ Wed May 26 11:40:14 -0700 2010
Status: 500 Internal Server Error
Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock
Is there a low-overhead way to catch this error and render a maintenance page instead?
I'm assuming that since connections are actually made in the active_record MySQL adapter the app never makes it to the controller stack before it throws the error, so you can't catch it in a controller.
You could create a view in whatever your root_path controller is:
map.root :controller => "foo", :action => "index"
Let's say you call this view "db_maintenance.html.erb". In your controller, do this:
def index
begin
#widgets = Widget.find(:all)
rescue Exception => e
# This will only happen if DB stuff fails
redirect_to :action => "db_maintenance", :error => e.message
end
end
...
def db_maintenance
#error = params[:error] # You might want to do something with this here or in the view
# renders the app/views/foo/db_maintenance.html.erb view
end
In your view, you could put something like:
<h1>Sorry for the inconvenience</h1>
blah blah blah. This happened because of:
<pre><code><%= #error %></code></pre>
This, ofcourse, only helps if the user hits your site's main page, but you could easily extrapolate from there. You could add the "def db_maintenance" action to the application controller and manually specify what view it should render too. It's not perfect, but it should get the job done.
I think this is about your front-end configuration. For example, if you have Apache in front of some mongrels, you can configure Apache through ErrorDocument instructions to show a suitable file in case of error.
What is your front-end?
Stephan
Related
I want to create a button in views/companies/edit.html.haml for destroy_referrals method, but I can't get the button to work. To call the action on a button I tried to create a new route, but I am still receiving the error The action 'destroy_referrals' could not be found for CompaniesController
I created a destroy_referrals method in Admin::CompaniesController,
def destroy_referrals
load_company
#company.destroy_referrals
flash[:notice] = 'Referrals deleted'
redirect_to :action => :index
end
it manipulates the company.rb model to find the referrals from users with the same company_id and destroys them,
def destroy_referrals
Referral.joins(:user).where("users.company_id = ?", self.id).destroy_all
end
Some things I've tried below...
routes.rb
post '/destroy_referrals' => 'admin/companies#destroy_referrals', :as => 'destroy_referrals'
views/companies/edit.html.haml
= button_to 'Destroy Referrals', destroy_referral_path
= button_to 'Destroy Referrals', admin_company_destroy_referral_path(#company)
HTML links (not “buttons”) aren't wired up to call “methods” directly.
The button_to method creates an HTML button, which behaves similarly to a link, but with different visual styles.
Ruby has methods, which are a common programing construct.
HTML links have an href attribute that a browser can follow by making a subsequent HTTP request to.
When a browser sends a request to your Rails application, the application uses your routes to determine how to handle that request.
The conventional way to handle a request in Rails is to route it to a controller action.
A RESTful request to /products/1 might, by convention, route to the show action on the ProductsController.
In Rails, an “action” is created by defining a method on the controller.
RESTful routes reflect the representational state transfer paradigm, involving reading, listing, creating, updating, and deleting records/objects.
To delete (or destroy) a record, RESTful convention would be to send a DELETE request to the Rails application, which would be routed to the relevant controller's destroy action, defined by the destroy method.
You can create an HTML link that results in a browser sending such a request using the link_to helper and specifying method: :delete in the parameters.
You can define a route instructing Rails to handle such a request using the built in “resourceful” routing: e.g. resources :products. Alternately, an explicit route can be defined: delete '/products/:id' => 'products#destroy'.
This all said, the error Rails is giving you is that it can't find the destroy_referrals method in CompaniesController. Defining methods in Admin::CompaniesController won't do anything to resolve this.
#Swards the route needed to be delete
'companies/:id/destroy_referrals' => 'companies#destroy_referrals',
:as => 'destroy_referrals'
I thought it might be a simple fix. Do you still need the admin namespace?
Consider this, instead of creating a named route
namespace 'admin' do
resources :companies do
delete 'destroy_referrals', :on => :member, :as => 'destroy_referrals'
end
end
And refer to it as admin_destroy_referrals_company_path
= button_to 'Destroy Referrals', admin_destroy_referrals_company_path(#company), :method => :delete
I am getting a "Can't verify CSRF token authenticity" in Rails production. My questions are:
Why is it doing this?
How can I fix it?
Here's my Heroku logs (some values anonymized):
2016-02-13T01:18:54.118956+00:00 heroku[router]: at=info method=POST path="/login" host=[MYURL] request_id=[ID STRING] fwd="FWDIP" dyno=web.1 connect=0ms service=6ms status=422 bytes=1783
2016-02-13T01:18:54.116581+00:00 app[web.1]: Started POST "/login" for [IPADDRESS] at 2016-02-13 01:18:54 +0000
2016-02-13T01:18:54.119372+00:00 app[web.1]: Completed 422 Unprocessable Entity in 1ms
2016-02-13T01:18:54.118587+00:00 app[web.1]: Processing by SessionsController#create as HTML
2016-02-13T01:18:54.118637+00:00 app[web.1]: Parameters: {"utf8"=>"✓", "authenticity_token"=>"[BIGLONGRANDOMTOKENSTRING]", "session"=>{"email"=>"[FRIENDSEMAILADDRESS]", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
2016-02-13T01:18:54.119082+00:00 app[web.1]: Can't verify CSRF token authenticity
2016-02-13T01:18:54.120565+00:00 app[web.1]:
2016-02-13T01:18:54.120567+00:00 app[web.1]: ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
2016-02-13T01:18:54.120569+00:00 app[web.1]: vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.0/lib/action_controller/metal/request_forgery_protection.rb:181:in `handle_unverified_request'
.
.
.etc
The only manifestation I'm aware of is when my friend tries to log in using Safari on his iPhone 5. His user account was created right about 6 months ago. I'm 99% sure that he accessed the site just fine with his phone at that time. Since then he hasn't logged in and I'm not aware of any changes I've made to the login/auth codes. Yesterday I had him hit my site for the first time in ~6 months and now he gets the CSRF error.
This issue doesn't happen to any other user account (that I'm aware of) or on any other device. In fact, logging in to his account from his older iPhone 4 works just fine.
I have a decent amount of dev experience but am totally new to web dev and everything Rails.
Here's what I have:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
Application layout:
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
My secrets file looks like this:
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
And I have a production environment var on Heroku for the secret_key_base.
def log_in(user)
session[:user_id] = user.id
end
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
Here's what I've done:
I began developing my app by following everything in Michael Hartl's Rails Tutorial to the letter up to/through Chapter 10. Most relevant is Chapter 8. Specifically, my app uses all the security / cookies / user auth stuff exactly as in the tut. I don't do anything fancy in my app...no AJAX or anything like that. I even yanked turbolinks.
My project has spanned the last 18 months so I'm not 100% positive which version I began on. I know it was 4.1.X and it was probably 4.1.6. I also am unsure the date I upgraded but at some point did to what I'm presently running; 4.2.0.
I've read just about every post I can find on the web regarding problems with CSRF + Rails. Seems like for almost everything I've read, the cause and solution have to do with AJAX or Devise, neither of which apply to me. iFrame issues are another common source on the web, which I'm neither using.
I've used my app's password reset feature to no avail. I tried changing protect_from_forgery to with: :reset_session. The only thing this changes is the Rails exception page is no longer displayed. But it won't let him go to any page requiring authentication. It just takes him back to root because I have this line in my routes:
get '*path' => redirect('/')
I don't want to clear his cookies/cache etc because I have dozens of other existing user accounts that I don't want to have to manually fix.
Frequently suggested solutions are some variant of turning off security, which I don't want to do for obvious reasons.
Some other things I have changed but haven't had a chance to test yet (because I don't have easy access to my friend's iPhone):
I changed the appstore name in session_store.rb:
Rails.application.config.session_store :cookie_store, key: '[NEWNAME]'
Ran the following commands:
heroku run rake assets:clean
heroku run rake assets:precompile
I am about to embark on a deep dive here, especially section 3.
Thanks for reading/considering. Any tips/ideas/suggestions/pointers would be greatly appreciated!
Turns out this gentleman was having the exact same issue as me and was able to create a repro case that worked for me. If I'm understanding right, Safari is caching the page but nuking the session. This causes the authenticity_token value to look legit'ish in my rails params but protect_from_forgery fails when verifying the token because the session was nuked.
The solution then is two fold: turn off caching and handle CSRF exceptions. You still need to handle exceptions even if you turn off caching because some browsers (e.g. Safari) don't respect no-cache settings. In this case a CSRF issue arises and hence the need to handle that also.
The workaround for me was to handle the CSRF exception by killing off all my cookies and session data, flash an "oops" message and redirect them to the login page. The redirect will pull down a fresh auth token that will verify when doing the login post. This idea came from here:
It is common to use persistent cookies to store user information, with cookies.permanent for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # Example method that will destroy the user cookies
end
The above method can be placed in the ApplicationController and will be called when a CSRF token is not present or is incorrect on a non-GET request.
cookies.permanent is exactly what I was using. So I implemented the above tip like this:
class ApplicationController < ActionController::Base
include SessionsHelper
protect_from_forgery with: :exception
before_filter :set_cache_headers
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies.delete(:user_id)
cookies.delete(:remember_token)
session.delete(:user_id)
#current_user = nil
flash[:danger] = "Oops, you got logged out. If this keeps happening please contact us. Thank you!"
redirect_to login_path
end
def set_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
end
Incidentally, after implementing the fix and verifying it worked in dev, my friend's phone was still unable to login, albeit with different behavior. Upon investigation I found he had "all cookies blocked" in his iPhone 5 Safari settings. This caused other weird behavior that made it hard to sort out which issue was causing what. The tipoff came when I realized I couldn't use his phone to log in to any online accounts (e.g. yahoo mail etc.). Going in to his Safari settings and allowing cookies solved things and now everything works great on his phone (and everywhere else that I'm aware of).
I'm creating an incredibly basic photo sharing app in Rails that displays albums from the local filesystem.
For example -
/path/to/pictures
|
|-> 2003_college_graduation
|-> 2002_miami_spring_break
However, anyone can take a look at the HTML source and get the absolute path to the image -
my.server.com/path/to/pictures/2003_college_graduation/IMG_0001.JPG
And with a little guesswork, anyone could navigate to other images on the server, even ones they don't have permission to.
Is there any way to "mask" the URL here?
One potential solution is to hash each filepath into a UUID and store the mappings in mysql table. Then when they request the URL with that hash I can look it up in the table and pull the correct image. But that makes the URL looks messy and creates a problem if the URL ever changes (because the hash will change).
Are there any libraries or other workarounds to mask the real path to a file?
Thanks!
You could use a url minifier (take your pick) and use that link. They'd still be able to see the original source if they followed it, but it would get it out of the html file.
What you're trying to achieve here is a security through obscurity, which isn't going to work in the end. One can get aware of the scrambled URLs from any other source and still have access to the pics he should not be seeing.
The real solution is to actually control access to the files. It is a pretty common problem with a pretty common solution. In order to force access control you have to invoke a Rails controller action before serving the file and verify the credentials, and then, if the credentials are valid, serve the actual file.
It could be like this in the controller:
class PhotoController < ApplicationController
def photo
if user_has_access?(params[:album], params[:photo])
# be *very* careful here to ensure that user_has_access? really validates
# album and photo access, otherwise, there's a chance of you letting a malicious
# user to get any file from your system by feeding in certain params[:album]
# and params[:photo]
send_file(File.join('/path/to/albums', params[:album], "#{params[:photo]}.jpg"), type: 'image/jpeg', disposition: 'inline')
else
render(file: File.join(Rails.root, 'public/403.html'), status: 403, layout: false)
end
end
private
def user_has_access?(album, photo)
# validate that the current user has access and return true, if he does,
# and false if not
end
end
And then in your routes file:
get '/photos/:album/:photo.jpg' => 'photo#photo', as: album_photo
And then in your views:
<%= image_tag album_photo_path('album', 'photo') %>
What's good about send_file is that it simply serves the file out of Rails in development mode, but in production it can be configured to offload it to the actual webserver to keep the performance of your Rails code optimal.
Hope that gives a basic idea of what it might be and helps a bit!
I've got 2 models, band and genre, and a many-to-many relation via an association table bands_genres (which doesn't have a model) the following way.
class Genre < ActiveRecord::Base
has_and_belongs_to_many :bands
class Band < ActiveRecord::Base
has_and_belongs_to_many :genres
Checking out the output logs of my app, I see that every call involving bands or genres end up doing this query:
SQL (1.8ms) describe `bands_genres`
Why is this happening? How could I cache somehow the result of this query to avoid doing it on each request?
Run your server in production mode. Table information is reloaded on each request when in development mode.
rails s -e production
It's because your current environment configuration tells Rails to do so. I assume you use "development", but you do have "production", and "test" also.
There is an option to cache classes in any of your envs' configuration. Check the current one (I assume you use "development"):
config/environments/development.rb
and modify this option to true:
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = true
Then, run server with your current environment. For development:
bundle exec rails s
is enough.
This does 2 things:
1) when Rails start, it will now read all classes' definitions
(models) up front, and keep it for each request.
When you change a class now, no code will be reloaded automatically
2) Rails will not ask database for model metadata change,
so no "describe table" will go to database in any request
"Production" env by default has this option set to "true". But "production" env is meant for production and not development. You may specify different options, urls, vars there..
It only happens in production, when we update some of the records through browser, the change was not saved. it does not seem to be a cache problem as we verified that the data in mysql was still the old data. However, the controller did get hit and flash message returned as if the change was made successfully.
However, we can make the change manually in rails console or mysql withhout any problem.
Any ideas why this is happening?
btw, we recently reconfigure the site to use SSL, it might have something to do with that.
Is there anything that could've prevented the model from being saved?
One way to ensure that the attributes are set and the model is saved is to use the exception raising version which can help fix problems like this:
def update
#model = Model.find(params[:id])
#model.update_attributes(params[:model])
redirect_to(model_path(#model))
end
This could be improved to a more reliable method:
def update
#model = Model.find(params[:id])
# Use exception-throwing update_attributes!
#model.update_attributes!(params[:model])
redirect_to(model_path(#model))
rescue ActiveRecord::RecordNotFound
render(:partial => 'not_found')
rescue ActiveRecord::RecordInvalid
# Delegate back to edit action, something's invalid
edit
render(:action => 'edit')
end
There are occasions where update_attributes may not successfully save.
If you can perform the same update on the same data with the same methods then that is peculiar.