validas-encrypted_cookie_store 0.4.1 → 0.4.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.
data/README.md
CHANGED
@@ -6,11 +6,7 @@ session data in a cookie), but it uses encryption so that people can't read
|
|
6
6
|
what's in the session data. This makes it possible to store sensitive data
|
7
7
|
in the session.
|
8
8
|
|
9
|
-
This version of EncryptedCookieStore is written for Rails 3.
|
10
|
-
|
11
|
-
* 3.0.0
|
12
|
-
* 3.0.7
|
13
|
-
* 3.0.8.rc4
|
9
|
+
This version of EncryptedCookieStore is written for Rails 3.1.0+. It will not work with Rails 3.0.0 or earlier. It does work with Rails 3.2.
|
14
10
|
|
15
11
|
The original version for Rails 2.3 can be found here: https://github.com/FooBarWidget/encrypted_cookie_store
|
16
12
|
|
@@ -21,17 +17,17 @@ Installation and usage
|
|
21
17
|
|
22
18
|
First, install it:
|
23
19
|
|
24
|
-
gem install
|
20
|
+
gem install validas-encrypted_cookie_store
|
25
21
|
|
26
22
|
Then, add it to you bundler Gemfile:
|
27
23
|
|
28
|
-
gem '
|
24
|
+
gem 'validas-encrypted_cookie_store', :require => 'encrypted_cookie_store'
|
29
25
|
|
30
26
|
Then edit `config/initializers/session_store.rb` and set your session store to
|
31
27
|
EncryptedCookieStore:
|
32
28
|
|
33
29
|
MyApp::Application.config.session_store(
|
34
|
-
|
30
|
+
ApplicationDispatch::Session::EncryptedCookieStore,
|
35
31
|
:key => '_myapp_session',
|
36
32
|
:encryption_key => '966a4....'
|
37
33
|
)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.2
|
@@ -1,145 +1,124 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
3
|
+
require 'rack/session/cookie'
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Session
|
7
|
+
class EncryptedCookieStore < Rack::Session::Cookie
|
8
|
+
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
|
9
|
+
|
10
|
+
ENCRYPTION_KEY_SIZE = 32
|
11
|
+
include Compatibility
|
12
|
+
include StaleSessionCheck
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :iv_cipher_type
|
16
|
+
attr_accessor :data_cipher_type
|
17
|
+
end
|
8
18
|
|
9
|
-
|
10
|
-
|
11
|
-
attr_accessor :data_cipher_type
|
12
|
-
end
|
19
|
+
self.iv_cipher_type = "aes-128-ecb".freeze
|
20
|
+
self.data_cipher_type = "aes-256-cfb".freeze
|
13
21
|
|
14
|
-
|
15
|
-
|
22
|
+
def initialize(app, options = {})
|
23
|
+
ensure_encryption_key_secure(options[:encryption_key])
|
24
|
+
@encryption_key = unhex(options[:encryption_key]).freeze
|
25
|
+
@iv_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.iv_cipher_type)
|
26
|
+
@data_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.data_cipher_type)
|
27
|
+
super(app, options)
|
28
|
+
end
|
16
29
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
30
|
+
# Override rack's method
|
31
|
+
def destroy_session(env, session_id, options)
|
32
|
+
new_sid = super
|
33
|
+
# Reset hash and Assign the new session id
|
34
|
+
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
|
35
|
+
new_sid
|
36
|
+
end
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
private
|
39
|
+
def unpacked_cookie_data(env)
|
40
|
+
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
|
41
|
+
stale_session_check! do
|
42
|
+
request = ActionDispatch::Request.new(env)
|
43
|
+
if data = request.cookie_jar.signed[@key]
|
44
|
+
data = unmarshal(data)
|
45
|
+
end
|
46
|
+
|
47
|
+
data || {}
|
48
|
+
end
|
49
|
+
end
|
31
50
|
end
|
32
51
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
52
|
+
def set_session(env, sid, session_data, options={})
|
53
|
+
session_data["session_id"] = sid
|
54
|
+
|
55
|
+
@iv_cipher.encrypt
|
56
|
+
@data_cipher.encrypt
|
57
|
+
@iv_cipher.key = @encryption_key
|
58
|
+
@data_cipher.key = @encryption_key
|
59
|
+
|
60
|
+
iv = @data_cipher.random_iv
|
61
|
+
@data_cipher.iv = iv
|
62
|
+
encrypted_iv = @iv_cipher.update(iv) << @iv_cipher.final
|
63
|
+
encrypted_session_data = @data_cipher.update(Marshal.dump(session_data)) << @data_cipher.final
|
64
|
+
|
65
|
+
"#{base64(encrypted_iv)}--#{base64(encrypted_session_data)}"
|
40
66
|
end
|
41
67
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
"#{digest}--#{data}"
|
68
|
+
def set_cookie(env, session_id, cookie)
|
69
|
+
request = ActionDispatch::Request.new(env)
|
70
|
+
request.cookie_jar.signed[@key] = cookie
|
46
71
|
end
|
47
72
|
|
48
73
|
private
|
49
|
-
def
|
50
|
-
|
74
|
+
def base64(data)
|
75
|
+
::Base64.strict_encode64(data)
|
51
76
|
end
|
52
|
-
end
|
53
77
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# - changes to the encryption key or initialization vector
|
58
|
-
# - a migration from the unencrypted CookieStore.
|
59
|
-
#
|
60
|
-
# Being able to detect these allows us to invalidate the old session data.
|
61
|
-
|
62
|
-
@iv_cipher.encrypt
|
63
|
-
@data_cipher.encrypt
|
64
|
-
@iv_cipher.key = @encryption_key
|
65
|
-
@data_cipher.key = @encryption_key
|
66
|
-
|
67
|
-
clear_session_data = super(env, sid, session_data, {})
|
68
|
-
iv = @data_cipher.random_iv
|
69
|
-
@data_cipher.iv = iv
|
70
|
-
encrypted_iv = @iv_cipher.update(iv) << @iv_cipher.final
|
71
|
-
encrypted_session_data = @data_cipher.update(Marshal.dump(clear_session_data)) << @data_cipher.final
|
72
|
-
|
73
|
-
"#{base64(encrypted_iv)}--#{base64(encrypted_session_data)}"
|
74
|
-
end
|
78
|
+
def unhex(hex_data)
|
79
|
+
[hex_data].pack("H*")
|
80
|
+
end
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
def unmarshal(cookie)
|
83
|
+
if cookie
|
84
|
+
b64_encrypted_iv, b64_encrypted_session_data = cookie.split("--", 2)
|
85
|
+
if b64_encrypted_iv && b64_encrypted_session_data
|
86
|
+
encrypted_iv = ::Base64.strict_decode64(b64_encrypted_iv)
|
87
|
+
encrypted_session_data = ::Base64.strict_decode64(b64_encrypted_session_data)
|
88
|
+
|
89
|
+
@iv_cipher.decrypt
|
90
|
+
@iv_cipher.key = @encryption_key
|
91
|
+
iv = @iv_cipher.update(encrypted_iv) << @iv_cipher.final
|
92
|
+
|
93
|
+
@data_cipher.decrypt
|
94
|
+
@data_cipher.key = @encryption_key
|
95
|
+
@data_cipher.iv = iv
|
96
|
+
Marshal.load(@data_cipher.update(encrypted_session_data) << @data_cipher.final) rescue nil
|
84
97
|
end
|
98
|
+
else
|
99
|
+
nil
|
85
100
|
end
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
def unmarshal(cookie)
|
90
|
-
if cookie
|
91
|
-
b64_encrypted_iv, b64_encrypted_session_data = cookie.split("--", 2)
|
92
|
-
if b64_encrypted_iv && b64_encrypted_session_data
|
93
|
-
encrypted_iv = ::Base64.strict_decode64(b64_encrypted_iv)
|
94
|
-
encrypted_session_data = ::Base64.strict_decode64(b64_encrypted_session_data)
|
95
|
-
|
96
|
-
@iv_cipher.decrypt
|
97
|
-
@iv_cipher.key = @encryption_key
|
98
|
-
iv = @iv_cipher.update(encrypted_iv) << @iv_cipher.final
|
99
|
-
|
100
|
-
@data_cipher.decrypt
|
101
|
-
@data_cipher.key = @encryption_key
|
102
|
-
@data_cipher.iv = iv
|
103
|
-
session_data = Marshal.load(@data_cipher.update(encrypted_session_data) << @data_cipher.final) rescue nil
|
104
|
-
end
|
105
|
-
else
|
101
|
+
rescue OpenSSLCipherError
|
106
102
|
nil
|
107
103
|
end
|
108
|
-
rescue OpenSSLCipherError
|
109
|
-
nil
|
110
|
-
end
|
111
104
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
"#{ENCRYPTION_KEY_SIZE * 2} bytes\", ... } in config/environment.rb"
|
120
|
-
end
|
105
|
+
def ensure_encryption_key_secure(encryption_key)
|
106
|
+
if encryption_key.blank?
|
107
|
+
raise ArgumentError, "An encryption key is required for encrypting the " +
|
108
|
+
"cookie session data. Please set config.action_controller.session = { " +
|
109
|
+
"..., :encryption_key => \"some random string of exactly " +
|
110
|
+
"#{ENCRYPTION_KEY_SIZE * 2} bytes\", ... } in config/environment.rb"
|
111
|
+
end
|
121
112
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
113
|
+
if encryption_key.size != ENCRYPTION_KEY_SIZE * 2
|
114
|
+
raise ArgumentError, "The EncryptedCookieStore encryption key must be a " +
|
115
|
+
"hexadecimal string of exactly #{ENCRYPTION_KEY_SIZE * 2} bytes. " +
|
116
|
+
"The value that you've provided, \"#{encryption_key}\", is " +
|
117
|
+
"#{encryption_key.size} bytes. You could use the following (randomly " +
|
118
|
+
"generated) string as encryption key: " +
|
119
|
+
SecureRandom.hex(ENCRYPTION_KEY_SIZE)
|
120
|
+
end
|
129
121
|
end
|
130
122
|
end
|
131
|
-
|
132
|
-
def verifier_for(secret, digest)
|
133
|
-
key = secret.respond_to?(:call) ? secret.call : secret
|
134
|
-
MessageVerifier.new(key, digest)
|
135
|
-
end
|
136
|
-
|
137
|
-
def base64(data)
|
138
|
-
::Base64.strict_encode64(data)
|
139
|
-
end
|
140
|
-
|
141
|
-
def unhex(hex_data)
|
142
|
-
[hex_data].pack("H*")
|
143
|
-
end
|
144
123
|
end
|
145
124
|
end
|
@@ -5,84 +5,84 @@ require 'rails'
|
|
5
5
|
require 'action_controller'
|
6
6
|
require 'encrypted_cookie_store'
|
7
7
|
|
8
|
-
describe
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def create(options = {})
|
16
|
-
EncryptedCookieStore::EncryptedCookieStore.new(nil, options.reverse_merge(
|
17
|
-
:key => 'key',
|
18
|
-
:secret => SECRET
|
19
|
-
))
|
20
|
-
end
|
21
|
-
|
22
|
-
it "checks whether an encryption key is given" do
|
23
|
-
lambda { create }.should raise_error(ArgumentError, /encryption key is required/)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "checks whether the encryption key has the correct size" do
|
27
|
-
encryption_key = "too small"
|
28
|
-
block = lambda { create(:encryption_key => encryption_key) }
|
29
|
-
block.should raise_error(ArgumentError, /must be a hexadecimal string of exactly \d+ bytes/)
|
30
|
-
end
|
31
|
-
|
32
|
-
specify "marshalling and unmarshalling data works" do
|
33
|
-
data = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:set_session, nil, nil, OBJECT)
|
34
|
-
object = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:unmarshal, data)
|
35
|
-
object[:user_id].should == 123
|
36
|
-
object[:admin].should be_true
|
37
|
-
object[:message].should == "hello world!"
|
38
|
-
end
|
39
|
-
|
40
|
-
it "uses a different initialization vector every time data is marshalled" do
|
41
|
-
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
42
|
-
data1 = store.send(:set_session, nil, nil, OBJECT)
|
43
|
-
data2 = store.send(:set_session, nil, nil, OBJECT)
|
44
|
-
data3 = store.send(:set_session, nil, nil, OBJECT)
|
45
|
-
data4 = store.send(:set_session, nil, nil, OBJECT)
|
46
|
-
data1.should_not == data2
|
47
|
-
data1.should_not == data3
|
48
|
-
data1.should_not == data4
|
49
|
-
end
|
50
|
-
|
51
|
-
it "invalidates the data if the encryption key is changed" do
|
52
|
-
data = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:set_session, nil, nil, OBJECT)
|
53
|
-
object = create(:encryption_key => ANOTHER_GOOD_ENCRYPTION_KEY).send(:unmarshal, data)
|
54
|
-
object.should be_nil
|
55
|
-
end
|
56
|
-
|
57
|
-
it "invalidates the data if the IV cannot be decrypted" do
|
58
|
-
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
59
|
-
data = store.send(:set_session, nil, nil, OBJECT)
|
60
|
-
iv_cipher = store.instance_variable_get(:@iv_cipher)
|
61
|
-
iv_cipher.should_receive(:update).and_raise(EncryptedCookieStore::EncryptedCookieStore::OpenSSLCipherError)
|
62
|
-
store.send(:unmarshal, data).should be_nil
|
63
|
-
end
|
8
|
+
describe ActionDispatch::Session::EncryptedCookieStore do
|
9
|
+
SECRET = "b6a30e998806a238c4bad45cc720ed55e56e50d9f00fff58552e78a20fe8262df61" <<
|
10
|
+
"42fcfdb0676018bb9767ed560d4a624fb7f3603b4e53c77ec189ae3853bd1"
|
11
|
+
GOOD_ENCRYPTION_KEY = "dd458e790c3b995e3606384c58efc53da431db892f585aa3ca2a17eabe6df75b"
|
12
|
+
ANOTHER_GOOD_ENCRYPTION_KEY = "ce6a45c34607d2048d735b0a31a769de4e1512eb83c7012059a66937158a8975"
|
13
|
+
OBJECT = { :user_id => 123, :admin => true, :message => "hello world!" }
|
64
14
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
15
|
+
def create(options = {})
|
16
|
+
ActionDispatch::Session::EncryptedCookieStore.new(nil, options.reverse_merge(
|
17
|
+
:key => 'key',
|
18
|
+
:secret => SECRET
|
19
|
+
))
|
20
|
+
end
|
21
|
+
|
22
|
+
it "checks whether an encryption key is given" do
|
23
|
+
lambda { create }.should raise_error(ArgumentError, /encryption key is required/)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "checks whether the encryption key has the correct size" do
|
27
|
+
encryption_key = "too small"
|
28
|
+
block = lambda { create(:encryption_key => encryption_key) }
|
29
|
+
block.should raise_error(ArgumentError, /must be a hexadecimal string of exactly \d+ bytes/)
|
30
|
+
end
|
31
|
+
|
32
|
+
specify "marshalling and unmarshalling data works" do
|
33
|
+
data = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:set_session, nil, nil, OBJECT)
|
34
|
+
object = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:unmarshal, data)
|
35
|
+
object[:user_id].should == 123
|
36
|
+
object[:admin].should be_true
|
37
|
+
object[:message].should == "hello world!"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "uses a different initialization vector every time data is marshalled" do
|
41
|
+
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
42
|
+
data1 = store.send(:set_session, nil, nil, OBJECT)
|
43
|
+
data2 = store.send(:set_session, nil, nil, OBJECT)
|
44
|
+
data3 = store.send(:set_session, nil, nil, OBJECT)
|
45
|
+
data4 = store.send(:set_session, nil, nil, OBJECT)
|
46
|
+
data1.should_not == data2
|
47
|
+
data1.should_not == data3
|
48
|
+
data1.should_not == data4
|
49
|
+
end
|
50
|
+
|
51
|
+
it "invalidates the data if the encryption key is changed" do
|
52
|
+
data = create(:encryption_key => GOOD_ENCRYPTION_KEY).send(:set_session, nil, nil, OBJECT)
|
53
|
+
object = create(:encryption_key => ANOTHER_GOOD_ENCRYPTION_KEY).send(:unmarshal, data)
|
54
|
+
object.should be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it "invalidates the data if the IV cannot be decrypted" do
|
58
|
+
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
59
|
+
data = store.send(:set_session, nil, nil, OBJECT)
|
60
|
+
iv_cipher = store.instance_variable_get(:@iv_cipher)
|
61
|
+
iv_cipher.should_receive(:update).and_raise(ActionDispatch::Session::EncryptedCookieStore::OpenSSLCipherError)
|
62
|
+
store.send(:unmarshal, data).should be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# FIXME: This test case is broken. The super classes' structure have changed since Rails 3.0.0.beta3
|
66
|
+
# and we're no longer getting a string to unmarshal, since this have been pushed up to rack.
|
67
|
+
# it "invalidates the data if we just migrated from CookieStore", :focus => true do
|
68
|
+
# old_store = ActionDispatch::Session::CookieStore.new(nil, :key => 'key', :secret => SECRET)
|
69
|
+
# legacy_data = old_store.send(:set_session, nil, nil, OBJECT, {})
|
70
|
+
# store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
71
|
+
# store.send(:unmarshal, legacy_data).should be_nil
|
72
|
+
# end
|
73
|
+
|
74
|
+
it "invalidates the data if it was tampered with" do
|
75
|
+
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
76
|
+
data = store.send(:set_session, nil, nil, OBJECT)
|
77
|
+
b64_encrypted_iv, b64_encrypted_session_data = data.split("--", 2)
|
78
|
+
b64_encrypted_session_data[0..1] = "AA"
|
79
|
+
data = "#{b64_encrypted_iv}--#{b64_encrypted_session_data}"
|
80
|
+
store.send(:unmarshal, data).should be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it "invalidates the data if it looks like garbage" do
|
84
|
+
store = create(:encryption_key => GOOD_ENCRYPTION_KEY)
|
85
|
+
garbage = "\202d\3477 jTf\274\360\200z\355\334N3\001\0036\321qLu\027\320\325*%:%\270D"
|
86
|
+
store.send(:unmarshal, garbage).should be_nil
|
87
|
+
end
|
88
88
|
end
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "validas-encrypted_cookie_store"
|
8
|
-
s.version = "0.4.
|
8
|
+
s.version = "0.4.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["FooBarWidget", "Scott W. Bradley", "Validas Engineering Team"]
|
12
|
-
s.date = "2012-10-
|
12
|
+
s.date = "2012-10-31"
|
13
13
|
s.description = "A Rails 3.1 & 3.2 version of Encrypted Cookie Store by FooBarWidget"
|
14
14
|
s.email = "scottwb@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,7 +26,6 @@ Gem::Specification.new do |s|
|
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"lib/encrypted_cookie_store.rb",
|
29
|
-
"lib/encrypted_cookie_store/constants.rb",
|
30
29
|
"lib/encrypted_cookie_store/encrypted_cookie_store.rb",
|
31
30
|
"lib/encrypted_cookie_store/railtie.rb",
|
32
31
|
"lib/tasks/encrypted_cookie_store.rake",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validas-encrypted_cookie_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-10-
|
14
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rails
|
@@ -110,7 +110,6 @@ files:
|
|
110
110
|
- Rakefile
|
111
111
|
- VERSION
|
112
112
|
- lib/encrypted_cookie_store.rb
|
113
|
-
- lib/encrypted_cookie_store/constants.rb
|
114
113
|
- lib/encrypted_cookie_store/encrypted_cookie_store.rb
|
115
114
|
- lib/encrypted_cookie_store/railtie.rb
|
116
115
|
- lib/tasks/encrypted_cookie_store.rake
|