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.
@@ -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
- def initialize(password, file_path)
7
+ class ReadOnly < RuntimeError; end
8
+
9
+ def initialize(password, file_path, options = {})
7
10
  self.password = password
8
- @data = YamlBackend.new(file_path)
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.puts YAML.dump(data)
150
+ f.write YAML.dump(data)
151
+ reset_mtime_tracker
152
+ true
123
153
  end
124
- true
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
@@ -1,3 +1,3 @@
1
1
  class SecretStore
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -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.3
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-11 00:00:00.000000000 Z
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: &2153518960 !ruby/object:Gem::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: *2153518960
24
+ version_requirements: *2161587900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &2153518540 !ruby/object:Gem::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: *2153518540
35
+ version_requirements: *2161587480
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &2153518120 !ruby/object:Gem::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: *2153518120
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: -2358538595218873167
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: -2358538595218873167
86
+ hash: -2410818951335814899
87
87
  requirements: []
88
88
  rubyforge_project: secret_store
89
89
  rubygems_version: 1.8.15