ruby-keepassx 0.2.0beta11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +9 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/README.md +28 -0
- data/Rakefile +5 -0
- data/lib/keepassx.rb +56 -0
- data/lib/keepassx/aes_crypt.rb +19 -0
- data/lib/keepassx/database.rb +431 -0
- data/lib/keepassx/entry.rb +155 -0
- data/lib/keepassx/entry_field.rb +27 -0
- data/lib/keepassx/exceptions.rb +4 -0
- data/lib/keepassx/field.rb +205 -0
- data/lib/keepassx/group.rb +138 -0
- data/lib/keepassx/group_field.rb +22 -0
- data/lib/keepassx/header.rb +178 -0
- data/lib/keepassx/item.rb +128 -0
- data/lib/keepassx/utilities.rb +226 -0
- data/ruby-keepassx.gemspec +23 -0
- data/spec/fixtures/test_data_array.yaml +54 -0
- data/spec/fixtures/test_data_hash.yaml +36 -0
- data/spec/fixtures/test_database.kdb +0 -0
- data/spec/keepassx/database_spec.rb +338 -0
- data/spec/keepassx/entry_spec.rb +72 -0
- data/spec/keepassx/group_spec.rb +47 -0
- data/spec/spec_helper.rb +64 -0
- metadata +126 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'ruby-keepassx'
|
3
|
+
s.summary = 'Ruby API access for KeePassX databases'
|
4
|
+
s.description = 'This is fork of Tony Pitluga\'s ' \
|
5
|
+
'Ruby API for keepassx with read-write support.'
|
6
|
+
s.version = '0.2.0beta11'
|
7
|
+
s.authors = ['Tony Pitluga', 'Paul Hinze', 'Tio Teath']
|
8
|
+
s.email = ['tony.pitluga@gmail.com', 'paul.t.hinze@gmail.com',
|
9
|
+
'tio.teath@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/tioteath/ruby-keepassx.git'
|
11
|
+
s.files = `git ls-files`.split "\n"
|
12
|
+
|
13
|
+
# TODO: This won't work, figure out why.
|
14
|
+
if RUBY_VERSION =~ /1\.8/
|
15
|
+
s.add_dependency 'backports', '~> 3.6.0'
|
16
|
+
end
|
17
|
+
|
18
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
19
|
+
s.add_development_dependency 'pry', '~> 0.9.12'
|
20
|
+
s.add_development_dependency 'rake', '~> 0.8', '>= 0.8.7'
|
21
|
+
# s.add_development_dependency 'respect', '~> 0.1.1'
|
22
|
+
# s.add_development_dependency 'rspec-prof', '~> 0'
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
---
|
2
|
+
- :title: :Internet
|
3
|
+
:icon: 1
|
4
|
+
:groups:
|
5
|
+
- :title: :Web
|
6
|
+
:icon: 61
|
7
|
+
:groups:
|
8
|
+
- :title: :Wikipedia
|
9
|
+
:icon: 54
|
10
|
+
:groups:
|
11
|
+
- :title: :Amazon
|
12
|
+
:icon: 30
|
13
|
+
:entries:
|
14
|
+
- :title: test entry2
|
15
|
+
:username: testuser
|
16
|
+
:url: http://example.com/testurl
|
17
|
+
:password: testpassword
|
18
|
+
:comment: test comment
|
19
|
+
:group: :Web
|
20
|
+
:uuid: f720371d-15f1-4b9b-918e-68bb83e4407e
|
21
|
+
:lastmod: 2008-09-07 23:40:43
|
22
|
+
:lastaccess: 2008-09-07 23:40:43
|
23
|
+
:creation: 2008-09-07 23:40:43
|
24
|
+
- :title: test entry3
|
25
|
+
:username: testuser3
|
26
|
+
:url: http://example.com/testurl3
|
27
|
+
:password: testpassword3
|
28
|
+
:comment: test comment3
|
29
|
+
:group: :Web
|
30
|
+
:uuid: e720371d-15f1-4c9b-918e-68bb83e4407e
|
31
|
+
:lastmod: 2008-09-07 23:43:43
|
32
|
+
:lastaccess: 2008-09-07 23:20:43
|
33
|
+
:creation: 2008-09-07 23:50:43
|
34
|
+
:entries:
|
35
|
+
- :title: test entry
|
36
|
+
:username: testuser
|
37
|
+
:url: http://example.com/testurl
|
38
|
+
:password: testpassword
|
39
|
+
:comment: test comment
|
40
|
+
:uuid: 2d3fae36-e89f-48bd-8900-389cc01ef248
|
41
|
+
:lastmod: 2008-09-03 23:40:43
|
42
|
+
:lastaccess: 2008-09-03 23:40:43
|
43
|
+
:creation: 2008-09-03 23:40:43
|
44
|
+
- :title: entry2
|
45
|
+
:username: user
|
46
|
+
:url: http://example.com
|
47
|
+
:password: pass2
|
48
|
+
:comment: comment
|
49
|
+
:uuid: 866216ea-3a57-43df-8847-becfc3593939
|
50
|
+
:lastmod: 2008-09-07 22:40:43
|
51
|
+
:lastaccess: 2008-09-07 22:40:43
|
52
|
+
:creation: 2008-09-07 22:40:43
|
53
|
+
- :title: :eMail
|
54
|
+
:icon: 19
|
@@ -0,0 +1,36 @@
|
|
1
|
+
:path: /tmp/new_database.kdb
|
2
|
+
:groups:
|
3
|
+
- :title: :Internet
|
4
|
+
:icon: 1
|
5
|
+
- :title: :Web
|
6
|
+
:icon: 61
|
7
|
+
:parent: :Internet
|
8
|
+
- :title: :Wikipedia
|
9
|
+
:icon: 54
|
10
|
+
:parent: :Web
|
11
|
+
- :title: :eMail
|
12
|
+
:icon: 19
|
13
|
+
:entries:
|
14
|
+
- :title: test entry2
|
15
|
+
:username: testuser
|
16
|
+
:url: http://example.com/testurl
|
17
|
+
:password: testpassword
|
18
|
+
:comment: test comment
|
19
|
+
:group: :Wikipedia
|
20
|
+
- :title: test entry
|
21
|
+
:username: testuser
|
22
|
+
:url: http://example.com/testurl
|
23
|
+
:password: testpassword
|
24
|
+
:comment: test comment
|
25
|
+
:group: :Internet
|
26
|
+
- :title: test entry
|
27
|
+
:username: testuser
|
28
|
+
:url: http://example.com/testurl
|
29
|
+
:password: testpassword
|
30
|
+
:comment: test comment
|
31
|
+
:group: :Internet
|
32
|
+
- :title: entry2
|
33
|
+
:username: user
|
34
|
+
:url: http://example.com
|
35
|
+
:password: pass2
|
36
|
+
:comment: comment
|
Binary file
|
@@ -0,0 +1,338 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
# require 'ruby-prof'
|
3
|
+
## FIXME: Add exception tests.
|
4
|
+
describe Keepassx::Database do
|
5
|
+
|
6
|
+
include_context :keepassx
|
7
|
+
|
8
|
+
describe '#open' do
|
9
|
+
it 'creates a new instance of the databse with the file' do
|
10
|
+
expect(test_db).to_not be nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe '#new' do
|
16
|
+
subject { Keepassx }
|
17
|
+
let(:db1) { subject.new data_array }
|
18
|
+
let(:db2) { subject.new data_array }
|
19
|
+
|
20
|
+
it 'has the same checksum for the same data' do
|
21
|
+
expect(db1.checksum).to eq db2.checksum
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe '#unlock' do
|
27
|
+
|
28
|
+
it 'returns false when the master password is incorrect' do
|
29
|
+
expect(test_db.unlock('bad password')).to be false
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
it 'returns true when the master password is correct' do
|
34
|
+
expect(test_db.unlock('testmasterpassword')).to be true
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
describe '#locked?' do
|
41
|
+
it 'returns false when unlocked' do
|
42
|
+
expect(test_db.locked?).to be false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
context 'unlocked database' do
|
48
|
+
|
49
|
+
it 'can find entries by their title' do
|
50
|
+
# FIXME: Add more field tests
|
51
|
+
expect(test_db.entry(:title => 'test entry').password).to eq 'testpassword'
|
52
|
+
expect(test_db.entry(:title => 'test entry').creation).to eq Time.
|
53
|
+
local 2011, 9, 3, 15, 34, 47
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it 'can find groups' do
|
58
|
+
expect(test_db.groups.map(&:title).sort).
|
59
|
+
to eq ['Backup', 'Internet', 'Web', 'Wikipedia', 'eMail']
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
it 'has "Internet" group level properly set' do
|
64
|
+
expect(test_db.group(:Internet).level).to eq 0
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
it 'has "Internet" group parent properly set' do
|
69
|
+
expect(test_db.group(:Internet).parent).to be nil
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
it 'has "Web" group level properly set' do
|
74
|
+
expect(test_db.group(:Web).level).to eq 1
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it 'has "Web" group parent properly set' do
|
79
|
+
expect(test_db.group(:Web).parent).to be test_db.group(:Internet)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it 'has "eMail" group level properly set' do
|
84
|
+
expect(test_db.group(:eMail).level).to eq 0
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it 'has "eMail" group parent properly set' do
|
89
|
+
expect(test_db.group(:eMail).parent).to be nil
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
it 'can search for entries' do
|
94
|
+
expect(test_db.search('test').first.title).to eq 'test entry'
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
it 'can search for entries case-insensitively' do
|
99
|
+
expect(test_db.search('TEST').first.title).to eq 'test entry'
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
it 'will find the current values of entries with history' do
|
104
|
+
entries = test_db.search 'entry2'
|
105
|
+
expect(entries.size).to be 1
|
106
|
+
expect(entries.first.title).to eq 'entry2'
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
it 'can add and delete groups and entries' do
|
111
|
+
expect { @group = test_db.add :group, :title => 'group3' }.to_not raise_error
|
112
|
+
|
113
|
+
expect { @entry = Keepassx::Entry.new(
|
114
|
+
:title => 'entry3',
|
115
|
+
:group => @group
|
116
|
+
) }.to_not raise_error
|
117
|
+
|
118
|
+
expect { test_db.add(@entry) }.to_not raise_error
|
119
|
+
expect(test_db.entry :title => 'entry3', :group => @group).to_not be nil
|
120
|
+
|
121
|
+
expect { @entry = Keepassx::Entry.new(
|
122
|
+
:title => 'entry4',
|
123
|
+
:group => @group
|
124
|
+
) }.to_not raise_error
|
125
|
+
expect { test_db.add(@entry) }.to_not raise_error
|
126
|
+
expect(test_db.entry :title => 'entry4', :group => @group).to_not be nil
|
127
|
+
|
128
|
+
expect(test_db.entries(:group => @group).length).to eq 2
|
129
|
+
|
130
|
+
expect(test_db.delete(:group, :title => 'group3').class).to be Keepassx::Group
|
131
|
+
expect(test_db.entries :group => @group).to eq []
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
it 'can be exported to XML' do
|
136
|
+
expect { @xml = test_db.to_xml }.to_not raise_error
|
137
|
+
expect(@xml.doctype.name).to eq 'KEEPASSX_DATABASE'
|
138
|
+
expect(@xml.root.name).to eq 'database'
|
139
|
+
# require 'rexml/formatters/pretty'
|
140
|
+
# formatter = REXML::Formatters::Pretty.new 2
|
141
|
+
# formatter.compact = true # This is the magic line that does what you need!
|
142
|
+
# formatter.write @xml, $stderr
|
143
|
+
# formatter.write @xml, File.new('/tmp/keepass.xml', 'w+')
|
144
|
+
# warn @xml
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
context 'new database' do
|
151
|
+
|
152
|
+
it 'is properly initialized' do
|
153
|
+
expect(new_db.valid?).to be true
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Unlocked by default for new container
|
158
|
+
it 'is unlocked' do
|
159
|
+
expect(new_db.locked?).to be false
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
it 'has non-empty checksum ' do
|
164
|
+
expect(test_db.checksum).to_not be nil
|
165
|
+
expect(test_db.checksum).to_not be ''
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
it 'can add groups and entries' do
|
170
|
+
[
|
171
|
+
{ :title => :Internet, :icon => 1 },
|
172
|
+
{ :title => :eMail, :icon => 19 },
|
173
|
+
{ :title => :Web, :parent => :Internet, :icon => 61 },
|
174
|
+
{ :title => :Wikipedia, :parent => :Web, :icon => 54 }
|
175
|
+
].each do |opts|
|
176
|
+
expect { new_db.add(:group, opts) }.to_not raise_error
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
it 'contains group "eMail" with proper index value' do
|
182
|
+
expect(new_db.index new_db.group(:eMail)).to eq 3
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
it 'contains group "eMail" with proper level value' do
|
187
|
+
expect(new_db.group(:eMail).level).to eq 0
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
it 'contains group "Web" with proper level value' do
|
192
|
+
expect(new_db.index new_db.group(:Web)).to eq 1
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
it 'contains group "Web" with proper level value' do
|
197
|
+
expect(new_db.group(:Web).level).to eq 1
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
it 'contains group "Wikipedia" with proper level value' do
|
202
|
+
expect(new_db.index new_db.group(:Wikipedia)).to eq 2
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'contains group "Wikipedia" with proper level value' do
|
206
|
+
expect(new_db.group(:Wikipedia).level).to eq 2
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
it 'adds new entry "test entry"' do
|
211
|
+
# FIXME: There's some value set for binary_data field
|
212
|
+
expect { new_db.add(:entry,
|
213
|
+
:title => 'test entry', :username => 'testuser',
|
214
|
+
:url => 'http://example.com/testurl', :password => 'testpassword',
|
215
|
+
:comment => 'test comment', :group => :Internet)
|
216
|
+
}.to_not raise_error
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
it 'adds new entry "entry2"' do
|
221
|
+
expect { new_db.add(:entry,
|
222
|
+
:title => 'entry2', :username => 'user', :url => 'http://example.com',
|
223
|
+
:password => 'pass2', :comment => 'comment',
|
224
|
+
:group => :Internet)
|
225
|
+
}.to_not raise_error
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
it 'adds new entry "test entry2"' do
|
230
|
+
expect { new_db.add(:entry,
|
231
|
+
:title => 'test entry2', :username => 'testuser',
|
232
|
+
:url => 'http://example.com/testurl', :password => 'testpassword',
|
233
|
+
:comment => 'test comment', :group => :Web)
|
234
|
+
}.to_not raise_error
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
it 'does not change checksum after save' do
|
239
|
+
@new_db_checksum = @new_db.checksum
|
240
|
+
expect { new_db.save 'testmasterpassword' }.to_not raise_error
|
241
|
+
expect(new_db.checksum).to eq @new_db_checksum
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
it 'saves checksum properly' do
|
246
|
+
expect(new_db.header.contents_hash).to eq new_db.checksum
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
it 'can delete entry' do
|
251
|
+
group = new_db.add(:group, :title => 'Test Group 1')
|
252
|
+
new_db.add_entry(:title => 'Test Entry 1', :group => group)
|
253
|
+
|
254
|
+
expect {new_db.delete group}.to_not raise_error
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
it 'matches reference database' do
|
259
|
+
# expect(new_db = Keepassx::Database.open(NEW_DATABASE_PATH)).to_not be nil
|
260
|
+
expect(new_db.header.group_number).to eq 4
|
261
|
+
|
262
|
+
# FIXME: Delete functionality seems not to work properly, should be 3 here
|
263
|
+
expect(new_db.header.entry_number).to eq 4 # TODO: Don't forget about meta entries
|
264
|
+
expect(new_db.unlock 'testmasterpassword').to be true
|
265
|
+
|
266
|
+
# expect(new_db.index new_db.group(:Backup)).to eq 4
|
267
|
+
# expect(new_db.group(:Backup).level).to eq 0
|
268
|
+
|
269
|
+
new_db.groups.each do |group|
|
270
|
+
expect(group.title).to eq test_db.group(:title => group.title).title
|
271
|
+
expect(group.level).to eq test_db.group(group.title).level
|
272
|
+
end
|
273
|
+
|
274
|
+
# TODO: Implement entries verification using reference database
|
275
|
+
# new_db.entries.each do |entry|
|
276
|
+
# expect(entry.username).to eq test_db.entry(:title => entry.title).username
|
277
|
+
# expect(entry.group.title).to eq test_db.entry(entry.title).
|
278
|
+
# group.title
|
279
|
+
# end
|
280
|
+
expect { new_db.save 'testmasterpassword' }.to_not raise_error
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'new database from array' do
|
287
|
+
|
288
|
+
it 'properly initialized from Array' do
|
289
|
+
expect { data_array_db }.to_not raise_error
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
it 'has grup_number counter properly set' do
|
294
|
+
expect(data_array_db.header.group_number).to eq 5
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
it 'contains proper number of test groups' do
|
299
|
+
expect(data_array_db.groups.length).to eq 5
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
it 'contains proper number of test entries' do
|
304
|
+
expect(data_array_db.entries.length).to eq 4
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
it 'has entry counter properly set' do
|
309
|
+
expect(data_array_db.header.entry_number).to eq 4
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
it 'preserves original Array' do
|
314
|
+
expect(data_array).to eq original_data_array
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
describe '#to_a' do
|
321
|
+
|
322
|
+
let :database_schema do
|
323
|
+
# FIXME: Figure out how to properly define schema for nested Array
|
324
|
+
Respect::ArraySchema.define do |s|
|
325
|
+
s.item do |i|
|
326
|
+
# i.hash
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'returns Array database representation' do
|
332
|
+
# expect(database_schema.validate? data_array_db.to_a).to be true
|
333
|
+
expect(data_array_db.to_a.class).to be Array
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|