safe_cookies 0.1.6 → 0.1.7
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 +8 -8
- data/README.md +40 -23
- data/lib/safe_cookies/cookie_path_fix.rb +17 -14
- data/lib/safe_cookies/helpers.rb +14 -11
- data/lib/safe_cookies/version.rb +1 -1
- data/lib/safe_cookies.rb +18 -18
- data/spec/cookie_path_fix_spec.rb +18 -9
- data/spec/safe_cookies_spec.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Njc0YjdlMzZlMTRmNjk3NGQ1NWQ5NDU0YjU0ZGQwMTA0OGYyM2Y1MQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YzQ4OWE2ODk3NWU4M2U0OTQ3MjRlZDdiYzIzM2MyNjFjNmE0YzRmOQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NmJjNjgzM2VjZTdmY2E4ZmM3OTQwOTEwNzgwNTQ1YTMxOTZlOWY4NDA3Nzgy
|
10
|
+
YTJlZmRkOTQyMjQwNTE1NWUxN2I2M2VmNTNjMjMyZTk5NGQ0MTlmY2M0MWMz
|
11
|
+
NmQwM2EwZmE4M2I4MjJlNmVjZDlmZmJmNWY0NDYxN2Q3ZWI2YmE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjU4NDU0NjUwNjgzZGNhYzNjNTBiM2JjOTQyMGZlZDY3YThjYjRiYTUzZDE5
|
14
|
+
ZDU5NjVhOTNlYjM1NWE2NDUwMjI1YzZmZmU5OGZhZmI3M2I2ZjM5YzMyNzNl
|
15
|
+
YWZjYWFjMGIwMWU5YTg2NThlMzJkNjExMDdjODgyMzhkY2ExYmY=
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SafeCookies
|
2
2
|
|
3
|
-
This Gem
|
3
|
+
This Gem has a middleware that will make all cookies secure. In detail, it will:
|
4
4
|
|
5
5
|
* set all new application cookies 'HttpOnly', unless specified otherwise
|
6
6
|
* set all new application cookies 'secure', if the request came via HTTPS and not specified otherwise
|
@@ -13,14 +13,14 @@ Add this line to your application's Gemfile:
|
|
13
13
|
|
14
14
|
gem 'safe_cookies'
|
15
15
|
|
16
|
-
Then run `bundle`.
|
16
|
+
Then run `bundle install`.
|
17
17
|
|
18
|
-
Though this gem is aimed at Rails applications, you may even use it without
|
19
|
-
`gem install safe_cookies`.
|
18
|
+
Though this gem is aimed at Rails applications, you may even use it without
|
19
|
+
Rails. In that case, install it with `gem install safe_cookies`.
|
20
20
|
|
21
21
|
|
22
22
|
### Step 2
|
23
|
-
**Rails 3**: add the following line in config/application.rb:
|
23
|
+
**Rails 3 and 4**: add the following line in config/application.rb:
|
24
24
|
|
25
25
|
class Application < Rails::Application
|
26
26
|
# ...
|
@@ -36,8 +36,8 @@ Though this gem is aimed at Rails applications, you may even use it without Rail
|
|
36
36
|
end
|
37
37
|
|
38
38
|
### Step 3
|
39
|
-
Register cookies, either just after the lines you added
|
40
|
-
(e.g. in `config/initializers/safe_cookies.rb):
|
39
|
+
Register cookies, either just after the lines you added above or in in an initializer
|
40
|
+
(e.g. in `config/initializers/safe_cookies.rb`):
|
41
41
|
|
42
42
|
SafeCookies.configure do |config|
|
43
43
|
config.register_cookie :remember_token, :expire_after => 1.year
|
@@ -46,28 +46,33 @@ Register cookies, either just after the lines you added in step 1 or in in an in
|
|
46
46
|
config.register_cookie :javascript_data, :expire_after => 1.day, :http_only => false
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
If a request has any of those four cookies, the middleware will set them anew. The `remember_token` and
|
50
|
+
`last_action` cookies will be made `secure` and `HttpOnly`.
|
51
|
+
Since we want to access the default language even if the user comes via HTTP, the `default_language`
|
52
|
+
cookie is not made secure. Analogous, the `javascript_data` cookie will be used by a script and hence is
|
53
|
+
not made `HttpOnly`.
|
54
|
+
|
52
55
|
Available options are: `:expire_after (required), :path, :secure, :http_only`.
|
53
56
|
|
54
|
-
### Step 4 (
|
55
|
-
Override `SafeCookies::Middleware#handle_unknown_cookies(cookies)`
|
56
|
-
cookies" below).
|
57
|
+
### Step 4 (important for Rails 2 only)
|
58
|
+
Override `SafeCookies::Middleware#handle_unknown_cookies(cookies)` to notify you
|
59
|
+
e.g. by email (see "Dealing with unregistered cookies" below).
|
57
60
|
|
58
61
|
|
59
62
|
## Dealing with unregistered cookies
|
60
63
|
|
61
|
-
The middleware is not able to secure cookies without knowing their attributes
|
62
|
-
expiry). Unfortunately, [the client won't ever tell us](http://tools.ietf.org/html/rfc6265#section-4.2.2)
|
63
|
-
if it stores the cookie with flags such as "secure" or which expiry date it
|
64
|
-
Therefore, it is important to register all cookies that
|
65
|
-
Unregistered cookies cannot be
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
The middleware is not able to secure cookies without knowing their attributes
|
65
|
+
(most importantly: their expiry). Unfortunately, [the client won't ever tell us](http://tools.ietf.org/html/rfc6265#section-4.2.2)
|
66
|
+
if it stores the cookie with flags such as "secure" or which expiry date it
|
67
|
+
currently has. Therefore, it is important to register all cookies that may be
|
68
|
+
sent by the client, specifying their properties. Unregistered cookies cannot be
|
69
|
+
secured.
|
70
|
+
|
71
|
+
If a request contains a cookie that is not registered, the middleware will raise
|
72
|
+
a `SafeCookies::UnknownCookieError`. Rails 3+ should handle the exception as any
|
73
|
+
other in your application, but by default, **you will not be notified from Rails
|
74
|
+
2 applications** and the user will see a standard 500 Server Error. Override
|
75
|
+
`SafeCookies::Middleware#handle_unknown_cookies(cookies)` in the config
|
71
76
|
initializer for customized exception handling (like, notifying you per email).
|
72
77
|
|
73
78
|
You should register any cookie that your application has to do with. However, there are cookies that you
|
@@ -87,3 +92,15 @@ The configuration option `config.fix_paths` turns on fixing this error. It requi
|
|
87
92
|
point of time from which cookies will be secured with the correct path. The middleware will fix the cookie
|
88
93
|
paths by rewriting all cookies that it has already secured, but only if the were secured before the time
|
89
94
|
you specified.
|
95
|
+
|
96
|
+
|
97
|
+
## Development
|
98
|
+
|
99
|
+
- Tests live in `spec`.
|
100
|
+
- You can run specs from the project root by saying `bundle exec rake`.
|
101
|
+
|
102
|
+
If you would like to contribute:
|
103
|
+
|
104
|
+
- Fork the repository.
|
105
|
+
- Push your changes **with passing specs**.
|
106
|
+
- Send us a pull request.
|
@@ -6,15 +6,14 @@ module SafeCookies
|
|
6
6
|
# (see below), leading to multiple cookies per domain.
|
7
7
|
#
|
8
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
|
10
|
-
# SECURED_COOKIE_NAME helper cookie.
|
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.
|
11
|
+
#
|
11
12
|
# The middleware still sees the request cookies and will rewrite them as
|
12
13
|
# if it hadn't seen them before, setting them on the correct path (root,
|
13
|
-
#
|
14
|
+
# by default).
|
14
15
|
def delete_cookies_on_bad_path
|
15
|
-
|
16
|
-
delete_cookie_for_current_directory(registered_cookie)
|
17
|
-
end
|
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
19
|
# Delete this cookie here, so the middleware believes it hasn't secured
|
@@ -25,17 +24,15 @@ module SafeCookies
|
|
25
24
|
private
|
26
25
|
|
27
26
|
def fix_cookie_paths?
|
28
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
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)
|
33
30
|
end
|
34
31
|
|
35
32
|
# Delete cookies by giving them an expiry in the past,
|
36
33
|
# cf. https://tools.ietf.org/html/rfc6265#section-4.1.2.
|
37
34
|
#
|
38
|
-
# Most
|
35
|
+
# Most importantly, as specified in
|
39
36
|
# https://tools.ietf.org/html/rfc6265#section-4.1.2.4 and in section 5.1.4,
|
40
37
|
# cookies set without a path will be set for the current "directory", that is:
|
41
38
|
#
|
@@ -57,12 +54,18 @@ module SafeCookies
|
|
57
54
|
end
|
58
55
|
|
59
56
|
def secured_old_cookies_timestamp
|
60
|
-
|
57
|
+
@request.cookies.has_key?(SafeCookies::SECURED_COOKIE_NAME) or return nil
|
58
|
+
|
59
|
+
Time.rfc2822(@request.cookies[SafeCookies::SECURED_COOKIE_NAME])
|
61
60
|
rescue ArgumentError
|
62
61
|
# If we cannot parse the secured_old_cookies time,
|
63
62
|
# assume it was before we noticed the bug to ensure
|
64
63
|
# broken cookie paths will be fixed.
|
65
|
-
|
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"
|
66
69
|
end
|
67
70
|
|
68
71
|
end
|
data/lib/safe_cookies/helpers.rb
CHANGED
@@ -3,6 +3,9 @@ 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
|
@@ -41,31 +44,31 @@ module SafeCookies
|
|
41
44
|
options[:secure] = should_be_secure?(name)
|
42
45
|
options[:httponly] = should_be_http_only?(name)
|
43
46
|
|
47
|
+
# Rack magic
|
44
48
|
Rack::Utils.set_cookie_header!(@headers, name, options)
|
45
49
|
end
|
46
50
|
|
47
51
|
|
48
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
|
49
58
|
|
50
59
|
def stored_application_cookie_names
|
51
60
|
store_cookie = @request.cookies[STORE_COOKIE_NAME] || ""
|
52
61
|
store_cookie.split(KNOWN_COOKIES_DIVIDER)
|
53
62
|
end
|
54
63
|
|
55
|
-
|
56
|
-
|
57
|
-
Util.slice(@configuration.registered_cookies, *request_cookies.keys)
|
64
|
+
def rewritable_request_cookies
|
65
|
+
Util.slice(request_cookies, *@config.registered_cookies.keys)
|
58
66
|
end
|
59
67
|
|
60
68
|
def known_cookie_names
|
61
69
|
known = [STORE_COOKIE_NAME, SECURED_COOKIE_NAME]
|
62
70
|
known += stored_application_cookie_names
|
63
|
-
known += @
|
64
|
-
end
|
65
|
-
|
66
|
-
# returns the request cookies minus ignored cookies
|
67
|
-
def request_cookies
|
68
|
-
Util.except!(@request.cookies.dup, *@configuration.ignored_cookies)
|
71
|
+
known += @config.registered_cookies.keys
|
69
72
|
end
|
70
73
|
|
71
74
|
|
@@ -77,7 +80,7 @@ module SafeCookies
|
|
77
80
|
|
78
81
|
def should_be_secure?(cookie)
|
79
82
|
cookie_name = cookie.split('=').first.strip
|
80
|
-
ssl? and not @
|
83
|
+
ssl? and not @config.insecure_cookie?(cookie_name)
|
81
84
|
end
|
82
85
|
|
83
86
|
def ssl?
|
@@ -91,7 +94,7 @@ module SafeCookies
|
|
91
94
|
|
92
95
|
def should_be_http_only?(cookie)
|
93
96
|
cookie_name = cookie.split('=').first.strip
|
94
|
-
not @
|
97
|
+
not @config.scriptable_cookie?(cookie_name)
|
95
98
|
end
|
96
99
|
|
97
100
|
end
|
data/lib/safe_cookies/version.rb
CHANGED
data/lib/safe_cookies.rb
CHANGED
@@ -5,6 +5,7 @@ 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
|
@@ -28,16 +29,16 @@ module SafeCookies
|
|
28
29
|
|
29
30
|
def initialize(app)
|
30
31
|
@app = app
|
31
|
-
@
|
32
|
+
@config = SafeCookies.configuration or raise "Don't know what to do without configuration"
|
32
33
|
end
|
33
34
|
|
34
35
|
def call(env)
|
35
36
|
reset_instance_variables
|
36
37
|
|
37
38
|
@request = Rack::Request.new(env)
|
38
|
-
|
39
|
+
check_if_request_has_unknown_cookies
|
39
40
|
|
40
|
-
#
|
41
|
+
# call the next middleware up the stack
|
41
42
|
status, @headers, body = @app.call(env)
|
42
43
|
cache_application_cookies_string
|
43
44
|
|
@@ -52,13 +53,14 @@ module SafeCookies
|
|
52
53
|
|
53
54
|
private
|
54
55
|
|
56
|
+
# Instance variables survive requests because the middleware is a singleton.
|
55
57
|
def reset_instance_variables
|
56
58
|
@request, @headers, @application_cookies_string = nil
|
57
59
|
end
|
58
60
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
def
|
61
|
+
# Do something if a request has an unregistered cookie, because we do not
|
62
|
+
# want any cookie to not be secured. By default, we raise an error.
|
63
|
+
def check_if_request_has_unknown_cookies
|
62
64
|
request_cookie_names = request_cookies.keys.map(&:to_s)
|
63
65
|
unknown_cookie_names = request_cookie_names - known_cookie_names
|
64
66
|
|
@@ -67,7 +69,7 @@ module SafeCookies
|
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
70
|
-
# Overwrites @header['Set-Cookie']
|
72
|
+
# Overwrites @header['Set-Cookie']!
|
71
73
|
def enhance_application_cookies!
|
72
74
|
if @application_cookies_string
|
73
75
|
cookies = @application_cookies_string.split("\n")
|
@@ -90,8 +92,8 @@ module SafeCookies
|
|
90
92
|
end
|
91
93
|
end
|
92
94
|
|
93
|
-
# Store the names of cookies that are set by the application.
|
94
|
-
# securing those and
|
95
|
+
# Store the names of cookies that are set by the application.
|
96
|
+
# (We are already securing those and will not need to rewrite them.)
|
95
97
|
def store_application_cookie_names
|
96
98
|
if @application_cookies_string
|
97
99
|
application_cookie_names = stored_application_cookie_names + @application_cookies_string.scan(COOKIE_NAME_REGEX)
|
@@ -107,23 +109,21 @@ module SafeCookies
|
|
107
109
|
# With the SECURED_COOKIE_NAME cookie we remember the exact time that we
|
108
110
|
# rewrote the cookies.
|
109
111
|
def rewrite_request_cookies
|
110
|
-
|
112
|
+
rewritable_cookies = rewritable_request_cookies
|
111
113
|
|
112
114
|
# don't rewrite request cookies that the application is setting in the response
|
113
115
|
if @application_cookies_string
|
114
|
-
|
115
|
-
Util.except!(
|
116
|
+
app_cookie_names = @application_cookies_string.scan(COOKIE_NAME_REGEX)
|
117
|
+
Util.except!(rewritable_cookies, *app_cookie_names)
|
116
118
|
end
|
117
119
|
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
|
120
|
+
if rewritable_cookies.any?
|
121
|
+
rewritable_cookies.each do |cookie_name, value|
|
122
|
+
options = @config.registered_cookies[cookie_name]
|
122
123
|
set_cookie!(cookie_name, value, options)
|
123
124
|
end
|
124
125
|
|
125
|
-
|
126
|
-
set_cookie!(SECURED_COOKIE_NAME, formatted_now, :expire_after => HELPER_COOKIES_LIFETIME)
|
126
|
+
set_cookie!(SECURED_COOKIE_NAME, Time.now.gmtime.rfc2822, :expire_after => HELPER_COOKIES_LIFETIME)
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -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
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
data/spec/safe_cookies_spec.rb
CHANGED
@@ -96,7 +96,7 @@ describe SafeCookies::Middleware do
|
|
96
96
|
env['PATH_INFO'] = '/special/path/subfolder'
|
97
97
|
|
98
98
|
code, headers, response = subject.call(env)
|
99
|
-
expected_expiry =
|
99
|
+
expected_expiry = (Time.now + 3600).gmtime.rfc2822 # a special date format needed here
|
100
100
|
headers['Set-Cookie'].should =~ %r(foo=bar; path=/special/path; expires=#{expected_expiry}; secure; HttpOnly)
|
101
101
|
end
|
102
102
|
|
@@ -208,7 +208,7 @@ describe SafeCookies::Middleware do
|
|
208
208
|
|
209
209
|
end
|
210
210
|
|
211
|
-
context '
|
211
|
+
context 'when a request has unknown cookies,' do
|
212
212
|
|
213
213
|
it 'raises an error if there is an unknown cookie' do
|
214
214
|
set_request_cookies(env, 'foo=bar')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_cookies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
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-
|
11
|
+
date: 2013-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|