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.
- data/CHANGES +14 -0
- data/COPYING +22 -0
- data/README +132 -0
- data/bin/keybox +18 -0
- data/bin/kpg +19 -0
- data/data/chargrams.txt +8432 -0
- data/lib/keybox.rb +29 -0
- data/lib/keybox/application/base.rb +114 -0
- data/lib/keybox/application/password_generator.rb +131 -0
- data/lib/keybox/application/password_safe.rb +410 -0
- data/lib/keybox/cipher.rb +6 -0
- data/lib/keybox/convert.rb +1 -0
- data/lib/keybox/convert/csv.rb +96 -0
- data/lib/keybox/digest.rb +13 -0
- data/lib/keybox/entry.rb +200 -0
- data/lib/keybox/error.rb +5 -0
- data/lib/keybox/password_hash.rb +33 -0
- data/lib/keybox/randomizer.rb +193 -0
- data/lib/keybox/storage.rb +2 -0
- data/lib/keybox/storage/container.rb +307 -0
- data/lib/keybox/storage/record.rb +103 -0
- data/lib/keybox/string_generator.rb +194 -0
- data/lib/keybox/term_io.rb +163 -0
- data/lib/keybox/uuid.rb +86 -0
- data/spec/base_app_spec.rb +56 -0
- data/spec/convert_csv_spec.rb +46 -0
- data/spec/entry_spec.rb +63 -0
- data/spec/keybox_app_spec.rb +268 -0
- data/spec/kpg_app_spec.rb +132 -0
- data/spec/password_hash_spec.rb +11 -0
- data/spec/randomizer_spec.rb +116 -0
- data/spec/storage_container_spec.rb +99 -0
- data/spec/storage_record_spec.rb +63 -0
- data/spec/string_generator_spec.rb +114 -0
- data/spec/uuid_spec.rb +74 -0
- metadata +83 -0
@@ -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
|
data/lib/keybox/uuid.rb
ADDED
@@ -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
|
data/spec/entry_spec.rb
ADDED
@@ -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
|