Posted by Jim Morris
Sat, 07 Jul 2007 01:22:00 GMT
For my social networking site snowdogsr.us I
decided to escape all user input that gets displayed. I know people
like to trick out their profiles with HTML but I want to avoid the
various hacks that it allows.
So thinking I had done a good job of using h everywhere I output user
input fields, I decided to see if I could actually test this with
RSpec view tests.
I recently switched to RSpec for my
testing needs, its cool :)
One thing it does is isolate the various things for testing using built
in mocking, and views can be entirely tested standalone without
accessing a model or a controller.
So how would it do testing for escaped user input I wondered?
Very well actually.
An example is worth a thousand words, so here is my RSpec for my home
page.
BTW I found about 4 places where embedded HTML in user input was
bleeding through, so it was well worth the effort.
So this goes in spec/views/home/home_spec.rb...
it "should escape all user input" do
@place= mock_model(Place, :name => 'place name<b>', :location => 'place location<b>', :tag_list => "place taglist <b>", :rated? => false)
@event= mock_model(Event, :name => 'event name<b>', :where => 'event where<b>', :tag_list => "event taglist <b>", :date_time => DateTime.now, :hosted_by => 'Event host<b>')
@post= mock_model(Input, :input => 'input body <b>', :tag_list => "post taglist <b>", :updated_at => DateTime.now, :created_at => DateTime.now, :created_by => 'post created by person<b>', :rated? => false)
@picture= mock_model(Picture, :public_filename => "filename<b>.png")
@pictures= [@picture]
@pet= mock_model(Pet, :name => 'pet name<b>', :owned_by => "pet owner <b>", :breed => 'breed <b>', :description => "pet description <b>", :neutered => true, :gender => 'M<b>', :pictures => @pictures, :owned_by? => false)
@posts= [@post]
@events= [@event]
@places= [@place]
@top_places= [@place]
@new_pets= [@pet]
@comment= mock_model(Comment)
@comment.stub!(:user).and_return(@user)
@comment.stub!(:created_at).and_return(DateTime.now)
@comment.stub!(:comment).and_return('comment body <b>')
@comments= [@comment]
@post.should_receive(:comments).and_return(@comments)
@new_stuff= []
@new_stuff << {:list => @posts, :title => 'Posts', :link => '#'}
@new_stuff << {:list => @events, :title => 'Events', :link => '#'}
@new_stuff << {:list => @places, :title => 'Places', :link => '#'}
@top= []
@top << {:list => @top_places, :title => 'Hot Places', :link => '#'}
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
render "/home/logged_in"
response.should_not have_text(/<b>/)
end
Its quite complex as the home page renders a lot of summaries of the
various lists I have.
First I mock the models that are called, and stub out the calls that
are made to them. I force them all to return an embedded <b> which I
don't use anyway, and with the new CSS oriented web styles shouldn't
be used in HTML anyway.
Then I just test that <b> does not appear anywhere. If I have
correctly used h to escape all the inputs then it should be rendered
as <b> instead.
The response.should_not have_text(/<b>/) should do that test.
One cool thing is the mocking will tell you if any new inputs (ie
calls to model attributes) have been added, or if you have forgotten
any. So this should keep you honest in the future if you add new
attributes that need escaping.
The
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
Sets the assigns to the variables that my view uses,simulating what
the controller would pass in.
The mock_model calls at the top also use a shortcut to define all
the attributes that get called, and what they return. You can also
explicitly do this...
@post.should_receive(:comments).and_return(@comments)
If you read the RSpec docs you can see that you can also test for
parameters passed in, how many times it is called and various other
nice things.
I added this snippet taken from the rails helpers to aid in finding
any errant HTML that bleads through. (I'm not sure how to call it
from the RSpec so I just copied the code into a private method).
private
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
if text.nil? || phrase.nil? then return end
phrase = Regexp.escape(phrase)
if found_pos = text.chars =~ /(#{phrase})/i
start_pos = [ found_pos - radius, 0 ].max
end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
prefix = start_pos > 0 ? excerpt_string : ""
postfix = end_pos < text.chars.length ? excerpt_string : ""
prefix + text.chars[start_pos..end_pos].strip + postfix
else
nil
end
end
and you can see the call that shows me where the errant <b> is...
puts excerpt(response.body, "<b>")
I also have some setup code that handles the login and log out
mocking, but I'll leave that for the end user to sort out ;)
So I think this will make sure that now and in the future this
particular view will not bleed user input HTML.
Once I did the complex one above the rest of the views were much easier and quicker to implement.
Here is an example of a really simple one...
it "should escape all user input" do
@person= mock_model(Person, :name => 'person name <b>', :first_name => 'person first name <b>', :last_name => 'person last name <b>', :alias => 'person alias <b>', :show_gender => 'Male', :about_me => 'about <b>', :updated_at => DateTime.now, :created_at => DateTime.now, :pets => [])
assigns[:person] = @person
render "/people/show"
response.should_not have_text(/<b>/)
end
Couldn't be much simpler, but I found one place where I was not escaping the HTML!
Posted in RSpec, Rails | Tags escapinghtml, rails, rspec | 4 comments | no trackbacks
Posted by Jim Morris
Sat, 07 Jul 2007 01:22:00 GMT
For my social networking site snowdogsr.us I
decided to escape all user input that gets displayed. I know people
like to trick out their profiles with HTML but I want to avoid the
various hacks that it allows.
So thinking I had done a good job of using h everywhere I output user
input fields, I decided to see if I could actually test this with
RSpec view tests.
I recently switched to RSpec for my
testing needs, its cool :)
One thing it does is isolate the various things for testing using built
in mocking, and views can be entirely tested standalone without
accessing a model or a controller.
So how would it do testing for escaped user input I wondered?
Very well actually.
An example is worth a thousand words, so here is my RSpec for my home
page.
BTW I found about 4 places where embedded HTML in user input was
bleeding through, so it was well worth the effort.
So this goes in spec/views/home/home_spec.rb...
it "should escape all user input" do
@place= mock_model(Place, :name => 'place name<b>', :location => 'place location<b>', :tag_list => "place taglist <b>", :rated? => false)
@event= mock_model(Event, :name => 'event name<b>', :where => 'event where<b>', :tag_list => "event taglist <b>", :date_time => DateTime.now, :hosted_by => 'Event host<b>')
@post= mock_model(Input, :input => 'input body <b>', :tag_list => "post taglist <b>", :updated_at => DateTime.now, :created_at => DateTime.now, :created_by => 'post created by person<b>', :rated? => false)
@picture= mock_model(Picture, :public_filename => "filename<b>.png")
@pictures= [@picture]
@pet= mock_model(Pet, :name => 'pet name<b>', :owned_by => "pet owner <b>", :breed => 'breed <b>', :description => "pet description <b>", :neutered => true, :gender => 'M<b>', :pictures => @pictures, :owned_by? => false)
@posts= [@post]
@events= [@event]
@places= [@place]
@top_places= [@place]
@new_pets= [@pet]
@comment= mock_model(Comment)
@comment.stub!(:user).and_return(@user)
@comment.stub!(:created_at).and_return(DateTime.now)
@comment.stub!(:comment).and_return('comment body <b>')
@comments= [@comment]
@post.should_receive(:comments).and_return(@comments)
@new_stuff= []
@new_stuff << {:list => @posts, :title => 'Posts', :link => '#'}
@new_stuff << {:list => @events, :title => 'Events', :link => '#'}
@new_stuff << {:list => @places, :title => 'Places', :link => '#'}
@top= []
@top << {:list => @top_places, :title => 'Hot Places', :link => '#'}
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
render "/home/logged_in"
response.should_not have_text(/<b>/)
end
Its quite complex as the home page renders a lot of summaries of the
various lists I have.
First I mock the models that are called, and stub out the calls that
are made to them. I force them all to return an embedded <b> which I
don't use anyway, and with the new CSS oriented web styles shouldn't
be used in HTML anyway.
Then I just test that <b> does not appear anywhere. If I have
correctly used h to escape all the inputs then it should be rendered
as <b> instead.
The response.should_not have_text(/<b>/) should do that test.
One cool thing is the mocking will tell you if any new inputs (ie
calls to model attributes) have been added, or if you have forgotten
any. So this should keep you honest in the future if you add new
attributes that need escaping.
The
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
Sets the assigns to the variables that my view uses,simulating what
the controller would pass in.
The mock_model calls at the top also use a shortcut to define all
the attributes that get called, and what they return. You can also
explicitly do this...
@post.should_receive(:comments).and_return(@comments)
If you read the RSpec docs you can see that you can also test for
parameters passed in, how many times it is called and various other
nice things.
I added this snippet taken from the rails helpers to aid in finding
any errant HTML that bleads through. (I'm not sure how to call it
from the RSpec so I just copied the code into a private method).
private
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
if text.nil? || phrase.nil? then return end
phrase = Regexp.escape(phrase)
if found_pos = text.chars =~ /(#{phrase})/i
start_pos = [ found_pos - radius, 0 ].max
end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
prefix = start_pos > 0 ? excerpt_string : ""
postfix = end_pos < text.chars.length ? excerpt_string : ""
prefix + text.chars[start_pos..end_pos].strip + postfix
else
nil
end
end
and you can see the call that shows me where the errant <b> is...
puts excerpt(response.body, "<b>")
I also have some setup code that handles the login and log out
mocking, but I'll leave that for the end user to sort out ;)
So I think this will make sure that now and in the future this
particular view will not bleed user input HTML.
Once I did the complex one above the rest of the views were much easier and quicker to implement.
Here is an example of a really simple one...
it "should escape all user input" do
@person= mock_model(Person, :name => 'person name <b>', :first_name => 'person first name <b>', :last_name => 'person last name <b>', :alias => 'person alias <b>', :show_gender => 'Male', :about_me => 'about <b>', :updated_at => DateTime.now, :created_at => DateTime.now, :pets => [])
assigns[:person] = @person
render "/people/show"
response.should_not have_text(/<b>/)
end
Couldn't be much simpler, but I found one place where I was not escaping the HTML!
Posted in RSpec, Rails | Tags escapinghtml, rails, rspec | 4 comments | no trackbacks
Posted by Jim Morris
Sat, 07 Jul 2007 01:22:00 GMT
For my social networking site snowdogsr.us I
decided to escape all user input that gets displayed. I know people
like to trick out their profiles with HTML but I want to avoid the
various hacks that it allows.
So thinking I had done a good job of using h everywhere I output user
input fields, I decided to see if I could actually test this with
RSpec view tests.
I recently switched to RSpec for my
testing needs, its cool :)
One thing it does is isolate the various things for testing using built
in mocking, and views can be entirely tested standalone without
accessing a model or a controller.
So how would it do testing for escaped user input I wondered?
Very well actually.
An example is worth a thousand words, so here is my RSpec for my home
page.
BTW I found about 4 places where embedded HTML in user input was
bleeding through, so it was well worth the effort.
So this goes in spec/views/home/home_spec.rb...
it "should escape all user input" do
@place= mock_model(Place, :name => 'place name<b>', :location => 'place location<b>', :tag_list => "place taglist <b>", :rated? => false)
@event= mock_model(Event, :name => 'event name<b>', :where => 'event where<b>', :tag_list => "event taglist <b>", :date_time => DateTime.now, :hosted_by => 'Event host<b>')
@post= mock_model(Input, :input => 'input body <b>', :tag_list => "post taglist <b>", :updated_at => DateTime.now, :created_at => DateTime.now, :created_by => 'post created by person<b>', :rated? => false)
@picture= mock_model(Picture, :public_filename => "filename<b>.png")
@pictures= [@picture]
@pet= mock_model(Pet, :name => 'pet name<b>', :owned_by => "pet owner <b>", :breed => 'breed <b>', :description => "pet description <b>", :neutered => true, :gender => 'M<b>', :pictures => @pictures, :owned_by? => false)
@posts= [@post]
@events= [@event]
@places= [@place]
@top_places= [@place]
@new_pets= [@pet]
@comment= mock_model(Comment)
@comment.stub!(:user).and_return(@user)
@comment.stub!(:created_at).and_return(DateTime.now)
@comment.stub!(:comment).and_return('comment body <b>')
@comments= [@comment]
@post.should_receive(:comments).and_return(@comments)
@new_stuff= []
@new_stuff << {:list => @posts, :title => 'Posts', :link => '#'}
@new_stuff << {:list => @events, :title => 'Events', :link => '#'}
@new_stuff << {:list => @places, :title => 'Places', :link => '#'}
@top= []
@top << {:list => @top_places, :title => 'Hot Places', :link => '#'}
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
render "/home/logged_in"
response.should_not have_text(/<b>/)
end
Its quite complex as the home page renders a lot of summaries of the
various lists I have.
First I mock the models that are called, and stub out the calls that
are made to them. I force them all to return an embedded <b> which I
don't use anyway, and with the new CSS oriented web styles shouldn't
be used in HTML anyway.
Then I just test that <b> does not appear anywhere. If I have
correctly used h to escape all the inputs then it should be rendered
as <b> instead.
The response.should_not have_text(/<b>/) should do that test.
One cool thing is the mocking will tell you if any new inputs (ie
calls to model attributes) have been added, or if you have forgotten
any. So this should keep you honest in the future if you add new
attributes that need escaping.
The
assigns[:new_stuff] = @new_stuff
assigns[:top] = @top
assigns[:new_pets] = @new_pets
Sets the assigns to the variables that my view uses,simulating what
the controller would pass in.
The mock_model calls at the top also use a shortcut to define all
the attributes that get called, and what they return. You can also
explicitly do this...
@post.should_receive(:comments).and_return(@comments)
If you read the RSpec docs you can see that you can also test for
parameters passed in, how many times it is called and various other
nice things.
I added this snippet taken from the rails helpers to aid in finding
any errant HTML that bleads through. (I'm not sure how to call it
from the RSpec so I just copied the code into a private method).
private
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
if text.nil? || phrase.nil? then return end
phrase = Regexp.escape(phrase)
if found_pos = text.chars =~ /(#{phrase})/i
start_pos = [ found_pos - radius, 0 ].max
end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
prefix = start_pos > 0 ? excerpt_string : ""
postfix = end_pos < text.chars.length ? excerpt_string : ""
prefix + text.chars[start_pos..end_pos].strip + postfix
else
nil
end
end
and you can see the call that shows me where the errant <b> is...
puts excerpt(response.body, "<b>")
I also have some setup code that handles the login and log out
mocking, but I'll leave that for the end user to sort out ;)
So I think this will make sure that now and in the future this
particular view will not bleed user input HTML.
Once I did the complex one above the rest of the views were much easier and quicker to implement.
Here is an example of a really simple one...
it "should escape all user input" do
@person= mock_model(Person, :name => 'person name <b>', :first_name => 'person first name <b>', :last_name => 'person last name <b>', :alias => 'person alias <b>', :show_gender => 'Male', :about_me => 'about <b>', :updated_at => DateTime.now, :created_at => DateTime.now, :pets => [])
assigns[:person] = @person
render "/people/show"
response.should_not have_text(/<b>/)
end
Couldn't be much simpler, but I found one place where I was not escaping the HTML!
Posted in RSpec, Rails | Tags escapinghtml, rails, rspec | 4 comments | no trackbacks
Posted by Jim Morris
Tue, 26 Jun 2007 22:14:04 GMT
This one is so blatantly obvious it bit me in the Butt at 4am this
morning when I had to get up and fix it! I am so embarrassed, luckily
no private data got out, as no-one has entered any private data yet.
I used the script/generate scaffold_resource to get started, and I
left in those nice format.xml things in, thinking I may use them in
the future. For the most part this is not a problem, but one of my
controllers is a profile table. Much of the data in there is public
anyway so no big deal, but a few columns are private data like email,
date of birth, phone numbers etc. These are specifically private and
not viewable publicly. This is enforced but not having a view that
shows any of that stuff to the general public.
However the tricky little scaffold-generated code...
def index
@profiles = Profile.find(:all, :order => "first_name, last_name, alias")
respond_to do |format|
format.html
format.xml { render :xml => @profiles.to_xml }
end
end
Has this cool .to_xml stanza, which happily takes every column and
converts it to XML and sends it back as a response to the query
/profiles.xml
Yikes, I woke up with a start when I realized that, and rushed to test
it and yep it works as it is supposed to.
Obviously this is easy to fix, Just exclude the attributes you don't
want shown:
@profiles.to_xml(:only => [:first_name, :last_name])
But it sure is a nasty back door if you forget!
Caveat Programmer!
Posted in Rails | Tags rails, scaffold_resource | 4 comments | no trackbacks