On Authorization Failures
As a slight extension to the previous post, I wanted to make a quick point about authorization failures.
Given you’ve raised SomeAuthorizationFailure exception in a controller action, you might have a general rescue handling it:
rescue_from 'SomeAuthorizationFailure' do
render :text => "Bad user!", :status => 403
end
The key here is the 403 status, Forbidden. This is a pretty natural, and technically correct status to feed the client.
Cool, let’s wrap that up, it’s done! Hold on, not so fast.
If you use Github (if?), you may have noticed something that struck you as curious the first time it happened. Say you’re hanging in the dev campfire room, and somebody pastes a link to a line of code for you to checkout (like /foocorp/awesomeproject/config/application.rb#L7). You clicked on the link but forgot you’ve logged out. Boom….403 Forbidden.
Wait..no that’s not a 403, it’s a 404. What the heck?
The answer is pretty simple. On your little todo app you run for friends and family, it’s probably not a huge deal for somebody to hit /todo_lists/42/item/5 and get a 403. Wow, somebody now knows you have a todo list 42 and item 5. Probably not a big deal.
But on a site like Github, let’s change that application.rb link to say, /foocorp/awesomeproject/config/initializers/devise.rb. 403? Oh look, that project is using Devise!
The moral of the story: best to give a 404 status on authorization failures if you don’t want to cater to mining and leaking of sensitive info.
—Apr 24, 2013
On Authorization Patterns
Once upon a time, I’d heavily lean upon scoped finds for cheap authorization in Rails controller actions. For instance, a system might have many users, for which each has many projects they can manage. In order to find a project that a user can administer, an action may include the following:
@project = current_user.projects.find(params[:id])
This can work. If a user tries to hit project id 42, for which they aren’t associated with, the execution short circuits at that point. The security on that project has been maintained.
I think most people know at this point, this is a poor general authorization scheme, because for one, it spreads your authorization logic, no matter how simple, around the application. With a few controllers in a small system, this probably isn’t a big deal.
Enter an authorization scheme, you might write:
@project = current_user.projects.find(params[:id])
authorize @project
The authorize method here will typically take the current user, lookup some policy object, and run a check. If a user can be associated to a project, but not be able to edit it, this will probably pan out as you expect.
Sometimes though, you could simply write such code to be:
@project = Project.find(params[:id])
authorize @project
This can be a subtle, but I believe powerful, difference. First, your finder usage is simplified. But second, and I believe more importantly, the code becomes more straightforward and your exceptions more accurate. Take another look:
# If no project is found, raise ActiveRecord::RecordNotFound
@project = Project.find(params[:id])
# If the user is not authorized, raise SomeAuthorizationException
authorize @project
This is a worthwhile difference. Want to keep metrics or get alerted on security violation attempts? Now it can be clearly split. Or perhaps, you take different action or set different flash messages; this can be handled more cleanly now.
When it comes to patterns, remember, it’s never one-size-fits-all. What’s good to realize is that sometimes you can write your code in simpler fashions, and more importantly, think about the explicit exceptions your system should be throwing, if any.
—Apr 23, 2013
A note about "friendly" passwords
Often in a web application, the time will come where you opt to generate temporary passwords for users. One common approach to this is to use a helper that combines a small about of random data (such as 4 random numbers) with a word randomly selected from a pared down dictionary list.
Please don’t do this. There are at least two reasons:
- Using a word list radically reduces password entropy
- Eventually a password will unintentionally perplex, or worse, offend someone
If the day comes and the system generates the password “cigar1984” for somebody trying to quit smoking, that could be awkward.
Now, I’m no crypto expert, but I’m going to assume that the ruby SecureRandom library will do a better job than me at outputting random strings. So it’s of use here. For example:
require "securerandom"
# outputs something like "Ht25IeNqIBUp"
SecureRandom.urlsafe_base64.gsub(/[^a-z0-9]+/i, '')[0,12]
This strips non-alphanumeric characters such as ‘_’ and ‘-’ out. This is useful because people are less used to typing them, and also, certain mouseclick-to-copy behaviors will split on those characters depending on the environment.
It’s worth noting that certain characters often will confuse users if they are manually entering a password as viewed from the screen. Fonts can make characters including the following difficult to distinguish
- 0 (zero)
- 1 (the number one)
- I (the uppercase i)
- O (the uppercase OH)
- l (lowercase L)
You can String#tr these out for substitutions, or strip them alltogether. This will slightly reduce entropy, but by keeping a longer password you compensate somewhat.
—Jan 26, 2013
An Example of Wrapping
You’ve been tasked with adding comments to some internal system at work. You throw together some new controllers and views into your app, and churn out the feature quickly and efficiently.
A few days pass, and a peer comes and informs you, “Hey, have you seen the comments? Some people are swearing up a storm and Bob is irritated!” You are left wondering, what to do. You quickly discover there’s an Obscenity gem for Ruby, and get cracking. At stage one, you’re just going to output sanitized versions of comments, rather than resort to draconian measures.
Let’s assume an overly simple, Comment model with one property, content, that looks like this:
Comment = Struct.new(:content) do
#...
end
Dont’ worry about database, etc, it’s beyond the point right now. Dropping in a #clean_content method is quick:
Comment = Struct.new(:content) do
def clean_content
content && Obscenity.sanitize(content.dup)
end
end
Now off to update the views and change the references to @comment.content to @comment.clean_content and you’re done. Wait, not so fast, that’s only one option, with others to consider. Possible options include:
- Changing the view references, as mentioned
- Using a helper method like sanitized_comment(@comment) to return the clean content
- Opening your model back up and changing the content to return sanitized content, and storing the original content in #unsanitized_content
- Wrapping your @comment instance and taking advantage of Ruby duck typing.
Here’s a quick example of accomplishing the last. The presenter/exhibit/delegate pattern in Ruby are often presented as a way to decorate new methods onto an instance, such as taking an underlying object with an #amount_in_cents attribute and adding a new method for outputing it as readable currency. Another way to leverage this is to intercept calls to an existing method, like #content, and change its behavior. Let me show you what I mean.
First, SimpleDelegator can provide an easy wrapping for instances:
class CleanComment < SimpleDelegator
# In case you want to get back at the original
def unsanitized_content
__getobj__.content
end
# Ensure clean content
def content
clean_content
end
end
When you want to sanitize the comment, say after finding it via a controller, wrap it:
@comment = CleanComment.new(comment)
Now your views can keep rolling on with a calls to @comment.content and be none the wiser. Remember, Ruby’s duck typing is powerful; rely on what instances respond to, as opposed to what they are instances of.
This is partially a matter of taste, remember there’s often not a “right way”. What’s important is to have options, and leverage the option that feels right given the situation at hand. Different approaches have different pros & cons. With wrapping, for instance, you have to remember to wrap! And if it’s a collection, you must wrap them all. There’s gems like draper or display-case that can help you on your way.
—Jan 15, 2013
Two Support Objects You May Have Missed
If you spend time daily in a large ruby project (such as a Rails app) that has ActiveSupport pulled in, you are likely relying on its string, time, hash, and other extensions. I’ve found two objects it provides prove useful, and having found them lesser known amongst my coding friends, figured they are worth sharing.
The first useful tidbit is the ActiveSupport::StringInquirer class. It’s a simple method missing call that lets you do prettier equality tests on strings. If you’ve ever done a Rails.env.development? check, it uses this implementation. Let’s go to the code:
Rails.env.development? # => true
Rails.env # => "development"
Rails.env.class # => ActiveSupport::StringInquirer
si = ActiveSupport::StringInquirer.new("foo")
si.foo? # => true
si.bar? # => false
I think this is great for two reasons. First, it’s a more expressive use of code, and secondly, it implies less coupling to a string outside of an object. Let’s take a simple example: a role for a User object. Imagine you start simple, where role is just a string. Now let’s say we’re using CanCan to add simple authorization to our app, with an ability class that looks like this:
class Ability
include CanCan::Ability
def initialize(user)
user = user || User.new
if user.role == "admin"
can :manage, :all
else
can :read, :all
end
end
end
Note we’re doing an #== for comparison on that role. This is a bit ugly and not as expressive as I’d like. Let’s get rid of ugly with the help of the StringInquirer.
class User < ActiveRecord::Base
def role
role = read_attribute(:role).to_s
ActiveSupport::StringInquirer.new(role)
end
end
class Ability
include CanCan::Ability
def initialize(user)
@user = user || User.new
if role.admin?
can :manage, :all
else
can :read, :all
end
end
private
def role
@user.role
end
end
I’ve redefined User#role to wrap the attribute in a StringInquirer, and updated the Ability class to call the role with the predicate #admin? method. Our end behavior for ability checking is the same, but I think we’ve got more readable code. There’s another win on this: we’ve decoupled from treating our role like a string which can pay out nicely in the future. Imagine for instance the day arrives when a user no longer has one role, but many. A simple user may be able to function as both a forum moderator or a comment moderator. You can shift to supporting many roles per user with a bitmask method and leave your external calls untouched. A simple application of define_method or method_missing on your role attribute wrapper is all you need to keep rolling. Now, you could also define #== on your role object for such string comparisons, but comparing to a string reads more like the caller knows too much of an implementation detail. I haven’t touched on the User#role= setter here; you may need some sanitizing and cleanup on it if you were assigning it the results from the getter method anywhere (and, ahem, possibly breaking encapsulation with your own string assignments, too). I’ll leave that as an exercise for the reader.
Our second friend is the ActiveSupport::SecureRandom interface. Actually, saying this is from ActiveSupport is a little misleading. If you are working on an older Rails 2 project, you’ll probably be using this by way of ActiveSupport. However for modern and future use, this is deprecated and delegated to Ruby 1.9.x stdlib’s SecureRandom. SecureRandom is great for generating random character strings on the fly that are useful as API keys, temporary passwords, tokens, etc. It’s simple to use, and can replace those naive calls to rand() you’ve been making for generating random strings. Don’t reinvent the wheel! I’ll leave you with a few examples:
SecureRandom.hex # => ace59c788b498fadcaa88216e45cf800
SecureRandom.base64 # => iJKR2NQ8Jk1wBdp0nU/fhA==
# Optionally pass 5 for 5 hex pairs
SecureRandom.hex(5) # => a5f8bf212f
—Oct 16, 2011