Sunday, January 14, 2007

Rails Tip : Access to Data

This wasn't immediately obvious to me when I started writing Ruby on Rails code. The solution(s) I was using prior to this dawning-on-me worked, but weren't as easy as what I've got illustrated below.

Once you're done reading this you'll probably say, "This is the biggest duh ever!" And to be honest, it really is.

But when you're first starting with Ruby on Rails, and you're seeing all these examples that say, "It's so easy! you just do this, SomeTotallyAwesomeClass.find(params[:id]), and it returns the model object!", it's easy to code in gigantic security holes into your controllers.

Let's say you've got the following:
-A web app that allows people to create their own account for the purpose of creating projects.
-Each project is associated with the account.
-Only users that are associated with the account that created a project can access the project.

For example, Bob creates an account named, "evilness", so he can manage his project, "Take Over The World". Bob adds his friend Bill to the account so they can both access it. They can reach their account via:

The web application prompts them for their user/pass, once provided, they're in. They can view their taking-over-the-world progress when clicking on the url:

Note: 23 is the ID of their project, "Take Over The World"

Now let's say that Bill (because he's totally evil), after logging into his account, types in this url:

Note: 24 is the ID of the project, "Save the African Swallows". A project being managed by a Monty Python fan, Michael. Michael has nothing to do with the "evilness" account, and Bill and Bob have nothing to do with Michael's totally-awesome account, "holygraillovers".

Michael reaches his project via the url:

What does the web application do when Bill, the evil bastard, types in that 24?

Well, let's find out. Here are the objects in play: Account, which can have one or more User objects, and one or more Project objects.

A very lazy way to look up a project object in your controller is to do this:

def show
@project = Project.find(params[:id])

That takes the "ID" in the URL, looks up the Project, and returns it to the view.

A very lazy way that happens to protect Michael from Bill and Bob's evil eyes is the following:

def show
@project = Project.find_by_id_and_account_id(params[:id], account_of_user().id)

When a user logs into your application, the application should know who they are. I.e. The app knows it's Bill, Bob, or Michael. Since the app knows who is logged in, it should know which account they are associated with. Use that information (illustrated by the function call to account_of_user()) when you do "find" calls in the controller and you'll be better off.

Using this technique the project just won't be found. It will look up the Project ID, "24", but since Bill's "evilness" account doesnt' have the same ID as Michael, "holygraillovers" account, the "find" won't find it.

This is by no means the end-all-be-all of security for a Rails app. This is meant to illustrate one tiny little thing you can do to help yourself. The above example sure as heck won't help you when the model object you're looking up isn't directly associated with the account.

For more things to "watch out for", read this nice list.

(Update: Check out this great post on the Rails Way site.)

Labels: ,


Blogger John Topley said...

Yes. This has actually covered in a section in the Agile Web Development with Rails book - Don't Trust ID Parameters. It's section 21.5 in the first edition and 26.3 in the second edition.

Thanks for linking to that checklist, I hadn't seen that.

Monday, January 15, 2007 4:06:00 AM  

Post a Comment

<< Home