public forms are now protected from csrf attacks (see issue #382
This commit is contained in:
commit
d90123e321
42
Gemfile.lock
42
Gemfile.lock
@ -45,7 +45,7 @@ PATH
|
|||||||
mimetype-fu (~> 0.1.2)
|
mimetype-fu (~> 0.1.2)
|
||||||
mongo (~> 1.5.2)
|
mongo (~> 1.5.2)
|
||||||
mongoid (~> 2.4.9)
|
mongoid (~> 2.4.9)
|
||||||
multi_json (= 1.3.4)
|
multi_json (~> 1.3.4)
|
||||||
rack-cache (~> 1.1)
|
rack-cache (~> 1.1)
|
||||||
rails (~> 3.2.3)
|
rails (~> 3.2.3)
|
||||||
rails-backbone (~> 0.6.1)
|
rails-backbone (~> 0.6.1)
|
||||||
@ -109,7 +109,7 @@ GEM
|
|||||||
carrierwave-mongoid (0.1.3)
|
carrierwave-mongoid (0.1.3)
|
||||||
carrierwave (>= 0.5.6)
|
carrierwave (>= 0.5.6)
|
||||||
mongoid (~> 2.1)
|
mongoid (~> 2.1)
|
||||||
cells (3.8.3)
|
cells (3.8.5)
|
||||||
actionpack (~> 3.0)
|
actionpack (~> 3.0)
|
||||||
railties (~> 3.0)
|
railties (~> 3.0)
|
||||||
childprocess (0.3.2)
|
childprocess (0.3.2)
|
||||||
@ -123,13 +123,12 @@ GEM
|
|||||||
coffee-script (2.2.0)
|
coffee-script (2.2.0)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.3.1)
|
coffee-script-source (1.3.3)
|
||||||
cucumber (1.1.9)
|
cucumber (1.2.0)
|
||||||
builder (>= 2.1.2)
|
builder (>= 2.1.2)
|
||||||
diff-lcs (>= 1.1.2)
|
diff-lcs (>= 1.1.3)
|
||||||
gherkin (~> 2.9.0)
|
gherkin (~> 2.10.0)
|
||||||
json (>= 1.4.6)
|
json (>= 1.4.6)
|
||||||
term-ansicolor (>= 1.0.6)
|
|
||||||
cucumber-rails (1.3.0)
|
cucumber-rails (1.3.0)
|
||||||
capybara (>= 1.1.2)
|
capybara (>= 1.1.2)
|
||||||
cucumber (>= 1.1.8)
|
cucumber (>= 1.1.8)
|
||||||
@ -149,7 +148,7 @@ GEM
|
|||||||
ejs (1.0.0)
|
ejs (1.0.0)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
excon (0.13.4)
|
excon (0.13.4)
|
||||||
execjs (1.3.1)
|
execjs (1.4.0)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
factory_girl (2.5.2)
|
factory_girl (2.5.2)
|
||||||
activesupport (>= 2.3.9)
|
activesupport (>= 2.3.9)
|
||||||
@ -169,14 +168,14 @@ GEM
|
|||||||
net-ssh (>= 2.1.3)
|
net-ssh (>= 2.1.3)
|
||||||
nokogiri (~> 1.5.0)
|
nokogiri (~> 1.5.0)
|
||||||
ruby-hmac
|
ruby-hmac
|
||||||
formatador (0.2.1)
|
formatador (0.2.3)
|
||||||
formtastic (2.0.2)
|
formtastic (2.0.2)
|
||||||
rails (~> 3.0)
|
rails (~> 3.0)
|
||||||
fssm (0.2.9)
|
fssm (0.2.9)
|
||||||
gherkin (2.9.3)
|
gherkin (2.10.0)
|
||||||
json (>= 1.4.6)
|
json (>= 1.4.6)
|
||||||
haml (3.1.4)
|
haml (3.1.6)
|
||||||
highline (1.6.11)
|
highline (1.6.12)
|
||||||
hike (1.2.1)
|
hike (1.2.1)
|
||||||
httparty (0.8.3)
|
httparty (0.8.3)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
@ -186,7 +185,8 @@ GEM
|
|||||||
jquery-rails (1.0.19)
|
jquery-rails (1.0.19)
|
||||||
railties (~> 3.0)
|
railties (~> 3.0)
|
||||||
thor (~> 0.14)
|
thor (~> 0.14)
|
||||||
json (1.7.0)
|
jruby-pageant (1.0.2)
|
||||||
|
json (1.7.3)
|
||||||
json_spec (1.0.3)
|
json_spec (1.0.3)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
rspec (~> 2.0)
|
rspec (~> 2.0)
|
||||||
@ -215,15 +215,16 @@ GEM
|
|||||||
mocha (0.9.12)
|
mocha (0.9.12)
|
||||||
mongo (1.5.2)
|
mongo (1.5.2)
|
||||||
bson (= 1.5.2)
|
bson (= 1.5.2)
|
||||||
mongoid (2.4.9)
|
mongoid (2.4.10)
|
||||||
activemodel (~> 3.1)
|
activemodel (~> 3.1)
|
||||||
mongo (~> 1.3)
|
mongo (~> 1.3)
|
||||||
tzinfo (~> 0.3.22)
|
tzinfo (~> 0.3.22)
|
||||||
multi_json (1.3.4)
|
multi_json (1.3.5)
|
||||||
multi_xml (0.4.4)
|
multi_xml (0.5.1)
|
||||||
net-scp (1.0.4)
|
net-scp (1.0.4)
|
||||||
net-ssh (>= 1.99.1)
|
net-ssh (>= 1.99.1)
|
||||||
net-ssh (2.3.0)
|
net-ssh (2.4.0)
|
||||||
|
jruby-pageant (>= 1.0.2)
|
||||||
nokogiri (1.5.2)
|
nokogiri (1.5.2)
|
||||||
orm_adapter (0.0.7)
|
orm_adapter (0.0.7)
|
||||||
pickle (0.4.10)
|
pickle (0.4.10)
|
||||||
@ -256,7 +257,7 @@ GEM
|
|||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
rdoc (~> 3.4)
|
rdoc (~> 3.4)
|
||||||
thor (~> 0.14.6)
|
thor (~> 0.14.6)
|
||||||
raindrops (0.8.0)
|
raindrops (0.9.0)
|
||||||
rake (0.9.2.2)
|
rake (0.9.2.2)
|
||||||
rdoc (3.12)
|
rdoc (3.12)
|
||||||
json (~> 1.4)
|
json (~> 1.4)
|
||||||
@ -283,7 +284,7 @@ GEM
|
|||||||
rubyzip (0.9.8)
|
rubyzip (0.9.8)
|
||||||
sanitize (2.0.3)
|
sanitize (2.0.3)
|
||||||
nokogiri (>= 1.4.4, < 1.6)
|
nokogiri (>= 1.4.4, < 1.6)
|
||||||
sass (3.1.17)
|
sass (3.1.18)
|
||||||
sass-rails (3.2.5)
|
sass-rails (3.2.5)
|
||||||
railties (~> 3.2.0)
|
railties (~> 3.2.0)
|
||||||
sass (>= 3.1.10)
|
sass (>= 3.1.10)
|
||||||
@ -300,7 +301,6 @@ GEM
|
|||||||
hike (~> 1.2)
|
hike (~> 1.2)
|
||||||
rack (~> 1.0)
|
rack (~> 1.0)
|
||||||
tilt (~> 1.1, != 1.3.0)
|
tilt (~> 1.1, != 1.3.0)
|
||||||
term-ansicolor (1.0.7)
|
|
||||||
thor (0.14.6)
|
thor (0.14.6)
|
||||||
tilt (1.3.3)
|
tilt (1.3.3)
|
||||||
treetop (1.4.10)
|
treetop (1.4.10)
|
||||||
@ -315,7 +315,7 @@ GEM
|
|||||||
rack
|
rack
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
unidecoder (1.1.1)
|
unidecoder (1.1.1)
|
||||||
warden (1.1.1)
|
warden (1.2.0)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
xpath (0.1.4)
|
xpath (0.1.4)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
@ -6,8 +6,6 @@ module Locomotive
|
|||||||
|
|
||||||
before_filter :sanitize_entry_params, :only => :create
|
before_filter :sanitize_entry_params, :only => :create
|
||||||
|
|
||||||
skip_before_filter :verify_authenticity_token
|
|
||||||
|
|
||||||
skip_load_and_authorize_resource
|
skip_load_and_authorize_resource
|
||||||
|
|
||||||
self.responder = Locomotive::ActionController::PublicResponder # custom responder
|
self.responder = Locomotive::ActionController::PublicResponder # custom responder
|
||||||
@ -17,7 +15,6 @@ module Locomotive
|
|||||||
def create
|
def create
|
||||||
@entry = @content_type.entries.create(params[:entry] || params[:content])
|
@entry = @content_type.entries.create(params[:entry] || params[:content])
|
||||||
flash[@content_type.slug.singularize] = @entry.to_presenter(:include_errors => true).as_json
|
flash[@content_type.slug.singularize] = @entry.to_presenter(:include_errors => true).as_json
|
||||||
Rails.logger.debug @entry.to_presenter(:include_errors => true).as_json
|
|
||||||
respond_with @entry, :location => self.callback_url
|
respond_with @entry, :location => self.callback_url
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -48,6 +45,13 @@ module Locomotive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_unverified_request
|
||||||
|
if Locomotive.config.csrf_protection
|
||||||
|
reset_session
|
||||||
|
redirect_to '/', :status => 302
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,7 @@ Feature: Contact form
|
|||||||
I want to be able to send them a message
|
I want to be able to send them a message
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
|
Given I enable the CSRF protection for public submission requests
|
||||||
Given I have the site: "test site" set up
|
Given I have the site: "test site" set up
|
||||||
And I have a custom model named "Messages" with
|
And I have a custom model named "Messages" with
|
||||||
| label | type | required |
|
| label | type | required |
|
||||||
@ -16,6 +17,7 @@ Feature: Contact form
|
|||||||
<head></head>
|
<head></head>
|
||||||
<body>
|
<body>
|
||||||
<form action="{{ contents.messages.public_submission_url }}" method="post">
|
<form action="{{ contents.messages.public_submission_url }}" method="post">
|
||||||
|
{% csrf_param %}
|
||||||
<input type="hidden" value="/success" name="success_callback" />
|
<input type="hidden" value="/success" name="success_callback" />
|
||||||
<input type="hidden" value="/contact" name="error_callback" />
|
<input type="hidden" value="/contact" name="error_callback" />
|
||||||
<label for="email">E-Mail Address</label>
|
<label for="email">E-Mail Address</label>
|
||||||
@ -55,6 +57,20 @@ Feature: Contact form
|
|||||||
And I press "Submit"
|
And I press "Submit"
|
||||||
Then I should see "Thanks did@locomotivecms.com"
|
Then I should see "Thanks did@locomotivecms.com"
|
||||||
|
|
||||||
|
Scenario: Can not send a message if the csrf tag is missing
|
||||||
|
Given I delete the following code "{% csrf_param %}" from the "contact" page
|
||||||
|
When I view the rendered page at "/contact"
|
||||||
|
And I press "Submit"
|
||||||
|
Then I should see "Content of the home page"
|
||||||
|
|
||||||
|
Scenario: Can send a message if the csrf protection is disabled
|
||||||
|
Given I disable the CSRF protection for public submission requests
|
||||||
|
And I view the rendered page at "/contact"
|
||||||
|
And I fill in "E-Mail Address" with "did@locomotivecms.com"
|
||||||
|
And I fill in "Message" with "LocomotiveCMS rocks"
|
||||||
|
And I press "Submit"
|
||||||
|
Then I should see "Thanks did@locomotivecms.com"
|
||||||
|
|
||||||
Scenario: Display errors
|
Scenario: Display errors
|
||||||
When I view the rendered page at "/contact"
|
When I view the rendered page at "/contact"
|
||||||
And I fill in "Message" with "LocomotiveCMS rocks"
|
And I fill in "Message" with "LocomotiveCMS rocks"
|
||||||
|
@ -39,3 +39,18 @@ end
|
|||||||
When /^I reload the page$/ do
|
When /^I reload the page$/ do
|
||||||
visit current_path
|
visit current_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Given /^I enable the CSRF protection for public submission requests$/ do
|
||||||
|
Locomotive.config.csrf_protection = true
|
||||||
|
Locomotive::Public::ContentEntriesController.any_instance.stubs(:protect_against_forgery?).returns(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
Given /^I disable the CSRF protection for public submission requests$/ do
|
||||||
|
Locomotive.config.csrf_protection = false
|
||||||
|
# pending # express the regexp above with the code you wish you had
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^it returns a (\d+) error page$/ do |code|
|
||||||
|
puts page.status_code
|
||||||
|
page.status_code.should == code.to_i
|
||||||
|
end
|
||||||
|
@ -35,9 +35,15 @@ When /^I update the "([^"]*)" page with the template:$/ do |page_slug, template|
|
|||||||
page.save!
|
page.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Given /^I delete the following code "([^"]*)" from the "([^"]*)" page$/ do |code, page_slug|
|
||||||
|
page = @site.pages.where(:slug => page_slug).first
|
||||||
|
page.raw_template = page.raw_template.gsub(code, '')
|
||||||
|
page.save!
|
||||||
|
end
|
||||||
|
|
||||||
# try to render a page by slug
|
# try to render a page by slug
|
||||||
When /^I view the rendered page at "([^"]*)"$/ do |path|
|
When /^I view the rendered page at "([^"]*)"$/ do |path|
|
||||||
# If we're running selenium then we need to use a differnt port
|
# If we're running selenium then we need to use a different port
|
||||||
if Capybara.current_driver == :selenium
|
if Capybara.current_driver == :selenium
|
||||||
visit "http://#{@site.domains.first}:#{Capybara.server_port}#{path}"
|
visit "http://#{@site.domains.first}:#{Capybara.server_port}#{path}"
|
||||||
else
|
else
|
||||||
|
@ -48,6 +48,13 @@ Locomotive.configure do |config|
|
|||||||
# add extra classes other than the defined content types among a site which will potentially used by the templatized pages.
|
# add extra classes other than the defined content types among a site which will potentially used by the templatized pages.
|
||||||
# config.models_for_templatization = %w(Product)
|
# config.models_for_templatization = %w(Product)
|
||||||
|
|
||||||
|
# "Public" forms can be protected from Cross-Site Request Forgery (CSRF) attacks.
|
||||||
|
# By default, that protection is disabled (false) in order to keep backwards compatibility with the existing public forms.
|
||||||
|
#
|
||||||
|
# Note: we strongly recommend to enable it. See the documentation about the "csrf_param" liquid tag.
|
||||||
|
#
|
||||||
|
# config.csrf_protection = true
|
||||||
|
|
||||||
# Rack-cache settings, mainly used for the inline resizing image module. Default options:
|
# Rack-cache settings, mainly used for the inline resizing image module. Default options:
|
||||||
# config.rack_cache = {
|
# config.rack_cache = {
|
||||||
# :verbose => true,
|
# :verbose => true,
|
||||||
|
@ -27,7 +27,8 @@ module Locomotive
|
|||||||
},
|
},
|
||||||
:devise_modules => [:rememberable, :database_authenticatable, :token_authenticatable, :recoverable, :trackable, :validatable, :encryptable, { :encryptor => :sha1 }],
|
:devise_modules => [:rememberable, :database_authenticatable, :token_authenticatable, :recoverable, :trackable, :validatable, :encryptable, { :encryptor => :sha1 }],
|
||||||
:context_assign_extensions => { },
|
:context_assign_extensions => { },
|
||||||
:models_for_templatization => []
|
:models_for_templatization => [],
|
||||||
|
:csrf_protection => false
|
||||||
}
|
}
|
||||||
|
|
||||||
cattr_accessor :settings
|
cattr_accessor :settings
|
||||||
|
40
lib/locomotive/liquid/tags/csrf.rb
Normal file
40
lib/locomotive/liquid/tags/csrf.rb
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module Locomotive
|
||||||
|
module Liquid
|
||||||
|
module Tags
|
||||||
|
module Csrf
|
||||||
|
|
||||||
|
class Param < ::Liquid::Tag
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
controller = context.registers[:controller]
|
||||||
|
name = controller.send(:request_forgery_protection_token).to_s
|
||||||
|
value = controller.send(:form_authenticity_token)
|
||||||
|
|
||||||
|
%(<input type="hidden" name="#{name}" value="#{value}" />)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class Meta < ::Liquid::Tag
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
controller = context.registers[:controller]
|
||||||
|
name = controller.send(:request_forgery_protection_token).to_s
|
||||||
|
value = controller.send(:form_authenticity_token)
|
||||||
|
|
||||||
|
%{
|
||||||
|
<meta name="csrf-param" content="#{name}" />
|
||||||
|
<meta name="csrf-token" content="#{value}" />
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
::Liquid::Template.register_tag('csrf_param', Csrf::Param)
|
||||||
|
::Liquid::Template.register_tag('csrf_meta', Csrf::Meta)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -28,6 +28,13 @@ module Mongoid#:nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Criterion
|
||||||
|
class Selector < Hash
|
||||||
|
# for some reason, the store method behaves differently than the []= one, causing regression bugs (query not localized)
|
||||||
|
alias :store :[]=
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# without callback feature
|
# without callback feature
|
||||||
module Callbacks #:nodoc:
|
module Callbacks #:nodoc:
|
||||||
module ClassMethods #:nodoc:
|
module ClassMethods #:nodoc:
|
||||||
|
@ -59,7 +59,7 @@ Gem::Specification.new do |s|
|
|||||||
s.add_dependency 'rack-cache', '~> 1.1'
|
s.add_dependency 'rack-cache', '~> 1.1'
|
||||||
s.add_dependency 'mimetype-fu', '~> 0.1.2'
|
s.add_dependency 'mimetype-fu', '~> 0.1.2'
|
||||||
|
|
||||||
s.add_dependency 'multi_json', '1.3.4'
|
s.add_dependency 'multi_json', '~> 1.3.4'
|
||||||
s.add_dependency 'httparty', '~> 0.8.1'
|
s.add_dependency 'httparty', '~> 0.8.1'
|
||||||
s.add_dependency 'actionmailer-with-request', '~> 0.3.0'
|
s.add_dependency 'actionmailer-with-request', '~> 0.3.0'
|
||||||
|
|
||||||
|
@ -59,6 +59,13 @@ Locomotive.configure do |config|
|
|||||||
# add extra classes other than the defined content types among a site which will potentially used by the templatized pages.
|
# add extra classes other than the defined content types among a site which will potentially used by the templatized pages.
|
||||||
config.models_for_templatization = %w(Foo)
|
config.models_for_templatization = %w(Foo)
|
||||||
|
|
||||||
|
# "Public" forms can be protected from Cross-Site Request Forgery (CSRF) attacks.
|
||||||
|
# By default, that protection is disabled (false) in order to keep backwards compatibility with the existing public forms.
|
||||||
|
#
|
||||||
|
# Note: we strongly recommend to enable it. See the documentation about the "csrf_param" liquid tag.
|
||||||
|
#
|
||||||
|
# config.csrf_protection = true
|
||||||
|
|
||||||
# Rack-cache settings, mainly used for the inline resizing image module. Default options:
|
# Rack-cache settings, mainly used for the inline resizing image module. Default options:
|
||||||
# config.rack_cache = {
|
# config.rack_cache = {
|
||||||
# :verbose => true,
|
# :verbose => true,
|
||||||
|
26
spec/lib/locomotive/liquid/tags/csrf_spec.rb
Normal file
26
spec/lib/locomotive/liquid/tags/csrf_spec.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Locomotive::Liquid::Tags::Csrf do
|
||||||
|
|
||||||
|
it 'renders the param tag for form' do
|
||||||
|
html = render_tag
|
||||||
|
html.should == '<input type="hidden" name="token" value="42" />'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders the meta tag used by ajax requests' do
|
||||||
|
html = render_tag('csrf_meta')
|
||||||
|
html.should include '<meta name="csrf-param" content="token" />'
|
||||||
|
html.should include '<meta name="csrf-token" content="42" />'
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_tag(tag_name = 'csrf_param')
|
||||||
|
controller = mock('controller', {
|
||||||
|
:request_forgery_protection_token => 'token',
|
||||||
|
:form_authenticity_token => '42'
|
||||||
|
})
|
||||||
|
registers = { :controller => controller }
|
||||||
|
liquid_context = ::Liquid::Context.new({}, {}, registers)
|
||||||
|
Liquid::Template.parse("{% #{tag_name} %}").render(liquid_context)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -22,6 +22,8 @@ def Locomotive.configure_for_test(force = false)
|
|||||||
|
|
||||||
config.enable_logs = true
|
config.enable_logs = true
|
||||||
|
|
||||||
|
config.csrf_protection = true
|
||||||
|
|
||||||
if force
|
if force
|
||||||
Locomotive.define_subdomain_and_domains_options
|
Locomotive.define_subdomain_and_domains_options
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user