pwl 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -1
- data/Gemfile.lock +19 -5
- data/VERSION +1 -1
- data/bin/pwl +47 -228
- data/lib/pwl.rb +12 -0
- data/lib/pwl/commands/add.rb +36 -0
- data/lib/pwl/commands/base.rb +98 -0
- data/lib/pwl/commands/delete.rb +18 -0
- data/lib/pwl/commands/export.rb +22 -0
- data/lib/pwl/commands/get.rb +26 -0
- data/lib/pwl/commands/init.rb +35 -0
- data/lib/pwl/commands/list.rb +38 -0
- data/lib/pwl/commands/passwd.rb +42 -0
- data/lib/pwl/commands/stats.rb +59 -0
- data/lib/pwl/entry.rb +31 -0
- data/lib/pwl/entry_mapper.rb +30 -0
- data/lib/pwl/locker.rb +18 -10
- data/lib/pwl/presenter/json.rb +6 -10
- data/lib/pwl/presenter/yaml.rb +8 -4
- data/pwl.gemspec +24 -5
- data/templates/export.html.erb +5 -5
- data/test/acceptance/test_export.rb +6 -3
- data/test/acceptance/test_export_json.rb +9 -6
- data/test/acceptance/test_export_yaml.rb +8 -5
- data/test/acceptance/test_init.rb +24 -4
- data/test/acceptance/test_list.rb +1 -1
- data/test/fixtures/test_all.html +3 -3
- data/test/fixtures/test_all.json +9 -6
- data/test/fixtures/test_all.yaml +6 -3
- data/test/helper.rb +4 -2
- data/test/unit/test_entry.rb +67 -0
- data/test/unit/test_entry_mapper.rb +33 -0
- data/test/unit/test_store_crud.rb +25 -11
- data/test/unit/test_store_password_policy.rb +1 -1
- data/test/unit/test_store_security.rb +2 -1
- metadata +51 -6
data/Gemfile
CHANGED
@@ -3,7 +3,8 @@ source "http://rubygems.org"
|
|
3
3
|
gem 'encryptor', "~> 1.1"
|
4
4
|
gem 'commander', "~> 4.1"
|
5
5
|
gem 'activesupport', "~> 3.2"
|
6
|
-
gem '
|
6
|
+
gem 'activemodel', "~> 3.2"
|
7
|
+
gem 'uuid', "~> 2.3"
|
7
8
|
|
8
9
|
group :test do
|
9
10
|
gem "rake", '~> 0.9'
|
@@ -15,6 +16,7 @@ end
|
|
15
16
|
# Include everything needed to run rake, tests, features, etc.
|
16
17
|
group :development do
|
17
18
|
gem "rdoc", "~> 3.1"
|
19
|
+
gem "pry" #, "~> 3.1"
|
18
20
|
gem "bundler", "~> 1.1"
|
19
21
|
gem "jeweler", "~> 1.8"
|
20
22
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,30 +1,38 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
activemodel (3.2.3)
|
5
|
+
activesupport (= 3.2.3)
|
6
|
+
builder (~> 3.0.0)
|
4
7
|
activesupport (3.2.3)
|
5
8
|
i18n (~> 0.6)
|
6
9
|
multi_json (~> 1.0)
|
7
|
-
|
10
|
+
builder (3.0.0)
|
11
|
+
coderay (1.0.6)
|
8
12
|
commander (4.1.2)
|
9
13
|
highline (~> 1.6.11)
|
10
14
|
encryptor (1.1.3)
|
11
15
|
git (1.2.5)
|
12
16
|
highline (1.6.11)
|
13
17
|
i18n (0.6.0)
|
14
|
-
jbuilder (0.4.0)
|
15
|
-
activesupport (>= 3.0.0)
|
16
|
-
blankslate (>= 2.1.2.4)
|
17
18
|
jeweler (1.8.3)
|
18
19
|
bundler (~> 1.0)
|
19
20
|
git (>= 1.2.5)
|
20
21
|
rake
|
21
22
|
rdoc
|
22
23
|
json (1.7.0)
|
24
|
+
macaddr (1.6.1)
|
25
|
+
systemu (~> 2.5.0)
|
26
|
+
method_source (0.7.1)
|
23
27
|
multi_json (1.3.4)
|
24
28
|
nokogiri (1.4.7)
|
25
29
|
nokogiri-diff (0.1.0)
|
26
30
|
nokogiri (~> 1.4.1)
|
27
31
|
tdiff (~> 0.3.2)
|
32
|
+
pry (0.9.9.6)
|
33
|
+
coderay (~> 1.0.5)
|
34
|
+
method_source (~> 0.7.1)
|
35
|
+
slop (>= 2.4.4, < 3)
|
28
36
|
rake (0.9.2.2)
|
29
37
|
rdoc (3.12)
|
30
38
|
json (~> 1.4)
|
@@ -32,19 +40,25 @@ GEM
|
|
32
40
|
multi_json (~> 1.3)
|
33
41
|
simplecov-html (~> 0.5.3)
|
34
42
|
simplecov-html (0.5.3)
|
43
|
+
slop (2.4.4)
|
44
|
+
systemu (2.5.1)
|
35
45
|
tdiff (0.3.2)
|
46
|
+
uuid (2.3.5)
|
47
|
+
macaddr (~> 1.0)
|
36
48
|
|
37
49
|
PLATFORMS
|
38
50
|
ruby
|
39
51
|
|
40
52
|
DEPENDENCIES
|
53
|
+
activemodel (~> 3.2)
|
41
54
|
activesupport (~> 3.2)
|
42
55
|
bundler (~> 1.1)
|
43
56
|
commander (~> 4.1)
|
44
57
|
encryptor (~> 1.1)
|
45
|
-
jbuilder (~> 0.4)
|
46
58
|
jeweler (~> 1.8)
|
47
59
|
nokogiri-diff (~> 0.1)
|
60
|
+
pry
|
48
61
|
rake (~> 0.9)
|
49
62
|
rdoc (~> 3.1)
|
50
63
|
simplecov (~> 0.6)
|
64
|
+
uuid (~> 2.3)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/bin/pwl
CHANGED
@@ -1,166 +1,29 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'commander/import'
|
5
4
|
|
6
5
|
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
6
|
+
|
7
|
+
require 'commander/import'
|
7
8
|
require 'pwl'
|
9
|
+
include Pwl::Commands
|
8
10
|
|
9
11
|
program :version, Pwl::VERSION
|
10
12
|
program :description, "#{program(:name)} is a secure password locker for the command line."
|
11
|
-
|
12
|
-
EXIT_CODES = {
|
13
|
-
:success => Pwl::Message.new('Success.'),
|
14
|
-
:aborted => Pwl::Message.new('Aborted by user.', 1),
|
15
|
-
:passwords_dont_match => Pwl::ErrorMessage.new('Passwords do not match.', 2),
|
16
|
-
:no_value_found => Pwl::Message.new('No value found for <%= name %>.', 3, :name => 'NAME'),
|
17
|
-
:file_exists => Pwl::ErrorMessage.new('There already exists a locker at <%= file %>. Use --force to overwrite it or --file to specify a different locker.', 4, :file => 'FILE'),
|
18
|
-
:file_not_found => Pwl::ErrorMessage.new('Locker file <%= file %> could not be found.', 5, :file => 'FILE'),
|
19
|
-
:name_blank => Pwl::ErrorMessage.new('Name may not be blank.', 6),
|
20
|
-
:value_blank => Pwl::ErrorMessage.new('Value may not be blank.', 7),
|
21
|
-
:list_empty => Pwl::Message.new('List is empty.', 8),
|
22
|
-
:list_empty_filter => Pwl::Message.new('No names found that match filter <%= filter %>.', 9, :filter => 'FILTER'),
|
23
|
-
:validation_new_failed => Pwl::ErrorMessage.new('<%= message %>.', 10, :message => 'Validation of new master password failed'),
|
24
|
-
:unknown_export_format => Pwl::ErrorMessage.new('<%= format %> is not a known export format.', 11, :format => 'FORMAT'),
|
25
|
-
}
|
26
|
-
|
27
|
-
program :help, 'Exit Status', "#{program(:name)} sets the following exit status:\n\n" + EXIT_CODES.values.sort{|l,r| l.exit_code <=> r.exit_code}.collect{|m| " #{m.exit_code.to_s.rjust(EXIT_CODES.size.to_s.size)}: #{m.to_s}"}.join("\n")
|
13
|
+
program :help, 'Exit Status', "#{program(:name)} sets the following exit status:\n\n" + Pwl::Commands::Base.exit_codes_help
|
28
14
|
program :help, 'Author', 'Nicholas E. Rabenau <nerab@gmx.at>'
|
29
15
|
|
30
|
-
DEFAULT_LOCKER_FILE = File.expand_path("~/.#{program(:name)}.pstore")
|
31
|
-
DEFAULT_EXPORT_TEMPLATE = File.join(File.dirname(__FILE__), *%w[.. templates export.html.erb])
|
32
|
-
|
33
|
-
locker_file = DEFAULT_LOCKER_FILE
|
34
|
-
|
35
16
|
global_option '-V', '--verbose', 'Enable verbose output'
|
36
|
-
global_option('-f', '--file FILE', 'Determine the file that holds the locker')
|
17
|
+
global_option('-f', '--file FILE', 'Determine the file that holds the locker')
|
37
18
|
global_option '-g', '--gui', 'Request the master password using an OS-specific GUI dialog. This option takes precedence over STDIN.'
|
38
19
|
|
39
|
-
command :init do |c|
|
40
|
-
c.syntax = "#{program(:name)} #{c.name}"
|
41
|
-
c.summary = 'Initializes a new locker'
|
42
|
-
c.description = 'This command initializes a new password locker. Password quality is enforced using validation rules.'
|
43
|
-
c.example "Initializes a new password locker in #{DEFAULT_LOCKER_FILE}", "#{program(:name)} #{c.name}"
|
44
|
-
c.example "Initializes a new password locker in /tmp/crackme.txt", "#{program(:name)} #{c.name} --file /tmp/crackme.txt"
|
45
|
-
c.option '--force', 'Force-overwrite an existing locker file'
|
46
|
-
c.action do |args, options|
|
47
|
-
# Locker checks this too, but we want to fail fast.
|
48
|
-
exit_with(:file_exists, options.verbose, :file => locker_file) if File.exists?(locker_file) && !options.force
|
49
|
-
|
50
|
-
begin
|
51
|
-
begin
|
52
|
-
master_password = get_password('Enter new master password:', options.gui)
|
53
|
-
end while begin
|
54
|
-
validate!(master_password) # Basic idea from http://stackoverflow.com/questions/136793/is-there-a-do-while-loop-in-ruby
|
55
|
-
rescue Pwl::InvalidMasterPasswordError => e
|
56
|
-
STDERR.puts e.message
|
57
|
-
options.gui || STDIN.tty? # only continue the loop when in interactive mode
|
58
|
-
end
|
59
|
-
|
60
|
-
# Ask for password confirmation if running in interactive mode (terminal)
|
61
|
-
if STDIN.tty? && master_password != get_password('Enter master password again:', options.gui)
|
62
|
-
exit_with(:passwords_dont_match, options.verbose)
|
63
|
-
end
|
64
|
-
rescue Pwl::Dialog::Cancelled
|
65
|
-
exit_with(:aborted, options.verbose)
|
66
|
-
end
|
67
|
-
|
68
|
-
Pwl::Locker.new(locker_file, master_password, {:force => options.force})
|
69
|
-
STDERR.puts "Successfully initialized new locker at #{locker_file}" if options.verbose
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
command :get do |c|
|
74
|
-
c.syntax = "#{program(:name)} #{c.name} NAME"
|
75
|
-
c.summary = 'Retrieves the value for NAME and prints it to STDOUT.'
|
76
|
-
c.description = 'This command retrieves the value stored under NAME and prints it on STDOUT.'
|
77
|
-
c.example 'Reads the value stored under the name "foo" and prints it to STDOUT', "#{program(:name)} #{c.name} foo"
|
78
|
-
c.action do |args, options|
|
79
|
-
# Locker checks this too, but we want to fail fast and provide a message.
|
80
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
81
|
-
exit_with(:name_blank, options.verbose) if 0 == args.size || args[0].blank?
|
82
|
-
|
83
|
-
begin
|
84
|
-
result = Pwl::Locker.open(locker_file, get_password("Enter the master password for #{program(:name)}:", options.gui)).get(args[0])
|
85
|
-
if result.blank?
|
86
|
-
exit_with(:no_value_found, options.verbose, :name => args[0])
|
87
|
-
else
|
88
|
-
puts result
|
89
|
-
end
|
90
|
-
rescue Pwl::Dialog::Cancelled
|
91
|
-
exit_with(:aborted, options.verbose)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
command :list do |c|
|
97
|
-
c.syntax = "#{program(:name)} #{c.name} [FILTER]"
|
98
|
-
c.summary = 'Lists all names with optional FILTER.'
|
99
|
-
c.description = 'This command prints all names to STDOUT. If present, only those names matching FILTER will be returned.'
|
100
|
-
c.example 'Prints all names', "#{program(:name)} #{c.name}"
|
101
|
-
c.example 'Prints all names which start with "foo"', "#{program(:name)} #{c.name} foo"
|
102
|
-
c.example 'Prints all names separated by comma', "#{program(:name)} #{c.name} --separator ,"
|
103
|
-
c.option '-s', '--separator SEPARATOR', String, 'Separate names by SEPARATOR'
|
104
|
-
c.action do |args, options|
|
105
|
-
# Locker checks this too, but we want to fail fast and provide a message.
|
106
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
107
|
-
|
108
|
-
options.default :separator => ' '
|
109
|
-
|
110
|
-
begin
|
111
|
-
result = Pwl::Locker.open(locker_file, get_password("Enter the master password for #{program(:name)}:", options.gui)).list(args[0]).join(options.separator)
|
112
|
-
if !result.blank?
|
113
|
-
puts result
|
114
|
-
else
|
115
|
-
if args[0] # filter given
|
116
|
-
exit_with(:list_empty_filter, options.verbose, args[0])
|
117
|
-
else
|
118
|
-
exit_with(:list_empty, options.verbose)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
rescue Pwl::Dialog::Cancelled
|
122
|
-
exit_with(:aborted, options.verbose)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
20
|
command :add do |c|
|
128
21
|
c.syntax = "#{program(:name)} #{c.name} NAME [VALUE]"
|
129
22
|
c.summary = 'Stores VALUE under NAME'
|
130
23
|
c.description = 'Adds or updates the entry stored under NAME. If NAME is already present in the locker, it will be updated with VALUE. If NAME is not already present in the locker, a new entry will be created. If VALUE is not given, it will be read from STDIN.'
|
131
24
|
c.example 'Stores the value "bar" under the name "foo"', "#{program(:name)} #{c.name} foo bar"
|
132
25
|
c.example 'Reads STDIN and stores that as value under the name "foo"', "#{program(:name)} #{c.name} foo"
|
133
|
-
c.action
|
134
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
135
|
-
exit_with(:name_blank, options.verbose) if 0 == args.size || args[0].blank?
|
136
|
-
|
137
|
-
# Ask for the master password _before_ it may be necessary to ask for the value in a terminal
|
138
|
-
begin
|
139
|
-
locker = Pwl::Locker.open(locker_file, get_password("Enter the master password for #{program(:name)}:", options.gui))
|
140
|
-
rescue Pwl::Dialog::Cancelled
|
141
|
-
exit_with(:aborted, options.verbose)
|
142
|
-
end
|
143
|
-
|
144
|
-
value = args[1]
|
145
|
-
|
146
|
-
if !value
|
147
|
-
exit_with(:value_blank, options.verbose) unless STDIN.tty? || options.gui
|
148
|
-
|
149
|
-
begin
|
150
|
-
value = get_text("Enter value for name '#{args[0]}':", options.gui)
|
151
|
-
rescue Pwl::Dialog::Cancelled
|
152
|
-
exit_with(:aborted, options.verbose)
|
153
|
-
end
|
154
|
-
|
155
|
-
# still blank, even after asking for it?
|
156
|
-
if value.blank?
|
157
|
-
exit_with(:value_blank, true)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
locker.add(args[0], value)
|
162
|
-
STDERR.puts "Successfully stored new value under #{args[0]}." if options.verbose
|
163
|
-
end
|
26
|
+
c.action Add.new
|
164
27
|
end
|
165
28
|
|
166
29
|
command :delete do |c|
|
@@ -168,63 +31,7 @@ command :delete do |c|
|
|
168
31
|
c.summary = 'Deletes the entry stored under NAME'
|
169
32
|
c.description = 'Deletes the complete entry that is stored under NAME. If NAME is not present in the locker, an error will thrown.'
|
170
33
|
c.example 'Deletes what was stored under the name "foo"', "#{program(:name)} #{c.name} foo"
|
171
|
-
c.action
|
172
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
173
|
-
exit_with(:name_blank, options.verbose) if 0 == args.size || args[0].blank?
|
174
|
-
|
175
|
-
begin
|
176
|
-
locker = Pwl::Locker.open(locker_file, get_password("Enter the master password for #{program(:name)}:", options.gui))
|
177
|
-
rescue Pwl::Dialog::Cancelled
|
178
|
-
exit_with(:aborted, options.verbose)
|
179
|
-
end
|
180
|
-
|
181
|
-
locker.delete(args[0])
|
182
|
-
STDERR.puts "Successfully deleted the value under #{args[0]}." if options.verbose
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
command :passwd do |c|
|
187
|
-
c.syntax = "#{program(:name)} #{c.name} [NEW_MASTER_PASSWORD]"
|
188
|
-
c.summary = 'Changes the master password to NEW_MASTER_PASSWORD.'
|
189
|
-
c.description = 'This command changes the master password of the locker. Password quality is enforced using validation rules.'
|
190
|
-
c.action do |args, options|
|
191
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
192
|
-
|
193
|
-
begin
|
194
|
-
locker = Pwl::Locker.open(locker_file, get_password("Enter the current master password for #{program(:name)}:", options.gui))
|
195
|
-
|
196
|
-
if !STDIN.tty? && !options.gui
|
197
|
-
# If we are in a pipe and do not run in GUI mode, we accept the new master password as args[0]
|
198
|
-
new_master_password = args[0]
|
199
|
-
|
200
|
-
begin
|
201
|
-
validate!(new_master_password)
|
202
|
-
rescue Pwl::InvalidMasterPasswordError => e
|
203
|
-
exit_with(:validation_new_failed, options.verbose, :message => e.message)
|
204
|
-
end
|
205
|
-
else
|
206
|
-
# If running interactively (console or gui), we loop until we get a valid password or break
|
207
|
-
begin
|
208
|
-
new_master_password = get_password("Enter the new master password for #{program(:name)}:", options.gui)
|
209
|
-
end while begin
|
210
|
-
validate!(new_master_password)
|
211
|
-
rescue Pwl::InvalidMasterPasswordError => e
|
212
|
-
STDERR.puts e.message
|
213
|
-
options.gui || STDIN.tty? # only continue the loop when in interactive mode
|
214
|
-
end
|
215
|
-
|
216
|
-
# Confirm new password
|
217
|
-
if new_master_password != get_password("Enter the new master password for #{program(:name)} again:", options.gui)
|
218
|
-
exit_with(:passwords_dont_match, options.verbose)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
rescue Pwl::Dialog::Cancelled
|
222
|
-
exit_with(:aborted, options.verbose)
|
223
|
-
end
|
224
|
-
|
225
|
-
locker.change_password!(new_master_password)
|
226
|
-
STDERR.puts "Successfully changed master password for #{program(:name)}." if options.verbose
|
227
|
-
end
|
34
|
+
c.action Delete.new
|
228
35
|
end
|
229
36
|
|
230
37
|
command :export do |c|
|
@@ -236,41 +43,53 @@ command :export do |c|
|
|
236
43
|
c.example 'Prints all entries in HTML format (default)', "#{program(:name)} #{c.name} --format html"
|
237
44
|
c.example 'Prints all entries in JSON format', "#{program(:name)} #{c.name} --format json"
|
238
45
|
c.example 'Prints all entries in YAML format', "#{program(:name)} #{c.name} --format yaml"
|
239
|
-
c.action
|
240
|
-
exit_with(:file_not_found, options.verbose, :file => locker_file) unless File.exists?(locker_file)
|
241
|
-
options.default :format => 'html'
|
242
|
-
|
243
|
-
presenter = {:html => Pwl::Presenter::Html, :json => Pwl::Presenter::Json, :yaml => Pwl::Presenter::Yaml}[options.format.to_sym]
|
244
|
-
exit_with(:unknown_export_format, options.verbose, :format => options.format) if presenter.nil?
|
245
|
-
|
246
|
-
begin
|
247
|
-
locker = Pwl::Locker.open(locker_file, get_password("Enter the master password for #{program(:name)}:", options.gui))
|
248
|
-
puts presenter.new(locker).to_s
|
249
|
-
rescue Pwl::Dialog::Cancelled
|
250
|
-
exit_with(:aborted, options.verbose)
|
251
|
-
end
|
252
|
-
end
|
46
|
+
c.action Export.new
|
253
47
|
end
|
254
48
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
49
|
+
command :get do |c|
|
50
|
+
c.syntax = "#{program(:name)} #{c.name} NAME [FIELD]"
|
51
|
+
c.summary = 'Retrieves the value for NAME and prints it to STDOUT.'
|
52
|
+
c.description = 'This command retrieves the value stored under NAME and prints it on STDOUT.'
|
53
|
+
c.example 'Reads the value stored under the name "foo" and prints it to STDOUT', "#{program(:name)} #{c.name} foo"
|
54
|
+
c.action Get.new
|
55
|
+
end
|
262
56
|
|
263
|
-
|
57
|
+
command :init do |c|
|
58
|
+
c.syntax = "#{program(:name)} #{c.name}"
|
59
|
+
c.summary = 'Initializes a new locker'
|
60
|
+
c.description = 'This command initializes a new password locker. Password quality is enforced using validation rules.'
|
61
|
+
c.example "Initializes a new password locker in #{Base.default_locker_file}", "#{program(:name)} #{c.name}"
|
62
|
+
c.example "Initializes a new password locker in /tmp/crackme.txt", "#{program(:name)} #{c.name} --file /tmp/crackme.txt"
|
63
|
+
c.option '--force', 'Force-overwrite an existing locker file'
|
64
|
+
c.action Init.new
|
264
65
|
end
|
265
66
|
|
266
|
-
|
267
|
-
|
67
|
+
command :list do |c|
|
68
|
+
c.syntax = "#{program(:name)} #{c.name} [FILTER]"
|
69
|
+
c.summary = 'Lists all names with optional FILTER.'
|
70
|
+
c.description = 'This command prints all names to STDOUT. If FILTER is present, only matching names will be printed.'
|
71
|
+
c.option '--long', 'List in long format.'
|
72
|
+
c.example 'Prints all names', "#{program(:name)} #{c.name}"
|
73
|
+
c.example 'Prints all names which start with "foo"', "#{program(:name)} #{c.name} foo"
|
74
|
+
c.example 'Prints all names separated by comma', "#{program(:name)} #{c.name} --separator ,"
|
75
|
+
c.option '-s', '--separator SEPARATOR', String, 'Separate names by SEPARATOR. Not applicable in long format.'
|
76
|
+
c.action List.new
|
268
77
|
end
|
269
78
|
|
270
|
-
|
271
|
-
|
79
|
+
command :passwd do |c|
|
80
|
+
c.syntax = "#{program(:name)} #{c.name} [NEW_MASTER_PASSWORD]"
|
81
|
+
c.summary = 'Changes the master password to NEW_MASTER_PASSWORD.'
|
82
|
+
c.description = 'This command changes the master password of the locker. Password quality is enforced using validation rules.'
|
83
|
+
c.action Passwd.new
|
272
84
|
end
|
273
85
|
|
274
|
-
|
275
|
-
|
86
|
+
command :stats do |c|
|
87
|
+
c.syntax = "#{program(:name)} #{c.name}"
|
88
|
+
c.summary = 'Provides simple statistics'
|
89
|
+
c.description = 'This command provides basic statistical information about a password locker.'
|
90
|
+
c.option '--format [FORMAT]', String, 'Specify format. If this option is not present, text format is assumed.'
|
91
|
+
c.example 'Prints stats in HTML format (default)', "#{program(:name)} #{c.name} --format html"
|
92
|
+
c.example 'Prints stats in JSON format', "#{program(:name)} #{c.name} --format json"
|
93
|
+
c.example 'Prints stats in YAML format', "#{program(:name)} #{c.name} --format yaml"
|
94
|
+
c.action Stats.new
|
276
95
|
end
|
data/lib/pwl.rb
CHANGED
@@ -8,11 +8,23 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[pwl])
|
|
8
8
|
require 'message'
|
9
9
|
require 'password_policy'
|
10
10
|
require 'locker'
|
11
|
+
require 'entry'
|
12
|
+
require 'entry_mapper'
|
11
13
|
require 'dialog'
|
12
14
|
require 'presenter/html'
|
13
15
|
require 'presenter/json'
|
14
16
|
require 'presenter/yaml'
|
15
17
|
|
18
|
+
require 'commands/base'
|
19
|
+
require 'commands/add'
|
20
|
+
require 'commands/delete'
|
21
|
+
require 'commands/export'
|
22
|
+
require 'commands/get'
|
23
|
+
require 'commands/init'
|
24
|
+
require 'commands/list'
|
25
|
+
require 'commands/passwd'
|
26
|
+
require 'commands/stats'
|
27
|
+
|
16
28
|
module Pwl
|
17
29
|
VERSION = File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION]))
|
18
30
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pwl
|
2
|
+
module Commands
|
3
|
+
class Add < Base
|
4
|
+
def call(args, options)
|
5
|
+
exit_with(:name_blank, options.verbose) if 0 == args.size || args[0].blank?
|
6
|
+
|
7
|
+
# Ask for the master password _before_ it may be necessary to ask for the value in a terminal
|
8
|
+
begin
|
9
|
+
locker = open_locker(options)
|
10
|
+
rescue Pwl::Dialog::Cancelled
|
11
|
+
exit_with(:aborted, options.verbose)
|
12
|
+
end
|
13
|
+
|
14
|
+
value = args[1]
|
15
|
+
|
16
|
+
if !value
|
17
|
+
exit_with(:value_blank, options.verbose) unless STDIN.tty? || options.gui
|
18
|
+
|
19
|
+
begin
|
20
|
+
value = get_text("Enter value for name '#{args[0]}':", options.gui)
|
21
|
+
rescue Pwl::Dialog::Cancelled
|
22
|
+
exit_with(:aborted, options.verbose)
|
23
|
+
end
|
24
|
+
|
25
|
+
# still blank, even after asking for it?
|
26
|
+
if value.blank?
|
27
|
+
exit_with(:value_blank, true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
locker.add(args[0], value)
|
32
|
+
msg "Successfully stored new value under #{args[0]}." if options.verbose
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|