secret_store 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/secret_store.rb +57 -6
- data/lib/secret_store/version.rb +1 -1
- data/spec/secret_store_spec.rb +46 -1
- metadata +10 -10
data/lib/secret_store.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
require "gibberish"
|
2
1
|
require "yaml"
|
2
|
+
require "timeout"
|
3
|
+
require "gibberish"
|
3
4
|
require "secret_store/version"
|
4
5
|
|
5
6
|
class SecretStore
|
6
|
-
|
7
|
+
class ReadOnly < RuntimeError; end
|
8
|
+
|
9
|
+
def initialize(password, file_path, options = {})
|
7
10
|
self.password = password
|
8
|
-
|
11
|
+
backend_class = options.fetch(:backend_class, YamlBackend)
|
12
|
+
@data = backend_class.new(file_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new_read_only(password, file_path)
|
16
|
+
store = new(password, file_path, :backend_class => ReadOnlyYamlBackend)
|
17
|
+
store
|
9
18
|
end
|
10
19
|
|
11
20
|
def store(key, secret)
|
@@ -31,6 +40,10 @@ class SecretStore
|
|
31
40
|
end
|
32
41
|
|
33
42
|
def change_password(new_password)
|
43
|
+
unless @data.permits_writes?
|
44
|
+
raise ReadOnly
|
45
|
+
end
|
46
|
+
|
34
47
|
decrypted = decrypted_data
|
35
48
|
self.password = new_password
|
36
49
|
replace_with_decrypted(decrypted)
|
@@ -61,7 +74,7 @@ class SecretStore
|
|
61
74
|
end
|
62
75
|
|
63
76
|
class YamlBackend
|
64
|
-
SAVE_FLAGS = File::RDWR | File::CREAT | File::LOCK_EX
|
77
|
+
SAVE_FLAGS = File::RDWR | File::CREAT | File::LOCK_EX | File::LOCK_NB
|
65
78
|
SAVE_PERMS = 0640
|
66
79
|
|
67
80
|
def initialize(file_path)
|
@@ -69,10 +82,12 @@ class SecretStore
|
|
69
82
|
end
|
70
83
|
|
71
84
|
def [](key)
|
85
|
+
reload_if_updated
|
72
86
|
data[key.to_s]
|
73
87
|
end
|
74
88
|
|
75
89
|
def keys
|
90
|
+
reload_if_updated
|
76
91
|
data.keys
|
77
92
|
end
|
78
93
|
|
@@ -102,12 +117,23 @@ class SecretStore
|
|
102
117
|
data && true
|
103
118
|
end
|
104
119
|
|
120
|
+
def permits_writes?
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
105
124
|
private
|
106
125
|
|
107
126
|
def delete!(key)
|
127
|
+
reset_mtime_tracker
|
108
128
|
data.delete(key.to_s)
|
109
129
|
end
|
110
130
|
|
131
|
+
def reload_if_updated
|
132
|
+
mtime = File.exists?(@file_path) && File.mtime(@file_path)
|
133
|
+
@mtime_tracker ||= mtime
|
134
|
+
@mtime_tracker != mtime && reload
|
135
|
+
end
|
136
|
+
|
111
137
|
def data
|
112
138
|
begin
|
113
139
|
@data ||= YAML.load_file(@file_path) || {}
|
@@ -117,11 +143,36 @@ class SecretStore
|
|
117
143
|
end
|
118
144
|
|
119
145
|
def save
|
146
|
+
#TODO figure out the WRONLY flag so we don't
|
147
|
+
# have to truncate
|
120
148
|
File.open(@file_path, SAVE_FLAGS, SAVE_PERMS) do |f|
|
121
149
|
f.truncate(0)
|
122
|
-
f.
|
150
|
+
f.write YAML.dump(data)
|
151
|
+
reset_mtime_tracker
|
152
|
+
true
|
123
153
|
end
|
124
|
-
|
154
|
+
end
|
155
|
+
|
156
|
+
def reset_mtime_tracker
|
157
|
+
@mtime_tracker = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class ReadOnlyYamlBackend < YamlBackend
|
162
|
+
[:insert, :overwrite, :delete, :save].each do |meth|
|
163
|
+
define_method meth do |*|
|
164
|
+
raise ReadOnly
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def permits_writes?
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def reload_if_updated
|
175
|
+
#no-op
|
125
176
|
end
|
126
177
|
end
|
127
178
|
end
|
data/lib/secret_store/version.rb
CHANGED
data/spec/secret_store_spec.rb
CHANGED
@@ -6,6 +6,12 @@ describe SecretStore, "initializing" do
|
|
6
6
|
it "takes a password and file path" do
|
7
7
|
subject = SecretStore.new("pass", tmpfile.path)
|
8
8
|
end
|
9
|
+
|
10
|
+
it "can be optionally injected with a backend class" do
|
11
|
+
klass = Class.new
|
12
|
+
klass.should_receive(:new)
|
13
|
+
subject = SecretStore.new("pass", tmpfile.path, :backend_class => klass)
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
describe SecretStore, "storing a secret" do
|
@@ -157,7 +163,46 @@ describe SecretStore::YamlBackend do
|
|
157
163
|
|
158
164
|
it "can save the data" do
|
159
165
|
subject.overwrite("foo", "123")
|
160
|
-
data = SecretStore::YamlBackend.new(tmpfile)
|
166
|
+
data = SecretStore::YamlBackend.new(tmpfile.path)
|
161
167
|
data["foo"].should == "123"
|
162
168
|
end
|
169
|
+
|
170
|
+
it "will auto reload the data if the file mtime changes" do
|
171
|
+
subject["foo"].should == "bar"
|
172
|
+
SecretStore::YamlBackend.new(tmpfile.path).overwrite("foo", "changed")
|
173
|
+
FileUtils.touch(tmpfile.path, :mtime => Time.now + 5)
|
174
|
+
subject["foo"].should == "changed"
|
175
|
+
end
|
176
|
+
|
177
|
+
it "can create a file that doesn't exist" do
|
178
|
+
path = tmpfile.path
|
179
|
+
tmpfile.unlink
|
180
|
+
store = SecretStore::YamlBackend.new(path)
|
181
|
+
store["foo"].should be_nil
|
182
|
+
store.insert("foo", "bar")
|
183
|
+
store["foo"].should == "bar"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe SecretStore::ReadOnlyYamlBackend do
|
188
|
+
let(:tmpfile) { Tempfile.new("secret_store") }
|
189
|
+
subject { SecretStore::ReadOnlyYamlBackend.new(tmpfile) }
|
190
|
+
|
191
|
+
it "doesn't allow inserts" do
|
192
|
+
lambda {
|
193
|
+
subject.insert("foo", "bar")
|
194
|
+
}.should raise_error(SecretStore::ReadOnly)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "doesn't allow overwrites" do
|
198
|
+
lambda {
|
199
|
+
subject.overwrite("foo", "bar")
|
200
|
+
}.should raise_error(SecretStore::ReadOnly)
|
201
|
+
end
|
202
|
+
|
203
|
+
it "doesn't allow deletes" do
|
204
|
+
lambda {
|
205
|
+
subject.delete("foo")
|
206
|
+
}.should raise_error(SecretStore::ReadOnly)
|
207
|
+
end
|
163
208
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secret_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: gibberish
|
16
|
-
requirement: &
|
16
|
+
requirement: &2161587900 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2161587900
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &2161587480 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2161587480
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &2161587060 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2161587060
|
47
47
|
description: Store secrets for your app in a encrypted in a yaml file.
|
48
48
|
email:
|
49
49
|
- xternal1+github@gmail.com
|
@@ -74,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
74
|
version: '0'
|
75
75
|
segments:
|
76
76
|
- 0
|
77
|
-
hash: -
|
77
|
+
hash: -2410818951335814899
|
78
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
79
|
none: false
|
80
80
|
requirements:
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
version: '0'
|
84
84
|
segments:
|
85
85
|
- 0
|
86
|
-
hash: -
|
86
|
+
hash: -2410818951335814899
|
87
87
|
requirements: []
|
88
88
|
rubyforge_project: secret_store
|
89
89
|
rubygems_version: 1.8.15
|