safe_cookies 0.1.5 → 0.2.2

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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MjI5NWM4ZWE1M2UzNWFjNjFkNzc2M2EzN2U4NzJhZTgzNjQ0NmQ1NQ==
5
- data.tar.gz: !binary |-
6
- OGNlMjkzMmI2MTFhZTFlNzA3MDZmMmYxOWQ3YmQ1ZGE4Njg0OWZlOA==
2
+ SHA256:
3
+ metadata.gz: 7175011cd4a253c98779e5fdb5d221ea99dbbe5708e3a9dce37547ac89d29361
4
+ data.tar.gz: 08e2daf10fdd7ec2a115162969eaffd241edca9f82671a146bfafd381bd9f7a3
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MjU5MGUyMzQyNTI2MzE5OTkzYjBkOTI4NjQ1ZjQxOTk4NGQzOTkxMDEyNDk3
10
- YmQ3YjJjZDBhYTU3ZjBhNzgzMGYwNzdmNzcyZTFmYWY1NzQ0N2RhZDUyYzA1
11
- NjJlZDc4NjdiMWU2M2NhOWY5YzBmNWFkNWNmY2Y1OTgxNjI1YjQ=
12
- data.tar.gz: !binary |-
13
- Yjg4YTA3OGYyY2RhYmJkZTAyYmNiNjE2ZGRjNTc4MTA0NDJjMTc1ZjA3Mjcy
14
- MjFhYjlhOGY1ZjQyYTE3OTEzODE3ZmU3YmE5YzIxOTNhOWE4NTg5M2M3ZTk4
15
- ZWMzMzVmODk4MWE3MDdjN2RkZjI1N2UwN2EyOTVlNmM0MmMwMjk=
6
+ metadata.gz: 8f9932ab978f4cdeabc001314496ffed4214f9a966b45847e246c2c78730b1388ee1169a4def1864edb78481fbb30cefbffe29fd366f1a3abe207d107175c681
7
+ data.tar.gz: d42dbf95c6d38e99c1e0f356f31925ef6da59ba465a768ad5d25f573a02bb75e671330dbe6dfc4a52bf039879eab7ee53fe371d50d7e540ddaa5f87cd93aa8f6
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea
data/README.md CHANGED
@@ -1,89 +1,127 @@
1
+ # This gem is no longer maintained!
2
+
3
+ Read about [reasons and alternatives](https://makandracards.com/makandra/53693-rails-making-all-cookies-secure-to-pass-a-security-audit).
4
+
5
+
6
+ --------------------
7
+
1
8
  # SafeCookies
2
9
 
3
- This Gem brings a middleware that will make all cookies secure. In detail, it will:
10
+ This gem has a middleware that will make all cookies secure, by setting the
11
+ `HttpOnly` and the `secure` flag for all cookies the application sets on the
12
+ client.
13
+
14
+ Making a cookie `HttpOnly` prevents Javascripts from seeing it, which really
15
+ should be the default. It makes it way harder to steal cookie information via
16
+ malicious Javascript.
17
+
18
+ Making a cookie `secure` tells the browser to only send the cookie over HTTPS
19
+ connections, protecting it from being sniffed by a man-in-the-middle. (Setting a
20
+ [HSTS](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) header
21
+ achieves the same, but Safari < 7 and IE < 11 don't speak HSTS.)
22
+
23
+ SafeCookies will *additionally* rewrite all cookies the user is sending. **But**
24
+ it can only do so, if the cookie was registered before (see below). It will rewrite
25
+ user cookies only once per user.
4
26
 
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
27
 
9
28
  ## Installation
10
29
 
11
- Add this line to your application's Gemfile:
30
+ 1. Add `gem 'safe_cookies'` to your application's Gemfile, then run `bundle install`.
12
31
 
13
- gem 'safe_cookies'
32
+ 2. Add a configuration block in an initializer (e.g. `config/initializers/safe_cookies.rb`):
14
33
 
15
- Then run:
34
+ SafeCookies.configure do |config|
35
+ # configuration ...
36
+ end
16
37
 
17
- $ bundle
38
+ 3. Register the middleware:
18
39
 
19
- Or install it yourself as:
40
+ Rails 3+: add the following lines to the application block in `config/application.rb`:
20
41
 
21
- $ gem install safe_cookies
42
+ require 'safe_cookies'
43
+ config.middleware.insert_before ActionDispatch::Cookies, SafeCookies::Middleware
22
44
 
45
+ Rails 2: add the following lines to the initializer block in `config/environment.rb`:
23
46
 
24
- ## Usage
47
+ require 'safe_cookies'
48
+ config.middleware.insert_before ActionController::Session::CookieStore, SafeCookies::Middleware
25
49
 
26
- ### Step 1
27
- **Rails 3**: add the following line in config/application.rb:
50
+ Now all new cookies will be made `secure` and `HttpOnly`. But what about cookies
51
+ already out there?
28
52
 
29
- class Application < Rails::Application
30
- # ...
31
- config.middleware.insert_before ActionDispatch::Cookies, SafeCookies::Middleware
32
- end
33
53
 
34
- **Rails 2:** add the following lines in config/environment.rb:
54
+ ## Updating existing cookies
55
+
56
+ Unfortunately, [the client won't ever tell us](http://tools.ietf.org/html/rfc6265#section-4.2.2)
57
+ if it stores a cookie with flags such as `secure` or which expiry date it is
58
+ stored with. Therefore, in order to make the middleware retroactively secure
59
+ cookies owned by the client, you need to register each of those cookies with
60
+ the middleware, specifying their properties.
35
61
 
36
- Rails::Initializer.run do |config|
37
- # ...
38
- require 'safe_cookies'
39
- config.middleware.insert_before ActionController::Session::CookieStore, SafeCookies::Middleware
62
+ Carefully scan your app for cookies you are using. There's no easy way to find
63
+ out if you missed one (but see below for some help the gem provides).
64
+
65
+ SafeCookies.configure do |config|
66
+ config.register_cookie 'remember_token', :expire_after => 1.year
67
+ config.register_cookie 'last_action', :expire_after => 30.days, :path => '/commerce'
40
68
  end
41
69
 
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):
70
+ Available options are: `:expire_after` (required)`, :path, :secure, :http_only`.
71
+ For cookies with "session" expiry, set `:expire_after => nil`.
72
+
73
+
74
+ ## Having a cookie non-secure or non-HttpOnly
75
+
76
+ Tell SafeCookies which cookies not to make `secure` or `HttpOnly` by registering
77
+ them, just like above:
45
78
 
46
79
  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
80
+ config.register_cookie 'default_language', :expire_after => 10.years, :secure => false
81
+ config.register_cookie 'javascript_data', :expire_after => 1.day, :http_only => false
51
82
  end
52
83
 
53
- This will have the `default_language` cookie not made secure, the `javascript_data` cookie
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
84
 
58
- ### Step 3
59
- Override `SafeCookies::Middleware#handle_unknown_cookies(cookies)` (see "Dealing with unregistered cookies" below).
85
+ ## Finding unregistered user cookies
86
+
87
+ There are lots of cookies your application receives that you never did set.
88
+ However, if you want to know about any unknown cookies touching your
89
+ application, SafeCookies gives you two tools.
90
+
91
+ 1) If you set `config.log_unknown_cookies = true` in the configuration block, all
92
+ unknown cookies will be written to the Rails log. When you start implementing
93
+ the middleware, closely watch it to find cookies you forgot to register.
94
+
95
+ 2) You may overwrite `SafeCookies::Middleware#handle_unknown_cookies(cookies)`
96
+ in the configuration block for customized behaviour (like, notifying you per
97
+ email).
98
+
99
+ To ignore cookies that are irrelevant to you, you may configure them to be
100
+ ignored. Use the `config.ignore_cookie` directive, which takes either a String
101
+ or a Regex parameter. *Be careful when using regular expressions!*
60
102
 
61
103
 
62
- ## Dealing with unregistered cookies
104
+ ## Fixing cookie paths
63
105
 
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.
106
+ In August 2013 we noticed a bug in SafeCookies < 0.1.4, by which secured cookies
107
+ would be set for the current "directory" (see comments in `cookie_path_fix.rb`)
108
+ instead of root (which usually is what you want). Users would get multiple
109
+ cookies for that domain, leading to issues like being unable to sign in.
69
110
 
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).
111
+ The configuration option `config.fix_paths` turns on fixing this error. It
112
+ expects an option `:for_cookies_secured_before => Time.parse('some minutes after
113
+ you will have deployed')` which reflects the point of time from which SafeCookies
114
+ can expect cookies to be set with the correct path. It will only rewrite cookies
115
+ with a new path if it had set them before that point of time.
75
116
 
76
- You should not ignore an unregistered cookie, but instead register it.
77
117
 
118
+ ## Development
78
119
 
79
- ## Fix cookie paths
120
+ - Tests live in `spec`.
121
+ - You can run specs from the project root by saying `bundle exec rake`.
80
122
 
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.
123
+ If you would like to contribute:
84
124
 
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.
125
+ - Fork the repository.
126
+ - Push your changes **with passing specs**.
127
+ - Send us a pull request.
data/lib/safe_cookies.rb CHANGED
@@ -5,12 +5,11 @@ require "safe_cookies/helpers"
5
5
  require "safe_cookies/util"
6
6
  require "safe_cookies/version"
7
7
  require "rack"
8
+ require "time" # add Time#rfc2822
8
9
 
9
10
  # Naming:
10
11
  # - application_cookies: cookies received from the application. The 'Set-Cookie' header is a string
11
12
  # - request_cookies: cookies received from the client. Rack::Request#cookies returns a Hash of { 'name' => 'value' }
12
- # - response_cookies: cookies to be sent to the client
13
- # (= application_cookies + any cookies set in the middleware)
14
13
 
15
14
  module SafeCookies
16
15
 
@@ -19,6 +18,7 @@ module SafeCookies
19
18
  STORE_COOKIE_NAME = '_safe_cookies__known_cookies'
20
19
  SECURED_COOKIE_NAME = 'secured_old_cookies'
21
20
  HELPER_COOKIES_LIFETIME = 10 * 365 * 24 * 60 * 60 # 10 years
21
+
22
22
 
23
23
  class Middleware
24
24
 
@@ -30,22 +30,23 @@ module SafeCookies
30
30
 
31
31
  def initialize(app)
32
32
  @app = app
33
- @configuration = SafeCookies.configuration or raise "Don't know what to do without configuration"
33
+ @config = SafeCookies.configuration or raise "Don't know what to do without configuration"
34
34
  end
35
35
 
36
36
  def call(env)
37
37
  reset_instance_variables
38
38
 
39
39
  @request = Rack::Request.new(env)
40
- ensure_no_unknown_cookies_in_request!
40
+ check_if_request_has_unknown_cookies
41
41
 
42
+ # call the next middleware up the stack
42
43
  status, @headers, body = @app.call(env)
43
44
  cache_application_cookies_string
44
45
 
45
- remove_application_cookies_from_request_cookies
46
- rewrite_application_cookies
46
+ enhance_application_cookies!
47
47
  store_application_cookie_names
48
- fix_cookie_paths if fix_cookie_paths?
48
+
49
+ delete_cookies_on_bad_path if fix_cookie_paths?
49
50
  rewrite_request_cookies unless cookies_have_been_rewritten_before?
50
51
 
51
52
  [ status, @headers, body ]
@@ -53,31 +54,27 @@ module SafeCookies
53
54
 
54
55
  private
55
56
 
57
+ # Instance variables survive requests because the middleware is a singleton.
56
58
  def reset_instance_variables
57
- @request, @headers, @application_cookies = nil
59
+ @request, @headers, @application_cookies_string = nil
58
60
  end
59
61
 
60
- def ensure_no_unknown_cookies_in_request!
62
+ def check_if_request_has_unknown_cookies
61
63
  request_cookie_names = request_cookies.keys.map(&:to_s)
62
64
  unknown_cookie_names = request_cookie_names - known_cookie_names
63
65
 
64
66
  if unknown_cookie_names.any?
65
- handle_unknown_cookies(unknown_cookie_names)
66
- end
67
- end
67
+ message = "Request for '#{@request.url}' had unknown cookies: #{unknown_cookie_names.join(', ')}"
68
+ log(message) if @config.log_unknown_cookies
68
69
 
69
- def remove_application_cookies_from_request_cookies
70
- if @application_cookies
71
- application_cookie_names = @application_cookies.scan(COOKIE_NAME_REGEX)
72
- application_cookie_names.each do |cookie|
73
- request_cookies.delete(cookie)
74
- end
70
+ handle_unknown_cookies(unknown_cookie_names)
75
71
  end
76
72
  end
77
-
78
- def rewrite_application_cookies
79
- if @application_cookies
80
- cookies = @application_cookies.split("\n")
73
+
74
+ # Overwrites @header['Set-Cookie']!
75
+ def enhance_application_cookies!
76
+ if @application_cookies_string
77
+ cookies = @application_cookies_string.split("\n")
81
78
 
82
79
  # On Rack 1.1, cookie values sometimes contain trailing newlines.
83
80
  # Example => ["foo=1; path=/\n", "bar=2; path=/"]
@@ -96,36 +93,51 @@ module SafeCookies
96
93
  @headers['Set-Cookie'] = cookies.join("\n")
97
94
  end
98
95
  end
99
-
96
+
97
+ # Store the names of cookies that are set by the application.
98
+ # (We are already securing those and will not need to rewrite them.)
100
99
  def store_application_cookie_names
101
- if @application_cookies
102
- application_cookie_names = stored_application_cookie_names + @application_cookies.scan(COOKIE_NAME_REGEX)
100
+ if @application_cookies_string
101
+ application_cookie_names = stored_application_cookie_names + @application_cookies_string.scan(COOKIE_NAME_REGEX)
103
102
  application_cookies_string = application_cookie_names.uniq.join(KNOWN_COOKIES_DIVIDER)
104
103
 
105
104
  set_cookie!(STORE_COOKIE_NAME, application_cookies_string, :expire_after => HELPER_COOKIES_LIFETIME)
106
105
  end
107
106
  end
108
107
 
109
- # This method takes all cookies sent with the request and rewrites them,
108
+ # This method takes the cookies sent with the request and rewrites them,
110
109
  # making them both secure and http-only (unless specified otherwise in
111
110
  # the configuration).
112
111
  # With the SECURED_COOKIE_NAME cookie we remember the exact time that we
113
112
  # rewrote the cookies.
114
113
  def rewrite_request_cookies
115
- if request_cookies.any?
116
- registered_cookies_in_request.each do |cookie_name, options|
117
- value = request_cookies[cookie_name]
118
-
114
+ rewritable_cookies = rewritable_request_cookies
115
+
116
+ # don't rewrite request cookies that the application is setting in the response
117
+ if @application_cookies_string
118
+ app_cookie_names = @application_cookies_string.scan(COOKIE_NAME_REGEX)
119
+ Util.except!(rewritable_cookies, *app_cookie_names)
120
+ end
121
+
122
+ if rewritable_cookies.any?
123
+ rewritable_cookies.each do |cookie_name, value|
124
+ options = @config.registered_cookies[cookie_name]
119
125
  set_cookie!(cookie_name, value, options)
120
126
  end
121
127
 
122
- formatted_now = Rack::Utils.rfc2822(Time.now.gmtime)
123
- set_cookie!(SECURED_COOKIE_NAME, formatted_now, :expire_after => HELPER_COOKIES_LIFETIME)
128
+ set_cookie!(SECURED_COOKIE_NAME, Time.now.gmtime.rfc2822, :expire_after => HELPER_COOKIES_LIFETIME)
124
129
  end
125
130
  end
126
131
 
132
+ # API method
127
133
  def handle_unknown_cookies(cookie_names)
128
- raise SafeCookies::UnknownCookieError.new("Request for '#{@request.url}' had unknown cookies: #{cookie_names.join(', ')}")
134
+ end
135
+
136
+ def log(error_message)
137
+ message = '** [SafeCookies] '
138
+ message << error_message
139
+
140
+ Rails.logger.error(message) if defined?(Rails)
129
141
  end
130
142
 
131
143
  end
@@ -13,12 +13,15 @@ module SafeCookies
13
13
  end
14
14
 
15
15
  class Configuration
16
- attr_reader :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp
16
+ attr_accessor :log_unknown_cookies
17
+ attr_reader :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp,
18
+ :ignored_cookies
17
19
 
18
20
  def initialize
19
21
  self.registered_cookies = {}
20
22
  self.insecure_cookies = []
21
23
  self.scriptable_cookies = []
24
+ self.ignored_cookies = []
22
25
  end
23
26
 
24
27
  # Register cookies you expect to receive. The middleware will rewrite all
@@ -45,6 +48,14 @@ module SafeCookies
45
48
  scriptable_cookies << name if options[:http_only] == false
46
49
  end
47
50
 
51
+ # Ignore cookies that you don't control like this:
52
+ #
53
+ # ignore_cookie 'ignored_cookie'
54
+ # ignore_cookie /^__utm/
55
+ def ignore_cookie(name_or_regex)
56
+ self.ignored_cookies << name_or_regex
57
+ end
58
+
48
59
  def fix_paths(options = {})
49
60
  options.has_key?(:for_cookies_secured_before) or raise MissingOptionError.new("Was told to fix paths without the :for_cookies_secured_before timestamp.")
50
61
 
@@ -63,7 +74,8 @@ module SafeCookies
63
74
  private
64
75
 
65
76
  attr_accessor :insecure_cookies, :scriptable_cookies
66
- attr_writer :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp
77
+ attr_writer :registered_cookies, :fix_cookie_paths, :correct_cookie_paths_timestamp,
78
+ :ignored_cookies
67
79
 
68
80
  end
69
81
 
@@ -2,39 +2,37 @@ module SafeCookies
2
2
  module CookiePathFix
3
3
 
4
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.
5
+ # cookies. Browsers then would assume and store the current "directory"
6
+ # (see below), leading to multiple cookies per domain.
7
+ #
8
+ # If the cookies were secured before the configured datetime, this method
9
+ # instructs the client to delete all cookies it sent with the request and
10
+ # that we are able to rewrite, plus the SECURED_COOKIE_NAME helper cookie.
7
11
  #
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
12
  # 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
13
+ # if it hadn't seen them before, setting them on the correct path (root,
14
+ # by default).
15
+ def delete_cookies_on_bad_path
16
+ rewritable_request_cookies.keys.each &method(:delete_cookie_for_current_directory)
18
17
  delete_cookie_for_current_directory(SafeCookies::SECURED_COOKIE_NAME)
19
18
 
20
- # Delete this cookie here, so the middleware will secure all cookies anew.
21
- request_cookies.delete(SafeCookies::SECURED_COOKIE_NAME)
19
+ # Delete this cookie here, so the middleware believes it hasn't secured
20
+ # the cookies yet.
21
+ @request.cookies.delete(SafeCookies::SECURED_COOKIE_NAME)
22
22
  end
23
23
 
24
24
  private
25
25
 
26
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
27
+ @config.fix_cookie_paths &&
28
+ cookies_have_been_rewritten_before? &&
29
+ (secured_old_cookies_timestamp < @config.correct_cookie_paths_timestamp)
32
30
  end
33
31
 
34
32
  # Delete cookies by giving them an expiry in the past,
35
33
  # cf. https://tools.ietf.org/html/rfc6265#section-4.1.2.
36
34
  #
37
- # Most important, as specified in
35
+ # Most importantly, as specified in
38
36
  # https://tools.ietf.org/html/rfc6265#section-4.1.2.4 and in section 5.1.4,
39
37
  # cookies set without a path will be set for the current "directory", that is:
40
38
  #
@@ -51,16 +49,23 @@ module SafeCookies
51
49
  end
52
50
 
53
51
  def current_directory_is_root?
54
- !@request.path[%r(^/[^/]+/[^\?]+), 0] # roughly: "there are not three slashes"
52
+ # in words: "there are not three slashes before any query params"
53
+ !@request.path[%r(^/[^/]+/[^\?]+), 0]
55
54
  end
56
55
 
57
56
  def secured_old_cookies_timestamp
58
- Time.rfc2822(request_cookies[SafeCookies::SECURED_COOKIE_NAME])
57
+ @request.cookies.has_key?(SafeCookies::SECURED_COOKIE_NAME) or return nil
58
+
59
+ Time.rfc2822(@request.cookies[SafeCookies::SECURED_COOKIE_NAME])
59
60
  rescue ArgumentError
60
61
  # If we cannot parse the secured_old_cookies time,
61
62
  # assume it was before we noticed the bug to ensure
62
63
  # broken cookie paths will be fixed.
63
- Time.parse "2013-08-25 0:00"
64
+ #
65
+ # One reason to get here is that Rack::Utils.rfc2822 produces an invalid
66
+ # datetime string in Rack v1.1, writing the date with dashes
67
+ # (e.g. '04-Nov-2013').
68
+ Time.parse "2013-08-25 00:00"
64
69
  end
65
70
 
66
71
  end
@@ -3,14 +3,18 @@ module SafeCookies
3
3
 
4
4
  KNOWN_COOKIES_DIVIDER = '|'
5
5
 
6
+ # Since we have to operate on and modify the actual @headers hash that the
7
+ # application returns, cache the @headers['Set-Cookie'] string so that
8
+ # later on, we still know what the application did set.
6
9
  def cache_application_cookies_string
7
10
  cookies = @headers['Set-Cookie']
8
11
  # Rack 1.1 returns an Array
9
12
  cookies = cookies.join("\n") if cookies.is_a?(Array)
10
13
 
11
14
  if cookies and cookies.length > 0
12
- @application_cookies = cookies
15
+ @application_cookies_string = cookies
13
16
  end
17
+ # else, @application_cookies_string will be `nil`
14
18
  end
15
19
 
16
20
  def secure(cookie)
@@ -40,42 +44,43 @@ module SafeCookies
40
44
  options[:secure] = should_be_secure?(name)
41
45
  options[:httponly] = should_be_http_only?(name)
42
46
 
47
+ # Rack magic
43
48
  Rack::Utils.set_cookie_header!(@headers, name, options)
44
49
  end
45
50
 
46
51
 
47
52
  # getters
53
+
54
+ # returns the request cookies minus ignored cookies
55
+ def request_cookies
56
+ Util.except!(@request.cookies.dup, *@config.ignored_cookies)
57
+ end
48
58
 
49
59
  def stored_application_cookie_names
50
- store_cookie = request_cookies[STORE_COOKIE_NAME] || ""
60
+ store_cookie = @request.cookies[STORE_COOKIE_NAME] || ""
51
61
  store_cookie.split(KNOWN_COOKIES_DIVIDER)
52
62
  end
53
63
 
54
- # returns those of the registered cookies that appear in the request
55
- def registered_cookies_in_request
56
- Util.slice(@configuration.registered_cookies, *request_cookies.keys)
64
+ def rewritable_request_cookies
65
+ Util.slice(request_cookies, *@config.registered_cookies.keys)
57
66
  end
58
67
 
59
68
  def known_cookie_names
60
69
  known = [STORE_COOKIE_NAME, SECURED_COOKIE_NAME]
61
70
  known += stored_application_cookie_names
62
- known += @configuration.registered_cookies.keys
63
- end
64
-
65
- def request_cookies
66
- @request.cookies
71
+ known += @config.registered_cookies.keys
67
72
  end
68
73
 
69
74
 
70
75
  # boolean
71
76
 
72
77
  def cookies_have_been_rewritten_before?
73
- request_cookies.has_key? SECURED_COOKIE_NAME
78
+ @request.cookies.has_key? SECURED_COOKIE_NAME
74
79
  end
75
80
 
76
81
  def should_be_secure?(cookie)
77
82
  cookie_name = cookie.split('=').first.strip
78
- ssl? and not @configuration.insecure_cookie?(cookie_name)
83
+ ssl? and not @config.insecure_cookie?(cookie_name)
79
84
  end
80
85
 
81
86
  def ssl?
@@ -89,7 +94,7 @@ module SafeCookies
89
94
 
90
95
  def should_be_http_only?(cookie)
91
96
  cookie_name = cookie.split('=').first.strip
92
- not @configuration.scriptable_cookie?(cookie_name)
97
+ not @config.scriptable_cookie?(cookie_name)
93
98
  end
94
99
 
95
100
  end
@@ -1,17 +1,22 @@
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
- }
1
+ class SafeCookies::Util
2
+ class << self
3
+
4
+ def slice(hash, *allowed_keys)
5
+ sliced_hash = hash.select { |key, _value|
6
+ allowed_keys.include? key
7
+ }
9
8
 
10
- # Normalize the result of Hash#select
11
- # (Ruby 1.8 returns an Array, Ruby 1.9 returns a Hash)
12
- Hash[sliced_hash]
9
+ # Normalize the result of Hash#select
10
+ # (Ruby 1.8 returns an Array, Ruby 1.9 returns a Hash)
11
+ Hash[sliced_hash]
12
+ end
13
+
14
+ # rejected_keys may be of type String or Regex
15
+ def except!(hash, *rejected_keys)
16
+ hash.delete_if do |key, _value|
17
+ rejected_keys.any? { |rejected| rejected === key }
13
18
  end
14
-
15
19
  end
20
+
16
21
  end
17
- end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module SafeCookies
2
- VERSION = '0.1.5'
2
+ VERSION = '0.2.2'
3
3
  end
data/safe_cookies.gemspec CHANGED
@@ -7,6 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.description = %q{Make all cookies `secure` and `HttpOnly`.}
8
8
  gem.summary = %q{Make all cookies `secure` and `HttpOnly`.}
9
9
  gem.homepage = "http://www.makandra.de"
10
+ gem.license = "MIT"
10
11
 
11
12
  gem.files = `git ls-files`.split($\)
12
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -3,7 +3,7 @@ require 'cgi'
3
3
 
4
4
  describe SafeCookies::Middleware do
5
5
 
6
- describe 'cookie path fix' do
6
+ describe 'cookie path fix,' do
7
7
 
8
8
  subject { described_class.new(app) }
9
9
  let(:app) { stub 'application' }
@@ -15,12 +15,11 @@ describe SafeCookies::Middleware do
15
15
  end
16
16
 
17
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)}")
18
+ set_request_cookies(env, 'cookie_to_update=some_data', "secured_old_cookies=#{CGI::escape(secured_at.gmtime.rfc2822)}")
20
19
  end
21
20
 
22
21
 
23
- context 'rewriting previously secured cookies' do
22
+ context 'rewriting previously secured cookies,' do
24
23
 
25
24
  before do
26
25
  SafeCookies.configure do |config|
@@ -109,11 +108,21 @@ describe SafeCookies::Middleware do
109
108
  headers['Set-Cookie'].should =~ %r(secured_old_cookies=\w+.*path=/;)
110
109
  end
111
110
 
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=\/;/
111
+ context 'unparseable secured_old_cookies timestamp,' do
112
+
113
+ before do
114
+ set_request_cookies(env, 'cookie_to_update=some_data', 'secured_old_cookies=rubbish')
115
+ code, @headers, response = subject.call(env)
116
+ end
117
+
118
+ it 'rewrites cookies anyway' do
119
+ @headers['Set-Cookie'].should include('cookie_to_update=some_data;')
120
+ end
121
+
122
+ it 'sets a new, parseable secured_old_cookies timestamp' do
123
+ @headers['Set-Cookie'].should include("secured_old_cookies=#{CGI::escape @now.gmtime.rfc2822}")
124
+ end
125
+
117
126
  end
118
127
 
119
128
  end
@@ -7,7 +7,7 @@ describe SafeCookies::Middleware do
7
7
  let(:app) { stub 'application' }
8
8
  let(:env) { { 'HTTPS' => 'on' } }
9
9
 
10
- it 'should rewrite registered request cookies as secure and http-only, but only once' do
10
+ it 'rewrites registered request cookies as secure and http-only, but only once' do
11
11
  SafeCookies.configure do |config|
12
12
  config.register_cookie('foo', :expire_after => 3600)
13
13
  end
@@ -35,7 +35,7 @@ describe SafeCookies::Middleware do
35
35
  headers['Set-Cookie'].to_s.should == ''
36
36
  end
37
37
 
38
- it 'should not make cookies secure if the request was not secure' do
38
+ it 'doesn’t make cookies secure if the request was not secure' do
39
39
  stub_app_call(app, :application_cookies => 'filter-settings=sort_by_date')
40
40
  env['HTTPS'] = 'off'
41
41
 
@@ -43,7 +43,7 @@ describe SafeCookies::Middleware do
43
43
  headers['Set-Cookie'].should include("filter-settings=sort_by_date")
44
44
  headers['Set-Cookie'].should_not match(/\bsecure\b/i)
45
45
  end
46
-
46
+
47
47
  it 'expires the secured_old_cookies helper cookie in ten years' do
48
48
  Timecop.freeze(Time.parse('2013-09-17 17:53'))
49
49
 
@@ -59,43 +59,47 @@ describe SafeCookies::Middleware do
59
59
  headers['Set-Cookie'].should =~ /secured_old_cookies.*expires=Fri, 15 Sep 2023 \d\d:\d\d:\d\d/
60
60
  end
61
61
 
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
62
+ context 'cookie attributes' do
63
+
64
+ it 'sets cookies on the root path' do
65
+ SafeCookies.configure do |config|
66
+ config.register_cookie('my_old_cookie', :expire_after => 3600)
67
+ end
66
68
 
67
- set_request_cookies(env, 'my_old_cookie=foobar')
68
- stub_app_call(app)
69
+ set_request_cookies(env, 'my_old_cookie=foobar')
70
+ stub_app_call(app)
69
71
 
70
- code, headers, response = subject.call(env)
72
+ code, headers, response = subject.call(env)
71
73
 
72
- cookies = headers['Set-Cookie'].split("\n")
73
- cookies.each do |cookie|
74
- cookie.should include('; path=/;')
74
+ cookies = headers['Set-Cookie'].split("\n")
75
+ cookies.each do |cookie|
76
+ cookie.should include('; path=/;')
77
+ end
75
78
  end
76
- end
77
79
 
78
- it 'should not alter cookie options coming from the application' do
79
- stub_app_call(app, :application_cookies => 'cookie=data; path=/; expires=next_week')
80
+ it 'should not alter cookie attributes coming from the application' do
81
+ stub_app_call(app, :application_cookies => 'cookie=data; path=/; expires=next_week')
80
82
 
81
- code, headers, response = subject.call(env)
82
- headers['Set-Cookie'].should =~ %r(cookie=data; path=/; expires=next_week; secure; HttpOnly)
83
- end
83
+ code, headers, response = subject.call(env)
84
+ headers['Set-Cookie'].should =~ %r(cookie=data; path=/; expires=next_week; secure; HttpOnly)
85
+ end
84
86
 
85
- it 'should respect cookie options set in the configuration' do
86
- Timecop.freeze
87
+ it 'should respect cookie attributes set in the configuration' do
88
+ Timecop.freeze
87
89
 
88
- SafeCookies.configure do |config|
89
- config.register_cookie('foo', :expire_after => 3600, :path => '/special/path')
90
- end
90
+ SafeCookies.configure do |config|
91
+ config.register_cookie('foo', :expire_after => 3600, :path => '/special/path')
92
+ end
91
93
 
92
- stub_app_call(app)
93
- set_request_cookies(env, 'foo=bar')
94
- env['PATH_INFO'] = '/special/path/subfolder'
94
+ stub_app_call(app)
95
+ set_request_cookies(env, 'foo=bar')
96
+ env['PATH_INFO'] = '/special/path/subfolder'
95
97
 
96
- code, headers, response = subject.call(env)
97
- expected_expiry = Rack::Utils.rfc2822((Time.now + 3600).gmtime) # a special date format needed here
98
- headers['Set-Cookie'].should =~ %r(foo=bar; path=/special/path; expires=#{expected_expiry}; secure; HttpOnly)
98
+ code, headers, response = subject.call(env)
99
+ expected_expiry = (Time.now + 3600).gmtime.rfc2822 # a special date format needed here
100
+ headers['Set-Cookie'].should =~ %r(foo=bar; path=/special/path; expires=#{expected_expiry}; secure; HttpOnly)
101
+ end
102
+
99
103
  end
100
104
 
101
105
  context 'cookies set by the application' do
@@ -173,49 +177,44 @@ describe SafeCookies::Middleware do
173
177
  headers['Set-Cookie'].should =~ /js-data=json;.* secure/
174
178
  headers['Set-Cookie'].should_not =~ /js-data=json;.* HttpOnly/
175
179
  end
176
-
180
+
177
181
  end
178
182
 
179
- context 'unknown request cookies' do
183
+ context 'ignored cookies' do
180
184
 
181
- it 'should raise an error if there is an unknown cookie' do
182
- set_request_cookies(env, 'foo=bar')
183
-
184
- expect{ subject.call(env) }.to raise_error(SafeCookies::UnknownCookieError)
185
+ before do
186
+ stub_app_call(app)
187
+ set_request_cookies(env, '__utma=123', '__utmz=456')
185
188
  end
186
-
187
- it 'should not raise an error if the (unregistered) cookie was initially set by the application' do
188
- # application sets cookie
189
- stub_app_call(app, :application_cookies => 'foo=bar; path=/some/path; secure')
190
-
191
- code, headers, response = subject.call(env)
192
189
 
193
- received_cookies = extract_cookies(headers['Set-Cookie'])
194
- received_cookies.should include('foo=bar') # sanity check
195
-
196
- # client returns with the cookie, `app` and `subject` are different
197
- # objects than in the previous request
198
- other_app = stub('application')
199
- other_subject = described_class.new(other_app)
200
-
201
- stub_app_call(other_app)
202
- set_request_cookies(env, *received_cookies)
190
+ it 'does not rewrite ignored cookies given as string' do
191
+ SafeCookies.configure do |config|
192
+ config.ignore_cookie '__utma'
193
+ config.ignore_cookie '__utmz'
194
+ end
203
195
 
204
- other_subject.call(env)
196
+ code, headers, response = subject.call(env)
197
+ headers['Set-Cookie'].should_not =~ /__utm/
205
198
  end
206
-
207
- it 'should not raise an error if the cookie is listed in the cookie configuration' do
199
+
200
+ it 'does not rewrite ignored cookies given as regex' do
208
201
  SafeCookies.configure do |config|
209
- config.register_cookie('foo', :expire_after => 3600)
202
+ config.ignore_cookie /^__utm/
210
203
  end
211
-
212
- stub_app_call(app)
213
- set_request_cookies(env, 'foo=bar')
214
204
 
215
- subject.call(env)
205
+ code, headers, response = subject.call(env)
206
+ headers['Set-Cookie'].should_not =~ /__utm/
216
207
  end
208
+
209
+ end
217
210
 
218
- it 'allows overwriting the error mechanism' do
211
+
212
+ # The unknown cookies mechanism was more important when we were sending
213
+ # notifications on encountering unknown cookies. Perhaps, it will regain
214
+ # importance in the future.
215
+ context 'when a request has unknown cookies,' do
216
+
217
+ it 'allows overwriting the handling mechanism' do
219
218
  stub_app_call(app)
220
219
  set_request_cookies(env, 'foo=bar')
221
220
 
@@ -227,6 +226,69 @@ describe SafeCookies::Middleware do
227
226
  subject.instance_variable_get('@custom_method_called').should == true
228
227
  end
229
228
 
229
+ context 'and it is configured to log unknown cookies' do
230
+
231
+ before do
232
+ SafeCookies.configure do |config|
233
+ config.log_unknown_cookies = true
234
+ end
235
+ end
236
+
237
+ it 'logs an error message' do
238
+ stub_app_call(app)
239
+ set_request_cookies(env, 'foo=bar')
240
+
241
+ subject.should_receive(:log).with(/unknown cookies: foo/)
242
+ subject.call(env)
243
+ end
244
+
245
+ it 'does not log an error if the (unregistered) cookie was initially set by the application' do
246
+ # application sets cookie
247
+ stub_app_call(app, :application_cookies => 'foo=bar; path=/some/path; secure')
248
+
249
+ code, headers, response = subject.call(env)
250
+
251
+ received_cookies = extract_cookies(headers['Set-Cookie'])
252
+ received_cookies.should include('foo=bar') # sanity check
253
+
254
+ # client returns with the cookie, `app` and `subject` are different
255
+ # objects than in the previous request
256
+ other_app = stub('application')
257
+ other_subject = described_class.new(other_app)
258
+
259
+ stub_app_call(other_app)
260
+ set_request_cookies(env, *received_cookies)
261
+
262
+ other_subject.should_not_receive(:log)
263
+ other_subject.call(env)
264
+ end
265
+
266
+ it 'does not log an error if the cookie is listed in the cookie configuration' do
267
+ SafeCookies.configure do |config|
268
+ config.register_cookie('foo', :expire_after => 3600)
269
+ end
270
+
271
+ stub_app_call(app)
272
+ set_request_cookies(env, 'foo=bar')
273
+
274
+ subject.should_not_receive(:log)
275
+ subject.call(env)
276
+ end
277
+
278
+ it 'does not log an error if the cookie is ignored' do
279
+ SafeCookies.configure do |config|
280
+ config.ignore_cookie '__utma'
281
+ end
282
+
283
+ stub_app_call(app)
284
+ set_request_cookies(env, '__utma=tracking')
285
+
286
+ subject.should_not_receive(:log)
287
+ subject.call(env)
288
+ end
289
+
290
+ end
291
+
230
292
  end
231
293
 
232
294
  end
data/spec/util_spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe SafeCookies::Util do
4
+
5
+ describe '.except!' do
6
+
7
+ before do
8
+ @hash = { 'a' => 1, 'ab' => 2, 'b' => 3 }
9
+ end
10
+
11
+ it 'deletes the given keys from the original hash' do
12
+ SafeCookies::Util.except!(@hash, 'a')
13
+ @hash.should == { 'ab' => 2, 'b' => 3 }
14
+ end
15
+
16
+ it 'deletes all keys that match the regex' do
17
+ SafeCookies::Util.except!(@hash, /b/)
18
+ @hash.should == { 'a' => 1 }
19
+ end
20
+
21
+ it 'returns the original hash' do
22
+ SafeCookies::Util.except!(@hash, /(?!)/).should == @hash
23
+ end
24
+
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe_cookies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Schöler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-18 00:00:00.000000000 Z
11
+ date: 2021-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: timecop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: debugger
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: Make all cookies `secure` and `HttpOnly`.
@@ -73,7 +73,7 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
- - .gitignore
76
+ - ".gitignore"
77
77
  - Gemfile
78
78
  - LICENSE
79
79
  - README.md
@@ -89,8 +89,10 @@ files:
89
89
  - spec/cookie_path_fix_spec.rb
90
90
  - spec/safe_cookies_spec.rb
91
91
  - spec/spec_helper.rb
92
+ - spec/util_spec.rb
92
93
  homepage: http://www.makandra.de
93
- licenses: []
94
+ licenses:
95
+ - MIT
94
96
  metadata: {}
95
97
  post_install_message:
96
98
  rdoc_options: []
@@ -98,17 +100,16 @@ require_paths:
98
100
  - lib
99
101
  required_ruby_version: !ruby/object:Gem::Requirement
100
102
  requirements:
101
- - - ! '>='
103
+ - - ">="
102
104
  - !ruby/object:Gem::Version
103
105
  version: '0'
104
106
  required_rubygems_version: !ruby/object:Gem::Requirement
105
107
  requirements:
106
- - - ! '>='
108
+ - - ">="
107
109
  - !ruby/object:Gem::Version
108
110
  version: '0'
109
111
  requirements: []
110
- rubyforge_project:
111
- rubygems_version: 2.1.2
112
+ rubygems_version: 3.2.15
112
113
  signing_key:
113
114
  specification_version: 4
114
115
  summary: Make all cookies `secure` and `HttpOnly`.
@@ -117,3 +118,4 @@ test_files:
117
118
  - spec/cookie_path_fix_spec.rb
118
119
  - spec/safe_cookies_spec.rb
119
120
  - spec/spec_helper.rb
121
+ - spec/util_spec.rb