secret_store 0.0.3 → 0.0.4

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.
@@ -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