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)
|
||||
mongo (~> 1.5.2)
|
||||
mongoid (~> 2.4.9)
|
||||
multi_json (= 1.3.4)
|
||||
multi_json (~> 1.3.4)
|
||||
rack-cache (~> 1.1)
|
||||
rails (~> 3.2.3)
|
||||
rails-backbone (~> 0.6.1)
|
||||
@ -109,7 +109,7 @@ GEM
|
||||
carrierwave-mongoid (0.1.3)
|
||||
carrierwave (>= 0.5.6)
|
||||
mongoid (~> 2.1)
|
||||
cells (3.8.3)
|
||||
cells (3.8.5)
|
||||
actionpack (~> 3.0)
|
||||
railties (~> 3.0)
|
||||
childprocess (0.3.2)
|
||||
@ -123,13 +123,12 @@ GEM
|
||||
coffee-script (2.2.0)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.3.1)
|
||||
cucumber (1.1.9)
|
||||
coffee-script-source (1.3.3)
|
||||
cucumber (1.2.0)
|
||||
builder (>= 2.1.2)
|
||||
diff-lcs (>= 1.1.2)
|
||||
gherkin (~> 2.9.0)
|
||||
diff-lcs (>= 1.1.3)
|
||||
gherkin (~> 2.10.0)
|
||||
json (>= 1.4.6)
|
||||
term-ansicolor (>= 1.0.6)
|
||||
cucumber-rails (1.3.0)
|
||||
capybara (>= 1.1.2)
|
||||
cucumber (>= 1.1.8)
|
||||
@ -149,7 +148,7 @@ GEM
|
||||
ejs (1.0.0)
|
||||
erubis (2.7.0)
|
||||
excon (0.13.4)
|
||||
execjs (1.3.1)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
factory_girl (2.5.2)
|
||||
activesupport (>= 2.3.9)
|
||||
@ -169,14 +168,14 @@ GEM
|
||||
net-ssh (>= 2.1.3)
|
||||
nokogiri (~> 1.5.0)
|
||||
ruby-hmac
|
||||
formatador (0.2.1)
|
||||
formatador (0.2.3)
|
||||
formtastic (2.0.2)
|
||||
rails (~> 3.0)
|
||||
fssm (0.2.9)
|
||||
gherkin (2.9.3)
|
||||
gherkin (2.10.0)
|
||||
json (>= 1.4.6)
|
||||
haml (3.1.4)
|
||||
highline (1.6.11)
|
||||
haml (3.1.6)
|
||||
highline (1.6.12)
|
||||
hike (1.2.1)
|
||||
httparty (0.8.3)
|
||||
multi_json (~> 1.0)
|
||||
@ -186,7 +185,8 @@ GEM
|
||||
jquery-rails (1.0.19)
|
||||
railties (~> 3.0)
|
||||
thor (~> 0.14)
|
||||
json (1.7.0)
|
||||
jruby-pageant (1.0.2)
|
||||
json (1.7.3)
|
||||
json_spec (1.0.3)
|
||||
multi_json (~> 1.0)
|
||||
rspec (~> 2.0)
|
||||
@ -215,15 +215,16 @@ GEM
|
||||
mocha (0.9.12)
|
||||
mongo (1.5.2)
|
||||
bson (= 1.5.2)
|
||||
mongoid (2.4.9)
|
||||
mongoid (2.4.10)
|
||||
activemodel (~> 3.1)
|
||||
mongo (~> 1.3)
|
||||
tzinfo (~> 0.3.22)
|
||||
multi_json (1.3.4)
|
||||
multi_xml (0.4.4)
|
||||
multi_json (1.3.5)
|
||||
multi_xml (0.5.1)
|
||||
net-scp (1.0.4)
|
||||
net-ssh (>= 1.99.1)
|
||||
net-ssh (2.3.0)
|
||||
net-ssh (2.4.0)
|
||||
jruby-pageant (>= 1.0.2)
|
||||
nokogiri (1.5.2)
|
||||
orm_adapter (0.0.7)
|
||||
pickle (0.4.10)
|
||||
@ -256,7 +257,7 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (~> 0.14.6)
|
||||
raindrops (0.8.0)
|
||||
raindrops (0.9.0)
|
||||
rake (0.9.2.2)
|
||||
rdoc (3.12)
|
||||
json (~> 1.4)
|
||||
@ -283,7 +284,7 @@ GEM
|
||||
rubyzip (0.9.8)
|
||||
sanitize (2.0.3)
|
||||
nokogiri (>= 1.4.4, < 1.6)
|
||||
sass (3.1.17)
|
||||
sass (3.1.18)
|
||||
sass-rails (3.2.5)
|
||||
railties (~> 3.2.0)
|
||||
sass (>= 3.1.10)
|
||||
@ -300,7 +301,6 @@ GEM
|
||||
hike (~> 1.2)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
term-ansicolor (1.0.7)
|
||||
thor (0.14.6)
|
||||
tilt (1.3.3)
|
||||
treetop (1.4.10)
|
||||
@ -315,7 +315,7 @@ GEM
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
unidecoder (1.1.1)
|
||||
warden (1.1.1)
|
||||
warden (1.2.0)
|
||||
rack (>= 1.0)
|
||||
xpath (0.1.4)
|
||||
nokogiri (~> 1.3)
|
||||
|
@ -6,8 +6,6 @@ module Locomotive
|
||||
|
||||
before_filter :sanitize_entry_params, :only => :create
|
||||
|
||||
skip_before_filter :verify_authenticity_token
|
||||
|
||||
skip_load_and_authorize_resource
|
||||
|
||||
self.responder = Locomotive::ActionController::PublicResponder # custom responder
|
||||
@ -17,7 +15,6 @@ module Locomotive
|
||||
def create
|
||||
@entry = @content_type.entries.create(params[:entry] || params[:content])
|
||||
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
|
||||
end
|
||||
|
||||
@ -48,6 +45,13 @@ module Locomotive
|
||||
end
|
||||
end
|
||||
|
||||
def handle_unverified_request
|
||||
if Locomotive.config.csrf_protection
|
||||
reset_session
|
||||
redirect_to '/', :status => 302
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,6 +4,7 @@ Feature: Contact form
|
||||
I want to be able to send them a message
|
||||
|
||||
Background:
|
||||
Given I enable the CSRF protection for public submission requests
|
||||
Given I have the site: "test site" set up
|
||||
And I have a custom model named "Messages" with
|
||||
| label | type | required |
|
||||
@ -16,6 +17,7 @@ Feature: Contact form
|
||||
<head></head>
|
||||
<body>
|
||||
<form action="{{ contents.messages.public_submission_url }}" method="post">
|
||||
{% csrf_param %}
|
||||
<input type="hidden" value="/success" name="success_callback" />
|
||||
<input type="hidden" value="/contact" name="error_callback" />
|
||||
<label for="email">E-Mail Address</label>
|
||||
@ -55,6 +57,20 @@ Feature: Contact form
|
||||
And I press "Submit"
|
||||
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
|
||||
When I view the rendered page at "/contact"
|
||||
And I fill in "Message" with "LocomotiveCMS rocks"
|
||||
|
@ -38,4 +38,19 @@ end
|
||||
|
||||
When /^I reload the page$/ do
|
||||
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!
|
||||
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
|
||||
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
|
||||
visit "http://#{@site.domains.first}:#{Capybara.server_port}#{path}"
|
||||
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.
|
||||
# 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:
|
||||
# config.rack_cache = {
|
||||
# :verbose => true,
|
||||
|
@ -27,7 +27,8 @@ module Locomotive
|
||||
},
|
||||
:devise_modules => [:rememberable, :database_authenticatable, :token_authenticatable, :recoverable, :trackable, :validatable, :encryptable, { :encryptor => :sha1 }],
|
||||
:context_assign_extensions => { },
|
||||
:models_for_templatization => []
|
||||
:models_for_templatization => [],
|
||||
:csrf_protection => false
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
module Callbacks #:nodoc:
|
||||
module ClassMethods #:nodoc:
|
||||
|
@ -56,7 +56,7 @@ module Locomotive
|
||||
'locale' => I18n.locale,
|
||||
'default_locale' => current_site.default_locale.to_s,
|
||||
'locales' => current_site.locales,
|
||||
'current_user' => Locomotive::Liquid::Drops::CurrentUser.new(current_locomotive_account)
|
||||
'current_user' => Locomotive::Liquid::Drops::CurrentUser.new(current_locomotive_account)
|
||||
}
|
||||
|
||||
assigns.merge!(Locomotive.config.context_assign_extensions)
|
||||
|
@ -59,7 +59,7 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency 'rack-cache', '~> 1.1'
|
||||
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 '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.
|
||||
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:
|
||||
# config.rack_cache = {
|
||||
# :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.csrf_protection = true
|
||||
|
||||
if force
|
||||
Locomotive.define_subdomain_and_domains_options
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user