Showing posts with label validations. Show all posts
Showing posts with label validations. Show all posts

Friday, May 4, 2007

Dynamic attribute validation for forms

Developing usable web application is a lot more difficult than many people think. Forms are an obvious point of friction for users, so anything you can do to help get the user through the form successfully is a good thing. One of my applications has a fair amount of restrictions on data that the models will accept (though validation restrictions). Users were frustrated after submitting a form and having it rejected due to failed validations. This presented a couple of challenges.

-There are a couple of AR models that are updated or created from the form

-The validation and feedback to the user should occur as each field is entered, and any error messages should be presented close to the offending field

-The validations on the models are somewhat complex

Here is a flexible, but somewhat expensive, solution I came up with and plopped in a controller. Substitute the AR models in your application for Model1, Model2, Model3 in the following code:

private

def validate_input_by_model
error_msgs = Hash::new
[Model1, Model2, Model3].each do |klass|
classname = klass.to_s.downcase
error_msgs[classname] = Hash::new
if params.has_key?(classname)
obj = klass::new(params[classname])
unless obj.valid?
obj.attributes.each do |k,v|
if params[classname].has_key?(k)
error_msgs[classname][k] = obj.errors.on(k)
end
end
end
end
end
error_msgs
end

public

def validate_field
if request.xml_http_request?
error_msgs = validate_input_by_model
render :update do |page|
error_msgs.each_key do |classname|
error_msgs[classname].each do |k,v|
page.inject_error_msg_if_condition "#{classname}_#{k}", v, v
end
end
end
else
render :nothing => true, :status => 401
end
end

An example of the view code:

<% css_form_for :model1, @model1obj, :url => { :controller => 'controller1', :action => 'confirm' } do |@f| %>
<%= @f.text_field :attribute1, :extra => 'Required' %>
<%= validate_remote_by_model 'model1_attribute1' %>
<% css_fields_for :model2, @model2obj do |@model2_f| %>
<%= @model2_f.text_field :attribute1 %>
<%= validate_remote_by_model 'model2_attribute1' %>
<% end %>
<% end %>

The Helper (for the controller associated views) code:

def validate_remote_by_model(id)
validate_local_by_regex(id, /^[^&]*$/, '&amp; characters are not allowed. ') + observe_field(id, :with => ("%s[%s]" % id.split(/_/)), :url => { :action => 'validate_field' })
end

def validate_local_by_regex(id, regex = nil, error_msg = nil)
default_regex, default_error_msg = *@@validations[@@validation_mapping[id]]
regex ||= default_regex
error_msg ||= default_error_msg
function = <<-SCRIPT
if (value.match(#{regex.inspect}))
{ Element.remove('l_#{id}_error'); }
else
{ var prev_error = document.getElementById('l_#{id}_error');
if (prev_error != null) {
Element.update('l_#{id}_error', '#{error_msg}');
} else {
new Insertion.Before('#{id}', '<div class=\"field_error\" id=\"l_#{id}_error\">#{error_msg}</div>');
}
}
SCRIPT
observe_field(id, :function => function)
end

This code is dense, but the object is to make the view code as simple and clear as possible and still be able to use the pleasant form_for (or css_form_for with plugin) helpers.

The helper code is knarly, since it is generating javascript, but its functionality is pretty simple. The field text is first check in the browser using a regular expression to screen for ampersands (see post about RoR/prototype issue), and then an AJAX call is made to validate_field sending the field and its value formatted in the standard ROR form syntax. The RoR form parameter syntax is model[attribute]=value. The controller code takes over from there and does its magic to see if the field value will pass validation when applied to the corresponding model. If validation fails and error messages are generated, the errors are passed back to the AJAX code. The javascript then inserts a new element in the DOM that is adjacent to the form field and contains the error message. The new element is updated or cleared based on the response to subsequent calls.

You can disregard that *@@validations[@@validation_mapping[id]] reference in validate_local_by_regex. It is simply a dictionary of regular expressions and error messages used for other in-browser validations. It is not used in this case.

Friday, December 15, 2006

UTF-8, rails vaildations and all that jazz

To be honest, it never occurred to me that Ruby did not natively support Unicode given its roots. Alas, Ruby really has no clue about multi-byte character encodings, except Regexp that can handle UTF-8 if the 'u' specifier is used. But wait, given that UTF-8 is the default encoding for XHTML etc., what affect does this have in Rails applications? A lot and not much at all at the same time. Let me explain the little I know about this.

I ran into this issue on the first rails app I deployed. Even though I had pretty tight validation regexs on my models, every once in a while user provided content would clearly contain Unicode and not display correctly. Damn, that is scary. My validations where not stopping this data from making it into the model. The app served out XHTML 1.0 Strict pages with charaset set to UTF-8. By default browsers POST form data in the same encoding as the page containing the form; In this case UTF-8. All it takes is one cut&paste from MS Office into a form field (think bullet points etc.) to end up with Unicode in the form parameter values. The reason I was ending up with multi-byte values that were not displaying correctly it two-part.

The more trivial side of the issue is that it turns out Safari has a bug when handling response data from XmlHttpRequest that contains multi-byte encoded characters. This issue is resolved by explicitly setting the encoding on AJAX responses. The more troublesome issue is how the multi-byte characters made it past the validations on the ActiveRecord models. After a little experimentation I came up with this:

irb(main):036:0> utf8 = "\342\200\271script\342\200\272"
=> "\342\200\271script\342\200\272"
irb(main):037:0> test_regex = /^[\w]+$/u
=> /^[\w]+$/u
irb(main):038:0> m = test_regex.match utf8
=> #
irb(main):039:0> m[0]
=> "\342\200\271script\342\200\272"
irb(main):040:0> test_regex = /^[-a-zA-Z0-9_.]+$/u
=> /^[-a-zA-Z0-9_.]+$/u
irb(main):041:0> m = test_regex.match utf8
=> nil

Argh, \w in UTF-8 enabled Regexp matches most, if not all, of the UTF-8 character set above the single-byte encodable ASCII subset. Fortunately, it appears that Ruby's Regexp UTF-8 string handling does not honor overlong-encoded characters, so it appears if you are doing validations that exclude many of the characters generally needed to inject HTML or Javascript, it is unlikely that they can simply be avoided by using overlong UTF-8 encodings. That is good news. However, if multi-byte encoded characters make it into your models and you use methods like sanitize() or h() to do some safety filtering on presentation, don't expect them to do their jobs well.

In summary, there is potential for some security issues to arise out of acceptance of multi-byte encoded data, but the most straight-forward issues like direct tag and script injection will generally not escape past your input validations. As I research this more, I will keep you posted.