Rails' Insecure Defaults - Code Climate Blog

Rails’ reputation as a relatively secure Web framework is well deserved. Out-of-the-box, there is protection against many common attacks: cross site scripting (XSS), cross site request forgery (CSRF) and SQL injection. Core members are knowledgeable and genuinely concerned with security.

However, there are places where the default behavior could be more secure. This post explores potential security issues in Rails 3 that are fixed in Rails 4, as well as some that are still risky. I hope this post will help you secure your own apps, as w...

How to set the user agent in tests

The User-Agent HTTP header identifies the client and is sent by "regular" browsers, search engine crawlers, or other web client software.

Cucumber

In Rack::Test, you can set your user agent like this on Capybara:

Given /^my user agent is "(.+)"$/ do |agent|
  page.driver.browser.header('User-Agent', agent)
  # Or, for older Capybaras:
  # page.driver.header('User-Agent', agent)
end

For Selenium tests with Firefox, it seems you can set the general.useragent.override profile setting to your preferred value. [See StackOver...

Test redirects to an external URL with Cucumber/Capybara

When a controller action redirects to an external URL (like http://somehost.com/some/path) you will find that this is hard to test with Cucumber and Capybara:

  • A non-Javascript Rack::Test scenario will just ignore the host and try to open /some/path in your local application
  • A Selenium test will actually follow the redirect, which you probably don't want either

There are two workarounds for this. You can use either, or a combination of both.

  1. Write a controller spec

Controller specs can test if a resp...

How to disable cookies in cucumber tests

Unfortunately, Capybara does not offer a switch to disable cookies in your test browser. However, you can work around that by using a tiny Rack middleware -- it works for both Selenium and non-Selenium tests.


Wouldn't it be nice to say something like this?

Given cookies are disabled
When I try to sign in
Then I should see "Can't sign you in. Please enable cookies."

You can! Put the code below into some place like lib/rack/cookie_stripper.rb.

module Rack
  class CookieStripper
    
    ENABLED = false

...

Cucumber step to set cookies in your Capybara session

To set a cookie in your test browser for cucumber tests, you need to know which driver you are using. Use the step below according to your driver.

Rack::Test

Given /^I have a "([^\"]+)" cookie set to "([^\"]+)"$/ do |key, value|
  headers = {}
  Rack::Utils.set_cookie_header!(headers, key, value)
  cookie_string = headers['Set-Cookie']

  Capybara.current_session.driver.browser.set_cookie(cookie_string)
end

Note that Rack::Utils is only used to find out the correct cookie header string (you don't want to generate it yours...

Comparing Rails' flash hashes will not respect their internal lists of used entries

Rails flashes (FlashHash) track a list of used keys, which is not respected when comparing flash hashes.

This does not concern you under most circumstances.

Basics

When ActionController picks up a flash object, it will call the #sweep method once; that method checks the list of used flash entries and deletes those. All other entries are flagged as used. This means they will be deleted on the next request, but are still be available for rendering during the current request.

Fun facts: When redirecting, this does not happen. Also,...

Tips And Tricks For Print Style Sheets

Smashing Magazine lists some handy tricks for print style sheets, all with CSS:

Capistrano: Bundler stalls and asks for "Username"

Given you use Capistrano together with bundler to automatically install your gems when deploying.

I recently had the problem that Capistrano stalled like this:

[err :: host.name.tld] Username:

It turned out that I this originated from GitHub. We had a gem in our Gemfile that explicitly pointed to a GitHub URL like that:

gem 'foogem', :git => 'https://github.com/blubb/foogem.git'

The URL was returning a 404 which caused the problems. You have to get another gem or point to a fork on GitHub.

How to fix: Session hash does not get updated when using "merge!"

tl;dr: Do not use merge! for session hashes. Use update instead.

Outline

Let's assume you're modifying the Rails session. For simplicity, let's also assume your session is empty when you start (same effect when there is data):

# In our example, we're in a Rack middleware
request = Rack::Request.new(env)
request.session.merge! :hello => 'Universe'
request.session
=> {}

Wat?

Even worse: When you inspect your request.session like above (e.g. in a debugger shell, o...

How to fix: "Error Bundler::HTTPError during request to dependency API"

If bundle install shows the following message for you ...

Error Bundler::HTTPError during request to dependency API

... upgrade to Bundler ≥ 1.2.4:

gem install bundler

Apparently, it just hides the message.

Prevent double clicks on link_to_remote (simple case)

This works well in the simplified case, when your link disappears after it was clicked.

Let link_to_remote behave as „disabled“ after the first click. Use the :before hook to replace the orignal link with a link that does nothing but looks like the original link:

:ruby
  label = "do_something"
  dummy_link = link_to(label)
  other_attributes_hash = { :url => ..., :method => ..., ... }
  
  disable_link_option = { :before => "$('your_link_selector').html('#{escape_javascript(dummy_link)}'" } # jquery

= link_to_remote(label, other_att...

CSS: Matching against attributes and their values (or parts of them)

You probably know that you can use CSS selectors to match against elements and their attributes, such as:

a[title] { /* any <a> that has a "title" */ }
a[data-fancy="true"] { /* any <a> that has their "data-fancy" attribute set to "true" */ }

But there is more: You do not need to match against "full" attribute values but can match against parts of them.

They work in all somewhat modern browsers, and IE9 or later.

Match variants

Exact match (CSS2)

[foo="bar"] (matches `<div foo="b...

CSS: Set content from other attributes

You can use the content CSS attribute to set an element's content -- which is especially useful for the :before and :after pseudo elements:

a:before {
  content: 'Click me: ';
}

The above example would prepend "Click me:" to any link on the page.

Note that you can also refer the contents of other attributes of the element. So, if your links have a helpful title set, you could do this:

a:before {
  content: attr(title) ": ";
}

There also is a jsFiddle for the examp...

What The Rails Security Issue Means For Your Startup

January has been a very bad month for Ruby on Rails developers, with two high-severity security bugs permitting remote code execution found in the framework and a separate-but-related compromise on rubygems.org, a community resource which virtually all Ruby on Rails developers sit downstream of. Many startups use Ruby on Rails. Other startups don’t but, like the Rails community, may one day find themselves asking What Do We Do When Apocalyptically Bad Things Happen On Our Framework of Choice? I thought I’d explain that for the general c...

Amazon Elastic Transcoder

Amazon Elastic Transcoder is video transcoding in the cloud. It is designed to be a highly scalable, easy to use and a cost effective way for developers and businesses to convert (or “transcode”) video files from their source format into versions that will playback on devices like smartphones, tablets and PCs.

This might be a good alternative for services like Panda which charge a large monthly fee just to be available for your encoding requests. Amazon's service bills by usage instead:

A 10 minute sourc...

Consul: Querying a power that might be nil

Consul 0.6.1+ gives your Power class a number of static methods that behave neutrally in case Power.current is nil. This allows you to create authorization-aware models that still work when there is no user at the end of a web browser, e.g. on the console, during tests or during batch processes.


You will often want to access Power.current from another model, to e.g. iterate through the list of accessible users:

class UserReport

  def data
    Power.current.users.c...

Understanding race conditions with duplicate unique keys in Rails

validates_uniqueness_of is not sufficient to ensure the uniqueness of a value. The reason for this is that in production, multiple worker processes can cause race conditions:

  1. Two concurrent requests try to create a user with the same name (and we want user names to be unique)
  2. The requests are accepted on the server by two worker processes who will now process them in parallel
  3. Both requests scan the users table and see that the name is available
  4. Both requests pass validation and create a user with the seemingly available name...

Geocoding Strategies - Google Maps API

The attached article outlines considerations when choosing client-side vs. server-side implementations of the Google Geocoding APIs (geocoder, directions, not maps drawing). The main points are:

  • On the server side you only get a fixed daily request quota
  • On the client side the quota is per-client, so basically unlimited
  • When implementing APIs on the server-side, be aware that quota is measured by IP. When hosting in the cloud **you don't always know which other services might...

Phusion Passenger 4 Technology Preview: Out-Of-Band Work – Phusion Corporate BlogPhusion Corporate Blog

The Out-of-Band Work feature allows one to perform arbitrary long-running work outside the request/response cycle without blocking HTTP clients. The primary use case is to run the garbage collector in between cycles so that your requests will finish faster because they will not be interrupted by the garbage collector.

Using Apache Benchmark (ab) on sites with authentication

Apache HTTP server benchmarking tool (ab) is a nice tool to test performance on sites delivered by HTTP. If the site you're about to test is placed behind a login, follow these steps to successfully use ab on it.

  1. Open the site to test in the browser of your choice. Do not login yet.
  2. Use developer tools to show all cookies used by the site. (Chrome: Ctrl+Shift+i, open the 'Resources' tab and click on the site below 'Cookies' on the left. Firefox: Right-click on the site, open 'We...

Ruby: How to ensure a Tempfile's extension

If you use Tempfile and pass your own filename containing an extension, it will just be consumed by the Tempfile's filename:

>> Tempfile.new('foobar.xlsx').path
=> "/tmp/foobar.xlsx20130115-19153-4ykpwm-0"

If you want to keep the file extension, pass filename and extension as an array:

>> Tempfile.new([ 'foobar', '.xlsx' ]).path
=> "/tmp/foobar20130115-19153-1xhbncb-0.xlsx"

How to get the hostname of the current machine in Rails or a Ruby script

Use Socket.gethostname. So for a machine whose hostname is "happycat", it will look like this:

>> Socket.gethostname
=> "happycat"

That should work right away for your Rails application. For plain Ruby, you first need to do:

require 'socket'

If you don't want to use Socket for some reason, you can still just use the hostname command, at least on non-Windows machines. Keep in mind that you need to remove trailing white space from the result of the system call.

>> `hostname`
=> "happycat\n"
>> `hostname`.stri...

Why your browser loses cookies when following hyperlinks from an Excel spreadsheet or Word document

Microsoft Office pre-fetches hyperlinks using an internal DLL (which doesn't know about your cookies), follows all redirects and opens your browser with the result. This is because of stupidity.

The "fix" is to not redirect but just render a text like "access denied" with 200 OK when you see that request.env['HTTP_USER_AGENT'].include?('ms-office').

Force GitHub Pull Requests to update the diff against its target branch

When you have a Pull Request on GitHub that includes commits from another Pull Request, you will still see them after the "child" PR has been merged. Unfortunately, GitHub won't automatically update the diff (or commit list).

Here is what worked for me:

  1. Check out the target branch
    1. git checkout my-target-branch
    2. Make sure you are up to date against origin (e.g. git fetch and git status). You should be 0 commits ahead or behind.
  2. Add and commit a file
    1. touch .please-update
    2. git add .please-update
    3. `gi...