request_forgery_protection_test.rb 9.8 KB
Newer Older
1
require 'abstract_unit'
M
Michael Koziarski 已提交
2
require 'digest/sha1'
3
require "active_support/log_subscriber/test_helper"
4

5 6 7 8 9
# common controller actions
module RequestForgeryProtectionActions
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
10

11
  def show_button
12
    render :inline => "<%= button_to('New', '/') %>"
13
  end
14

15 16 17 18 19 20 21 22
  def external_form
    render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
  end

  def external_form_without_protection
    render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
  end

23 24 25
  def unsafe
    render :text => 'pwn'
  end
26

J
Jeremy Kemper 已提交
27
  def meta
28
    render :inline => "<%= csrf_meta_tags %>"
J
Jeremy Kemper 已提交
29 30
  end

31
  def external_form_for
32
    render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
33 34 35
  end

  def form_for_without_protection
36
    render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
37
  end
38 39 40 41 42

  def form_for_remote
    render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>"
  end

43 44 45 46
  def form_for_remote_with_token
    render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>"
  end

47 48 49 50 51 52 53 54
  def form_for_with_token
    render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>"
  end

  def form_for_remote_with_external_token
    render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
  end

55
  def rescue_action(e) raise e end
56 57 58
end

# sample controllers
59
class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base
60
  include RequestForgeryProtectionActions
61
  protect_from_forgery :only => %w(index meta), :with => :reset_session
62 63
end

64
class RequestForgeryProtectionControllerUsingException < ActionController::Base
65
  include RequestForgeryProtectionActions
66
  protect_from_forgery :only => %w(index meta), :with => :exception
67 68 69
end


70
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
71
  self.allow_forgery_protection = false
72

73 74 75
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
76

77
  def show_button
78
    render :inline => "<%= button_to('New', '/') %>"
79 80 81
  end
end

82
class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession
83 84 85 86 87
  def form_authenticity_param
    'foobar'
  end
end

88
# common test methods
89
module RequestForgeryProtectionTests
90 91
  def setup
    @token      = "cf50faa3fe97702ca1ae"
92

93
    SecureRandom.stubs(:base64).returns(@token)
94
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
E
Emilio Tagua 已提交
95 96
  end

97 98 99
  def teardown
    ActionController::Base.request_forgery_protection_token = nil
  end
100

101 102 103 104
  def test_should_render_form_with_token_tag
    assert_not_blocked do
      get :index
    end
105
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
106 107
  end

E
Emilio Tagua 已提交
108
  def test_should_render_button_to_with_token_tag
109 110 111
    assert_not_blocked do
      get :show_button
    end
112
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
E
Emilio Tagua 已提交
113 114
  end

115
  def test_should_render_form_without_token_tag_if_remote
116 117 118
    assert_not_blocked do
      get :form_for_remote
    end
119
    assert_no_match(/authenticity_token/, response.body)
120 121
  end

122 123
  def test_should_render_form_with_token_tag_if_remote_and_embedding_token_is_on
    original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
124
    begin
125
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
126 127 128
      assert_not_blocked do
        get :form_for_remote
      end
129
      assert_match(/authenticity_token/, response.body)
130
    ensure
131
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
132 133 134
    end
  end

135 136 137 138
  def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on
    original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
    begin
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
139
      assert_not_blocked do
140
        get :form_for_remote_with_external_token
141
      end
142
      assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
143
    ensure
144
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
145
    end
146 147
  end

148 149 150 151 152 153 154
  def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested
    assert_not_blocked do
      get :form_for_remote_with_external_token
    end
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
  end

155 156 157 158 159 160 161
  def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
    assert_not_blocked do
      get :form_for_remote_with_token
    end
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
  end

162 163 164 165 166 167 168
  def test_should_render_form_with_token_tag_with_authenticity_token_requested
    assert_not_blocked do
      get :form_for_with_token
    end
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
  end

E
Emilio Tagua 已提交
169
  def test_should_allow_get
170
    assert_not_blocked { get :index }
E
Emilio Tagua 已提交
171 172 173
  end

  def test_should_allow_post_without_token_on_unsafe_action
174
    assert_not_blocked { post :unsafe }
175 176
  end

177 178
  def test_should_not_allow_post_without_token
    assert_blocked { post :index }
179 180
  end

181 182
  def test_should_not_allow_post_without_token_irrespective_of_format
    assert_blocked { post :index, :format=>'xml' }
183 184
  end

185 186 187 188
  def test_should_not_allow_patch_without_token
    assert_blocked { patch :index }
  end

189 190
  def test_should_not_allow_put_without_token
    assert_blocked { put :index }
191
  end
192

193 194
  def test_should_not_allow_delete_without_token
    assert_blocked { delete :index }
195
  end
196

197 198
  def test_should_not_allow_xhr_post_without_token
    assert_blocked { xhr :post, :index }
199
  end
200

201
  def test_should_allow_post_with_token
202
    assert_not_blocked { post :index, :custom_authenticity_token => @token }
203
  end
204

205 206 207 208
  def test_should_allow_patch_with_token
    assert_not_blocked { patch :index, :custom_authenticity_token => @token }
  end

209
  def test_should_allow_put_with_token
210
    assert_not_blocked { put :index, :custom_authenticity_token => @token }
211
  end
212

213
  def test_should_allow_delete_with_token
214
    assert_not_blocked { delete :index, :custom_authenticity_token => @token }
215
  end
216

217 218 219
  def test_should_allow_post_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { post :index }
220
  end
221

222 223 224
  def test_should_allow_delete_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { delete :index }
225
  end
226

227 228 229 230 231
  def test_should_allow_patch_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { patch :index }
  end

232 233 234
  def test_should_allow_put_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { put :index }
235
  end
236

237 238 239 240 241 242 243 244 245 246
  def test_should_warn_on_missing_csrf_token
    old_logger = ActionController::Base.logger
    logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
    ActionController::Base.logger = logger

    begin
      assert_blocked { post :index }

      assert_equal 1, logger.logged(:warn).size
      assert_match(/CSRF token authenticity/, logger.logged(:warn).last)
M
Mike Dillon 已提交
247
    ensure
248 249 250 251
      ActionController::Base.logger = old_logger
    end
  end

252 253 254 255
  def assert_blocked
    session[:something_like_user_id] = 1
    yield
    assert_nil session[:something_like_user_id], "session values are still present"
256 257
    assert_response :success
  end
258

259 260
  def assert_not_blocked
    assert_nothing_raised { yield }
261 262 263 264
    assert_response :success
  end
end

265
# OK let's get our test on
266

267
class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase
268
  include RequestForgeryProtectionTests
J
Jeremy Kemper 已提交
269

270 271 272 273 274 275 276 277
  setup do
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
  end

  teardown do
    ActionController::Base.request_forgery_protection_token = nil
  end

278
  test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
279
    SecureRandom.stubs(:base64).returns(@token + '<=?')
J
Jeremy Kemper 已提交
280
    get :meta
281
    assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token'
282
    assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
J
Jeremy Kemper 已提交
283
  end
284 285
end

286
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
287 288 289 290 291 292 293 294
  include RequestForgeryProtectionTests
  def assert_blocked
    assert_raises(ActionController::InvalidAuthenticityToken) do
      yield
    end
  end
end

295
class FreeCookieControllerTest < ActionController::TestCase
296 297 298 299
  def setup
    @controller = FreeCookieController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
J
Jeremy Kemper 已提交
300
    @token      = "cf50faa3fe97702ca1ae"
301

302
    SecureRandom.stubs(:base64).returns(@token)
303
  end
304

305 306 307 308
  def test_should_not_render_form_with_token_tag
    get :index
    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
  end
309

310 311 312 313
  def test_should_not_render_button_to_with_token_tag
    get :show_button
    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
  end
314

315
  def test_should_allow_all_methods_without_token
316
    [:post, :patch, :put, :delete].each do |method|
317 318 319
      assert_nothing_raised { send(method, :index)}
    end
  end
J
Jeremy Kemper 已提交
320 321 322

  test 'should not emit a csrf-token meta tag' do
    get :meta
323
    assert_blank @response.body
J
Jeremy Kemper 已提交
324
  end
325
end
326 327 328

class CustomAuthenticityParamControllerTest < ActionController::TestCase
  def setup
329 330 331 332 333
    ActionController::Base.request_forgery_protection_token = :custom_token_name
    super
  end

  def teardown
J
Jeremy Kemper 已提交
334
    ActionController::Base.request_forgery_protection_token = :authenticity_token
335
    super
336 337 338
  end

  def test_should_allow_custom_token
339
    post :index, :custom_token_name => 'foobar'
340 341 342
    assert_response :ok
  end
end