Wednesday, January 31, 2007

Ugly bug in prototype/rails

While testing an app, I just noticed that the observe_field (Form.Element.EventObserver), defined this way:

new Form.Element.EventObserver('customer_Name', function(element, value) {new Ajax.Request('/foo/validate_field', {asynchronous:true, evalScripts:true, parameters:'customer[Name]=' + value})})

passed the value through without any munging, which is then parsed by rails into the params hash. Rails param parsing simply splits on & and seems to take the last assignment, so input something like '&' into an input tag with the name 'customer[Name]' will generate:

Processing FooController#validate_field (for 127.0.0.1 at 2007-01-31 17:58:39) [POST]
Session ID: 02685db5b8bfde88f7b858eeec0b3a9d
Parameters: {"action"=>"validate_field", "amp;"=>"", "controller"=>"foo", "customer"=>{"Name"=>""}}
Customer Columns (0.001306) SHOW FIELDS FROM customers
Completed in 0.13547 (7 reqs/sec) | Rendering: 0.02115 (15%) | DB: 0.00131 (0%) | 200 OK [http://127.0.0.1/foo/validate_field]

And input '&customer[Name]=bar' will generate a request like:

Processing FooController#validate_field (for 127.0.0.1 at 2007-01-31 18:16:20) [POST]
Session ID: 02685db5b8bfde88f7b858eeec0b3a9d
Parameters: {"action"=>"validate_field", "controller"=>"foo", "customer"=>{"Name"=>"bar"}}
Customer Columns (0.001054) SHOW FIELDS FROM customers
Completed in 0.13539 (7 reqs/sec) | Rendering: 0.02108 (15%) | DB: 0.00105 (0%) | 200 OK [http://127.0.0.1/foo/validate_field]

Oops. That is not good. Considering the parameters you hope for would include '"customer"=>{"Name"=>"&customer[Name]=bar"}, but instead you get "customer"=>{"Name"=>"bar"}. The value from the field, when parsed by rails, over-wrote the previously generated customer hash. This has a number of implications:

- if you are using an AJAX (prototype) methods to validate form field data, input that includes ampersand(s) will have some or all of the string hidden from the receiver. This assumes that the controller method is trying to receive the data to validate from a specific key(s) in the params hash.

- if you are using AJAX (prototype) methods to receive model-mapped input to update an object, it is possible for someone to piggyback additional attributes (assuming bulk attribute assignment) straight from input fields. No mucking around and hand-crafting a request. Just &model[current_attribute]=data&model[other_attribute]=data_you_did_not_expect. Of course it is still subject to validation and accessible restrictions (you do use those right?). This is not the default BTW. Most of the helpers like observe_field simply send the data from the field as the post body. It will get parsed, and ampersands will cause separate entries in params. It really depends on your expectations and whether you use the :with option to place the data where you expect to find it (or mapped for attribute assignment to an AR object).

- worse I assume. It really depends on a lot of factors. If your models have all attributes accessible to bulk assignment, you use one or more attributes to store information that is state sensitive and you update attributes on that model using AJAX, you may find yourself in trouble.

There are a couple ways to code around this issue, but the right one for you depends greatly on your implementation.

No comments: