In my last post for “Smibs on Code†I discussed the tradeoffs between pre-filtering and post-filtering user data in a web application. I also mentioned that Rails is focused on the post-filter approach, while I prefer pre-filtering. This week I’ll show you a plug-in we put together to perform the filtering for our Smibs Network and Doorbell.
This plug-in relies on a second smaller plug-in I wrote, which extends the String class. One of the functions added is ‘filter’. It escapes all dangerous characters. An example would be replacing all ‘<‘ characters (which can be used to run javascript code) with ‘<’ which the browser will display as a ‘<‘, but is no longer dangerous.
The main plug-in extends the ActiveRecord class. This adds a function called apply_filter_on which is used to specify which fields you wish to filter, and a before_validation function which will be used to filter the user inputs when saving, but before the validations are run.
module ModelLevelSecurity def self.included(mod) mod.extend(ClassMethods) mod.before_validation(:apply_filter_now) end module ClassMethods def apply_filter_on(*var_names) write_inheritable_array(:filter_applies_on, var_names) end end private def apply_filter_now var_names = (self.class.read_inheritable_attribute(:filter_applies_on) || []) var_names.each{|var_name| unless read_attribute(var_name).blank? write_attribute(var_name,read_attribute(var_name).to_s.filter) end } end end
This lets me specify which data fields should be filtered. Obviously, only the strings need to be filtered. Integers can’t really be hacked to the same level (NOTE: You still have to escape the SQL, but this is done separately). In the model I can specify the data fields with:
class Person < ActiveRecord::Base ... attr_accessible :fname, :lname, :salut, :byear, :bmonth, :bday, apply_filter_on :fname, :lname, :salut ... end
As I said before, there are many ways of solving this problem, and you may disagree with me on the best location to place the filter. Hopefully this helps some developers, who like me, felt that it was better to play it safe and escape when you grab the data from the user, rather than the alternative.