From b23ffd0dac895aa3fd3afd8d9be36794941731b2 Mon Sep 17 00:00:00 2001
From: Lukasz Sarnacki <lukesarnacki@gmail.com>
Date: Fri, 10 Jan 2014 12:57:50 +0100
Subject: Allow session serializer key in config.session_store

MessageEncryptor has :serializer option, where any serializer object can
be passed. This commit make it possible to set this serializer from configuration
level.

There are predefined serializers (:marshal_serializer, :json_serialzier)
and custom serializer can be passed as String, Symbol (camelized and
constantized in ActionDispatch::Session namepspace) or serializer object.

Default :json_serializer was also added to generators to provide secure
defalt.
---
 actionpack/CHANGELOG.md                            | 14 +++++++++
 actionpack/lib/action_dispatch.rb                  | 10 ++++---
 .../lib/action_dispatch/middleware/cookies.rb      | 16 +++++++++--
 .../middleware/session/json_serializer.rb          | 13 +++++++++
 .../middleware/session/marshal_serializer.rb       | 14 +++++++++
 actionpack/test/dispatch/cookies_test.rb           | 33 ++++++++++++++++++++++
 6 files changed, 94 insertions(+), 6 deletions(-)
 create mode 100644 actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
 create mode 100644 actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb

(limited to 'actionpack')

diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index fc05ae3cec..60b0195510 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -49,6 +49,20 @@
 
     *Alessandro Diaferia*
 
+*   Add `:serializer` option for `config.session_store :cookie_store`. This
+    changes default serializer when using `:cookie_store` to
+    `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal.
+
+    It is also possible to pass:
+
+    * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and
+      `JSON.generate` methods with quirks mode;
+    * any other Symbol or String like `:my_custom_serializer` which will be
+      camelized and constantized in `ActionDispatch::Session` namespace;
+    * serializer object with `load` and `dump` methods defined.
+
+    *Ɓukasz Sarnacki*
+
 *   Allow an absolute controller path inside a module scope. Fixes #12777.
 
     Example:
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 920e651b08..36dcca2905 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -82,10 +82,12 @@ module ActionDispatch
   end
 
   module Session
-    autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
-    autoload :CookieStore,   'action_dispatch/middleware/session/cookie_store'
-    autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
-    autoload :CacheStore,    'action_dispatch/middleware/session/cache_store'
+    autoload :AbstractStore,     'action_dispatch/middleware/session/abstract_store'
+    autoload :CookieStore,       'action_dispatch/middleware/session/cookie_store'
+    autoload :MemCacheStore,     'action_dispatch/middleware/session/mem_cache_store'
+    autoload :CacheStore,        'action_dispatch/middleware/session/cache_store'
+    autoload :JsonSerializer,    'action_dispatch/middleware/session/json_serializer'
+    autoload :MarshalSerializer, 'action_dispatch/middleware/session/marshal_serializer'
   end
 
   mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index fe110d7938..f9f034952e 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -89,6 +89,7 @@ module ActionDispatch
     ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
     SECRET_TOKEN = "action_dispatch.secret_token".freeze
     SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
+    SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze
 
     # Cookies can typically store 4096 bytes.
     MAX_COOKIE_SIZE = 4096
@@ -210,7 +211,8 @@ module ActionDispatch
           encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
           secret_token: env[SECRET_TOKEN],
           secret_key_base: env[SECRET_KEY_BASE],
-          upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+          upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
+          session_serializer: env[SESSION_SERIALIZER]
         }
       end
 
@@ -435,7 +437,7 @@ module ActionDispatch
         @options = options
         secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
         sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
-        @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+        @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: serializer)
       end
 
       def [](name)
@@ -462,6 +464,16 @@ module ActionDispatch
         rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
           nil
         end
+
+        def serializer
+          serializer = @options[:session_serializer] || :marshal_serializer
+          case serializer
+          when Symbol, String
+            ActionDispatch::Session.const_get(serializer.to_s.camelize)
+          else
+            serializer
+          end
+        end
     end
 
     # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
diff --git a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
new file mode 100644
index 0000000000..d341853f7a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb
@@ -0,0 +1,13 @@
+module ActionDispatch
+  module Session
+    class JsonSerializer
+      def self.load(value)
+        JSON.parse(value, quirks_mode: true)
+      end
+
+      def self.dump(value)
+        JSON.generate(value, quirks_mode: true)
+      end
+    end
+  end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
new file mode 100644
index 0000000000..26622f682d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb
@@ -0,0 +1,14 @@
+module ActionDispatch
+  module Session
+    class MarshalSerializer
+      def self.load(value)
+        Marshal.load(value)
+      end
+
+      def self.dump(value)
+        Marshal.dump(value)
+      end
+    end
+  end
+end
+
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 91ac13e7c6..b19ce905f5 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -379,6 +379,39 @@ class CookiesTest < ActionController::TestCase
     assert_equal 'bar', cookies.encrypted[:foo]
   end
 
+  class ActionDispatch::Session::CustomJsonSerializer
+    def self.load(value)
+      JSON.load(value) + " and loaded"
+    end
+
+    def self.dump(value)
+      JSON.dump(value + " was dumped")
+    end
+  end
+
+  def test_encrypted_cookie_using_custom_json_serializer
+    @request.env["action_dispatch.session_serializer"] = :custom_json_serializer
+    get :set_encrypted_cookie
+    assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+  end
+
+  def test_encrypted_cookie_using_serializer_object
+    @request.env["action_dispatch.session_serializer"] = ActionDispatch::Session::CustomJsonSerializer
+    get :set_encrypted_cookie
+    assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+  end
+
+  def test_encrypted_cookie_using_json_serializer
+    @request.env["action_dispatch.session_serializer"] = :json_serializer
+    get :set_encrypted_cookie
+    cookies = @controller.send :cookies
+    assert_not_equal 'bar', cookies[:foo]
+    assert_raises TypeError do
+      cookies.signed[:foo]
+    end
+    assert_equal 'bar', cookies.encrypted[:foo]
+  end
+
   def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
     get :set_encrypted_cookie
     assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
-- 
cgit v1.2.3