keyrack 0.3.0.pre → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +8 -15
  4. data/Guardfile +6 -0
  5. data/LICENSE.txt +4 -2
  6. data/Rakefile +2 -56
  7. data/keyrack.gemspec +22 -104
  8. data/lib/keyrack.rb +9 -1
  9. data/lib/keyrack/database.rb +64 -98
  10. data/lib/keyrack/event.rb +13 -0
  11. data/lib/keyrack/exceptions.rb +4 -0
  12. data/lib/keyrack/group.rb +225 -0
  13. data/lib/keyrack/migrator.rb +45 -0
  14. data/lib/keyrack/runner.rb +98 -44
  15. data/lib/keyrack/site.rb +92 -0
  16. data/lib/keyrack/ui/console.rb +234 -95
  17. data/lib/keyrack/utils.rb +0 -18
  18. data/lib/keyrack/version.rb +3 -0
  19. data/test/fixtures/database-3.dat +0 -0
  20. data/test/helper.rb +11 -1
  21. data/test/integration/test_interactive_console.rb +139 -0
  22. data/test/unit/store/test_filesystem.rb +21 -0
  23. data/test/unit/store/test_ssh.rb +32 -0
  24. data/test/unit/test_database.rb +201 -0
  25. data/test/unit/test_event.rb +41 -0
  26. data/test/unit/test_group.rb +703 -0
  27. data/test/unit/test_migrator.rb +105 -0
  28. data/test/unit/test_runner.rb +407 -0
  29. data/test/unit/test_site.rb +181 -0
  30. data/test/unit/test_store.rb +13 -0
  31. data/test/unit/test_utils.rb +8 -0
  32. data/test/unit/ui/test_console.rb +495 -0
  33. metadata +98 -94
  34. data/Gemfile.lock +0 -45
  35. data/TODO +0 -4
  36. data/VERSION +0 -1
  37. data/features/console.feature +0 -103
  38. data/features/step_definitions/keyrack_steps.rb +0 -61
  39. data/features/support/env.rb +0 -16
  40. data/test/fixtures/aes +0 -0
  41. data/test/fixtures/config.yml +0 -4
  42. data/test/fixtures/id_rsa +0 -54
  43. data/test/keyrack/store/test_filesystem.rb +0 -25
  44. data/test/keyrack/store/test_ssh.rb +0 -36
  45. data/test/keyrack/test_database.rb +0 -111
  46. data/test/keyrack/test_runner.rb +0 -111
  47. data/test/keyrack/test_store.rb +0 -15
  48. data/test/keyrack/test_utils.rb +0 -41
  49. data/test/keyrack/ui/test_console.rb +0 -308
@@ -7,23 +7,5 @@ module Keyrack
7
7
  end
8
8
  result
9
9
  end
10
-
11
- def self.generate_rsa_key(password)
12
- rsa = OpenSSL::PKey::RSA.new(4096)
13
- cipher = OpenSSL::Cipher::Cipher.new('des3')
14
- [rsa, rsa.to_pem(cipher, password)]
15
- end
16
-
17
- def self.generate_aes_key
18
- SecureRandom.base64(128)[0..127]
19
- end
20
-
21
- def self.open_rsa_key(path, password)
22
- OpenSSL::PKey::RSA.new(File.read(path), password)
23
- end
24
-
25
- def self.open_aes_data(path, rsa_key)
26
- Marshal.load(rsa_key.private_decrypt(File.read(path)))
27
- end
28
10
  end
29
11
  end
@@ -0,0 +1,3 @@
1
+ module Keyrack
2
+ VERSION = "0.3.0"
3
+ end
@@ -10,7 +10,7 @@ rescue Bundler::BundlerError => e
10
10
  exit e.status_code
11
11
  end
12
12
  require 'test/unit'
13
- require 'mocha'
13
+ require 'mocha/setup'
14
14
 
15
15
  $LOAD_PATH.unshift(File.dirname(__FILE__))
16
16
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -34,3 +34,13 @@ class Test::Unit::TestCase
34
34
  end
35
35
  end
36
36
  end
37
+
38
+ class SequenceHelper < Object
39
+ def initialize(name)
40
+ @seq = Mocha::Sequence.new(name)
41
+ end
42
+
43
+ def <<(object)
44
+ object.in_sequence(@seq)
45
+ end
46
+ end
@@ -0,0 +1,139 @@
1
+ require 'helper'
2
+ require 'pty'
3
+ require 'stringio'
4
+
5
+ class TestInteractiveConsole < Test::Unit::TestCase
6
+ def setup
7
+ @keyrack_dir = Dir::Tmpname.create('keyrack') { }
8
+ Dir.mkdir(@keyrack_dir)
9
+ @all_data = ""
10
+ end
11
+
12
+ def teardown
13
+ FileUtils.rm_rf(@keyrack_dir)
14
+ if @pid
15
+ begin
16
+ Process.kill("TERM", @pid)
17
+ rescue Errno::ESRCH
18
+ end
19
+ end
20
+ end
21
+
22
+ def assert_output_matches(pattern, sentinel = "\r\n", timeout = 1)
23
+ assert_match pattern, get_output(sentinel, timeout)
24
+ end
25
+
26
+ def assert_output_equals(expected, timeout = 1)
27
+ assert_equal expected, get_output(expected, timeout)
28
+ end
29
+
30
+ def get_output(sentinel = "\r\n", timeout = 1)
31
+ output = ""
32
+ until output.end_with?(sentinel)
33
+ begin
34
+ data = @out.read_nonblock(1)
35
+ output << data
36
+ @all_data << data
37
+ rescue IO::WaitReadable
38
+ if IO.select([@out], [], [], timeout).nil?
39
+ flunk "output timed out"
40
+ else
41
+ retry
42
+ end
43
+ end
44
+ end
45
+ output
46
+ end
47
+ alias :eat_output :get_output
48
+
49
+ def run_keyrack
50
+ @out, @in, @pid = PTY.spawn(%{bundle exec ruby -Ilib bin/keyrack -d "#{@keyrack_dir}"})
51
+ end
52
+
53
+ def send_input(string, hidden = false)
54
+ @in.puts(string)
55
+
56
+ # Eat the echo
57
+ chomp = hidden ? "\r\n\r\n" : "\r\n"
58
+ get_output(string + chomp)
59
+ end
60
+
61
+ def clipboard
62
+ Clipboard.paste
63
+ end
64
+
65
+ test "keyrack session" do
66
+ run_keyrack
67
+
68
+ assert_output_matches /first time/
69
+ assert_output_equals "New passphrase: "
70
+ send_input "secret", true
71
+
72
+ assert_output_equals "Confirm passphrase: "
73
+ send_input "secret", true
74
+
75
+ menu = get_output("? ")
76
+ assert_match /Choose storage type:/, menu
77
+ assert_match /filesystem/, menu
78
+ assert_match /ssh/, menu
79
+ send_input "filesystem"
80
+
81
+ menu = get_output("? ")
82
+ assert_match /Keyrack Main Menu/, menu
83
+ assert_match /Mode: copy/, menu
84
+ assert_match "[n]ew", menu
85
+ send_input "n"
86
+
87
+ assert_output_equals "Label: "
88
+ send_input "Foo"
89
+ assert_output_equals "Username: "
90
+ send_input "dude"
91
+ assert_output_equals "Generate password? [ync] "
92
+ send_input "n"
93
+ assert_output_equals "Password: "
94
+ send_input "secret", true
95
+ assert_output_equals "Password (again): "
96
+ send_input "secret", true
97
+
98
+ menu = get_output("? ")
99
+ assert_match "1. Foo [dude]", menu
100
+ send_input "1"
101
+ assert_equal "secret", clipboard
102
+
103
+ menu = get_output("? ")
104
+ assert_match "[g]roup", menu
105
+ send_input "g"
106
+ assert_output_equals "Group: "
107
+ send_input "Stuff"
108
+
109
+ menu = get_output("? ")
110
+ assert_match "Stuff", menu.lines.first
111
+ assert_match "[g]roup", menu
112
+ send_input "g"
113
+ assert_output_equals "Group: "
114
+ send_input "More Stuff"
115
+
116
+ menu = get_output("? ")
117
+ assert_match "More Stuff", menu.lines.first
118
+ assert_match "[u]p", menu
119
+ send_input "u"
120
+
121
+ menu = get_output("? ")
122
+ assert_match "Stuff", menu.lines.first
123
+ assert_match "[t]op", menu
124
+ send_input "t"
125
+
126
+ menu = get_output("? ")
127
+ assert_match "Keyrack Main Menu", menu.lines.first
128
+ assert_match "[s]ave", menu
129
+ send_input "s"
130
+ assert_output_equals "Keyrack password: "
131
+ send_input "secret"
132
+
133
+ menu = get_output("? ", 10)
134
+ assert_match "[q]uit", menu
135
+ send_input "q"
136
+
137
+ Process.waitpid(@pid)
138
+ end
139
+ end
@@ -0,0 +1,21 @@
1
+ require 'helper'
2
+
3
+ class TestFilesystem < Test::Unit::TestCase
4
+ def test_read
5
+ path = fixture_path('foo.txt')
6
+ store = Keyrack::Store::Filesystem.new('path' => path)
7
+ assert_equal File.read(path), store.read
8
+ end
9
+
10
+ def test_write
11
+ path = get_tmpname
12
+ store = Keyrack::Store::Filesystem.new('path' => path)
13
+ store.write("foobar")
14
+ assert_equal "foobar", File.read(path)
15
+ end
16
+
17
+ def test_read_returns_nil_for_non_existant_file
18
+ store = Keyrack::Store::Filesystem.new('path' => 'blargityblargh')
19
+ assert_nil store.read
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+
3
+ class TestSSH < Test::Unit::TestCase
4
+ def test_read
5
+ store = Keyrack::Store::SSH.new('host' => 'example.com', 'user' => 'dude', 'port' => 22, 'path' => 'foo.txt')
6
+ scp = mock('scp session')
7
+ scp.expects(:download!).with("foo.txt").returns("foo")
8
+ session = mock('ssh session', :scp => scp)
9
+ Net::SSH.expects(:start).with('example.com', 'dude', :port => 22).yields(session)
10
+ assert_equal "foo", store.read
11
+ end
12
+
13
+ def test_write
14
+ store = Keyrack::Store::SSH.new('host' => 'example.com', 'user' => 'dude', 'path' => 'foo.txt')
15
+ scp = mock('scp session')
16
+ session = mock('ssh session', :scp => scp)
17
+ Net::SSH.expects(:start).with('example.com', 'dude', :port => 22).yields(session)
18
+ scp.expects(:upload!).with do |local, remote|
19
+ local.is_a?(StringIO) && local.read == "foo" && remote == "foo.txt"
20
+ end
21
+ store.write("foo")
22
+ end
23
+
24
+ def test_read_returns_nil_for_non_existant_file
25
+ store = Keyrack::Store::SSH.new('host' => 'example.com', 'user' => 'dude', 'port' => 22, 'path' => 'foo.txt')
26
+ scp = mock('scp session')
27
+ scp.expects(:download!).with("foo.txt").raises(Net::SCP::Error)
28
+ session = mock('ssh session', :scp => scp)
29
+ Net::SSH.expects(:start).with('example.com', 'dude', :port => 22).yields(session)
30
+ assert_nil store.read
31
+ end
32
+ end
@@ -0,0 +1,201 @@
1
+ require 'helper'
2
+
3
+ class TestDatabase < Test::Unit::TestCase
4
+ class TestReload < self
5
+ def setup
6
+ super
7
+ @database = Keyrack::Database.new(@key, @store, @encrypt_options, @decrypt_options)
8
+ end
9
+ end
10
+
11
+ def self.database_test(test_description, reload, &block)
12
+ if reload
13
+ TestReload.class_eval do
14
+ test(test_description, &block)
15
+ end
16
+ else
17
+ test(test_description, &block)
18
+ end
19
+ end
20
+
21
+ def setup
22
+ @path = get_tmpname
23
+ @store = Keyrack::Store['filesystem'].new('path' => @path)
24
+
25
+ @encrypt_options = { :maxmem => 0, :maxmemfrac => 0.05, :maxtime => 0.1 }
26
+ @decrypt_options = { :maxmem => 0, :maxmemfrac => 0.10, :maxtime => 1.0 }
27
+ @key = "secret"
28
+ @database = Keyrack::Database.new(@key, @store, @encrypt_options, @decrypt_options)
29
+ @twitter = Keyrack::Site.new('Twitter', 'dude', 'p4ssword')
30
+ @database.top_group.add_site(@twitter)
31
+ assert @database.save(@key)
32
+ end
33
+
34
+ def decrypt(data, key = @key, options = @decrypt_options)
35
+ Scrypty.decrypt(data, key, *options.values_at(:maxmem, :maxmemfrac, :maxtime))
36
+ end
37
+
38
+ test "encrypting database" do
39
+ encrypted_data = File.read(@path)
40
+ yaml = decrypt(encrypted_data, @key)
41
+ data = YAML.load(yaml)
42
+ expected = {
43
+ 'groups' => {
44
+ 'top' => {
45
+ 'name' => 'top',
46
+ 'sites' => [
47
+ {
48
+ 'name' => 'Twitter',
49
+ 'username' => 'dude',
50
+ 'password' => 'p4ssword'
51
+ }
52
+ ],
53
+ 'groups' => {}
54
+ }
55
+ },
56
+ 'version' => Keyrack::Database::VERSION
57
+ }
58
+ assert_equal(expected, data)
59
+ end
60
+
61
+ test "auto-migrating database from version 3" do
62
+ store = Keyrack::Store['filesystem'].new('path' => fixture_path('database-3.dat'))
63
+ database = Keyrack::Database.new('foobar', store)
64
+ assert database.dirty?
65
+ end
66
+
67
+ [true, false].each do |reload|
68
+ database_test "database is dirty after adding site to top group", reload do
69
+ assert !@database.dirty?
70
+ site = Keyrack::Site.new('Foo', 'foo', 'bar')
71
+ @database.top_group.add_site(site)
72
+ assert @database.dirty?
73
+ end
74
+
75
+ database_test "database is dirty after removing site from top group", reload do
76
+ assert !@database.dirty?
77
+ @database.top_group.remove_site(@twitter)
78
+ assert @database.dirty?
79
+ end
80
+
81
+ database_test "database is dirty after changing username", reload do
82
+ assert !@database.dirty?
83
+ twitter = @database.top_group.site(0)
84
+ twitter.username = 'bro'
85
+ assert @database.dirty?
86
+ end
87
+
88
+ database_test "database is dirty after changing password", reload do
89
+ assert !@database.dirty?
90
+ twitter = @database.top_group.site(0)
91
+ twitter.password = 'secret'
92
+ assert @database.dirty?
93
+ end
94
+
95
+ database_test "database is dirty after adding subgroup", reload do
96
+ assert !@database.dirty?
97
+ group = Keyrack::Group.new('Foo')
98
+ @database.top_group.add_group(group)
99
+ assert @database.dirty?
100
+ end
101
+
102
+ database_test "database is dirty after removing subgroup", reload do
103
+ group = Keyrack::Group.new('Foo')
104
+ @database.top_group.add_group(group)
105
+ assert @database.save(@key)
106
+
107
+ assert !@database.dirty?
108
+ @database.top_group.remove_group('Foo')
109
+ assert @database.dirty?
110
+ end
111
+
112
+ database_test "database is dirty after adding site to subgroup", reload do
113
+ assert !@database.dirty?
114
+ group = Keyrack::Group.new('Foo')
115
+ @database.top_group.add_group(group)
116
+ assert @database.save(@key)
117
+
118
+ assert !@database.dirty?
119
+ site = Keyrack::Site.new('Bar', 'bar', 'baz')
120
+ group.add_site(site)
121
+ assert @database.dirty?
122
+ end
123
+
124
+ database_test "database is dirty after removing site from subgroup", reload do
125
+ assert !@database.dirty?
126
+ group = Keyrack::Group.new('Foo')
127
+ @database.top_group.add_group(group)
128
+ assert @database.save(@key)
129
+
130
+ assert !@database.dirty?
131
+ site = Keyrack::Site.new('Bar', 'bar', 'baz')
132
+ group.add_site(site)
133
+ assert @database.save(@key)
134
+
135
+ assert !@database.dirty?
136
+ group.remove_site(site)
137
+ assert @database.dirty?
138
+ end
139
+
140
+ database_test "database is dirty after adding group to subgroup", reload do
141
+ assert !@database.dirty?
142
+ group = Keyrack::Group.new('Foo')
143
+ @database.top_group.add_group(group)
144
+ assert @database.save(@key)
145
+
146
+ assert !@database.dirty?
147
+ subgroup = Keyrack::Group.new('Bar')
148
+ group.add_group(subgroup)
149
+ assert @database.dirty?
150
+ end
151
+
152
+ database_test "database is dirty after changing group name", reload do
153
+ assert !@database.dirty?
154
+ group = Keyrack::Group.new('Foo')
155
+ @database.top_group.add_group(group)
156
+ assert @database.save(@key)
157
+
158
+ assert !@database.dirty?
159
+ group.name = "Bar"
160
+ assert @database.dirty?
161
+ end
162
+
163
+ database_test "large number of entries", reload do
164
+ site_name = "abcdefg"; username = "1234567"; password = "zyxwvut" * 2
165
+ 500.times do |i|
166
+ site = Keyrack::Site.new(site_name, username, password)
167
+ @database.top_group.add_site(site)
168
+ site_name = site_name.next
169
+ username = username.next
170
+ password = password.next
171
+ end
172
+ assert @database.save(@key)
173
+ assert_equal 501, @database.top_group.sites.length
174
+ end
175
+
176
+ database_test "saving requires same password as creation", reload do
177
+ site = Keyrack::Site.new("Foo", 'foo', 'bar')
178
+ @database.top_group.add_site(site)
179
+
180
+ assert !@database.save("bogus")
181
+ end
182
+
183
+ database_test "changing database password successfully", reload do
184
+ assert @database.change_password(@key, "new-secret")
185
+
186
+ site = Keyrack::Site.new("Foo", 'bar', 'baz')
187
+ @database.top_group.add_site(site)
188
+
189
+ assert @database.save("new-secret")
190
+ end
191
+
192
+ database_test "attempting to change database password with wrong existing password", reload do
193
+ assert !@database.change_password("bogus", "new-secret")
194
+
195
+ site = Keyrack::Site.new("Foo", 'foo', 'bar')
196
+ @database.top_group.add_site(site)
197
+
198
+ assert !@database.save("new-secret")
199
+ end
200
+ end
201
+ end