public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Change the forgery token implementation to just be a simple random string.

This deprecates the use of :secret and :digest which were only needed when we 
were hashing session ids.
NZKoz (author)
Thu Nov 20 14:06:19 -0800 2008
commit  9fdb15e60f4d4e37916e5354c50d559773bbe014
tree    2f1c465eea8f798b0a29470ea3bd1f3ab8302ce0
parent  ed7549da2899e6eb398c209bb0ac680c8bdb6087
...
5
6
7
8
9
10
11
12
...
14
15
16
17
 
18
19
20
...
57
58
59
60
61
62
63
64
65
66
67
68
...
70
71
72
73
74
75
76
77
78
79
 
 
 
80
81
82
...
90
91
92
93
 
94
95
96
...
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
 
134
135
 
136
137
138
...
5
6
7
 
 
8
9
10
...
12
13
14
 
15
16
17
18
...
55
56
57
 
58
59
 
 
 
60
61
62
...
64
65
66
 
 
 
67
68
69
 
70
71
72
73
74
75
...
83
84
85
 
86
87
88
89
...
98
99
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
102
 
103
104
105
106
0
@@ -5,8 +5,6 @@ module ActionController #:nodoc:
0
   module RequestForgeryProtection
0
     def self.included(base)
0
       base.class_eval do
0
-        class_inheritable_accessor :request_forgery_protection_options
0
-        self.request_forgery_protection_options = {}
0
         helper_method :form_authenticity_token
0
         helper_method :protect_against_forgery?
0
       end
0
@@ -14,7 +12,7 @@ module ActionController #:nodoc:
0
     end
0
     
0
     # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
0
-    # forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
0
+    # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
0
     # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller.  Only
0
     # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
0
     # scheme there anyway).  Also, GET requests are not protected as these should be idempotent anyway.
0
@@ -57,12 +55,8 @@ module ActionController #:nodoc:
0
       # Example:
0
       #
0
       #   class FooController < ApplicationController
0
-      #     # uses the cookie session store (then you don't need a separate :secret)
0
       #     protect_from_forgery :except => :index
0
       #
0
-      #     # uses one of the other session stores that uses a session_id value.
0
-      #     protect_from_forgery :secret => 'my-little-pony', :except => :index
0
-      #
0
       #     # you can disable csrf protection on controller-by-controller basis:
0
       #     skip_before_filter :verify_authenticity_token
0
       #   end
0
@@ -70,13 +64,12 @@ module ActionController #:nodoc:
0
       # Valid Options:
0
       #
0
       # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call.  Set which actions are verified.
0
-      # * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
0
-      #   Leave this off if you are using the cookie session store.
0
-      # * <tt>:digest</tt> - Message digest used for hashing.  Defaults to 'SHA1'.
0
       def protect_from_forgery(options = {})
0
         self.request_forgery_protection_token ||= :authenticity_token
0
         before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
0
-        request_forgery_protection_options.update(options)
0
+        if options[:secret] || options[:digest]
0
+          ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
0
+        end
0
       end
0
     end
0
 
0
@@ -90,7 +83,7 @@ module ActionController #:nodoc:
0
       #
0
       # * is the format restricted?  By default, only HTML and AJAX requests are checked.
0
       # * is it a GET request?  Gets should be safe and idempotent
0
-      # * Does the form_authenticity_token match the given _token value from the params?
0
+      # * Does the form_authenticity_token match the given token value from the params?
0
       def verified_request?
0
         !protect_against_forgery?     ||
0
           request.method == :get      ||
0
@@ -105,34 +98,9 @@ module ActionController #:nodoc:
0
       # Sets the token value for the current session.  Pass a <tt>:secret</tt> option
0
       # in +protect_from_forgery+ to add a custom salt to the hash.
0
       def form_authenticity_token
0
-        @form_authenticity_token ||= if !session.respond_to?(:session_id)
0
-          raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session.  Use #allow_forgery_protection to disable it, or use a valid session."
0
-        elsif request_forgery_protection_options[:secret]
0
-          authenticity_token_from_session_id
0
-        elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
0
-          authenticity_token_from_cookie_session
0
-        else
0
-          raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call.  Set that or use a session store capable of generating its own keys (Cookie Session Store)."
0
-        end
0
-      end
0
-      
0
-      # Generates a unique digest using the session_id and the CSRF secret.
0
-      def authenticity_token_from_session_id
0
-        key = if request_forgery_protection_options[:secret].respond_to?(:call)
0
-          request_forgery_protection_options[:secret].call(@session)
0
-        else
0
-          request_forgery_protection_options[:secret]
0
-        end
0
-        digest = request_forgery_protection_options[:digest] ||= 'SHA1'
0
-        OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
0
-      end
0
-      
0
-      # No secret was given, so assume this is a cookie session store.
0
-      def authenticity_token_from_cookie_session
0
-        session[:csrf_id] ||= CGI::Session.generate_unique_id
0
-        session.dbman.generate_digest(session[:csrf_id])
0
+        session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
0
       end
0
-      
0
+
0
       def protect_against_forgery?
0
         allow_forgery_protection && request_forgery_protection_token
0
       end
...
883
884
885
 
886
887
888
889
890
891
 
892
893
894
...
883
884
885
886
887
888
889
890
891
 
892
893
894
895
0
@@ -883,12 +883,13 @@ class RenderTest < ActionController::TestCase
0
   end
0
 
0
   def test_enum_rjs_test
0
+    ActiveSupport::SecureRandom.stubs(:base64).returns("asdf")
0
     get :enum_rjs_test
0
     body = %{
0
       $$(".product").each(function(value, index) {
0
       new Effect.Highlight(element,{});
0
       new Effect.Highlight(value,{});
0
-      Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
0
+      Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value) + '&authenticity_token=' + encodeURIComponent('asdf')})}});
0
       new Draggable(value, {});
0
       });
0
     }.gsub(/^      /, '').strip
...
5
6
7
8
9
10
11
12
13
14
15
16
17
...
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 
62
63
64
...
237
238
239
240
241
242
243
244
245
246
 
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
 
279
280
281
...
285
286
287
288
 
 
 
289
290
291
...
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
...
5
6
7
 
 
 
 
 
 
 
8
9
10
...
29
30
31
 
 
 
 
 
 
 
 
 
 
 
32
33
34
 
 
 
 
 
 
 
 
 
35
36
37
38
...
211
212
213
 
 
 
 
 
 
 
214
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
217
218
219
...
223
224
225
 
226
227
228
229
230
231
...
244
245
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
@@ -5,13 +5,6 @@ ActionController::Routing::Routes.draw do |map|
0
   map.connect ':controller/:action/:id'
0
 end
0
 
0
-# simulates cookie session store
0
-class FakeSessionDbMan
0
-  def self.generate_digest(data)
0
-    Digest::SHA1.hexdigest("secure")
0
-  end
0
-end
0
-
0
 # common controller actions
0
 module RequestForgeryProtectionActions
0
   def index
0
@@ -36,29 +29,10 @@ end
0
 # sample controllers
0
 class RequestForgeryProtectionController < ActionController::Base
0
   include RequestForgeryProtectionActions
0
-  protect_from_forgery :only => :index, :secret => 'abc'
0
-end
0
-
0
-class RequestForgeryProtectionWithoutSecretController < ActionController::Base
0
-  include RequestForgeryProtectionActions
0
-  protect_from_forgery
0
-end
0
-
0
-# no token is given, assume the cookie store is used
0
-class CsrfCookieMonsterController < ActionController::Base
0
-  include RequestForgeryProtectionActions
0
   protect_from_forgery :only => :index
0
 end
0
 
0
-# sessions are turned off
0
-class SessionOffController < ActionController::Base
0
-  protect_from_forgery :secret => 'foobar'
0
-  session :off
0
-  def rescue_action(e) raise e end
0
-  include RequestForgeryProtectionActions
0
-end
0
-
0
-class FreeCookieController < CsrfCookieMonsterController
0
+class FreeCookieController < RequestForgeryProtectionController
0
   self.allow_forgery_protection = false
0
   
0
   def index
0
@@ -237,45 +211,9 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase
0
     @request    = ActionController::TestRequest.new
0
     @request.format = :html
0
     @response   = ActionController::TestResponse.new
0
-    class << @request.session
0
-      def session_id() '123' end
0
-    end
0
-    @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
0
-    ActionController::Base.request_forgery_protection_token = :authenticity_token
0
-  end
0
-end
0
+    @token      = "cf50faa3fe97702ca1ae"
0
 
0
-class RequestForgeryProtectionWithoutSecretControllerTest < ActionController::TestCase
0
-  def setup
0
-    @controller = RequestForgeryProtectionWithoutSecretController.new
0
-    @request    = ActionController::TestRequest.new
0
-    @response   = ActionController::TestResponse.new
0
-    class << @request.session
0
-      def session_id() '123' end
0
-    end
0
-    @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
0
-    ActionController::Base.request_forgery_protection_token = :authenticity_token
0
-  end
0
-  
0
-  # def test_should_raise_error_without_secret
0
-  #   assert_raises ActionController::InvalidAuthenticityToken do
0
-  #     get :index
0
-  #   end
0
-  # end
0
-end
0
-
0
-class CsrfCookieMonsterControllerTest < ActionController::TestCase
0
-  include RequestForgeryProtectionTests
0
-  def setup
0
-    @controller = CsrfCookieMonsterController.new
0
-    @request    = ActionController::TestRequest.new
0
-    @response   = ActionController::TestResponse.new
0
-    class << @request.session
0
-      attr_accessor :dbman
0
-    end
0
-    # simulate a cookie session store
0
-    @request.session.dbman = FakeSessionDbMan
0
-    @token = Digest::SHA1.hexdigest("secure")
0
+    ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
0
     ActionController::Base.request_forgery_protection_token = :authenticity_token
0
   end
0
 end
0
@@ -285,7 +223,9 @@ class FreeCookieControllerTest < ActionController::TestCase
0
     @controller = FreeCookieController.new
0
     @request    = ActionController::TestRequest.new
0
     @response   = ActionController::TestResponse.new
0
-    @token      = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
0
+    @token      = "cf50faa3fe97702ca1ae"
0
+
0
+    ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
0
   end
0
   
0
   def test_should_not_render_form_with_token_tag
0
@@ -304,24 +244,3 @@ class FreeCookieControllerTest < ActionController::TestCase
0
     end
0
   end
0
 end
0
-
0
-class SessionOffControllerTest < ActionController::TestCase
0
-  def setup
0
-    @controller = SessionOffController.new
0
-    @request    = ActionController::TestRequest.new
0
-    @response   = ActionController::TestResponse.new
0
-    @token      = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123')
0
-  end
0
-
0
-  # TODO: Rewrite this test.
0
-  # This test was passing but for the wrong reason.
0
-  # Sessions aren't really being turned off, so an exception was raised
0
-  # because sessions weren't on - not because the token didn't match.
0
-  #
0
-  # def test_should_raise_correct_exception
0
-  #   @request.session = {} # session(:off) doesn't appear to work with controller tests
0
-  #   assert_raises(ActionController::InvalidAuthenticityToken) do
0
-  #     post :index, :authenticity_token => @token, :format => :html
0
-  #   end
0
-  # end
0
-end

Comments