Another step that isn’t strictly necessary at this stage—but is a really good idea nevertheless—is to tell Rails which attributes of the model are accessible, i.e., which attributes can be modified by outside users (such as users submitting requests with web browsers).
That's not what attr_accessible means --- the real meaning of attr_accessible is subtly different in a way that leads people to make mistakes. attr_accessible means attributes can be modified automatically based on user input. End-user web browser requests can modify things that aren't in attr_accessible --- you just have to write the code to let them.
The error this leads to is overly-permissive attr_accessible statements, because people fall into a trap of believing anything that might be changed as a direct result of a user request must be in the attr_accessible statement.
If this is 'harping', please keep doing it. Mass assignment/attr_accessible is an important and oft-neglected issue, and I appreciate your taking the time to help. I agree that readers could get the wrong idea by thinking they're better-protected than they really are. I've clarified the wording by adding "automatically" as a modifier:
i.e., which attributes can be modified automatically by outside users
The detailed meaning is then deferred to later in the tutorial, when the effects of attr_accessible are illustrated with concrete examples of mass assignment.
`attr_accessible` and friends are a nightmare anyway. They push a controller-layer concern into the model-layer. It breaks the hash-initialization syntax that is so useful in irb, in unit tests, in background jobs, etc.
Instead, I've been using ActiveSupport's Hash#slice method in my controllers.
Declarative syntax for which attributes the model designer is comfortable exposing to mass assignment seems like a perfectly legitimate model concern. In what sense is attr_accessible different from, say, "validate"?
It mascarades and is documented as a security feature, but it's a superficial restriction. You could simply loop and send("#{key}=", value) instead of calling attributes=(hash).
In practice, all it does it prevent you from writing `MyModel.new(:foo => bar)` and force you to write `m = MyModel.new; m.foo = bar` This is simply annoying at the repl.
Validation applies always the same (or when some predicates are satisfied), but restriction on changing fields can vary by view or via permissions (ie. only admins can flip that bit!)
The security concern comes up when you bring "params" into the picture. You don't want to ever do: MyModel.new(params) for fear of params[:is_admin] == true.
A better solution would be to automatically mark the values in `params` as tainted and throw an error on mass-assignment of tainted values. This way you can still use mass assignment for non-user-input (like in tests or at the repl). You could have an `safe_attrs` to disable tainted value filtering for some known-safe attributes.
I see where you're coming from. In the past 10 years, I've learned to appreciate things that, while not solving fundamentally a specific security problem, act as a circuit breaker to keep people from mindlessly making the exact same mistake over and over again.
AR::Base#update_attributes is a bad interface, from a security perspective. But it's vital to the programmer experience of Rails development. There's no good fix for the problem; AR models don't even list their attributes, let alone express a coherent strategy for defending them. I'll take the little wins I can get.
"Mass assignment security is a feature that is aimed at dealing with form input. By placing it in the model, we end up making the implicit assumption that all ‘normal’ interaction with the model happens via a HTTP request. This assumption is incorrect and causes problems."
I agree with you, though. Model-level attr_accessible, if nothing else, is flatly annoying. I feel inconvenienced by a measure that's supposed to protect against malicious users. And I feel like the terrorists have already won. I'm no MVC guru, but it makes sense to me that the Controller would deal with what amounts to the params that are on their way to the model. Why should the model have to worry about mass assignment?
Michael, your rails tutorial continues to be the best source for those who want to learn the basics of rails. I used it as an introduction, and I recommend it to anyone who wants to begin creating web apps. I am glad to see that it was enough of a success to warrant a second edition, and wish you continued success.
* A full update to Rails 3.2, including coverage of the
asset pipeline
* A complete rewrite of the authentication & authorization
system, taking advantage of the new has_secure_password method
* A revised test suite using the latest techniques in
RSpec programming
Thanks Michael, I was having a hard time understanding TDD and git until I went through your tutorial. Also, although I had been programming in Rails for a couple years, it really helped me understand some of the basics of Rails that I had somehow missed.
Michael, this is great and incredible stuff. I think your work goes unmatched and that there is no introduction (paid or free) that is as comprehensive or welcoming. I have passed on (even forced) this onto all my friends. Even those who know Rails so that if someone may ask them in the future, they will point them in your direction.
Putting that behind us, I hope you're not planning to post on HN every week or two when a new chapter is released. I think your last post and perhaps one when the book is completed would be sufficient.
Thanks for the feedback and suggestion. I can understand not wanting to have an announcement hit the HN front page every time a new chapter is released. But even if I don't submit the story, the chances are good that someone else will. By submitting it myself, I can control the timing; in particular, I can make sure to be available to respond to comments. In any case, if people don't vote it up, the story will soon disappear.
Another step that isn’t strictly necessary at this stage—but is a really good idea nevertheless—is to tell Rails which attributes of the model are accessible, i.e., which attributes can be modified by outside users (such as users submitting requests with web browsers).
That's not what attr_accessible means --- the real meaning of attr_accessible is subtly different in a way that leads people to make mistakes. attr_accessible means attributes can be modified automatically based on user input. End-user web browser requests can modify things that aren't in attr_accessible --- you just have to write the code to let them.
The error this leads to is overly-permissive attr_accessible statements, because people fall into a trap of believing anything that might be changed as a direct result of a user request must be in the attr_accessible statement.