Skip to content
This repository
Browse code

Added cookies.permanent, cookies.signed, and cookies.permanent.signed…

… accessor for common cookie actions [DHH]
  • Loading branch information...
commit 0200e20f148c96afceeebc4da7b5985643f9f707 1 parent e4ebaab
David Heinemeier Hansson authored December 15, 2009
19  actionpack/CHANGELOG
... ...
@@ -1,3 +1,22 @@
  1
+*Edge*
  2
+
  3
+* Added cookies.permanent, cookies.signed, and cookies.permanent.signed accessor for common cookie actions [DHH]. Examples:
  4
+
  5
+    cookies.permanent[:prefers_open_id] = true
  6
+    # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
  7
+
  8
+    cookies.signed[:discount] = 45
  9
+    # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
  10
+
  11
+    cookies.signed[:discount]
  12
+    # => 45 (if the cookie was changed, you'll get a InvalidSignature exception)
  13
+
  14
+    cookies.permanent.signed[:remember_me] = current_user.id
  15
+    # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
  16
+    
  17
+  ...to use the signed cookies, you need to set a secret to ActionController::Base.cookie_verifier_secret (automatically done in config/initializers/cookie_verification_secret.rb for new Rails applications).
  18
+
  19
+
1 20
 *2.3.5 (November 25, 2009)*
2 21
 
3 22
 * Minor Bug Fixes and deprecation warnings
94  actionpack/lib/action_controller/cookies.rb
@@ -46,6 +46,7 @@ module ActionController #:nodoc:
46 46
   module Cookies
47 47
     def self.included(base)
48 48
       base.helper_method :cookies
  49
+      base.cattr_accessor :cookie_verifier_secret
49 50
     end
50 51
 
51 52
     protected
@@ -56,6 +57,8 @@ def cookies
56 57
   end
57 58
 
58 59
   class CookieJar < Hash #:nodoc:
  60
+    attr_reader :controller
  61
+    
59 62
     def initialize(controller)
60 63
       @controller, @cookies = controller, controller.request.cookies
61 64
       super()
@@ -91,5 +94,96 @@ def delete(key, options = {})
91 94
       @controller.response.delete_cookie(key, options)
92 95
       value
93 96
     end
  97
+
  98
+    # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
  99
+    #
  100
+    #   cookies.permanent[:prefers_open_id] = true
  101
+    #   # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
  102
+    #
  103
+    # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
  104
+    #
  105
+    # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
  106
+    #
  107
+    #   cookies.permanent.signed[:remember_me] = current_user.id
  108
+    #   # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
  109
+    def permanent
  110
+      @permanent ||= PermanentCookieJar.new(self)
  111
+    end
  112
+    
  113
+    # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
  114
+    # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
  115
+    # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
  116
+    # be raised.
  117
+    #
  118
+    # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret.
  119
+    #
  120
+    # Example:
  121
+    #
  122
+    #   cookies.signed[:discount] = 45
  123
+    #   # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
  124
+    #
  125
+    #   cookies.signed[:discount] # => 45
  126
+    def signed
  127
+      @signed ||= SignedCookieJar.new(self)
  128
+    end
  129
+  end
  130
+  
  131
+  class PermanentCookieJar < CookieJar #:nodoc:
  132
+    def initialize(parent_jar)
  133
+      @parent_jar = parent_jar
  134
+    end
  135
+
  136
+    def []=(key, options)
  137
+      if options.is_a?(Hash)
  138
+        options.symbolize_keys!
  139
+      else
  140
+        options = { :value => options }
  141
+      end
  142
+      
  143
+      options[:expires] = 20.years.from_now
  144
+      @parent_jar[key] = options
  145
+    end
  146
+
  147
+    def signed
  148
+      @signed ||= SignedCookieJar.new(self)
  149
+    end
  150
+
  151
+    def controller
  152
+      @parent_jar.controller
  153
+    end
  154
+
  155
+    def method_missing(method, *arguments, &block)
  156
+      @parent_jar.send(method, *arguments, &block)
  157
+    end
  158
+  end
  159
+  
  160
+  class SignedCookieJar < CookieJar #:nodoc:
  161
+    def initialize(parent_jar)
  162
+      unless parent_jar.controller.class.cookie_verifier_secret
  163
+        raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies"
  164
+      end
  165
+
  166
+      @parent_jar = parent_jar
  167
+      @verifier = ActiveSupport::MessageVerifier.new(@parent_jar.controller.class.cookie_verifier_secret)
  168
+    end
  169
+    
  170
+    def [](name)
  171
+      @verifier.verify(@parent_jar[name])
  172
+    end
  173
+    
  174
+    def []=(key, options)
  175
+      if options.is_a?(Hash)
  176
+        options.symbolize_keys!
  177
+        options[:value] = @verifier.generate(options[:value])
  178
+      else
  179
+        options = { :value => @verifier.generate(options) }
  180
+      end
  181
+      
  182
+      @parent_jar[key] = options
  183
+    end
  184
+    
  185
+    def method_missing(method, *arguments, &block)
  186
+      @parent_jar.send(method, *arguments, &block)
  187
+    end
94 188
   end
95 189
 end
33  actionpack/test/controller/cookie_test.rb
@@ -2,6 +2,8 @@
@@ -39,6 +41,18 @@ def delete_cookie_with_path
@@ -131,4 +145,21 @@ def test_cookies_persist_throughout_request
3  railties/CHANGELOG
... ...
@@ -1,7 +1,10 @@
1 1
 *Edge*
2 2
 
  3
+* Added config/initializers/cookie_verification_secret.rb with an auto-generated secret for using ActionController::Base#cookies.signed [DHH]
  4
+
3 5
 * Fixed that the debugger wouldn't go into IRB mode because of left-over ARGVs [DHH]
4 6
 
  7
+
5 8
 * 1.9 compatibility
6 9
 
7 10
 *2.3.4 (September 4, 2009)*
7  railties/configs/initializers/cookie_verification_secret.rb
... ...
@@ -0,0 +1,7 @@
3  railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -193,6 +193,9 @@ def create_initializer_files(m)
193 193
 
194 194
       m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb", 
195 195
         :assigns => { :app_name => @app_name, :app_secret => ActiveSupport::SecureRandom.hex(64) }
  196
+
  197
+      m.template "configs/initializers/cookie_verification_secret.rb", 
  198
+        :assigns => { :app_secret => ActiveSupport::SecureRandom.hex(64) }
196 199
     end
197 200
 
198 201
     def create_locale_file(m)

0 notes on commit 0200e20

Please sign in to comment.
Something went wrong with that request. Please try again.