safe_cookies 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTlmZjRlYWYyZDJmZjZmOTZhNDcyNGFhZDMyODE0Nzk3YTRmN2I3ZA==
5
+ data.tar.gz: !binary |-
6
+ MGY4OGY1Mjk1OGNiNDViZDA3Yjk2NTdkM2UzMGI0ZWUyMTZkYzQ4MA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ M2FmYjk3YjYxYTA1MWQ0MmIzYzMzMDcwNzE5NGMzYTc2MGM0ZmZiNTBjMmI5
10
+ MzU5MzEwNjE0OTY4M2Q2ZWEzYjQxZGRiYmVlMTE2YmY5YjZjYTYwM2EyMTcz
11
+ ZDBhNTRiMTE4N2ExYWIzNjhhMDIyOWQzMmZhOWRjYjkyNjQ5MjQ=
12
+ data.tar.gz: !binary |-
13
+ MGVkY2ZhM2UzNmYyMDhlYjBiZGE4Y2Q3MjM3MzU3MTFmZTJmMzg2YTJkYTY4
14
+ MjBkYWZjYzk1MjRkYzljYWMwNGRkYWE4OTIzMjRhYjc3ZWYxZDY4MjJmZmVm
15
+ NWQyYTk4ZTZhNjA1YWY0YjFiMmI3NWMxZjI2MTEyYmI3YzM3MjI=
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # SafeCookies
2
2
 
3
- This Gem brings a Middleware that will make all cookies secure. In detail, it will
3
+ This Gem brings a middleware that will make all cookies secure. In detail, it will:
4
4
 
5
- * set all new cookies 'HttpOnly', unless specified otherwise
6
- * set all new cookies 'secure', if the request came via HTTPS and not specified otherwise
7
- * rewrite existing cookies, setting both flags as above
5
+ * set all new application cookies 'HttpOnly', unless specified otherwise
6
+ * set all new application cookies 'secure', if the request came via HTTPS and not specified otherwise
7
+ * rewrite request cookies, setting both flags as above
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,7 +12,7 @@ Add this line to your application's Gemfile:
12
12
 
13
13
  gem 'safe_cookies'
14
14
 
15
- And then execute:
15
+ Then run:
16
16
 
17
17
  $ bundle
18
18
 
@@ -20,25 +20,70 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install safe_cookies
22
22
 
23
+
23
24
  ## Usage
24
25
 
25
- In config/environment.rb:
26
+ ### Step 1
27
+ **Rails 3**: add the following line in config/application.rb:
28
+
29
+ class Application < Rails::Application
30
+ # ...
31
+ config.middleware.insert_before ActionDispatch::Cookies, SafeCookies::Middleware
32
+ end
33
+
34
+ **Rails 2:** add the following lines in config/environment.rb:
26
35
 
27
- config.middleware.use SafeCookies::Middleware,
28
- :remember_token => 1.year,
29
- :last_action => 30.days,
30
- :non_secure => %w[default_language],
31
- :non_http_only => %w[javascript_data]
36
+ Rails::Initializer.run do |config|
37
+ # ...
38
+ require 'safe_cookies'
39
+ config.middleware.insert_before ActionController::Session::CookieStore, SafeCookies::Middleware
40
+ end
41
+
42
+ ### Step 2
43
+ Register cookies, either just after the lines you added in step 1 or in in an initializer
44
+ (e.g. config/initializers/safe_cookies.rb):
45
+
46
+ SafeCookies.configure do |config|
47
+ config.register_cookie :remember_token, :expire_after => 1.year
48
+ config.register_cookie :last_action, :expire_after => 30.days
49
+ config.register_cookie :default_language, :expire_after => 10.years, :secure => false
50
+ config.register_cookie :javascript_data, :expire_after => 1.day, :http_only => false
51
+ end
32
52
 
33
53
  This will have the `default_language` cookie not made secure, the `javascript_data` cookie
34
- not made HttpOnly. It will update the `remember_token` with an expiry of one year and the
35
- `last_action` cookie with an expiry of 30 days, making both of them secure and HttpOnly.
54
+ not made http-only. It will rewrite the `remember_token` with an expiry of one year and the
55
+ `last_action` cookie with an expiry of 30 days, making both of them secure and http-only.
56
+ Available options are: `:expire_after (required), :path, :secure, :http_only`.
57
+
58
+ ### Step 3
59
+ Override `SafeCookies::Middleware#handle_unknown_cookies(cookies)` (see "Dealing with unregistered cookies" below).
60
+
61
+
62
+ ## Dealing with unregistered cookies
63
+
64
+ The middleware is not able to secure cookies without knowing their properties (most important: their
65
+ expiry). Unfortunately, the [client won't ever tell us](http://tools.ietf.org/html/rfc6265#section-4.2.2)
66
+ if the cookie was originally sent with flags such as "secure" or which expiry date it currently has.
67
+ Therefore, it is important to register all cookies that users may come with, specifying their properties.
68
+ Unregistered cookies cannot be secured.
69
+
70
+ If a request brings a cookie that is not registered, the middleware will raise
71
+ `SafeCookies::UnknownCookieError`. Rails 3+ should handle the exception as any other in your application,
72
+ but by default, **you will not be notified from Rails 2 applications** and the user will see a standard
73
+ 500 Server Error. Override `SafeCookies::Middleware#handle_unknown_cookies(cookies)` in the config
74
+ initializer for customized exception handling (like, notifying you per email).
75
+
76
+ You should not ignore an unregistered cookie, but instead register it.
36
77
 
37
- ## About Rails and Cookies
38
78
 
39
- Cookie syntax example:
79
+ ## Fix cookie paths
40
80
 
41
- Set-Cookie: cookie1=value; secure,cookie2=value; path=/
81
+ In August 2013 we noticed a bug in SafeCookies < 0.1.4, by which secured cookies would be set for the
82
+ current "directory" (see comments in `cookie_path_fix.rb`) instead of root (which usually is what you want).
83
+ Users would get multiple cookies for that domain, leading to issues like being unable to sign in.
42
84
 
43
- Actually, there should be one cookie per Set-Cookie header, but since Rails headers
44
- are implemented as Hash, it is not possible to have several Set-Cookie fields.
85
+ The configuration option `config.fix_paths` turns on fixing this error. It requires an option
86
+ `:for_cookies_secured_before => Time.parse('some minutes after you will have deployed')` which reflects the
87
+ point of time from which cookies will be secured with the correct path. The middleware will fix the cookie
88
+ paths by rewriting all cookies that it has already secured, but only if the were secured before the time
89
+ you specified.
@@ -0,0 +1,69 @@
1
+ module SafeCookies
2
+
3
+ MissingOptionError = Class.new(StandardError)
4
+
5
+ class << self
6
+ attr_accessor :configuration
7
+
8
+ def configure
9
+ self.configuration ||= Configuration.new
10
+ yield(configuration)
11
+ end
12
+
13
+ end
14
+
15
+ class Configuration
16
+ attr_reader :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp
17
+
18
+ def initialize
19
+ self.registered_cookies = {}
20
+ self.insecure_cookies = []
21
+ self.scriptable_cookies = []
22
+ end
23
+
24
+ # Register cookies you expect to receive. The middleware will rewrite all
25
+ # registered cookies it receives, making them both secure and http_only.
26
+ #
27
+ # Unfortunately, the client won't ever tell us if the cookie was originally
28
+ # sent with flags such as "secure" or which expiry date it currently has:
29
+ # http://tools.ietf.org/html/rfc6265#section-4.2.2
30
+ #
31
+ # Therefore, specify an expiry, and more options if needed:
32
+ #
33
+ # :expire_after => 1.year
34
+ # :secure => false
35
+ # :http_only = false
36
+ # :path => '/foo/path'
37
+ #
38
+ def register_cookie(name, options)
39
+ options.has_key?(:expire_after) or raise MissingOptionError.new("Cookie #{name.inspect} was registered without an expiry")
40
+ raise NotImplementedError if options.has_key?(:domain)
41
+
42
+ registered_cookies[name.to_s] = (options || {}).freeze
43
+ insecure_cookies << name if options[:secure] == false
44
+ scriptable_cookies << name if options[:http_only] == false
45
+ end
46
+
47
+ def fix_paths(options = {})
48
+ options.has_key?(:for_cookies_secured_before) or raise MissingOptionError.new("Was told to fix paths without the :for_cookies_secured_before timestamp.")
49
+
50
+ self.fix_cookie_paths = true
51
+ self.correct_cookie_paths_timestamp = options[:for_cookies_secured_before]
52
+ end
53
+
54
+ def insecure_cookie?(name)
55
+ insecure_cookies.include? name
56
+ end
57
+
58
+ def scriptable_cookie?(name)
59
+ scriptable_cookies.include? name
60
+ end
61
+
62
+ private
63
+
64
+ attr_accessor :insecure_cookies, :scriptable_cookies
65
+ attr_writer :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,64 @@
1
+ module SafeCookies
2
+ module CookiePathFix
3
+
4
+ # Previously, the SafeCookies gem would not set a path when rewriting
5
+ # cookies. Browsers then would assume and store the current "directory",
6
+ # leading to multiple cookies per domain.
7
+ #
8
+ # If cookies had been secured before the configured datetime, the method
9
+ # `fix_cookie_paths` deletes all cookies coming with the request, and the
10
+ # SECURED_COOKIE_NAME helper cookie.
11
+ # The middleware still sees the request cookies and will rewrite them as
12
+ # if it hadn't seen them before.
13
+
14
+ def fix_cookie_paths
15
+ registered_cookies_in_request.keys.each do |registered_cookie|
16
+ delete_cookie_for_current_directory(registered_cookie)
17
+ end
18
+ delete_cookie_for_current_directory(SafeCookies::SECURED_COOKIE_NAME)
19
+
20
+ # Delete this cookie here, so the middleware will secure all cookies anew.
21
+ @request.cookies.delete(SafeCookies::SECURED_COOKIE_NAME)
22
+ end
23
+
24
+ private
25
+
26
+ def fix_cookie_paths?
27
+ @configuration.fix_cookie_paths or return false
28
+
29
+ cookies_need_path_fix = (secured_old_cookies_timestamp < @configuration.correct_cookie_paths_timestamp)
30
+
31
+ cookies_have_been_rewritten_before and cookies_need_path_fix
32
+ end
33
+
34
+ # Delete cookies by giving them an expiry in the past,
35
+ # cf. https://tools.ietf.org/html/rfc6265#section-4.1.2.
36
+ #
37
+ # Most important, as specified in
38
+ # https://tools.ietf.org/html/rfc6265#section-4.1.2.4 and in section 5.1.4,
39
+ # cookies set without a path will be set for the current "directory", that is:
40
+ #
41
+ # > ... the characters of the uri-path from the first character up
42
+ # > to, but not including, the right-most %x2F ("/").
43
+ #
44
+ # However, Firefox includes the right-most slash when guessing the cookie path,
45
+ # so we must resort to letting browsers estimate the deletion cookie path again.
46
+ def delete_cookie_for_current_directory(cookie_name)
47
+ current_directory_is_not_root = @request.path[%r(^/[^/]+/[^\?]+), 0]
48
+
49
+ if current_directory_is_not_root
50
+ one_week = (7 * 24 * 60 * 60)
51
+ set_cookie!(cookie_name, "", :path => nil, :expire_after => -one_week)
52
+ end
53
+ end
54
+
55
+ def secured_old_cookies_timestamp
56
+ Time.rfc2822(@request.cookies[SafeCookies::SECURED_COOKIE_NAME])
57
+ rescue ArgumentError
58
+ # If we cannot parse the secured_old_cookies time,
59
+ # assume it was before we noticed the bug to ensure
60
+ # broken cookie paths will be fixed.
61
+ Time.parse "2013-08-25 0:00"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+ module SafeCookies
2
+ class Util
3
+ class << self
4
+
5
+ def slice(hash, *allowed_keys)
6
+ sliced_hash = hash.select { |key, value|
7
+ allowed_keys.include? key
8
+ }
9
+
10
+ # Normalize the result of Hash#select
11
+ # (Ruby 1.8 returns an Array, Ruby 1.9 returns a Hash)
12
+ Hash[sliced_hash]
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module SafeCookies
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
data/lib/safe_cookies.rb CHANGED
@@ -1,49 +1,55 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ require "safe_cookies/configuration"
3
+ require "safe_cookies/cookie_path_fix"
4
+ require "safe_cookies/util"
2
5
  require "safe_cookies/version"
3
6
  require "rack"
4
7
 
5
8
  module SafeCookies
6
- class Middleware
7
9
 
10
+ UnknownCookieError = Class.new(StandardError)
11
+
12
+ CACHE_COOKIE_NAME = '_safe_cookies__known_cookies'
13
+ SECURED_COOKIE_NAME = 'secured_old_cookies'
14
+ HELPER_COOKIES_LIFETIME = 10 * 365 * 24 * 60 * 60 # 10 years
8
15
 
9
- def initialize(app, options = {})
10
- # Pass a hash for `cookies_to_update` with name as key and lifetime as value.
11
- # Use this to update existing cookies that you expect to receive.
12
- #
13
- # Unfortunately, the client won't ever tell us if the cookie was originally
14
- # sent with flags such as "secure" or which expiry date it currently has:
15
- # http://tools.ietf.org/html/rfc6265#section-4.2.2
16
- #
17
- # The :non_secure option specifies cookies that will not be made secure. Use
18
- # this for storing site usage settings like filters etc. that need to be available
19
- # when not on HTTPS (this should rarely be the case).
20
- #
21
- # The :non_http_only option is analog, use it for storing data you want to access
22
- # with javascript.
16
+ class Middleware
17
+
18
+ include CookiePathFix
19
+
20
+ KNOWN_COOKIES_DIVIDER = '|'
23
21
 
24
- options = options.dup
25
22
 
23
+ def initialize(app)
26
24
  @app = app
27
- @non_secure = (options.delete(:non_secure) || []).map(&:to_s)
28
- @non_http_only = (options.delete(:non_http_only) || []).map(&:to_s)
29
- @cookies_to_update = options
25
+ @configuration = SafeCookies.configuration or raise "Don't know what to do without configuration"
30
26
  end
31
27
 
32
28
  def call(env)
33
- @env = env
34
- status, headers, body = @app.call(@env)
29
+ reset_instance_variables
30
+
31
+ @request = Rack::Request.new(env)
32
+ ensure_no_unknown_cookies!
33
+
34
+ status, @headers, body = @app.call(env)
35
35
 
36
- secure_old_cookies!(headers) if @cookies_to_update.any?
37
- secure_new_cookies!(headers)
36
+ fix_cookie_paths if fix_cookie_paths?
37
+ rewrite_request_cookies unless cookies_have_been_rewritten_before
38
+ cache_application_cookies
39
+ rewrite_application_cookies
38
40
 
39
- [ status, headers, body ]
41
+ [ status, @headers, body ]
40
42
  end
41
43
 
42
44
  private
45
+
46
+ def reset_instance_variables
47
+ @request, @headers = nil
48
+ end
43
49
 
44
50
  def secure(cookie)
45
51
  # Regexp from https://github.com/tobmatth/rack-ssl-enforcer/
46
- if secure?(cookie) and cookie !~ /(^|;\s)secure($|;)/
52
+ if should_be_secure?(cookie) and cookie !~ /(^|;\s)secure($|;)/
47
53
  "#{cookie}; secure"
48
54
  else
49
55
  cookie
@@ -51,40 +57,46 @@ module SafeCookies
51
57
  end
52
58
 
53
59
  def http_only(cookie)
54
- if http_only?(cookie) and cookie !~ /(^|;\s)HttpOnly($|;)/
60
+ if should_be_http_only?(cookie) and cookie !~ /(^|;\s)HttpOnly($|;)/
55
61
  "#{cookie}; HttpOnly"
56
62
  else
57
63
  cookie
58
64
  end
59
65
  end
60
-
61
- def secure_old_cookies!(headers)
62
- request = Rack::Request.new(@env)
63
- return if request.cookies['secured_old_cookies']
64
-
65
- @cookies_to_update.each do |key, expiry|
66
- key = key.to_s
67
- if request.cookies.has_key?(key)
68
- value = request.cookies[key]
69
- set_secure_cookie!(headers, key, value, expiry)
66
+
67
+ # This method takes all cookies sent with the request and rewrites them,
68
+ # making them both secure and http-only (unless specified otherwise in
69
+ # the configuration).
70
+ # With the SECURED_COOKIE_NAME cookie we remember the exact time that we
71
+ # rewrote the cookies.
72
+ def rewrite_request_cookies
73
+ if @request.cookies.any?
74
+ registered_cookies_in_request.each do |registered_cookie, options|
75
+ value = @request.cookies[registered_cookie]
76
+
77
+ set_cookie!(registered_cookie, value, options)
70
78
  end
79
+
80
+ formatted_now = Rack::Utils.rfc2822(Time.now.gmtime)
81
+ set_cookie!(SECURED_COOKIE_NAME, formatted_now, :expire_after => HELPER_COOKIES_LIFETIME)
71
82
  end
72
- set_secure_cookie!(headers, 'secured_old_cookies', Rack::Utils.rfc2822(Time.now.gmtime))
73
83
  end
74
84
 
75
- def set_secure_cookie!(headers, key, value, expire_after = 365 * 24 * 60 * 60) # one year
76
- options = {
77
- :path => '/',
78
- :value => value,
79
- :secure => secure?(key),
80
- :httponly => http_only?(key),
81
- :expires => Time.now + expire_after # This is what Rails does
82
- }
83
- Rack::Utils.set_cookie_header!(headers, key, options)
85
+ def set_cookie!(name, value, options)
86
+ options = options.dup
87
+ expire_after = options.delete(:expire_after)
88
+
89
+ options[:expires] = Time.now + expire_after if expire_after
90
+ options[:path] = '/' unless options.has_key?(:path) # allow setting path = nil
91
+ options[:value] = value
92
+ options[:secure] = should_be_secure?(name)
93
+ options[:httponly] = should_be_http_only?(name)
94
+
95
+ Rack::Utils.set_cookie_header!(@headers, name, options)
84
96
  end
85
97
 
86
- def secure_new_cookies!(headers)
87
- cookies = headers['Set-Cookie']
98
+ def rewrite_application_cookies
99
+ cookies = @headers['Set-Cookie']
88
100
  if cookies
89
101
  # Rails 2.3 / Rack 1.1 offers an array which is actually nice.
90
102
  cookies = cookies.split("\n") unless cookies.is_a?(Array)
@@ -103,22 +115,72 @@ module SafeCookies
103
115
  # It contains more information than the "HTTP_COOKIE" header from the
104
116
  # browser's request contained, so a `Rack::Request` can't parse it for
105
117
  # us. A `Rack::Response` doesn't offer a way either.
106
- headers['Set-Cookie'] = cookies.join("\n")
118
+ @headers['Set-Cookie'] = cookies.join("\n")
119
+ end
120
+ end
121
+
122
+ def should_be_secure?(cookie)
123
+ cookie_name = cookie.split('=').first.strip
124
+ ssl? and not @configuration.insecure_cookie?(cookie_name)
125
+ end
126
+
127
+ def ssl?
128
+ if @request.respond_to?(:ssl?)
129
+ @request.ssl?
130
+ else
131
+ # older Rack versions
132
+ @request.scheme == 'https'
133
+ end
134
+ end
135
+
136
+ def should_be_http_only?(cookie)
137
+ cookie_name = cookie.split('=').first.strip
138
+ not @configuration.scriptable_cookie?(cookie_name)
139
+ end
140
+
141
+ def ensure_no_unknown_cookies!
142
+ request_cookies = @request.cookies.keys.map(&:to_s)
143
+ unknown_cookies = request_cookies - known_cookies
144
+
145
+ if unknown_cookies.any?
146
+ handle_unknown_cookies(unknown_cookies)
147
+ end
148
+ end
149
+
150
+ def handle_unknown_cookies(cookies)
151
+ raise SafeCookies::UnknownCookieError.new("Request for '#{@request.url}' had unknown cookies: #{cookies.join(', ')}")
152
+ end
153
+
154
+ def cache_application_cookies
155
+ new_application_cookies = @headers['Set-Cookie']
156
+
157
+ if new_application_cookies
158
+ new_application_cookies = new_application_cookies.join("\n") if new_application_cookies.is_a?(Array)
159
+ application_cookies = cached_application_cookies + new_application_cookies.scan(/(?=^|\n)[^\n;,=]+/i)
160
+ application_cookies_string = application_cookies.uniq.join(KNOWN_COOKIES_DIVIDER)
161
+
162
+ set_cookie!(CACHE_COOKIE_NAME, application_cookies_string, :expire_after => HELPER_COOKIES_LIFETIME)
107
163
  end
108
164
  end
109
165
 
110
- def secure?(cookie)
111
- name = cookie.split('=').first.strip
112
- ssl_request? and not @non_secure.include?(name)
166
+ def cached_application_cookies
167
+ cache_cookie = @request.cookies[CACHE_COOKIE_NAME] || ""
168
+ cache_cookie.split(KNOWN_COOKIES_DIVIDER)
169
+ end
170
+
171
+ def known_cookies
172
+ known = [CACHE_COOKIE_NAME, SECURED_COOKIE_NAME]
173
+ known += cached_application_cookies
174
+ known += @configuration.registered_cookies.keys
113
175
  end
114
176
 
115
- def http_only?(cookie)
116
- name = cookie.split('=').first.strip
117
- not @non_http_only.include?(name)
177
+ def cookies_have_been_rewritten_before
178
+ @request.cookies.has_key? SECURED_COOKIE_NAME
118
179
  end
119
180
 
120
- def ssl_request?
121
- @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' # this is how Rails does it
181
+ # returns those of the registered cookies that appear in the request
182
+ def registered_cookies_in_request
183
+ Util.slice(@configuration.registered_cookies, *@request.cookies.keys)
122
184
  end
123
185
 
124
186
  end
data/safe_cookies.gemspec CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/safe_cookies/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Dominik Schöler"]
6
6
  gem.email = ["dominik.schoeler@makandra.de"]
7
- gem.description = %q{Make cookies as `secure` and `HttpOnly` as possible.}
8
- gem.summary = %q{Make cookies as `secure` and `HttpOnly` as possible.}
7
+ gem.description = %q{Make all cookies `secure` and `HttpOnly`.}
8
+ gem.summary = %q{Make all cookies `secure` and `HttpOnly`.}
9
9
  gem.homepage = "http://www.makandra.de"
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe SafeCookies::Middleware do
5
+
6
+ it 'does not allow registered cookies to be altered' do
7
+ SafeCookies.configure do |config|
8
+ config.register_cookie('filter', :expire_after => 3600)
9
+ end
10
+
11
+ filter_options = SafeCookies.configuration.registered_cookies['filter']
12
+ expect { filter_options[:foo] = 'bar' }.to raise_error(Exception, /can't modify frozen hash/i)
13
+ end
14
+
15
+ describe '.configure' do
16
+
17
+ it 'currently does not support the :domain cookie option' do
18
+ registration_with_domain = lambda do
19
+ SafeCookies.configure do |config|
20
+ config.register_cookie('filter', :domain => 'example.com', :expire_after => 3600)
21
+ end
22
+ end
23
+
24
+ expect(&registration_with_domain).to raise_error(NotImplementedError)
25
+ end
26
+
27
+ describe 'register_cookie' do
28
+
29
+ context 'cookie name formatting' do
30
+
31
+ let(:set_cookie) do
32
+ # These tests for the Configuration module require an integration with
33
+ # the middleware itself. Therefore, we need to actually use it.
34
+
35
+ app = stub('app')
36
+ env = { 'HTTPS' => 'on' }
37
+ stub_app_call(app, :application_cookies => 'cookie_name=value')
38
+
39
+ middleware = described_class.new(app)
40
+ code, headers, response = middleware.call(env)
41
+ headers['Set-Cookie']
42
+ end
43
+
44
+ it 'understands cookies registered as symbol' do
45
+ SafeCookies.configure do |config|
46
+ config.register_cookie(:cookie_name, :expire_after => nil)
47
+ end
48
+
49
+ set_cookie.should =~ /cookie_name=value;.* secure; HttpOnly/
50
+ end
51
+
52
+ it 'understands cookies registered as string' do
53
+ SafeCookies.configure do |config|
54
+ config.register_cookie('cookie_name', :expire_after => nil)
55
+ end
56
+
57
+ set_cookie.should =~ /cookie_name=value;.* secure; HttpOnly/
58
+ end
59
+
60
+ end
61
+
62
+ it 'raises an error if a cookie is registered without passing its expiry' do
63
+ registration_without_expiry = lambda do
64
+ SafeCookies.configure do |config|
65
+ config.register_cookie(:filter, :some => :option)
66
+ end
67
+ end
68
+
69
+ expect(&registration_without_expiry).to raise_error(SafeCookies::MissingOptionError)
70
+ end
71
+
72
+ it 'allows nil as expiry (means session cookie)' do
73
+ registration_with_nil_expiry = lambda do
74
+ SafeCookies.configure do |config|
75
+ config.register_cookie(:filter, :expire_after => nil)
76
+ end
77
+ end
78
+
79
+ expect(&registration_with_nil_expiry).to_not raise_error(SafeCookies::MissingOptionError)
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+ require 'cgi'
3
+
4
+ describe SafeCookies::Middleware do
5
+
6
+ describe 'cookie path fix' do
7
+
8
+ subject { described_class.new(app) }
9
+ let(:app) { stub 'application' }
10
+ let(:env) { { 'HTTPS' => 'on' } }
11
+
12
+ before do
13
+ @now = Time.parse('2050-01-01 00:00')
14
+ Timecop.travel(@now)
15
+ end
16
+
17
+ def set_default_request_cookies(secured_at = Time.parse('2040-01-01 00:00'))
18
+ secured_old_cookies_time = Rack::Utils.rfc2822(secured_at.gmtime)
19
+ set_request_cookies(env, 'cookie_to_update=some_data', "secured_old_cookies=#{CGI::escape(secured_old_cookies_time)}")
20
+ end
21
+
22
+
23
+ context 'rewriting previously secured cookies' do
24
+
25
+ before do
26
+ SafeCookies.configure do |config|
27
+ config.register_cookie('cookie_to_update', :expire_after => 3600)
28
+ config.fix_paths :for_cookies_secured_before => Time.parse('2050-01-02 00:00')
29
+ end
30
+
31
+ stub_app_call(app)
32
+ set_default_request_cookies
33
+ end
34
+
35
+ it 'updates the timestamp on the root secured_old_cookies cookie' do
36
+ code, headers, response = subject.call(env)
37
+
38
+ updated_secured_old_cookies_timestamp = 'Fri%2C+31+Dec+2049+23%3A00%3A00+-0000'
39
+ headers['Set-Cookie'].should =~ /secured_old_cookies=#{Regexp.escape updated_secured_old_cookies_timestamp}; path=\/;/
40
+ end
41
+
42
+ it 'sets the cookie path to "/"' do
43
+ code, headers, response = subject.call(env)
44
+ headers['Set-Cookie'].should =~ /cookie_to_update=some_data;.*path=\/;/
45
+ end
46
+
47
+ it 'deletes cookies for the current "directory"' do
48
+ env['PATH_INFO'] = '/complex/sub/path'
49
+
50
+ code, headers, response = subject.call(env)
51
+ set_cookie = headers['Set-Cookie']
52
+
53
+ # overwrite the cookie with an empty value
54
+ set_cookie.should =~ /cookie_to_update=;/
55
+
56
+ # the deletion cookie must not have a path, so browsers use their own implementation of cookie path
57
+ # determination, the same they used when the cookie was implicitly set on the wrong path
58
+ set_cookie.should_not =~ %r(cookie_to_update=;.*path=)
59
+
60
+ # cookies are deleted by giving them an expiry in the past
61
+ deletion_expiry = set_cookie[/cookie_to_update=;.*expires=([^;]+)/, 1]
62
+ Time.parse(deletion_expiry).should < @now
63
+ end
64
+
65
+ it 'does not delete cookies from root ("/") requests, since root cookies are the default we expect' do
66
+ env['PATH_INFO'] = '/'
67
+
68
+ code, headers, response = subject.call(env)
69
+ headers['Set-Cookie'].should_not =~ /cookie_to_update=;/
70
+ end
71
+
72
+ it 'does not delete cookies from first-level paths like "/first_level", since their "directory" is "/"' do
73
+ env['PATH_INFO'] = '/first_level'
74
+
75
+ code, headers, response = subject.call(env)
76
+ headers['Set-Cookie'].should_not =~ /cookie_to_update=;/
77
+ end
78
+
79
+ it 'does not delete cookies from first-level paths like "/first_level/", since their "directory" is "/"' do
80
+ env['PATH_INFO'] = '/first_level'
81
+
82
+ code, headers, response = subject.call(env)
83
+ headers['Set-Cookie'].should_not =~ /cookie_to_update=;/
84
+ end
85
+
86
+ it 'should not be confused by query parameters' do
87
+ env['PATH_INFO'] = '/some/sub/directory/with?query=params&and=/another/path'
88
+
89
+ code, headers, response = subject.call(env)
90
+ headers['Set-Cookie'].should =~ %r(cookie_to_update=;)
91
+ end
92
+
93
+ it 'should not "fix" a path set by the application' do
94
+ stub_app_call(app, :application_cookies => 'new_cookie=NEW_DATA; path=/special/path')
95
+ env['PATH_INFO'] = '/special/path/sub/folder'
96
+
97
+ code, headers, response = subject.call(env)
98
+ headers['Set-Cookie'].should =~ %r(new_cookie=NEW_DATA;.*path=/special/path;)
99
+ end
100
+
101
+ it 'deletes the secured_old_cookies cookie on the current "directory", so future requests to that directory do not trigger a rewrite of all cookies' do
102
+ env['PATH_INFO'] = '/complex/sub/path'
103
+
104
+ code, headers, response = subject.call(env)
105
+
106
+ # delete the "directory" secured_old_cookies cookie ...
107
+ headers['Set-Cookie'].should =~ %r(secured_old_cookies=;)
108
+ # ... but do not delete the root secured_old_cookies cookie
109
+ headers['Set-Cookie'].should =~ %r(secured_old_cookies=\w+.*path=/;)
110
+ end
111
+
112
+ it 'rewrites cookies even if it cannot parse the secured_old_cookies timestamp' do
113
+ set_request_cookies(env, 'cookie_to_update=some_data', 'secured_old_cookies=rubbish')
114
+
115
+ code, headers, response = subject.call(env)
116
+ headers['Set-Cookie'].should =~ /cookie_to_update=some_data;.*path=\/;/
117
+ end
118
+
119
+ end
120
+
121
+ it 'does not rewrite previously secured cookies if not told so' do
122
+ SafeCookies.configure do |config|
123
+ config.register_cookie('cookie_to_update', :expire_after => 3600)
124
+ # missing config.fix_paths
125
+ end
126
+ stub_app_call(app)
127
+ set_default_request_cookies
128
+
129
+ code, headers, response = subject.call(env)
130
+ headers['Set-Cookie'].should be_nil
131
+ end
132
+
133
+ it 'raises an error if told to fix cookie paths without specifying a date' do
134
+ fix_paths_without_timestamp = lambda do
135
+ SafeCookies.configure do |config|
136
+ config.fix_paths # missing :for_cookies_secured_before option
137
+ end
138
+ end
139
+
140
+ expect(&fix_paths_without_timestamp).to raise_error(SafeCookies::MissingOptionError)
141
+ end
142
+
143
+ it 'does not rewrite cookies that were secured after the correct_cookie_paths_timestamp' do
144
+ SafeCookies.configure do |config|
145
+ config.register_cookie('cookie_to_update', :expire_after => 3600)
146
+ config.fix_paths :for_cookies_secured_before => Time.parse('2050-01-02 00:00')
147
+ end
148
+ stub_app_call(app)
149
+ set_default_request_cookies(Time.parse('2050-01-03 00:00'))
150
+
151
+ code, headers, response = subject.call(env)
152
+ headers['Set-Cookie'].should be_nil
153
+ end
154
+
155
+ end
156
+
157
+ end
@@ -1,136 +1,229 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'spec_helper'
3
3
 
4
- # Explanation:
5
- # app#call(env) is how the middleware calls the app
6
- # returns the app's response
7
- # subject#call(env) is how the middleware is called "from below"
8
- # returns the response that is passed through the web server to the client
9
-
10
4
  describe SafeCookies::Middleware do
11
5
 
6
+ subject { described_class.new(app) }
12
7
  let(:app) { stub 'application' }
13
8
  let(:env) { { 'HTTPS' => 'on' } }
14
- subject { described_class.new(app) }
15
-
16
- it 'should rewrite specified existing cookies as "secure" and "HttpOnly", but only once' do
17
- Timecop.freeze do
18
- # first request: rewrite cookie
19
- subject = described_class.new(app, :foo => 24 * 60 * 60)
20
- app.should_receive(:call).and_return([ stub, {}, stub ])
21
- env['HTTP_COOKIE'] = 'foo=bar'
9
+
10
+ it 'should rewrite registered request cookies as secure and http-only, but only once' do
11
+ SafeCookies.configure do |config|
12
+ config.register_cookie('foo', :expire_after => 3600)
13
+ end
14
+
15
+ # first request: rewrite cookie
16
+ stub_app_call(app)
17
+ set_request_cookies(env, 'foo=bar')
18
+
19
+ code, headers, response = subject.call(env)
20
+ headers['Set-Cookie'].should =~ /foo=bar;.* secure; HttpOnly/
22
21
 
23
- code, headers, response = subject.call(env)
24
- expected_expiry = Rack::Utils.rfc2822((Time.now + 24 * 60 * 60).gmtime) # a special date format needed here
25
- headers['Set-Cookie'].should =~ /foo=bar;[^\n]* HttpOnly/
26
- headers['Set-Cookie'].should =~ /foo=bar;[^\n]* secure/
27
- headers['Set-Cookie'].should =~ /expires=#{expected_expiry}/
28
- headers['Set-Cookie'].should =~ /secured_old_cookies=/ # the indication cookie
29
-
30
- # second request: do not rewrite cookie again
31
- subject = described_class.new(app, :foo => 24 * 60 * 60)
32
- app.should_receive(:call).and_return([ stub, {}, stub ])
33
- received_cookies = headers['Set-Cookie'].scan(/[^\n;]+=[^\n;]+(?=;\s)/i) # extract cookies
34
- env['HTTP_COOKIE'] = received_cookies.join(',')
22
+ # second request: do not rewrite cookie again
23
+ received_cookies = extract_cookies(headers['Set-Cookie'])
24
+ received_cookies.should include('foo=bar') # sanity check
25
+
26
+ # client returns with the cookies, `app` and `subject` are different
27
+ # objects than in the previous request
28
+ other_app = stub('application')
29
+ other_subject = described_class.new(other_app)
30
+
31
+ stub_app_call(other_app)
32
+ set_request_cookies(env, *received_cookies)
35
33
 
36
- code, headers, response = subject.call(env)
37
- headers['Set-Cookie'].to_s.should == ""
38
- end
34
+ code, headers, response = other_subject.call(env)
35
+ headers['Set-Cookie'].to_s.should == ''
39
36
  end
40
-
41
- it "should make new cookies secure" do
42
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'neuer_cookie=neuer_cookie_wert'}, stub ])
37
+
38
+ it 'should not make cookies secure if the request was not secure' do
39
+ stub_app_call(app, :application_cookies => 'filter-settings=sort_by_date')
40
+ env['HTTPS'] = 'off'
43
41
 
44
42
  code, headers, response = subject.call(env)
45
- headers['Set-Cookie'].should =~ /neuer_cookie=neuer_cookie_wert;.* secure/
43
+ headers['Set-Cookie'].should include("filter-settings=sort_by_date")
44
+ headers['Set-Cookie'].should_not match(/\bsecure\b/i)
46
45
  end
47
-
48
- it "should make new cookies http_only" do
49
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'neuer_cookie=neuer_cookie_wert'}, stub ])
46
+
47
+ it 'expires the secured_old_cookies helper cookie in ten years' do
48
+ Timecop.freeze(Time.parse('2013-09-17 17:53'))
49
+
50
+ SafeCookies.configure do |config|
51
+ config.register_cookie('cookie_to_update', :expire_after => 3600)
52
+ end
50
53
 
54
+ set_request_cookies(env, 'cookie_to_update=some_data')
55
+ stub_app_call(app)
56
+
51
57
  code, headers, response = subject.call(env)
52
- headers['Set-Cookie'].should =~ /neuer_cookie=neuer_cookie_wert;.* HttpOnly/
58
+
59
+ headers['Set-Cookie'].should =~ /secured_old_cookies.*expires=Fri, 15 Sep 2023 \d\d:\d\d:\d\d/
53
60
  end
54
61
 
55
- it "should not make new cookies secure that are specified as 'non_secure'" do
56
- subject = described_class.new(app, :non_secure => %w[filter-settings])
57
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'filter-settings=sort_by_date'}, stub ])
62
+ it 'sets cookies on the root path' do
63
+ SafeCookies.configure do |config|
64
+ config.register_cookie('my_old_cookie', :expire_after => 3600)
65
+ end
58
66
 
67
+ set_request_cookies(env, 'my_old_cookie=foobar')
68
+ stub_app_call(app)
69
+
59
70
  code, headers, response = subject.call(env)
60
- headers['Set-Cookie'].should include("filter-settings=sort_by_date")
61
- headers['Set-Cookie'].should_not match(/secure/i)
71
+
72
+ cookies = headers['Set-Cookie'].split("\n")
73
+ cookies.size.should == 3 # my_old_cookie and secured_old_cookies and _known_cookies
74
+ cookies.each do |cookie|
75
+ cookie.should include('; path=/;')
76
+ end
62
77
  end
63
78
 
64
- it "should not make new cookies http_only that are specified as 'non_http_only'" do
65
- subject = described_class.new(app, :non_http_only => %w[javascript-cookie])
66
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'javascript-cookie=xss'}, stub ])
67
-
79
+ it 'should not alter cookie options coming from the application' do
80
+ stub_app_call(app, :application_cookies => 'cookie=data; path=/; expires=next_week')
81
+
68
82
  code, headers, response = subject.call(env)
69
- headers['Set-Cookie'].should include("javascript-cookie=xss")
70
- headers['Set-Cookie'].should_not match(/HttpOnly/i)
83
+ headers['Set-Cookie'].should =~ %r(cookie=data; path=/; expires=next_week; secure; HttpOnly)
71
84
  end
72
85
 
73
- it "should prefer the application's cookie if both client and app are sending one" do
74
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'cookie=überschrieben'}, stub ])
75
- env['HTTP_COOKIE'] = 'cookie=wert'
86
+ it 'should respect cookie options set in the configuration' do
87
+ Timecop.freeze
76
88
 
89
+ SafeCookies.configure do |config|
90
+ config.register_cookie('foo', :expire_after => 3600, :path => '/special/path')
91
+ end
92
+
93
+ stub_app_call(app)
94
+ set_request_cookies(env, 'foo=bar')
95
+ env['PATH_INFO'] = '/special/path/subfolder'
96
+
77
97
  code, headers, response = subject.call(env)
78
- headers['Set-Cookie'].should include("cookie=überschrieben")
98
+ expected_expiry = Rack::Utils.rfc2822((Time.now + 3600).gmtime) # a special date format needed here
99
+ headers['Set-Cookie'].should =~ %r(foo=bar; path=/special/path; expires=#{expected_expiry}; secure; HttpOnly)
79
100
  end
101
+
102
+ context 'cookies set by the application' do
103
+
104
+ it 'should make application cookies secure and http-only' do
105
+ stub_app_call(app, :application_cookies => 'application_cookie=value')
80
106
 
81
- it "should not make existing cookies secure that are specified as 'non_secure'" do
82
- subject = described_class.new(app, :filter => 24 * 60 * 60, :non_secure => %w[filter])
83
- app.should_receive(:call).and_return([ stub, {}, stub ])
84
- env['HTTP_COOKIE'] = 'filter=cars_only'
107
+ code, headers, response = subject.call(env)
108
+ headers['Set-Cookie'].should =~ /application_cookie=value;.* secure; HttpOnly/
109
+ end
110
+
111
+ it 'should not make application cookies secure that are specified as non-secure' do
112
+ SafeCookies.configure do |config|
113
+ config.register_cookie('filter-settings', :expire_after => 3600, :secure => false)
114
+ end
85
115
 
86
- code, headers, response = subject.call(env)
87
- set_cookie = headers['Set-Cookie'].gsub(/,(?=\s\d)/, '') # remove commas in expiry dates to simplify matching below
88
- set_cookie.should =~ /filter=cars_only;.* HttpOnly/
89
- set_cookie.should_not match(/filter=cars_only;.* secure/)
90
- end
116
+ stub_app_call(app, :application_cookies => 'filter-settings=sort_by_date')
117
+
118
+ code, headers, response = subject.call(env)
119
+ headers['Set-Cookie'].should include("filter-settings=sort_by_date")
120
+ headers['Set-Cookie'].should_not =~ /filter-settings=.*secure/i
121
+ end
91
122
 
92
- it "should not make existing cookies http_only that are specified as 'non_http_only'" do
93
- subject = described_class.new(app, :js_data => 24 * 60 * 60, :non_http_only => %w[js_data])
94
- app.should_receive(:call).and_return([ stub, {}, stub ])
95
- env['HTTP_COOKIE'] = 'js_data=json'
123
+ it 'should not make application cookies http-only that are specified as non-http-only' do
124
+ SafeCookies.configure do |config|
125
+ config.register_cookie('javascript-cookie', :expire_after => 3600, :http_only => false)
126
+ end
96
127
 
97
- code, headers, response = subject.call(env)
98
- set_cookie = headers['Set-Cookie']
99
- set_cookie.should =~ /js_data=json;.* secure/
100
- set_cookie.should_not match(/js_data=json;.* HttpOnly/)
101
- end
128
+ stub_app_call(app, :application_cookies => 'javascript-cookie=xss')
129
+
130
+ code, headers, response = subject.call(env)
131
+ headers['Set-Cookie'].should include("javascript-cookie=xss")
132
+ headers['Set-Cookie'].should_not =~ /javascript-cookie=.*HttpOnly/i
133
+ end
102
134
 
103
- it "should not make cookies secure if the request was not secure" do
104
- subject = described_class.new(app)
105
- app.should_receive(:call).and_return([ stub, { 'Set-Cookie' => 'filter-settings=sort_by_date'}, stub ])
106
- env['HTTPS'] = 'off'
135
+ it 'should prefer the application cookie over a client cookie' do
136
+ stub_app_call(app, :application_cookies => 'cookie=from_application')
137
+ set_request_cookies(env, 'cookie=from_client,_safe_cookies__known_cookies=cookie')
107
138
 
108
- code, headers, response = subject.call(env)
109
- headers['Set-Cookie'].should include("filter-settings=sort_by_date")
110
- headers['Set-Cookie'].should_not match(/secure/i)
111
- end
112
-
113
- it 'does not mutate an options hash passed to it' do
114
- options = { :cookie1 => 3600, :non_secure => [:cookie2], :non_http_only => [:cookie3] }
115
- described_class.new(app, options)
139
+ code, headers, response = subject.call(env)
140
+ headers['Set-Cookie'].should include("cookie=from_application")
141
+ headers['Set-Cookie'].should_not include("cookie=from_client")
142
+ end
116
143
 
117
- options[:cookie1].should == 3600
118
- options[:non_secure].should == [:cookie2]
119
- options[:non_http_only].should == [:cookie3]
144
+ end
145
+
146
+ context 'cookies sent by the client' do
147
+
148
+ it 'should not make request cookies secure that are specified as non-secure' do
149
+ SafeCookies.configure do |config|
150
+ config.register_cookie('filter', :expire_after => 3600, :secure => false)
151
+ end
152
+
153
+ stub_app_call(app)
154
+ set_request_cookies(env, 'filter=cars_only')
155
+
156
+ code, headers, response = subject.call(env)
157
+ headers['Set-Cookie'].should =~ /filter=cars_only;.* HttpOnly/
158
+ headers['Set-Cookie'].should_not =~ /filter=cars_only;.* secure/
159
+ end
160
+
161
+ it 'should not make request cookies http-only that are specified as non-http-only' do
162
+ SafeCookies.configure do |config|
163
+ config.register_cookie('js-data', :expire_after => 3600, :http_only => false)
164
+ end
165
+
166
+ stub_app_call(app)
167
+ set_request_cookies(env, 'js-data=json')
168
+
169
+ code, headers, response = subject.call(env)
170
+ headers['Set-Cookie'].should =~ /js-data=json;.* secure/
171
+ headers['Set-Cookie'].should_not =~ /js-data=json;.* HttpOnly/
172
+ end
173
+
120
174
  end
121
175
 
122
- it 'sets cookies on the root path (if no "path" is supplied, browsers assume the current)' do
123
- subject = described_class.new(app, :my_old_cookie => 3600)
124
- env['HTTP_COOKIE'] = 'my_old_cookie=foobar'
125
- app.should_receive(:call).and_return([ stub, {}, stub ])
176
+ context 'unknown request cookies' do
177
+
178
+ it 'should raise an error if there is an unknown cookie' do
179
+ set_request_cookies(env, 'foo=bar')
180
+
181
+ expect{ subject.call(env) }.to raise_error(SafeCookies::UnknownCookieError)
182
+ end
183
+
184
+ it 'should not raise an error if the (unregistered) cookie was initially set by the application' do
185
+ # application sets cookie
186
+ stub_app_call(app, :application_cookies => 'foo=bar; path=/some/path; secure')
187
+
188
+ code, headers, response = subject.call(env)
126
189
 
127
- code, headers, response = subject.call(env)
190
+ received_cookies = extract_cookies(headers['Set-Cookie'])
191
+ received_cookies.should include('foo=bar') # sanity check
192
+
193
+ # client returns with the cookie, `app` and `subject` are different
194
+ # objects than in the previous request
195
+ other_app = stub('application')
196
+ other_subject = described_class.new(other_app)
197
+
198
+ stub_app_call(other_app)
199
+ set_request_cookies(env, *received_cookies)
128
200
 
129
- cookies = headers['Set-Cookie'].split("\n")
130
- cookies.size.should > 0
131
- cookies.each do |cookie|
132
- cookie.should include('; path=/;')
201
+ other_subject.call(env)
202
+ end
203
+
204
+ it 'should not raise an error if the cookie is listed in the cookie configuration' do
205
+ SafeCookies.configure do |config|
206
+ config.register_cookie('foo', :expire_after => 3600)
207
+ end
208
+
209
+ stub_app_call(app)
210
+ set_request_cookies(env, 'foo=bar')
211
+
212
+ subject.call(env)
133
213
  end
214
+
215
+ it 'allows overwriting the error mechanism' do
216
+ stub_app_call(app)
217
+ set_request_cookies(env, 'foo=bar')
218
+
219
+ def subject.handle_unknown_cookies(*args)
220
+ @custom_method_called = true
221
+ end
222
+
223
+ subject.call(env)
224
+ subject.instance_variable_get('@custom_method_called').should == true
225
+ end
226
+
134
227
  end
135
228
 
136
229
  end
data/spec/spec_helper.rb CHANGED
@@ -11,4 +11,24 @@ RSpec.configure do |config|
11
11
  # the seed, which is printed after each run.
12
12
  # --seed 1234
13
13
  config.order = 'random'
14
+
15
+ config.before(:each) { SafeCookies.configure {} }
16
+ config.after(:each) {
17
+ SafeCookies.configuration = nil
18
+ Timecop.return
19
+ }
20
+ end
21
+
22
+ def stub_app_call(app, options = {})
23
+ env = {}
24
+ env['Set-Cookie'] = options[:application_cookies] if options[:application_cookies]
25
+ app.stub :call => [ stub, env, stub ]
26
+ end
27
+
28
+ def set_request_cookies(env, *cookies)
29
+ env['HTTP_COOKIE'] = cookies.join(',')
30
+ end
31
+
32
+ def extract_cookies(set_cookies_header)
33
+ set_cookies_header.scan(/(?=^|\n)[^\n;]+=[^\n;]+(?=;\s)/i)
14
34
  end
metadata CHANGED
@@ -1,119 +1,104 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: safe_cookies
3
- version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease:
6
- segments:
7
- - 0
8
- - 1
9
- - 3
10
- version: 0.1.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
11
5
  platform: ruby
12
- authors:
13
- - "Dominik Sch\xC3\xB6ler"
6
+ authors:
7
+ - Dominik Schöler
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2013-08-27 00:00:00 +02:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
11
+ date: 2013-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
22
14
  name: rack
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
33
20
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rspec
37
21
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
47
34
  type: :development
48
- version_requirements: *id002
49
- - !ruby/object:Gem::Dependency
50
- name: timecop
51
35
  prerelease: false
52
- requirement: &id003 !ruby/object:Gem::Requirement
53
- none: false
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- hash: 3
58
- segments:
59
- - 0
60
- version: "0"
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: timecop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
61
48
  type: :development
62
- version_requirements: *id003
63
- description: Make cookies as `secure` and `HttpOnly` as possible.
64
- email:
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Make all cookies `secure` and `HttpOnly`.
56
+ email:
65
57
  - dominik.schoeler@makandra.de
66
58
  executables: []
67
-
68
59
  extensions: []
69
-
70
60
  extra_rdoc_files: []
71
-
72
- files:
61
+ files:
73
62
  - .gitignore
74
63
  - Gemfile
75
64
  - LICENSE
76
65
  - README.md
77
66
  - Rakefile
78
67
  - lib/safe_cookies.rb
68
+ - lib/safe_cookies/configuration.rb
69
+ - lib/safe_cookies/cookie_path_fix.rb
70
+ - lib/safe_cookies/util.rb
79
71
  - lib/safe_cookies/version.rb
80
72
  - safe_cookies.gemspec
73
+ - spec/configuration_spec.rb
74
+ - spec/cookie_path_fix_spec.rb
81
75
  - spec/safe_cookies_spec.rb
82
76
  - spec/spec_helper.rb
83
- has_rdoc: true
84
77
  homepage: http://www.makandra.de
85
78
  licenses: []
86
-
79
+ metadata: {}
87
80
  post_install_message:
88
81
  rdoc_options: []
89
-
90
- require_paths:
82
+ require_paths:
91
83
  - lib
92
- required_ruby_version: !ruby/object:Gem::Requirement
93
- none: false
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- hash: 3
98
- segments:
99
- - 0
100
- version: "0"
101
- required_rubygems_version: !ruby/object:Gem::Requirement
102
- none: false
103
- requirements:
104
- - - ">="
105
- - !ruby/object:Gem::Version
106
- hash: 3
107
- segments:
108
- - 0
109
- version: "0"
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
110
94
  requirements: []
111
-
112
95
  rubyforge_project:
113
- rubygems_version: 1.3.9.5
96
+ rubygems_version: 2.1.2
114
97
  signing_key:
115
- specification_version: 3
116
- summary: Make cookies as `secure` and `HttpOnly` as possible.
117
- test_files:
98
+ specification_version: 4
99
+ summary: Make all cookies `secure` and `HttpOnly`.
100
+ test_files:
101
+ - spec/configuration_spec.rb
102
+ - spec/cookie_path_fix_spec.rb
118
103
  - spec/safe_cookies_spec.rb
119
104
  - spec/spec_helper.rb