mpw 4.1.1 → 4.2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +0 -1
- data/.travis.yml +10 -6
- data/CHANGELOG.md +32 -11
- data/Gemfile +6 -0
- data/README.md +32 -31
- data/VERSION +1 -1
- data/bin/mpw-config +1 -1
- data/bin/mpw-update +2 -0
- data/bin/mpw-wallet +4 -4
- data/i18n/en.yml +50 -48
- data/i18n/fr.yml +2 -0
- data/lib/mpw/cli.rb +81 -56
- data/lib/mpw/config.rb +17 -18
- data/lib/mpw/item.rb +14 -5
- data/lib/mpw/mpw.rb +34 -31
- data/test/files/fixtures-import.yml +19 -0
- data/test/files/fixtures.yml +23 -23
- data/test/init.rb +6 -2
- data/test/test_cli.rb +265 -0
- data/test/test_config.rb +13 -11
- data/test/test_item.rb +75 -73
- data/test/test_mpw.rb +24 -28
- data/test/test_translate.rb +1 -1
- metadata +7 -5
- data/test/tests.rb +0 -7
data/i18n/fr.yml
CHANGED
@@ -111,8 +111,10 @@ fr:
|
|
111
111
|
login: "L'identifiant a été copié dans le presse papier"
|
112
112
|
password: "Le mot de passe a été copié dans le presse papier pour 30s!"
|
113
113
|
otp: "Le code OTP a été copié dans le presse papier il est valable %{time}s!"
|
114
|
+
url: "L'URL a été copié dans le presse papier"
|
114
115
|
help:
|
115
116
|
name: "Aide"
|
117
|
+
url: "Pressez <u> pour copier l'URL"
|
116
118
|
login: "Pressez <l> pour copier l'identifiant"
|
117
119
|
password: "Pressez <p> pour copier le mot de passe"
|
118
120
|
otp_code: "Pressez <o> pour copier le code OTP"
|
data/lib/mpw/cli.rb
CHANGED
@@ -28,14 +28,13 @@ require 'mpw/mpw'
|
|
28
28
|
|
29
29
|
module MPW
|
30
30
|
class Cli
|
31
|
-
#
|
32
|
-
# @args: config -> the config
|
31
|
+
# @param config [Config]
|
33
32
|
def initialize(config)
|
34
33
|
@config = config
|
35
34
|
end
|
36
35
|
|
37
36
|
# Change a parameter int the config after init
|
38
|
-
# @
|
37
|
+
# @param options [Hash] param to change
|
39
38
|
def set_config(options)
|
40
39
|
@config.setup(options)
|
41
40
|
|
@@ -46,7 +45,7 @@ module MPW
|
|
46
45
|
end
|
47
46
|
|
48
47
|
# Change the wallet path
|
49
|
-
# @
|
48
|
+
# @param path [String] new path
|
50
49
|
def set_wallet_path(path)
|
51
50
|
@config.set_wallet_path(path, @wallet)
|
52
51
|
|
@@ -57,7 +56,7 @@ module MPW
|
|
57
56
|
end
|
58
57
|
|
59
58
|
# Create a new config file
|
60
|
-
# @
|
59
|
+
# @param options [Hash]
|
61
60
|
def setup(options)
|
62
61
|
options[:lang] = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
|
63
62
|
|
@@ -74,7 +73,7 @@ module MPW
|
|
74
73
|
end
|
75
74
|
|
76
75
|
# Setup a new GPG key
|
77
|
-
# @
|
76
|
+
# @param gpg_key [String] gpg key name
|
78
77
|
def setup_gpg_key(gpg_key)
|
79
78
|
return if @config.check_gpg_key?
|
80
79
|
|
@@ -106,7 +105,7 @@ module MPW
|
|
106
105
|
'lang' => @config.lang,
|
107
106
|
'gpg_key' => @config.gpg_key,
|
108
107
|
'default_wallet' => @config.default_wallet,
|
109
|
-
'
|
108
|
+
'wallet_dir' => @config.wallet_dir,
|
110
109
|
'pinmode' => @config.pinmode,
|
111
110
|
'gpg_exe' => @config.gpg_exe
|
112
111
|
}
|
@@ -127,20 +126,28 @@ module MPW
|
|
127
126
|
|
128
127
|
# Request the GPG password and decrypt the file
|
129
128
|
def decrypt
|
130
|
-
|
131
|
-
@
|
132
|
-
|
133
|
-
|
129
|
+
if defined?(@mpw)
|
130
|
+
@mpw.read_data
|
131
|
+
else
|
132
|
+
begin
|
133
|
+
@mpw = MPW.new(@config.gpg_key, @wallet_file, nil, @config.gpg_exe, @config.pinmode)
|
134
|
+
|
135
|
+
@mpw.read_data
|
136
|
+
rescue
|
137
|
+
@password = ask(I18n.t('display.gpg_password')) { |q| q.echo = false }
|
138
|
+
@mpw = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe, @config.pinmode)
|
134
139
|
|
135
|
-
|
140
|
+
@mpw.read_data
|
141
|
+
end
|
142
|
+
end
|
136
143
|
rescue => e
|
137
144
|
puts "#{I18n.t('display.error')} #11: #{e}".red
|
138
145
|
exit 2
|
139
146
|
end
|
140
147
|
|
141
148
|
# Format list on a table
|
142
|
-
# @
|
143
|
-
#
|
149
|
+
# @param title [String] name of table
|
150
|
+
# @param list an array or hash
|
144
151
|
def table_list(title, list)
|
145
152
|
length = { k: 0, v: 0 }
|
146
153
|
|
@@ -172,7 +179,7 @@ module MPW
|
|
172
179
|
end
|
173
180
|
|
174
181
|
# Format items on a table
|
175
|
-
# @
|
182
|
+
# @param items [Array]
|
176
183
|
def table_items(items = [])
|
177
184
|
group = '.'
|
178
185
|
i = 1
|
@@ -180,16 +187,19 @@ module MPW
|
|
180
187
|
data = { id: { length: 3, color: 'cyan' },
|
181
188
|
host: { length: 9, color: 'yellow' },
|
182
189
|
user: { length: 7, color: 'green' },
|
183
|
-
protocol: { length: 9, color: 'white' },
|
184
|
-
port: { length: 5, color: 'white' },
|
185
190
|
otp: { length: 4, color: 'white' },
|
186
191
|
comment: { length: 14, color: 'magenta' } }
|
187
192
|
|
188
193
|
items.each do |item|
|
189
194
|
data.each do |k, v|
|
190
|
-
|
191
|
-
|
192
|
-
|
195
|
+
case k
|
196
|
+
when :id, :otp
|
197
|
+
next
|
198
|
+
when :host
|
199
|
+
v[:length] = item.url.length + 3 if item.url.length >= v[:length]
|
200
|
+
else
|
201
|
+
v[:length] = item.send(k.to_s).to_s.length + 3 if item.send(k.to_s).to_s.length >= v[:length]
|
202
|
+
end
|
193
203
|
end
|
194
204
|
end
|
195
205
|
data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length]
|
@@ -232,16 +242,22 @@ module MPW
|
|
232
242
|
data.each do |k, v|
|
233
243
|
next if k == :id
|
234
244
|
|
235
|
-
|
236
|
-
|
245
|
+
print '| '
|
246
|
+
|
247
|
+
case k
|
248
|
+
when :otp
|
237
249
|
item.otp ? (print ' X ') : 4.times { print ' ' }
|
238
250
|
|
239
|
-
|
240
|
-
|
251
|
+
when :host
|
252
|
+
print "#{item.protocol}://".light_black if item.protocol
|
253
|
+
print item.host.send(v[:color])
|
254
|
+
print ":#{item.port}".light_black if item.port
|
255
|
+
(v[:length] - item.url.to_s.length).times { print ' ' }
|
241
256
|
|
242
|
-
|
243
|
-
|
244
|
-
|
257
|
+
else
|
258
|
+
print item.send(k.to_s).to_s.send(v[:color])
|
259
|
+
(v[:length] - item.send(k.to_s).to_s.length).times { print ' ' }
|
260
|
+
end
|
245
261
|
end
|
246
262
|
print "\n"
|
247
263
|
|
@@ -252,7 +268,7 @@ module MPW
|
|
252
268
|
end
|
253
269
|
|
254
270
|
# Display the query's result
|
255
|
-
# @
|
271
|
+
# @param options [Hash] the options to search
|
256
272
|
def list(**options)
|
257
273
|
result = @mpw.list(options)
|
258
274
|
|
@@ -264,8 +280,8 @@ module MPW
|
|
264
280
|
end
|
265
281
|
|
266
282
|
# Get an item when multiple choice
|
267
|
-
# @
|
268
|
-
# @
|
283
|
+
# @param items [Array] list of items
|
284
|
+
# @return [Item] an item
|
269
285
|
def get_item(items)
|
270
286
|
return items[0] if items.length == 1
|
271
287
|
|
@@ -276,8 +292,8 @@ module MPW
|
|
276
292
|
end
|
277
293
|
|
278
294
|
# Copy in clipboard the login and password
|
279
|
-
# @
|
280
|
-
#
|
295
|
+
# @param item [Item]
|
296
|
+
# @param clipboard [Boolean] enable clipboard
|
281
297
|
def clipboard(item, clipboard = true)
|
282
298
|
# Security: force quit after 90s
|
283
299
|
Thread.new do
|
@@ -292,6 +308,14 @@ module MPW
|
|
292
308
|
when 'q', 'quit'
|
293
309
|
break
|
294
310
|
|
311
|
+
when 'u', 'url'
|
312
|
+
if clipboard
|
313
|
+
Clipboard.copy(item.url)
|
314
|
+
puts I18n.t('form.clipboard.url').green
|
315
|
+
else
|
316
|
+
puts item.url
|
317
|
+
end
|
318
|
+
|
295
319
|
when 'l', 'login'
|
296
320
|
if clipboard
|
297
321
|
Clipboard.copy(item.user)
|
@@ -324,6 +348,7 @@ module MPW
|
|
324
348
|
|
325
349
|
else
|
326
350
|
puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan
|
351
|
+
puts I18n.t('form.clipboard.help.url')
|
327
352
|
puts I18n.t('form.clipboard.help.login')
|
328
353
|
puts I18n.t('form.clipboard.help.password')
|
329
354
|
puts I18n.t('form.clipboard.help.otp_code')
|
@@ -350,7 +375,7 @@ module MPW
|
|
350
375
|
end
|
351
376
|
|
352
377
|
# Display the wallet
|
353
|
-
# @
|
378
|
+
# @param wallet [String] wallet name
|
354
379
|
def get_wallet(wallet = nil)
|
355
380
|
@wallet =
|
356
381
|
if wallet.to_s.empty?
|
@@ -375,7 +400,7 @@ module MPW
|
|
375
400
|
end
|
376
401
|
|
377
402
|
# Add a new public key
|
378
|
-
#
|
403
|
+
# @param key [String] key name or key file to add
|
379
404
|
def add_key(key)
|
380
405
|
@mpw.add_key(key)
|
381
406
|
@mpw.write_data
|
@@ -386,7 +411,7 @@ module MPW
|
|
386
411
|
end
|
387
412
|
|
388
413
|
# Add new public key
|
389
|
-
#
|
414
|
+
# @param key [String] key name to delete
|
390
415
|
def delete_key(key)
|
391
416
|
@mpw.delete_key(key)
|
392
417
|
@mpw.write_data
|
@@ -397,10 +422,10 @@ module MPW
|
|
397
422
|
end
|
398
423
|
|
399
424
|
# Text editor interface
|
400
|
-
# @
|
401
|
-
#
|
402
|
-
#
|
403
|
-
# @
|
425
|
+
# @param template_name [String] template name
|
426
|
+
# @param item [Item] the item to edit
|
427
|
+
# @param password [Boolean] disable field password
|
428
|
+
# @return [Hash] the values for an item
|
404
429
|
def text_editor(template_name, password = false, item = nil, **options)
|
405
430
|
editor = ENV['EDITOR'] || 'nano'
|
406
431
|
opts = {}
|
@@ -429,9 +454,9 @@ module MPW
|
|
429
454
|
end
|
430
455
|
|
431
456
|
# Form to add a new item
|
432
|
-
# @
|
433
|
-
#
|
434
|
-
#
|
457
|
+
# @param password [Boolean] generate a random password
|
458
|
+
# @param text_editor [Boolean] enable text editor mode
|
459
|
+
# @param values [Hash] multiples value to set the item
|
435
460
|
def add(password = false, text_editor = false, **values)
|
436
461
|
options = text_editor('add_form', password, nil, values) if text_editor
|
437
462
|
item = Item.new(options)
|
@@ -448,15 +473,15 @@ module MPW
|
|
448
473
|
end
|
449
474
|
|
450
475
|
# Update an item
|
451
|
-
# @
|
452
|
-
#
|
453
|
-
#
|
454
|
-
#
|
476
|
+
# @param password [Boolean] generate a random password
|
477
|
+
# @param text_editor [Boolean] enable text editor mode
|
478
|
+
# @param options [Hash] the options to search
|
479
|
+
# @param values [Hash] multiples value to set the item
|
455
480
|
def update(password = false, text_editor = false, options = {}, **values)
|
456
481
|
items = @mpw.list(options)
|
457
482
|
|
458
483
|
if items.empty?
|
459
|
-
puts
|
484
|
+
puts I18n.t('display.nothing')
|
460
485
|
else
|
461
486
|
table_items(items) if items.length > 1
|
462
487
|
|
@@ -476,12 +501,12 @@ module MPW
|
|
476
501
|
end
|
477
502
|
|
478
503
|
# Remove an item
|
479
|
-
# @
|
504
|
+
# @param options [Hash] the options to search
|
480
505
|
def delete(**options)
|
481
506
|
items = @mpw.list(options)
|
482
507
|
|
483
508
|
if items.empty?
|
484
|
-
puts
|
509
|
+
puts I18n.t('display.nothing')
|
485
510
|
else
|
486
511
|
table_items(items)
|
487
512
|
|
@@ -500,8 +525,8 @@ module MPW
|
|
500
525
|
end
|
501
526
|
|
502
527
|
# Copy a password, otp, login
|
503
|
-
# @
|
504
|
-
#
|
528
|
+
# @param clipboard [Boolean] enable clipboard
|
529
|
+
# @param options [Hash] the options to search
|
505
530
|
def copy(clipboard = true, **options)
|
506
531
|
items = @mpw.list(options)
|
507
532
|
|
@@ -517,9 +542,9 @@ module MPW
|
|
517
542
|
puts "#{I18n.t('display.error')} #14: #{e}".red
|
518
543
|
end
|
519
544
|
|
520
|
-
# Export the items in
|
521
|
-
# @
|
522
|
-
#
|
545
|
+
# Export the items in an yaml file
|
546
|
+
# @param file [String] the path of destination file
|
547
|
+
# @param options [Hash] options to search
|
523
548
|
def export(file, options)
|
524
549
|
file = 'export-mpw.yml' if file.to_s.empty?
|
525
550
|
items = @mpw.list(options)
|
@@ -549,8 +574,8 @@ module MPW
|
|
549
574
|
puts "#{I18n.t('display.error')} #17: #{e}".red
|
550
575
|
end
|
551
576
|
|
552
|
-
# Import items from
|
553
|
-
# @
|
577
|
+
# Import items from an yaml file
|
578
|
+
# @param file [String] path of import file
|
554
579
|
def import(file)
|
555
580
|
raise I18n.t('form.import.file_empty') if file.to_s.empty?
|
556
581
|
raise I18n.t('form.import.file_not_exist') unless File.exist?(file)
|
data/lib/mpw/config.rb
CHANGED
@@ -35,8 +35,7 @@ module MPW
|
|
35
35
|
attr_accessor :password
|
36
36
|
attr_accessor :pinmode
|
37
37
|
|
38
|
-
#
|
39
|
-
# @args: config_file -> the specify config file
|
38
|
+
# @param config_file [String] path of config file
|
40
39
|
def initialize(config_file = nil)
|
41
40
|
@config_file = config_file
|
42
41
|
@config_dir =
|
@@ -52,19 +51,20 @@ module MPW
|
|
52
51
|
end
|
53
52
|
|
54
53
|
# Create a new config file
|
55
|
-
# @
|
56
|
-
# @rtrn: true if le config file is create
|
54
|
+
# @param options [Hash] the value to set the config file
|
57
55
|
def setup(**options)
|
58
56
|
gpg_key = options[:gpg_key] || @gpg_key
|
59
57
|
lang = options[:lang] || @lang
|
60
58
|
wallet_dir = options[:wallet_dir] || @wallet_dir
|
61
59
|
default_wallet = options[:default_wallet] || @default_wallet
|
62
60
|
gpg_exe = options[:gpg_exe] || @gpg_exe
|
63
|
-
pinmode = options[:pinmode]
|
64
|
-
password = {
|
65
|
-
|
66
|
-
|
67
|
-
|
61
|
+
pinmode = options.key?(:pinmode) ? options[:pinmode] : @pinmode
|
62
|
+
password = {
|
63
|
+
numeric: true,
|
64
|
+
alpha: true,
|
65
|
+
special: false,
|
66
|
+
length: 16
|
67
|
+
}
|
68
68
|
|
69
69
|
%w[numeric special alpha length].each do |k|
|
70
70
|
if options.key?("pwd_#{k}".to_sym)
|
@@ -99,11 +99,10 @@ module MPW
|
|
99
99
|
end
|
100
100
|
|
101
101
|
# Setup a new gpg key
|
102
|
-
# @
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
# @rtrn: true if the GPG key is create, else false
|
102
|
+
# @param password [String] gpg key password
|
103
|
+
# @param name [String] the name of user
|
104
|
+
# @param length [Integer] length of the gpg key
|
105
|
+
# @param expire [Integer] time of expire to gpg key
|
107
106
|
def setup_gpg_key(password, name, length = 4096, expire = 0)
|
108
107
|
raise I18n.t('error.config.genkey_gpg.name') if name.to_s.empty?
|
109
108
|
raise I18n.t('error.config.genkey_gpg.password') if password.to_s.empty?
|
@@ -137,7 +136,7 @@ module MPW
|
|
137
136
|
@default_wallet = config['default_wallet']
|
138
137
|
@gpg_exe = config['gpg_exe']
|
139
138
|
@password = config['password'] || {}
|
140
|
-
@pinmode = config['pinmode']
|
139
|
+
@pinmode = config['pinmode'] || false
|
141
140
|
|
142
141
|
raise if @gpg_key.empty? || @wallet_dir.empty?
|
143
142
|
|
@@ -147,7 +146,7 @@ module MPW
|
|
147
146
|
end
|
148
147
|
|
149
148
|
# Check if private key exist
|
150
|
-
# @
|
149
|
+
# @return [Boolean] true if the key exist, else false
|
151
150
|
def check_gpg_key?
|
152
151
|
ctx = GPGME::Ctx.new
|
153
152
|
ctx.each_key(@gpg_key, true) do
|
@@ -158,8 +157,8 @@ module MPW
|
|
158
157
|
end
|
159
158
|
|
160
159
|
# Change the path of one wallet
|
161
|
-
# @
|
162
|
-
#
|
160
|
+
# @param path [String]new directory path
|
161
|
+
# @param wallet [String] wallet name
|
163
162
|
def set_wallet_path(path, wallet)
|
164
163
|
path = @wallet_dir if path == 'default'
|
165
164
|
|
data/lib/mpw/item.rb
CHANGED
@@ -31,10 +31,7 @@ module MPW
|
|
31
31
|
attr_accessor :last_edit
|
32
32
|
attr_accessor :created
|
33
33
|
|
34
|
-
#
|
35
|
-
# Create a new item
|
36
|
-
# @args: options -> a hash of parameter
|
37
|
-
# raise an error if the hash hasn't the key name
|
34
|
+
# @param options [Hash] the option :host is required
|
38
35
|
def initialize(**options)
|
39
36
|
if !options.key?(:host) || options[:host].to_s.empty?
|
40
37
|
raise I18n.t('error.update.host_empty')
|
@@ -54,7 +51,7 @@ module MPW
|
|
54
51
|
end
|
55
52
|
|
56
53
|
# Update the item
|
57
|
-
# @
|
54
|
+
# @param options [Hash]
|
58
55
|
def update(**options)
|
59
56
|
if options.key?(:host) && options[:host].to_s.empty?
|
60
57
|
raise I18n.t('error.update.host_empty')
|
@@ -84,6 +81,17 @@ module MPW
|
|
84
81
|
@last_edit = nil
|
85
82
|
end
|
86
83
|
|
84
|
+
# Return data on url format
|
85
|
+
# @return [String] an url
|
86
|
+
def url
|
87
|
+
url = ''
|
88
|
+
url += "#{@protocol}://" if @protocol
|
89
|
+
url += @host
|
90
|
+
url += ":#{@port}" if @port
|
91
|
+
|
92
|
+
url
|
93
|
+
end
|
94
|
+
|
87
95
|
def empty?
|
88
96
|
@id.to_s.empty?
|
89
97
|
end
|
@@ -95,6 +103,7 @@ module MPW
|
|
95
103
|
private
|
96
104
|
|
97
105
|
# Generate an random id
|
106
|
+
# @return [String] random string
|
98
107
|
def generate_id
|
99
108
|
[*('A'..'Z'), *('a'..'z'), *('0'..'9')].sample(16).join
|
100
109
|
end
|