request_forgery_protection_test.rb 10.9 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
end

69 70 71 72 73 74 75 76 77 78 79 80
class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base
  protect_from_forgery :with => :null_session

  def signed
    cookies.signed[:foo] = 'bar'
    render :nothing => true
  end

  def encrypted
    cookies.encrypted[:foo] = 'bar'
    render :nothing => true
  end
81 82 83 84 85

  def try_to_reset_session
    reset_session
    render :nothing => true
  end
86
end
87

88
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
89
  self.allow_forgery_protection = false
90

91 92 93
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
94

95
  def show_button
96
    render :inline => "<%= button_to('New', '/') %>"
97 98 99
  end
end

100
class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession
101 102 103 104 105
  def form_authenticity_param
    'foobar'
  end
end

106
# common test methods
107
module RequestForgeryProtectionTests
108 109
  def setup
    @token      = "cf50faa3fe97702ca1ae"
110

111
    SecureRandom.stubs(:base64).returns(@token)
112
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
E
Emilio Tagua 已提交
113 114
  end

115 116 117
  def teardown
    ActionController::Base.request_forgery_protection_token = nil
  end
118

119 120 121 122
  def test_should_render_form_with_token_tag
    assert_not_blocked do
      get :index
    end
123
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
124 125
  end

E
Emilio Tagua 已提交
126
  def test_should_render_button_to_with_token_tag
127 128 129
    assert_not_blocked do
      get :show_button
    end
130
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
E
Emilio Tagua 已提交
131 132
  end

133
  def test_should_render_form_without_token_tag_if_remote
134 135 136
    assert_not_blocked do
      get :form_for_remote
    end
137
    assert_no_match(/authenticity_token/, response.body)
138 139
  end

140 141
  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
142
    begin
143
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
144 145 146
      assert_not_blocked do
        get :form_for_remote
      end
147
      assert_match(/authenticity_token/, response.body)
148
    ensure
149
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
150 151 152
    end
  end

153 154 155 156
  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
157
      assert_not_blocked do
158
        get :form_for_remote_with_external_token
159
      end
160
      assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
161
    ensure
162
      ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
163
    end
164 165
  end

166 167 168 169 170 171 172
  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

173 174 175 176 177 178 179
  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

180 181 182 183 184 185 186
  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 已提交
187
  def test_should_allow_get
188
    assert_not_blocked { get :index }
E
Emilio Tagua 已提交
189 190
  end

191 192 193 194
  def test_should_allow_head
    assert_not_blocked { head :index }
  end

E
Emilio Tagua 已提交
195
  def test_should_allow_post_without_token_on_unsafe_action
196
    assert_not_blocked { post :unsafe }
197 198
  end

199 200
  def test_should_not_allow_post_without_token
    assert_blocked { post :index }
201 202
  end

203 204
  def test_should_not_allow_post_without_token_irrespective_of_format
    assert_blocked { post :index, :format=>'xml' }
205 206
  end

207 208 209 210
  def test_should_not_allow_patch_without_token
    assert_blocked { patch :index }
  end

211 212
  def test_should_not_allow_put_without_token
    assert_blocked { put :index }
213
  end
214

215 216
  def test_should_not_allow_delete_without_token
    assert_blocked { delete :index }
217
  end
218

219 220
  def test_should_not_allow_xhr_post_without_token
    assert_blocked { xhr :post, :index }
221
  end
222

223
  def test_should_allow_post_with_token
224
    assert_not_blocked { post :index, :custom_authenticity_token => @token }
225
  end
226

227 228 229 230
  def test_should_allow_patch_with_token
    assert_not_blocked { patch :index, :custom_authenticity_token => @token }
  end

231
  def test_should_allow_put_with_token
232
    assert_not_blocked { put :index, :custom_authenticity_token => @token }
233
  end
234

235
  def test_should_allow_delete_with_token
236
    assert_not_blocked { delete :index, :custom_authenticity_token => @token }
237
  end
238

239 240 241
  def test_should_allow_post_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { post :index }
242
  end
243

244 245 246
  def test_should_allow_delete_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { delete :index }
247
  end
248

249 250 251 252 253
  def test_should_allow_patch_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { patch :index }
  end

254 255 256
  def test_should_allow_put_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { put :index }
257
  end
258

259 260 261 262 263 264 265 266 267 268
  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 已提交
269
    ensure
270 271 272 273
      ActionController::Base.logger = old_logger
    end
  end

274 275 276 277
  def assert_blocked
    session[:something_like_user_id] = 1
    yield
    assert_nil session[:something_like_user_id], "session values are still present"
278 279
    assert_response :success
  end
280

281 282
  def assert_not_blocked
    assert_nothing_raised { yield }
283 284 285 286
    assert_response :success
  end
end

287
# OK let's get our test on
288

289
class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase
290
  include RequestForgeryProtectionTests
J
Jeremy Kemper 已提交
291

292 293 294 295 296 297 298 299
  setup do
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
  end

  teardown do
    ActionController::Base.request_forgery_protection_token = nil
  end

300
  test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
301
    SecureRandom.stubs(:base64).returns(@token + '<=?')
J
Jeremy Kemper 已提交
302
    get :meta
303
    assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token'
304
    assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
J
Jeremy Kemper 已提交
305
  end
306 307
end

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
class NullSessionDummyKeyGenerator
  def generate_key(secret)
    '03312270731a2ed0d11ed091c2338a06'
  end
end

class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase  
  def setup
    @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
  end

  test 'should allow to set signed cookies' do
    post :signed
    assert_response :ok
  end

  test 'should allow to set encrypted cookies' do
    post :encrypted
    assert_response :ok
  end
328 329 330 331 332

  test 'should allow reset_session' do
    post :try_to_reset_session
    assert_response :ok
  end
333 334
end

335
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
336 337 338 339 340 341 342 343
  include RequestForgeryProtectionTests
  def assert_blocked
    assert_raises(ActionController::InvalidAuthenticityToken) do
      yield
    end
  end
end

344
class FreeCookieControllerTest < ActionController::TestCase
345 346 347 348
  def setup
    @controller = FreeCookieController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
J
Jeremy Kemper 已提交
349
    @token      = "cf50faa3fe97702ca1ae"
350

351
    SecureRandom.stubs(:base64).returns(@token)
352
  end
353

354 355 356 357
  def test_should_not_render_form_with_token_tag
    get :index
    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
  end
358

359 360 361 362
  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
363

364
  def test_should_allow_all_methods_without_token
365
    [:post, :patch, :put, :delete].each do |method|
366 367 368
      assert_nothing_raised { send(method, :index)}
    end
  end
J
Jeremy Kemper 已提交
369 370 371

  test 'should not emit a csrf-token meta tag' do
    get :meta
372
    assert @response.body.blank?
J
Jeremy Kemper 已提交
373
  end
374
end
375 376 377

class CustomAuthenticityParamControllerTest < ActionController::TestCase
  def setup
378 379 380 381 382
    ActionController::Base.request_forgery_protection_token = :custom_token_name
    super
  end

  def teardown
J
Jeremy Kemper 已提交
383
    ActionController::Base.request_forgery_protection_token = :authenticity_token
384
    super
385 386 387
  end

  def test_should_allow_custom_token
388
    post :index, :custom_token_name => 'foobar'
389 390 391
    assert_response :ok
  end
end