request_forgery_protection_test.rb 10.7 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 81
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
end
82

83
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
84
  self.allow_forgery_protection = false
85

86 87 88
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
89

90
  def show_button
91
    render :inline => "<%= button_to('New', '/') %>"
92 93 94
  end
end

95
class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession
96 97 98 99 100
  def form_authenticity_param
    'foobar'
  end
end

101
# common test methods
102
module RequestForgeryProtectionTests
103 104
  def setup
    @token      = "cf50faa3fe97702ca1ae"
105

106
    SecureRandom.stubs(:base64).returns(@token)
107
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
E
Emilio Tagua 已提交
108 109
  end

110 111 112
  def teardown
    ActionController::Base.request_forgery_protection_token = nil
  end
113

114 115 116 117
  def test_should_render_form_with_token_tag
    assert_not_blocked do
      get :index
    end
118
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
119 120
  end

E
Emilio Tagua 已提交
121
  def test_should_render_button_to_with_token_tag
122 123 124
    assert_not_blocked do
      get :show_button
    end
125
    assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
E
Emilio Tagua 已提交
126 127
  end

128
  def test_should_render_form_without_token_tag_if_remote
129 130 131
    assert_not_blocked do
      get :form_for_remote
    end
132
    assert_no_match(/authenticity_token/, response.body)
133 134
  end

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

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

161 162 163 164 165 166 167
  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

168 169 170 171 172 173 174
  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

175 176 177 178 179 180 181
  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 已提交
182
  def test_should_allow_get
183
    assert_not_blocked { get :index }
E
Emilio Tagua 已提交
184 185
  end

186 187 188 189
  def test_should_allow_head
    assert_not_blocked { head :index }
  end

E
Emilio Tagua 已提交
190
  def test_should_allow_post_without_token_on_unsafe_action
191
    assert_not_blocked { post :unsafe }
192 193
  end

194 195
  def test_should_not_allow_post_without_token
    assert_blocked { post :index }
196 197
  end

198 199
  def test_should_not_allow_post_without_token_irrespective_of_format
    assert_blocked { post :index, :format=>'xml' }
200 201
  end

202 203 204 205
  def test_should_not_allow_patch_without_token
    assert_blocked { patch :index }
  end

206 207
  def test_should_not_allow_put_without_token
    assert_blocked { put :index }
208
  end
209

210 211
  def test_should_not_allow_delete_without_token
    assert_blocked { delete :index }
212
  end
213

214 215
  def test_should_not_allow_xhr_post_without_token
    assert_blocked { xhr :post, :index }
216
  end
217

218
  def test_should_allow_post_with_token
219
    assert_not_blocked { post :index, :custom_authenticity_token => @token }
220
  end
221

222 223 224 225
  def test_should_allow_patch_with_token
    assert_not_blocked { patch :index, :custom_authenticity_token => @token }
  end

226
  def test_should_allow_put_with_token
227
    assert_not_blocked { put :index, :custom_authenticity_token => @token }
228
  end
229

230
  def test_should_allow_delete_with_token
231
    assert_not_blocked { delete :index, :custom_authenticity_token => @token }
232
  end
233

234 235 236
  def test_should_allow_post_with_token_in_header
    @request.env['HTTP_X_CSRF_TOKEN'] = @token
    assert_not_blocked { post :index }
237
  end
238

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

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

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

254 255 256 257 258 259 260 261 262 263
  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 已提交
264
    ensure
265 266 267 268
      ActionController::Base.logger = old_logger
    end
  end

269 270 271 272
  def assert_blocked
    session[:something_like_user_id] = 1
    yield
    assert_nil session[:something_like_user_id], "session values are still present"
273 274
    assert_response :success
  end
275

276 277
  def assert_not_blocked
    assert_nothing_raised { yield }
278 279 280 281
    assert_response :success
  end
end

282
# OK let's get our test on
283

284
class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase
285
  include RequestForgeryProtectionTests
J
Jeremy Kemper 已提交
286

287 288 289 290 291 292 293 294
  setup do
    ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
  end

  teardown do
    ActionController::Base.request_forgery_protection_token = nil
  end

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

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
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
end

325
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
326 327 328 329 330 331 332 333
  include RequestForgeryProtectionTests
  def assert_blocked
    assert_raises(ActionController::InvalidAuthenticityToken) do
      yield
    end
  end
end

334
class FreeCookieControllerTest < ActionController::TestCase
335 336 337 338
  def setup
    @controller = FreeCookieController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
J
Jeremy Kemper 已提交
339
    @token      = "cf50faa3fe97702ca1ae"
340

341
    SecureRandom.stubs(:base64).returns(@token)
342
  end
343

344 345 346 347
  def test_should_not_render_form_with_token_tag
    get :index
    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
  end
348

349 350 351 352
  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
353

354
  def test_should_allow_all_methods_without_token
355
    [:post, :patch, :put, :delete].each do |method|
356 357 358
      assert_nothing_raised { send(method, :index)}
    end
  end
J
Jeremy Kemper 已提交
359 360 361

  test 'should not emit a csrf-token meta tag' do
    get :meta
362
    assert @response.body.blank?
J
Jeremy Kemper 已提交
363
  end
364
end
365 366 367

class CustomAuthenticityParamControllerTest < ActionController::TestCase
  def setup
368 369 370 371 372
    ActionController::Base.request_forgery_protection_token = :custom_token_name
    super
  end

  def teardown
J
Jeremy Kemper 已提交
373
    ActionController::Base.request_forgery_protection_token = :authenticity_token
374
    super
375 376 377
  end

  def test_should_allow_custom_token
378
    post :index, :custom_token_name => 'foobar'
379 380 381
    assert_response :ok
  end
end