keybox 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|