Upgrading to Rails 4 - Parameters Security Tour :
Are you ready for Strong Parameters in Rails 4? In this blog post, we're taking a look at what's going to change for controller parameters from Rails 3 to Rails 4, along with an opinionated dose of philosophy and pitfalls.
It's important to start planning your upgrade strategy for Rails 4, and one of the significant new changes is strong parameters, which can easily be included in your Rails 3 app today. Since Strong Parameters is a big shift from the current implementation and something that we're unlikely to opt-out of (looking at you, Turbolinks), we're taking a bit more time to provide an in-depth look.
Quick History Lesson:
In March of this year, there was a breach in GitHub's security that exploited a mass assignment vulnerability that Rails allowed you to create by default. Mass Assignment issues have been known for a while, Rails' official guide even has a chapter on the topic, but as with most high profile security breaches, the event saw a lot of attention and brought change in a short amount of time (as it was intended to do).
Let's get our hands dirty
Since we can implement this new approach to security in Rails 3 right now, let's do it (I'll mention where it deviates from Rails 4 to the best of my knowledge). This will allow us to get a true side by side understanding of what's coming down the pipe in the next month or two.
To see the transition we'll create two resources Appetizer and Dessert, that will be similar in structure and purpose. Follow along as we create a new project in Rails 3 and our resource via scaffolding (the culprit of mass assignment issues before).
$rails new securitylesson $rails generate scaffold Appetizer name:string title:string recipe:string
This generates the controller you're probably familiar with. Parameters are handled like so:
Now let's add the new gem that will be a default part of Rails 4, and create another scaffold. Add gem 'strong_parameters' to your Gemfile and then switch to the command line and run:
$bundle install $rails generate scaffold Dessert name:string title:string recipe:string
Inspecting our new Dessert controller, you'll see that the gem has adjusted the scaffold generator to move parameters into their own private controller method.
Excellent! There's our friendly old params object with some new syntax. Let's break these down:
params.require(:dessert).permit(:name, :recipe, :title)
require - At first glance it might come across that require is purely a validation step and other parameters can be required, or perhaps chained here (maybe like we see in permit). That isn't the strictly the case, require does 2 things.
- It grabs ONLY the parameters passed to that model. Seeing that our Appetizer model has the same fields, there needs to be a way to fetch exactly what model you're dealing with, so the "permit" method can perform it's own magic. For this reason, you don't use require to enforce that some fields are required, use your model validation for that.
- It will raise an exception if the model params are not found. I know in the previous note, I just said leave the validation to the Model, so why are we validating presence and raising exceptions? DHH spoke directly on this here. He basically says, it's common to get a broken request, especially in an API, and he wants to make sure they get routed the correct way. This may not be what you want, but there are options to get around it (like using a hash fetch with a block instead).
permits - This is much more straight forward. It's simply a list of allowed parameter fields in the model. What happens when the user posts a value that's not in permits, but exists on the model? It simply ignores it! You can quickly test this by removing any element from permit, and using scaffold form to post again.
The Models and Differences in Rails 4
Though we've got things working in our new-style Dessert controller, we're still vulnerable in the Appetizer controller. The strong_parameters change introduces protections to stop and warn us developers when we're making such mistakes. Up until the whitelist of parameters was added, Rails would happily and silently allow you to create these mass assignment errors, a significant problem for newcomers. In Rails 4 we're [more] secure by default because these elements are built in, but for now we're going to need to implement this protection manually for both models.
What we've created just now is sort of a hybrid between 3 and 4 at this point. Looking at the models for Appetizer and Dessert you'll see they are basically the same except for the class name and both still have attr_accessibles that dictate what's permitted. The reason for this is that the generator for the strong_parameters gem doesn't currently handle changing model generation, but Rafael Franca (@rafaelfranca) has stated that he will happily accept any pull requests to handle new model generation in the scaffolding. So while what we created is working, we still have some cleanup to do to fully upgrade to the latest conventions.
To 'dehybridize' our old models, we're first going need to get rid of our whitelist security from Rails 3.2 by going into config/application.rb and setting config.active_record.whitelist_attributes to false.
Additionally we need to replace the attr_accessible whitelists with the Forbidden Attributes Protection module inclusion, in our model. This changes the Appetizer model to this:
Now the old Appetizer code will throw an error as it violates our new attribute protection. For consistency, you should do the same to Dessert.
Summary and Philosophy
Overall it wasn't too complicated to upgrade, and now we're seated in a good spot to observe the benefits of what this change brings. Below are all just different sides of the same coin, but they impact your implementation in each of these ways.
- Until now, we were relying too much on the Model being smart. Writing code is often a balancing act and it's felt like the "fat model" trend has been pushing everything it can to that side of the application, this change allows the app to be more evenly delineated again. Models don't have to worry as much about varied access rights to data which is something that usually sounds like the controller's job. They can return to simply making the sure data operations are valid.
- Scaffolds and Generators are safer. You've got a centralized spot to worry about what should and should not be exposed to a controller or action, and your code is less likely to have mass assignment slip through the cracks. It's been a common pattern to have parameter access delegated through a private method but now that good idea is enforced.
- Mass assignment is still there if you need it. Some scenarios might need a mass assignment operation. Now user input can be restricted by a controller, but other code using the models (perhaps a rake task) can still work beyond those restrictions as needed.
- Roles and Permissions can reside in the Controller. With everything running through your parameter methods, you can more easily implement advanced role and permission logic at the controller level where it belongs.