keybox 1.0.0

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.
@@ -0,0 +1,163 @@
1
+ module Keybox
2
+ # including this module assumes that the class included has
3
+ # @stdout and @stdin member variables.
4
+ #
5
+ # This module also assumes that stty is available
6
+ module TermIO
7
+
8
+ # http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
9
+ #
10
+ ESCAPE = "\e"
11
+ BOLD_ON = "[1m"
12
+ RESET = "[0m"
13
+
14
+ FG_BLACK = "[30m"
15
+ FG_RED = "[31m"
16
+ FG_GREEN = "[32m"
17
+ FG_YELLOW = "[33m"
18
+ FG_BLUE = "[34m"
19
+ FG_MAGENTA = "[35m"
20
+ FG_CYAN = "[36m"
21
+ FG_WHITE = "[37m"
22
+
23
+ COLORS = {
24
+ :black => FG_BLACK,
25
+ :red => FG_RED,
26
+ :green => FG_GREEN,
27
+ :yellow => FG_YELLOW,
28
+ :blue => FG_BLUE,
29
+ :magenta => FG_MAGENTA,
30
+ :cyan => FG_CYAN,
31
+ :white => FG_WHITE,
32
+ }
33
+
34
+ VALID_COLORS = COLORS.keys()
35
+
36
+
37
+ STTY = "stty"
38
+ STTY_SAVE_CMD = "#{STTY} -g"
39
+ STTY_RAW_CMD = "#{STTY} raw -echo isig"
40
+
41
+ EOL_CHARS = [10, # '\n'
42
+ 13, # '\r'
43
+ ]
44
+ #
45
+ # prompt for input, returning what was typed. If echo is false,
46
+ # then '*' is printed out for each character typed in. If it is
47
+ # any other character then that is output instead.
48
+ #
49
+ # If validate is set to true, then it will prompt twice and make
50
+ # sure that the two values match
51
+ #
52
+ def prompt(p,echo = true, validate = false, width = 20)
53
+ validated = false
54
+ line = ""
55
+ extra_prompt = " (again)"
56
+ original_prompt = p
57
+ validation_prompt = original_prompt + extra_prompt
58
+ width += extra_prompt.length
59
+
60
+ until validated do
61
+ line = prompt_and_return(original_prompt.rjust(width),echo)
62
+
63
+ # if we are validating then prompt again to validate
64
+ if validate then
65
+ v = prompt_and_return(validation_prompt.rjust(width),echo)
66
+ if v != line then
67
+ color_puts "Entries do not match, try again.", :red
68
+ else
69
+ validated = true
70
+ end
71
+ else
72
+ validated = true
73
+ end
74
+ end
75
+ return line
76
+ end
77
+
78
+ def prompt_y_n(p)
79
+ response = prompt(p)
80
+ if response.size > 0 and response.downcase[0].chr == 'y' then
81
+ true
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ def get_one_char
88
+ stty_original = %x{#{STTY_SAVE_CMD}}
89
+ char = nil
90
+ begin
91
+ system STTY_RAW_CMD
92
+ char = @stdin.getc
93
+ ensure
94
+ system "#{STTY} #{stty_original}"
95
+ end
96
+
97
+ return char
98
+ end
99
+
100
+ def prompt_and_return(the_prompt,echo)
101
+ line = ""
102
+ color_print "#{the_prompt} : ", :white
103
+ if echo != true then
104
+
105
+ echo_char = echo || '*'
106
+
107
+ if has_stty? then
108
+ stty_original = %x{#{STTY_SAVE_CMD}}
109
+
110
+ begin
111
+ system STTY_RAW_CMD
112
+ while char = @stdin.getc
113
+ line << char
114
+ break if EOL_CHARS.include? char
115
+ @stdout.putc echo_char
116
+ end
117
+ ensure
118
+ system "#{STTY} #{stty_original}"
119
+ end
120
+ @stdout.puts
121
+ end
122
+ else
123
+ line = @stdin.gets
124
+ end
125
+
126
+ # if we got end of file or some other input resulting in
127
+ # line becoming nil then set it to the empty string
128
+ line = line || ""
129
+
130
+ return line.rstrip
131
+ end
132
+
133
+ def has_stty?
134
+ system "which stty > /dev/null 2>&1"
135
+ end
136
+
137
+ def colorize(text,color,bold=true)
138
+ before = ""
139
+ after = ""
140
+ if VALID_COLORS.include?(color) then
141
+ before = ESCAPE + COLORS[color]
142
+ before = ESCAPE + BOLD_ON + before if bold
143
+ after = ESCAPE + RESET
144
+ end
145
+ "#{before}#{text}#{after}"
146
+ end
147
+
148
+ def colorize_if_io_isatty(io,text,color,bold)
149
+ if io.tty? then
150
+ text = colorize(text,color,bold)
151
+ end
152
+ text
153
+ end
154
+
155
+ def color_puts(text, color, bold = true)
156
+ @stdout.puts colorize_if_io_isatty(@stdout,text,color,bold)
157
+ end
158
+
159
+ def color_print(text,color, bold = true)
160
+ @stdout.print colorize_if_io_isatty(@stdout,text,color,bold)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,86 @@
1
+ require 'keybox/randomizer'
2
+ require 'yaml'
3
+ module Keybox
4
+ #
5
+ # A quick implementation of a UUID class using the internal
6
+ # randomizer for byte generation
7
+ #
8
+ class UUID
9
+
10
+ attr_reader :bytes
11
+
12
+ XF = "%0.2x"
13
+ FORMAT = sprintf("%s-%s-%s-%s-%s", XF * 4, XF * 2, XF * 2, XF * 2, XF * 6)
14
+ REGEX = %r|^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$|
15
+
16
+ #
17
+ # the UUID can be initialized with:
18
+ # - nothing ( default case ) in this case the UUID is generated
19
+ # - a string in the standarde uuid format, in this case it is
20
+ # decoded and converted to the internal format
21
+ # - a string of bytes, in this case they are considered to be
22
+ # the bytes used internally so 16 of them are taken
23
+ def initialize(bytes = nil)
24
+
25
+ if bytes.nil? then
26
+ @bytes = Keybox::RandomDevice.random_bytes(16)
27
+ elsif bytes.size == 36 and bytes.split("-").size == 5 then
28
+ if bytes =~ REGEX then
29
+ # remove the dashes and make sure that we're all
30
+ # lowercase
31
+ b = bytes.gsub(/-/,'').downcase
32
+
33
+ # convert to an array of hex strings
34
+ b = b.unpack("a2"*16)
35
+
36
+ # convert the hex strings to integers
37
+ b.collect! { |x| x.to_i(16) }
38
+
39
+ # and pack those integers into a string
40
+ @bytes = b.pack("C*")
41
+
42
+ # of course this could all be done in one line with
43
+ # @bytes = bytes.gsub(/-/,'').downcase.unpack("a2"*16").collect {|x| x.to_i(16) }.pack("C*")
44
+ else
45
+ raise ArgumentError, "[#{bytes}] is not a hex encoded UUID"
46
+ end
47
+ elsif bytes.kind_of?(String) and bytes.length >= 16
48
+ @bytes = bytes[0..16]
49
+ else
50
+ raise ArgumentError, "[#{bytes}] cannot be converted to a UUID"
51
+ end
52
+ end
53
+
54
+ #
55
+ # convert the bytes to the hex encoded string format of
56
+ # XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
57
+ #
58
+ def to_s
59
+ sprintf(FORMAT,*to_a)
60
+ end
61
+
62
+ def to_a
63
+ @bytes.unpack("C*")
64
+ end
65
+
66
+ def ==(other)
67
+ self.eql?(other)
68
+ end
69
+
70
+ def eql?(other)
71
+ case other
72
+ when Keybox::UUID
73
+ self.bytes == other.bytes
74
+ when String
75
+ begin
76
+ o = Keybox::UUID.new(other)
77
+ self == o
78
+ rescue
79
+ false
80
+ end
81
+ else
82
+ false
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,56 @@
1
+ require 'keybox'
2
+ require 'keybox/application/base'
3
+
4
+ context "Keybox Base Application" do
5
+
6
+ specify "nil argv should do nothing" do
7
+ kba = Keybox::Application::Base.new(nil)
8
+ kba.error_message.should_be nil
9
+ end
10
+
11
+ specify "executing with no args should have output on stdout" do
12
+ kba = Keybox::Application::Base.new(nil)
13
+ kba.stdout = StringIO.new
14
+ kba.run
15
+ kba.stdout.string.size.should_be > 0
16
+ end
17
+
18
+
19
+ specify "invalid options set the error message, exit 1 and have output on stderr" do
20
+ kba = Keybox::Application::Base.new(["--invalid-option"])
21
+ kba.stderr = StringIO.new
22
+ kba.stdout = StringIO.new
23
+ begin
24
+ kba.run
25
+ rescue SystemExit => se
26
+ kba.error_message.should_satisfy { |msg| msg =~ /Try.*--help/m }
27
+ kba.stderr.string.should_satisfy { |msg| msg =~ /Try.*--help/m }
28
+ kba.stdout.string.size.should_eql 0
29
+ se.status.should == 1
30
+ end
31
+ end
32
+
33
+ specify "help has output on stdout and exits 0" do
34
+ kba = Keybox::Application::Base.new(["--h"])
35
+ kba.stdout = StringIO.new
36
+ begin
37
+ kba.run
38
+ rescue SystemExit => se
39
+ se.status.should_eql 0
40
+ kba.stdout.string.length.should_be > 0
41
+ end
42
+ end
43
+
44
+ specify "version has output on stdout and exits 0" do
45
+ kba = Keybox::Application::Base.new(["--ver"])
46
+ kba.stdout = StringIO.new
47
+ begin
48
+ kba.run
49
+ rescue SystemExit => se
50
+ se.status.should_eql 0
51
+ kba.stdout.string.length.should_be > 0
52
+ end
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,46 @@
1
+ require 'tempfile'
2
+ require 'keybox'
3
+ require 'keybox/convert/csv'
4
+ context "CSV Convert class" do
5
+ setup do
6
+ @import_csv = Tempfile.new("keybox_import.csv")
7
+ @import_csv.puts "title,hostname,username,password,additional_info"
8
+ @import_csv.puts "example host,host.example.com,guest,mysecretpassword,use this account only for honeybots"
9
+ @import_csv.puts "example site,http://www.example.com,guest,mywebpassword,web forum login"
10
+ @import_csv.close
11
+
12
+ @bad_import_csv = Tempfile.new("keybox_bad_header.csv")
13
+ # missing a valid header
14
+ @bad_import_csv.puts "ttle,host,username,password,additional_info"
15
+ @bad_import_csv.puts "example host,host.example.com,guest,mysecretpassword,use this account only for honeybots"
16
+ @bad_import_csv.puts "example site,http://www.example.com,guest,mywebpassword,web forum login"
17
+
18
+ @export_csv = Tempfile.new("keybox_export.csv")
19
+
20
+ end
21
+
22
+ teardown do
23
+ @import_csv.unlink
24
+ @bad_import_csv.unlink
25
+ @export_csv.unlink
26
+ end
27
+
28
+ specify "able to load records from a file" do
29
+ entries = Keybox::Convert::CSV.from_file(@import_csv.path)
30
+ entries.size.should_eql 2
31
+ entries[0].hostname.should_eql "host.example.com"
32
+ entries[1].password.should_eql "mywebpassword"
33
+ end
34
+
35
+ specify "throws error if the header is bad" do
36
+ lambda { Keybox::Convert::CSV.from_file(@bad_import_csv.path) }.should_raise Keybox::ValidationError
37
+ end
38
+
39
+ specify "able to write to a csv file" do
40
+ entries = Keybox::Convert::CSV.from_file(@import_csv.path)
41
+ Keybox::Convert::CSV.to_file(entries,@export_csv.path)
42
+ @export_csv.open
43
+ data = @export_csv.read
44
+ data.size.should_be > 0
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ require 'keybox'
2
+ context "Account Entry" do
3
+ specify "fields get set correctly" do
4
+ k = Keybox::AccountEntry.new("a test title", "user")
5
+ k.username.should_eql "user"
6
+ k.title.should_eql "a test title"
7
+ end
8
+
9
+ specify "fields can be accessed" do
10
+ k = Keybox::AccountEntry.new("a test title", "user")
11
+ k.fields.should_include "title"
12
+ k.fields.should_include "username"
13
+ k.fields.should_include "additional_info"
14
+ end
15
+
16
+ specify "fields can be private or visible" do
17
+ k = Keybox::AccountEntry.new("a test title", "user")
18
+ k.private_fields.size.should_eql 0
19
+ k.visible_field?("title").should_eql true
20
+ end
21
+ end
22
+
23
+ context "Host Account" do
24
+ specify "fields get set correctly" do
25
+ ha = Keybox::HostAccountEntry.new("a title", "host", "user", "password")
26
+ ha.title.should_eql "a title"
27
+ ha.username.should_eql "user"
28
+ ha.hostname.should_eql "host"
29
+ ha.password.should_eql "password"
30
+ end
31
+
32
+ specify "password is displayable, private and non-visible" do
33
+ ha = Keybox::HostAccountEntry.new("a title", "host", "user", "password")
34
+ ha.display_fields.should_include "password"
35
+ ha.private_fields.should_include "password"
36
+ ha.visible_fields.should_not_include "password"
37
+ end
38
+ end
39
+
40
+ context "URL Account" do
41
+ specify "fields get set correctly" do
42
+ urla = Keybox::URLAccountEntry.new("url title", "http://www.example.com", "someuser")
43
+ urla.title.should_eql "url title"
44
+ urla.url.should_eql "http://www.example.com"
45
+ urla.username.should_eql "someuser"
46
+ end
47
+
48
+ specify "password hash is used" do
49
+ container = Keybox::Storage::Container.new("i love ruby", "/tmp/junk.yml")
50
+ urla = Keybox::URLAccountEntry.new("url title", "http://www.example.com", "someuser")
51
+ container << urla
52
+ urla.password.should_eql "589c0d91d"
53
+ end
54
+
55
+ specify "there is no password storage field, but there a private password field" do
56
+ urla = Keybox::URLAccountEntry.new("url title", "http://www.example.com", "someuser")
57
+ urla.fields.should_not_include "password"
58
+ urla.private_fields.should_include "password"
59
+ urla.private_field?("password").should_eql true
60
+ end
61
+
62
+ end
63
+
@@ -0,0 +1,268 @@
1
+ require 'tempfile'
2
+ require 'keybox'
3
+ require 'keybox/application/password_safe'
4
+
5
+ context "Keybox Password Safe Application" do
6
+ setup do
7
+
8
+ @passphrase = "i love ruby"
9
+ @testing_db = Tempfile.new("kps_db.yml")
10
+ @testing_cfg = Tempfile.new("kps_cfg.yml")
11
+ @path = @testing_db.path
12
+ container = Keybox::Storage::Container.new(@passphrase, @testing_db.path)
13
+ container << Keybox::HostAccountEntry.new("test account","localhost","guest", "rubyrocks")
14
+ container << Keybox::URLAccountEntry.new("example site", "http://www.example.com", "rubyhacker")
15
+ container.save
16
+ container.save("/tmp/jjh-db.yml")
17
+
18
+ @import_csv = Tempfile.new("keybox_import.csv")
19
+ @import_csv.puts "title,hostname,username,password,additional_info"
20
+ @import_csv.puts "example host,host.example.com,guest,mysecretpassword,use this account only for honeybots"
21
+ @import_csv.puts "example site,http://www.example.com,guest,mywebpassword,web forum login"
22
+ @import_csv.close
23
+
24
+ @bad_import_csv = Tempfile.new("keybox_bad_header.csv")
25
+ # missing a valid header
26
+ @bad_import_csv.puts "title,host,username,password,additional_info"
27
+ @bad_import_csv.puts "example host,host.example.com,guest,mysecretpassword,use this account only for honeybots"
28
+ @bad_import_csv.puts "example site,http://www.example.com,guest,mywebpassword,web forum login"
29
+
30
+ @export_csv = Tempfile.new("keybox_export.csv")
31
+
32
+ end
33
+
34
+ teardown do
35
+ @testing_db.unlink
36
+ @testing_cfg.unlink
37
+ @import_csv.unlink
38
+ @bad_import_csv.unlink
39
+ @export_csv.unlink
40
+ end
41
+
42
+ specify "nil argv should do nothing" do
43
+ kps = Keybox::Application::PasswordSafe.new
44
+ kps.error_message.should_be nil
45
+ end
46
+
47
+ specify "executing with no args should have output on stdout" do
48
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
49
+ kps.stdout = StringIO.new
50
+ kps.stdin = StringIO.new(@passphrase)
51
+ kps.run
52
+ kps.stdout.string.size.should_be > 0
53
+ end
54
+
55
+ specify "general options get set correctly" do
56
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--debug", "--no-use-hash-for-url"])
57
+ kps.merge_options
58
+ kps.options.db_file.should_eql @testing_db.path
59
+ kps.options.config_file.should_eql @testing_cfg.path
60
+ kps.options.debug.should_eql true
61
+ kps.options.use_password_hash_for_url.should_eql false
62
+ end
63
+
64
+ specify "more than one command options is an error" do
65
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "account", "--edit", "account"])
66
+ kps.stderr = StringIO.new
67
+ kps.stdout = StringIO.new
68
+ begin
69
+ kps.run
70
+ rescue SystemExit => se
71
+ kps.error_message.should_satisfy { |msg| msg =~ /Only one of/m }
72
+ kps.stderr.string.should_satisfy { |msg| msg =~ /Only one of/m }
73
+ se.status.should_eql 1
74
+ end
75
+ end
76
+
77
+ specify "space separated words are okay for names" do
78
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "An Example"])
79
+ kps.stderr = StringIO.new
80
+ kps.stdout = StringIO.new
81
+ prompted_values = [@passphrase, "An example"] + %w(example.com someuser apassword apassword noinfo yes)
82
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
83
+ kps.run
84
+ kps.db.records.size.should_eql 3
85
+ end
86
+
87
+
88
+ specify "invalid options set the error message, exit 1 and have output on stderr" do
89
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"--invalid-option"])
90
+ kps.stderr = StringIO.new
91
+ kps.stdout = StringIO.new
92
+ begin
93
+ kps.run
94
+ rescue SystemExit => se
95
+ kps.error_message.should_satisfy { |msg| msg =~ /Try.*--help/m }
96
+ kps.stderr.string.should_satisfy { |msg| msg =~ /Try.*--help/m }
97
+ kps.stdout.string.size.should_eql 0
98
+ se.status.should == 1
99
+ end
100
+ end
101
+
102
+ specify "help has output on stdout and exits 0" do
103
+ kps = Keybox::Application::PasswordSafe.new(["--h"])
104
+ kps.stdout = StringIO.new
105
+ begin
106
+ kps.run
107
+ rescue SystemExit => se
108
+ se.status.should_eql 0
109
+ kps.stdout.string.length.should_be > 0
110
+ end
111
+ end
112
+
113
+ specify "version has output on stdout and exits 0" do
114
+ kps = Keybox::Application::PasswordSafe.new(["--ver"])
115
+ kps.stdout = StringIO.new
116
+ begin
117
+ kps.run
118
+ rescue SystemExit => se
119
+ se.status.should_eql 0
120
+ kps.stdout.string.length.should_be > 0
121
+ end
122
+ end
123
+
124
+ specify "prompted for password twice to create database initially" do
125
+ File.unlink(@testing_db.path)
126
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
127
+ kps.stdout = StringIO.new
128
+ kps.stdin = StringIO.new([@passphrase,@passphrase].join("\n"))
129
+ kps.run
130
+ kps.db.records.size.should_eql 0
131
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Creating initial database./m }
132
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Initial Password for/m }
133
+ end
134
+
135
+ specify "file can be opened with password" do
136
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
137
+ kps.stdout = StringIO.new
138
+ kps.stdin = StringIO.new(@passphrase + "\n")
139
+ kps.run
140
+ kps.db.records.size.should_eql 2
141
+ end
142
+
143
+ specify "adding an entry to the database works" do
144
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "example.com"])
145
+ kps.stdout = StringIO.new
146
+ prompted_values = [@passphrase] + %w(example.com example.com someuser apassword apassword noinfo yes)
147
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
148
+ kps.run
149
+ kps.db.records.size.should_eql 3
150
+ end
151
+
152
+ specify "editing an entry in the database works" do
153
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--edit", "localhost"])
154
+ kps.stdout = StringIO.new
155
+ prompted_values = [@passphrase] + %w(yes example.com example.com someother anewpassword anewpassword someinfo yes)
156
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
157
+ kps.run
158
+ kps.db.records.size.should_eql 2
159
+ kps.db.find("someother")[0].additional_info.should_eql "someinfo"
160
+ end
161
+
162
+ specify "add a url entry to the database" do
163
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--use-hash-for-url", "--add", "http://www.example.com"])
164
+ kps.stdout = StringIO.new
165
+ prompted_values = [@passphrase] + %w(www.example.com http://www.example.com someuser noinfo yes)
166
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
167
+ kps.run
168
+ kps.db.records.size.should_eql 3
169
+ end
170
+
171
+ specify "double prompting on failed password for entry to the database works" do
172
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "example.com"])
173
+ kps.stdout = StringIO.new
174
+ prompted_values = [@passphrase, ""] + %w(example.com someuser apassword abadpassword abcdef abcdef noinfo yes)
175
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
176
+ kps.run
177
+ kps.db.records.size.should_eql 3
178
+ end
179
+
180
+ specify "able to delete an entry" do
181
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--delete", "example"])
182
+ kps.stdout = StringIO.new
183
+ prompted_values = [@passphrase] + %w(Yes)
184
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
185
+ kps.run
186
+ kps.db.records.size.should_eql 1
187
+ kps.stdout.string.should_satisfy { |msg| msg =~ /example' deleted/ }
188
+ end
189
+
190
+ specify "able to cancel deletion" do
191
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--delete", "example"])
192
+ kps.stdout = StringIO.new
193
+ prompted_values = [@passphrase] + %w(No)
194
+ kps.stdin = StringIO.new(prompted_values.join("\n"))
195
+ kps.run
196
+ kps.db.records.size.should_eql 2
197
+ kps.stdout.string.should_satisfy { |msg| msg =~ /example' deleted/ }
198
+ end
199
+
200
+ specify "list all the entries" do
201
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--list"])
202
+ kps.stdout = StringIO.new
203
+ kps.stdin = StringIO.new(@passphrase)
204
+ kps.run
205
+ kps.stdout.string.should_satisfy { |msg| msg =~ /2./m }
206
+ end
207
+
208
+ specify "listing no entries found" do
209
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--list", "nothing"])
210
+ kps.stdout = StringIO.new
211
+ kps.stdin = StringIO.new(@passphrase)
212
+ kps.run
213
+ kps.stdout.string.should_satisfy { |msg| msg =~ /No matching records were found./ }
214
+ end
215
+
216
+ specify "showing no entries found" do
217
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--show", "nothing"])
218
+ kps.stdout = StringIO.new
219
+ kps.stdin = StringIO.new(@passphrase)
220
+ kps.run
221
+ kps.stdout.string.should_satisfy { |msg| msg =~ /No matching records were found./ }
222
+ end
223
+
224
+
225
+ specify "show all the entries" do
226
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--show"])
227
+ kps.stdout = StringIO.new
228
+ kps.stdin = StringIO.new(@passphrase)
229
+ kps.run
230
+ kps.stdout.string.should_satisfy { |msg| msg =~ /2./m }
231
+ end
232
+
233
+ specify "changing master password works" do
234
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--master-password"])
235
+ kps.stdout = StringIO.new
236
+ kps.stdin = StringIO.new([@passphrase, "I really love ruby.", "I really love ruby."].join("\n"))
237
+ kps.run
238
+ kps.stdout.string.should_satisfy { |msg| msg =~ /New master password set/m }
239
+ end
240
+
241
+ specify "importing from a valid csv file" do
242
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-i", @import_csv.path])
243
+ kps.stdout = StringIO.new
244
+ kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
245
+ kps.run
246
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Imported \d* records from/m }
247
+ end
248
+
249
+ specify "Error message give on invalid imported csv" do
250
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-i", @bad_import_csv.path])
251
+ kps.stdout = StringIO.new
252
+ kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
253
+ begin
254
+ kps.run
255
+ rescue SystemExit => se
256
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Error: There must be a header on the CSV /m }
257
+ se.status.should_eql 1
258
+ end
259
+ end
260
+
261
+ specify "able to export to a csv" do
262
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-x", @export_csv.path])
263
+ kps.stdout = StringIO.new
264
+ kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
265
+ kps.run
266
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Exported \d* records to/m }
267
+ end
268
+ end