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 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 'jbuilder', '~> 0.4'
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
@@ -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
- blankslate (2.1.2.4)
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.2
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'){|file| locker_file = file}
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 do |args, options|
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 do |args, options|
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 do |args, options|
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
- def exit_with(error_code, verbose, msg_args = {})
256
- msg = EXIT_CODES[error_code]
257
- raise "No message defined for error #{error_code}" if !msg
258
-
259
- if msg.error? || verbose # always print errors; messages only when verbose
260
- STDERR.puts msg.to_s(msg_args)
261
- end
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
- exit(msg.exit_code)
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
- def get_password(prompt, gui = false)
267
- (gui ? Pwl::Dialog::Password.new(program(:name), prompt) : Pwl::Dialog::ConsolePasswordDialog.new(prompt)).get_input
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
- def get_text(prompt, gui = false)
271
- (gui ? Pwl::Dialog::Text.new(program(:name), prompt) : Pwl::Dialog::ConsoleTextDialog.new(prompt)).get_input
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
- def validate!(pwd)
275
- Pwl::Locker.password_policy.validate!(pwd)
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