safe_cookies 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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