ktheory-json_cookie_store 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.rdoc +3 -0
  2. data/lib/json_cookie_store.rb +175 -0
  3. metadata +63 -0
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = json_cookie_store
2
+
3
+ A Rails session cookie store that uses JSON to store data rather than Marshaled ruby.
@@ -0,0 +1,175 @@
1
+ require 'cgi'
2
+ require 'cgi/session'
3
+ require 'openssl' # to generate the HMAC message digest
4
+ # Requiring JSON here causese complications between Rails' json methods
5
+ # and the JSON gem. Not requiring 'json' works. IDK why. --Aaron
6
+ #require 'json'
7
+
8
+ # This cookie-based session store is the Rails default. Sessions typically
9
+ # contain at most a user_id and flash message; both fit within the 4K cookie
10
+ # size limit. Cookie-based sessions are dramatically faster than the
11
+ # alternatives.
12
+ #
13
+ # If you have more than 4K of session data or don't want your data to be
14
+ # visible to the user, pick another session store.
15
+ #
16
+ # CookieOverflow is raised if you attempt to store more than 4K of data.
17
+ # TamperedWithCookie is raised if the data integrity check fails.
18
+ #
19
+ # A message digest is included with the cookie to ensure data integrity:
20
+ # a user cannot alter his +user_id+ without knowing the secret key included in
21
+ # the hash. New apps are generated with a pregenerated secret in
22
+ # config/environment.rb. Set your own for old apps you're upgrading.
23
+ #
24
+ # Session options:
25
+ #
26
+ # * <tt>:secret</tt>: An application-wide key string or block returning a string
27
+ # called per generated digest. The block is called with the CGI::Session
28
+ # instance as an argument. It's important that the secret is not vulnerable to
29
+ # a dictionary attack. Therefore, you should choose a secret consisting of
30
+ # random numbers and letters and more than 30 characters. Examples:
31
+ #
32
+ # :secret => '449fe2e7daee471bffae2fd8dc02313d'
33
+ # :secret => Proc.new { User.current_user.secret_key }
34
+ #
35
+ # * <tt>:digest</tt>: The message digest algorithm used to verify session
36
+ # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
37
+ # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
38
+ #
39
+ # To generate a secret key for an existing application, run
40
+ # "rake secret" and set the key in config/environment.rb.
41
+ #
42
+ # Note that changing digest or secret invalidates all existing sessions!
43
+ class CGI::Session::JsonCookieStore
44
+ # Cookies can typically store 4096 bytes.
45
+ MAX = 4096
46
+ SECRET_MIN_LENGTH = 30 # characters
47
+
48
+ # Raised when storing more than 4K of session data.
49
+ class CookieOverflow < StandardError; end
50
+
51
+ # Raised when the cookie fails its integrity check.
52
+ class TamperedWithCookie < StandardError; end
53
+
54
+ # Called from CGI::Session only.
55
+ def initialize(session, options = {})
56
+ # The session_key option is required.
57
+ if options['session_key'].blank?
58
+ raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
59
+ end
60
+
61
+ # The secret option is required.
62
+ ensure_secret_secure(options['secret'])
63
+
64
+ # Keep the session and its secret on hand so we can read and write cookies.
65
+ @session, @secret = session, options['secret']
66
+
67
+ # Message digest defaults to SHA1.
68
+ @digest = options['digest'] || 'SHA1'
69
+
70
+ # Default cookie options derived from session settings.
71
+ @cookie_options = {
72
+ 'name' => options['session_key'],
73
+ 'path' => options['session_path'],
74
+ 'domain' => options['session_domain'],
75
+ 'expires' => options['session_expires'],
76
+ 'secure' => options['session_secure'],
77
+ 'http_only' => options['session_http_only']
78
+ }
79
+
80
+ # Set no_hidden and no_cookies since the session id is unused and we
81
+ # set our own data cookie.
82
+ options['no_hidden'] = true
83
+ options['no_cookies'] = true
84
+ end
85
+
86
+ # To prevent users from using something insecure like "Password" we make sure that the
87
+ # secret they've provided is at least 30 characters in length.
88
+ def ensure_secret_secure(secret)
89
+ # There's no way we can do this check if they've provided a proc for the
90
+ # secret.
91
+ return true if secret.is_a?(Proc)
92
+
93
+ if secret.blank?
94
+ raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
95
+ end
96
+
97
+ if secret.length < SECRET_MIN_LENGTH
98
+ raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
99
+ end
100
+ end
101
+
102
+ # Restore session data from the cookie.
103
+ def restore
104
+ @original = read_cookie
105
+ @data = unmarshal(@original) || {}
106
+ end
107
+
108
+ # Wait until close to write the session data cookie.
109
+ def update; end
110
+
111
+ # Write the session data cookie if it was loaded and has changed.
112
+ def close
113
+ if defined?(@data) && !@data.blank?
114
+ updated = marshal(@data)
115
+ raise CookieOverflow if updated.size > MAX
116
+ write_cookie('value' => updated) unless updated == @original
117
+ end
118
+ end
119
+
120
+ # Delete the session data by setting an expired cookie with no data.
121
+ def delete
122
+ @data = nil
123
+ clear_old_cookie_value
124
+ write_cookie('value' => nil, 'expires' => 1.year.ago)
125
+ end
126
+
127
+ # Generate the HMAC keyed message digest. Uses SHA1 by default.
128
+ def generate_digest(data)
129
+ key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
130
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
131
+ end
132
+
133
+ private
134
+ # Marshal a session hash into safe cookie data. Include an integrity hash.
135
+ def marshal(session)
136
+ data = ActiveSupport::Base64.encode64s(::JSON.dump(session))
137
+ "#{data}--#{generate_digest(data)}"
138
+ end
139
+
140
+ # Unmarshal cookie data to a hash and verify its integrity.
141
+ def unmarshal(cookie)
142
+ if cookie
143
+ data, digest = cookie.split('--')
144
+
145
+ # Do two checks to transparently support old double-escaped data.
146
+ unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
147
+ delete
148
+ raise TamperedWithCookie
149
+ end
150
+
151
+ res = ::JSON.load(ActiveSupport::Base64.decode64(data))
152
+ # Yay for FlashHash hacks!
153
+ # Convert res['flash'] to a FlashHash
154
+ # Note this breaks Flash functionality
155
+ res['flash'] = ActionController::Flash::FlashHash.new
156
+ res
157
+ end
158
+ end
159
+
160
+ # Read the session data cookie.
161
+ def read_cookie
162
+ @session.cgi.cookies[@cookie_options['name']].first
163
+ end
164
+
165
+ # CGI likes to make you hack.
166
+ def write_cookie(options)
167
+ cookie = CGI::Cookie.new(@cookie_options.merge(options))
168
+ @session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
169
+ end
170
+
171
+ # Clear cookie value so subsequent new_session doesn't reload old data.
172
+ def clear_old_cookie_value
173
+ @session.cgi.cookies[@cookie_options['name']].clear
174
+ end
175
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ktheory-json_cookie_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Suggs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-12 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.2.2
23
+ version:
24
+ description: Like Rails' CookieStore for sessions, but uses JSON format rather than Marshaled ruby objects
25
+ email: aaronktheory.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README.rdoc
32
+ files:
33
+ - lib/json_cookie_store.rb
34
+ - README.rdoc
35
+ has_rdoc: true
36
+ homepage: http://github.com/ktheory/json_cookie_store
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --main
40
+ - README.rdoc
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.2.0
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: JSON cookie store for Rails' sessions
62
+ test_files: []
63
+