request_forgery_protection_test.rb 9.8 KB
Newer Older
1
require 'abstract_unit'
M
Michael Koziarski 已提交
2
require 'digest/sha1'
3 4 5 6 7

ActionController::Routing::Routes.draw do |map|
  map.connect ':controller/:action/:id'
end

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# simulates cookie session store
class FakeSessionDbMan
  def self.generate_digest(data)
    Digest::SHA1.hexdigest("secure")
  end
end

# common controller actions
module RequestForgeryProtectionActions
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
  
  def show_button
    render :inline => "<%= button_to('New', '/') {} %>"
  end
  
25 26 27 28
  def remote_form
    render :inline => "<% form_remote_tag(:url => '/') {} %>"
  end

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
  def unsafe
    render :text => 'pwn'
  end
  
  def rescue_action(e) raise e end
end

# sample controllers
class RequestForgeryProtectionController < ActionController::Base
  include RequestForgeryProtectionActions
  protect_from_forgery :only => :index, :secret => 'abc'
end

class RequestForgeryProtectionWithoutSecretController < ActionController::Base
  include RequestForgeryProtectionActions
  protect_from_forgery
end

# no token is given, assume the cookie store is used
class CsrfCookieMonsterController < ActionController::Base
  include RequestForgeryProtectionActions
  protect_from_forgery :only => :index
end

53 54 55 56 57 58 59 60
# sessions are turned off
class SessionOffController < ActionController::Base
  protect_from_forgery :secret => 'foobar'
  session :off
  def rescue_action(e) raise e end
  include RequestForgeryProtectionActions
end

61 62 63 64 65 66 67 68 69 70 71 72 73 74
class FreeCookieController < CsrfCookieMonsterController
  self.allow_forgery_protection = false
  
  def index
    render :inline => "<%= form_tag('/') {} %>"
  end
  
  def show_button
    render :inline => "<%= button_to('New', '/') {} %>"
  end
end

# common test methods

75
module RequestForgeryProtectionTests
76 77 78
  def teardown
    ActionController::Base.request_forgery_protection_token = nil
  end
79
  
80

81
  def test_should_render_form_with_token_tag
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
     get :index
     assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
   end

   def test_should_render_button_to_with_token_tag
     get :show_button
     assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
   end

   def test_should_render_remote_form_with_only_one_token_parameter
     get :remote_form
     assert_equal 1, @response.body.scan(@token).size
   end

   def test_should_allow_get
     get :index
     assert_response :success
   end

   def test_should_allow_post_without_token_on_unsafe_action
     post :unsafe
     assert_response :success
   end

  def test_should_not_allow_html_post_without_token
    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
    assert_raises(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
109
  end
110
  
111 112 113
  def test_should_not_allow_html_put_without_token
    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
    assert_raises(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
114 115
  end
  
116 117 118
  def test_should_not_allow_html_delete_without_token
    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
    assert_raises(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
119
  end
120

121 122
  def test_should_allow_api_formatted_post_without_token
    assert_nothing_raised do
123 124 125 126
      post :index, :format => 'xml'
    end
  end

127
  def test_should_not_allow_api_formatted_put_without_token
128
    assert_nothing_raised do
129 130 131 132
      put :index, :format => 'xml'
    end
  end

133 134
  def test_should_allow_api_formatted_delete_without_token
    assert_nothing_raised do
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
      delete :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
    assert_raises(ActionController::InvalidAuthenticityToken) do
      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
      post :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
    assert_raises(ActionController::InvalidAuthenticityToken) do
      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
      put :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
    assert_raises(ActionController::InvalidAuthenticityToken) do
      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
      delete :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
    assert_raises(ActionController::InvalidAuthenticityToken) do
      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
      post :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
    assert_raises(ActionController::InvalidAuthenticityToken) do
      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
      put :index, :format => 'xml'
    end
  end

  def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
175
    assert_raises(ActionController::InvalidAuthenticityToken) do
176
      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
177 178 179 180
      delete :index, :format => 'xml'
    end
  end

181 182 183 184 185 186
  def test_should_allow_xhr_post_without_token
    assert_nothing_raised { xhr :post, :index }
  end
  def test_should_not_allow_xhr_post_with_html_without_token
    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
    assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index }
187 188
  end
  
189 190
  def test_should_allow_xhr_put_without_token
    assert_nothing_raised { xhr :put, :index }
191 192
  end
  
193 194
  def test_should_allow_xhr_delete_without_token
    assert_nothing_raised { xhr :delete, :index }
195 196 197
  end
  
  def test_should_allow_post_with_token
198
    post :index, :authenticity_token => @token
199 200 201 202
    assert_response :success
  end
  
  def test_should_allow_put_with_token
203
    put :index, :authenticity_token => @token
204 205 206 207
    assert_response :success
  end
  
  def test_should_allow_delete_with_token
208
    delete :index, :authenticity_token => @token
209 210 211 212
    assert_response :success
  end
  
  def test_should_allow_post_with_xml
213
    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
214 215 216 217 218
    post :index, :format => 'xml'
    assert_response :success
  end
  
  def test_should_allow_put_with_xml
219
    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
220 221 222 223 224
    put :index, :format => 'xml'
    assert_response :success
  end
  
  def test_should_allow_delete_with_xml
225
    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
226 227 228 229 230
    delete :index, :format => 'xml'
    assert_response :success
  end
end

231
# OK let's get our test on
232

233
class RequestForgeryProtectionControllerTest < ActionController::TestCase
234 235 236 237
  include RequestForgeryProtectionTests
  def setup
    @controller = RequestForgeryProtectionController.new
    @request    = ActionController::TestRequest.new
238
    @request.format = :html
239 240 241 242 243 244 245 246 247
    @response   = ActionController::TestResponse.new
    class << @request.session
      def session_id() '123' end
    end
    @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
    ActionController::Base.request_forgery_protection_token = :authenticity_token
  end
end

248
class RequestForgeryProtectionWithoutSecretControllerTest < ActionController::TestCase
249 250 251 252 253 254 255 256 257
  def setup
    @controller = RequestForgeryProtectionWithoutSecretController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    class << @request.session
      def session_id() '123' end
    end
    @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
    ActionController::Base.request_forgery_protection_token = :authenticity_token
258 259
  end
  
260 261 262 263 264
  # def test_should_raise_error_without_secret
  #   assert_raises ActionController::InvalidAuthenticityToken do
  #     get :index
  #   end
  # end
265 266
end

267
class CsrfCookieMonsterControllerTest < ActionController::TestCase
268
  include RequestForgeryProtectionTests
269 270 271 272 273
  def setup
    @controller = CsrfCookieMonsterController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    class << @request.session
274
      attr_accessor :dbman
275
    end
276 277
    # simulate a cookie session store
    @request.session.dbman = FakeSessionDbMan
278
    @token = Digest::SHA1.hexdigest("secure")
279
    ActionController::Base.request_forgery_protection_token = :authenticity_token
280 281 282
  end
end

283
class FreeCookieControllerTest < ActionController::TestCase
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
  def setup
    @controller = FreeCookieController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @token      = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
  end
  
  def test_should_not_render_form_with_token_tag
    get :index
    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
  end
  
  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
  
  def test_should_allow_all_methods_without_token
    [:post, :put, :delete].each do |method|
      assert_nothing_raised { send(method, :index)}
    end
  end
306
end
307

308
class SessionOffControllerTest < ActionController::TestCase
309 310 311 312 313 314 315
  def setup
    @controller = SessionOffController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @token      = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
  end

316 317 318 319 320 321 322 323 324 325 326
  # TODO: Rewrite this test.
  # This test was passing but for the wrong reason.
  # Sessions aren't really being turned off, so an exception was raised
  # because sessions weren't on - not because the token didn't match.
  #
  # def test_should_raise_correct_exception
  #   @request.session = {} # session(:off) doesn't appear to work with controller tests
  #   assert_raises(ActionController::InvalidAuthenticityToken) do
  #     post :index, :authenticity_token => @token, :format => :html
  #   end
  # end
327
end