mpw 3.2.1 → 4.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +9 -9
- data/README.md +99 -80
- data/VERSION +1 -1
- data/bin/mpw +23 -193
- data/bin/mpw-add +61 -0
- data/bin/mpw-config +108 -0
- data/bin/mpw-copy +71 -0
- data/bin/mpw-delete +66 -0
- data/bin/mpw-export +70 -0
- data/bin/mpw-genpwd +50 -0
- data/bin/mpw-import +61 -0
- data/bin/mpw-list +66 -0
- data/bin/mpw-update +70 -0
- data/bin/mpw-wallet +116 -0
- data/i18n/en.yml +70 -32
- data/i18n/fr.yml +70 -32
- data/lib/mpw/cli.rb +333 -220
- data/lib/mpw/config.rb +53 -40
- data/lib/mpw/item.rb +13 -13
- data/lib/mpw/mpw.rb +75 -98
- data/mpw.gemspec +9 -9
- data/templates/add_form.erb +9 -0
- data/templates/update_form.erb +17 -0
- data/test/files/fixtures.yml +0 -4
- data/test/init.rb +19 -0
- data/test/test_config.rb +64 -0
- data/test/test_item.rb +42 -88
- data/test/test_mpw.rb +88 -139
- data/test/test_translate.rb +31 -0
- data/test/tests.rb +4 -1
- metadata +96 -27
- data/CHANGELOG +0 -13
- data/test/files/test_import.csv +0 -3
- data/test/files/test_import.yml +0 -23
data/lib/mpw/cli.rb
CHANGED
@@ -17,10 +17,12 @@
|
|
17
17
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
18
18
|
|
19
19
|
require 'readline'
|
20
|
+
require 'locale'
|
20
21
|
require 'i18n'
|
21
22
|
require 'colorize'
|
22
23
|
require 'highline/import'
|
23
24
|
require 'clipboard'
|
25
|
+
require 'tmpdir'
|
24
26
|
require 'mpw/item'
|
25
27
|
require 'mpw/mpw'
|
26
28
|
|
@@ -30,33 +32,32 @@ class Cli
|
|
30
32
|
# Constructor
|
31
33
|
# @args: config -> the config
|
32
34
|
# sync -> boolean for sync or not
|
33
|
-
|
34
|
-
# otp -> enable otp
|
35
|
-
def initialize(config, clipboard=true, sync=true, otp=false)
|
35
|
+
def initialize(config, sync=true)
|
36
36
|
@config = config
|
37
|
-
@clipboard = clipboard
|
38
37
|
@sync = sync
|
39
|
-
|
38
|
+
end
|
39
|
+
|
40
|
+
# Change a parameter int the config after init
|
41
|
+
# @args: options -> param to change
|
42
|
+
def set_config(options)
|
43
|
+
@config.setup(options)
|
44
|
+
|
45
|
+
puts "#{I18n.t('form.set_config.valid')}".green
|
46
|
+
rescue Exception => e
|
47
|
+
puts "#{I18n.t('display.error')} #15: #{e}".red
|
48
|
+
exit 2
|
40
49
|
end
|
41
50
|
|
42
51
|
# Create a new config file
|
43
|
-
# @args:
|
44
|
-
def setup(
|
45
|
-
|
46
|
-
puts '--------------------'
|
47
|
-
language = ask(I18n.t('form.setup_config.lang', lang: lang)).to_s
|
48
|
-
key = ask(I18n.t('form.setup_config.gpg_key')).to_s
|
49
|
-
wallet_dir = ask(I18n.t('form.setup_config.wallet_dir', home: "#{@config.config_dir}")).to_s
|
50
|
-
gpg_exe = ask(I18n.t('form.setup_config.gpg_exe')).to_s
|
51
|
-
|
52
|
-
if language.nil? or language.empty?
|
53
|
-
language = lang
|
54
|
-
end
|
55
|
-
I18n.locale = language.to_sym
|
52
|
+
# @args: options -> set param
|
53
|
+
def setup(options)
|
54
|
+
options[:lang] = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
|
56
55
|
|
57
|
-
|
56
|
+
I18n.locale = options[:lang].to_sym
|
58
57
|
|
59
|
-
|
58
|
+
@config.setup(options)
|
59
|
+
|
60
|
+
load_config
|
60
61
|
|
61
62
|
puts "#{I18n.t('form.setup_config.valid')}".green
|
62
63
|
rescue Exception => e
|
@@ -65,16 +66,10 @@ class Cli
|
|
65
66
|
end
|
66
67
|
|
67
68
|
# Setup a new GPG key
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
ask = ask(I18n.t('form.setup_gpg_key.ask')).to_s
|
72
|
-
|
73
|
-
if not ['Y', 'y', 'O', 'o'].include?(ask)
|
74
|
-
raise I18n.t('form.setup_gpg_key.no_create')
|
75
|
-
end
|
69
|
+
# @args: gpg_key -> the key name
|
70
|
+
def setup_gpg_key(gpg_key)
|
71
|
+
return if @config.check_gpg_key?
|
76
72
|
|
77
|
-
name = ask(I18n.t('form.setup_gpg_key.name')).to_s
|
78
73
|
password = ask(I18n.t('form.setup_gpg_key.password')) {|q| q.echo = false}
|
79
74
|
confirm = ask(I18n.t('form.setup_gpg_key.confirm_password')) {|q| q.echo = false}
|
80
75
|
|
@@ -82,16 +77,11 @@ class Cli
|
|
82
77
|
raise I18n.t('form.setup_gpg_key.error_password')
|
83
78
|
end
|
84
79
|
|
85
|
-
|
86
|
-
expire = ask(I18n.t('form.setup_gpg_key.expire')).to_s
|
87
|
-
password = password.to_s
|
88
|
-
|
89
|
-
length = length.nil? or length.empty? ? 2048 : length.to_i
|
90
|
-
expire = expire.nil? or expire.empty? ? 0 : expire.to_i
|
80
|
+
@password = password.to_s
|
91
81
|
|
92
82
|
puts I18n.t('form.setup_gpg_key.wait')
|
93
83
|
|
94
|
-
@config.setup_gpg_key(password,
|
84
|
+
@config.setup_gpg_key(@password, gpg_key)
|
95
85
|
|
96
86
|
puts "#{I18n.t('form.setup_gpg_key.valid')}".green
|
97
87
|
rescue Exception => e
|
@@ -100,23 +90,17 @@ class Cli
|
|
100
90
|
end
|
101
91
|
|
102
92
|
# Setup wallet config for sync
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
puts I18n.t('form.setup_wallet.title')
|
108
|
-
puts '--------------------'
|
109
|
-
config['sync']['type'] = ask(I18n.t('form.setup_wallet.sync_type')).to_s
|
110
|
-
|
111
|
-
if ['ftp', 'ssh'].include?(config['sync']['type'].downcase)
|
112
|
-
config['sync']['host'] = ask(I18n.t('form.setup_wallet.sync_host')).to_s
|
113
|
-
config['sync']['port'] = ask(I18n.t('form.setup_wallet.sync_port')).to_s
|
114
|
-
config['sync']['user'] = ask(I18n.t('form.setup_wallet.sync_user')).to_s
|
115
|
-
config['sync']['password'] = ask(I18n.t('form.setup_wallet.sync_pwd')).to_s
|
116
|
-
config['sync']['path'] = ask(I18n.t('form.setup_wallet.sync_path')).to_s
|
93
|
+
# @args: options -> value to change
|
94
|
+
def setup_wallet_config(options={})
|
95
|
+
if not options[:password].nil?
|
96
|
+
options[:password] = ask(I18n.t('form.setup_wallet.password')) {|q| q.echo = false}
|
117
97
|
end
|
118
98
|
|
119
|
-
@mpw.
|
99
|
+
#wallet_file = wallet.nil? ? "#{@config.wallet_dir}/default.mpw" : "#{@config.wallet_dir}/#{wallet}.mpw"
|
100
|
+
|
101
|
+
@mpw = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe)
|
102
|
+
@mpw.read_data
|
103
|
+
@mpw.set_config(options)
|
120
104
|
@mpw.write_data
|
121
105
|
|
122
106
|
puts "#{I18n.t('form.setup_wallet.valid')}".green
|
@@ -125,11 +109,25 @@ class Cli
|
|
125
109
|
exit 2
|
126
110
|
end
|
127
111
|
|
112
|
+
# List gpg keys in wallet
|
113
|
+
def list_keys
|
114
|
+
table_list('keys', @mpw.list_keys)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Load config
|
118
|
+
def load_config
|
119
|
+
@config.load_config
|
120
|
+
|
121
|
+
rescue Exception => e
|
122
|
+
puts "#{I18n.t('display.error')} #10: #{e}".red
|
123
|
+
exit 2
|
124
|
+
end
|
125
|
+
|
128
126
|
# Request the GPG password and decrypt the file
|
129
127
|
def decrypt
|
130
128
|
if not defined?(@mpw)
|
131
129
|
@password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
|
132
|
-
@mpw = MPW.new(@config.
|
130
|
+
@mpw = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe)
|
133
131
|
end
|
134
132
|
|
135
133
|
@mpw.read_data
|
@@ -139,97 +137,143 @@ class Cli
|
|
139
137
|
exit 2
|
140
138
|
end
|
141
139
|
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
result = @mpw.list(options)
|
140
|
+
# Format list on a table
|
141
|
+
def table_list(title, list)
|
142
|
+
i = 1
|
143
|
+
length = 0
|
147
144
|
|
148
|
-
|
149
|
-
|
150
|
-
|
145
|
+
list.each do |item|
|
146
|
+
length = item.length if length < item.length
|
147
|
+
end
|
148
|
+
length += 7
|
149
|
+
|
150
|
+
puts "\n#{I18n.t("display.#{title}")}".red
|
151
|
+
print ' '
|
152
|
+
length.times { print '=' }
|
153
|
+
print "\n"
|
154
|
+
|
155
|
+
list.each do |item|
|
156
|
+
print " #{i}".cyan
|
157
|
+
(3 - i.to_s.length).times { print ' ' }
|
158
|
+
puts "| #{item}"
|
159
|
+
i += 1
|
160
|
+
end
|
151
161
|
|
152
|
-
|
153
|
-
|
162
|
+
print "\n"
|
163
|
+
end
|
154
164
|
|
155
|
-
|
156
|
-
|
157
|
-
|
165
|
+
# Format items on a table
|
166
|
+
def table_items(items=[])
|
167
|
+
group = '.'
|
168
|
+
i = 1
|
169
|
+
length_total = 10
|
170
|
+
data = { id: { length: 3, color: 'cyan' },
|
171
|
+
host: { length: 9, color: 'yellow' },
|
172
|
+
user: { length: 7, color: 'green' },
|
173
|
+
protocol: { length: 9, color: 'white' },
|
174
|
+
port: { length: 5, color: 'white' },
|
175
|
+
otp: { length: 4, color: 'white' },
|
176
|
+
comment: { length: 14, color: 'magenta' },
|
177
|
+
}
|
178
|
+
|
179
|
+
items.each do |item|
|
180
|
+
data.each do |k, v|
|
181
|
+
next if k == :id or k == :otp
|
182
|
+
|
183
|
+
v[:length] = item.send(k.to_s).to_s.length + 3 if item.send(k.to_s).to_s.length >= v[:length]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length]
|
187
|
+
|
188
|
+
data.each_value { |v| length_total += v[:length] }
|
189
|
+
items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }
|
158
190
|
|
159
|
-
|
191
|
+
items.each do |item|
|
192
|
+
if group != item.group
|
193
|
+
group = item.group
|
160
194
|
|
161
|
-
|
162
|
-
|
163
|
-
|
195
|
+
if group.to_s.empty?
|
196
|
+
puts "\n#{I18n.t('display.no_group')}".red
|
197
|
+
else
|
198
|
+
puts "\n#{group}".red
|
199
|
+
end
|
164
200
|
|
165
|
-
|
166
|
-
|
201
|
+
print ' '
|
202
|
+
length_total.times { print '=' }
|
203
|
+
print "\n "
|
204
|
+
data.each do |k, v|
|
205
|
+
case k
|
206
|
+
when :id
|
207
|
+
print ' ID'
|
208
|
+
when :otp
|
209
|
+
print '| OTP'
|
167
210
|
else
|
168
|
-
|
211
|
+
print "| #{k.to_s.capitalize}"
|
169
212
|
end
|
170
|
-
end
|
171
213
|
|
172
|
-
|
173
|
-
|
174
|
-
print
|
175
|
-
|
214
|
+
(v[:length] - k.to_s.length).times { print ' ' }
|
215
|
+
end
|
216
|
+
print "\n "
|
217
|
+
length_total.times { print '=' }
|
176
218
|
print "\n"
|
177
|
-
|
178
|
-
i += 1
|
179
219
|
end
|
180
220
|
|
181
|
-
print "
|
182
|
-
|
221
|
+
print " #{i}".send(data[:id][:color])
|
222
|
+
(data[:id][:length] - i.to_s.length).times { print ' ' }
|
223
|
+
data.each do |k, v|
|
224
|
+
next if k == :id
|
183
225
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
226
|
+
if k == :otp
|
227
|
+
print '| '
|
228
|
+
if item.otp; print ' X ' else 4.times { print ' ' } end
|
229
|
+
|
230
|
+
next
|
231
|
+
end
|
232
|
+
|
233
|
+
print '| '
|
234
|
+
print "#{item.send(k.to_s)}".send(v[:color])
|
235
|
+
(v[:length] - item.send(k.to_s).to_s.length).times { print ' ' }
|
188
236
|
end
|
237
|
+
print "\n"
|
238
|
+
|
239
|
+
i += 1
|
189
240
|
end
|
241
|
+
|
242
|
+
print "\n"
|
190
243
|
end
|
191
244
|
|
192
|
-
# Display
|
193
|
-
# @args:
|
194
|
-
def
|
195
|
-
|
196
|
-
print 'Id: '.cyan
|
197
|
-
puts item.id
|
198
|
-
print "#{I18n.t('display.name')}: ".cyan
|
199
|
-
puts item.name
|
200
|
-
print "#{I18n.t('display.group')}: ".cyan
|
201
|
-
puts item.group
|
202
|
-
print "#{I18n.t('display.server')}: ".cyan
|
203
|
-
puts item.host
|
204
|
-
print "#{I18n.t('display.protocol')}: ".cyan
|
205
|
-
puts item.protocol
|
206
|
-
print "#{I18n.t('display.login')}: ".cyan
|
207
|
-
puts item.user
|
208
|
-
|
209
|
-
if @clipboard
|
210
|
-
print "#{I18n.t('display.password')}: ".cyan
|
211
|
-
puts '***********'
|
212
|
-
else
|
213
|
-
print "#{I18n.t('display.password')}: ".cyan
|
214
|
-
puts @mpw.get_password(item.id)
|
245
|
+
# Display the query's result
|
246
|
+
# @args: options -> the option to search
|
247
|
+
def list(options={})
|
248
|
+
result = @mpw.list(options)
|
215
249
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
250
|
+
if result.length == 0
|
251
|
+
puts I18n.t('display.nothing')
|
252
|
+
else
|
253
|
+
table_items(result)
|
220
254
|
end
|
255
|
+
end
|
221
256
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
257
|
+
# Get an item when multiple choice
|
258
|
+
# @args: items -> array of items
|
259
|
+
# @rtrn: item
|
260
|
+
def get_item(items)
|
261
|
+
return items[0] if items.length == 1
|
226
262
|
|
227
|
-
|
263
|
+
items.sort! { |a,b| a.group.to_s.downcase <=> b.group.to_s.downcase }
|
264
|
+
choice = ask(I18n.t('form.select')).to_i
|
265
|
+
|
266
|
+
if choice >= 1 and choice <= items.length
|
267
|
+
return items[choice-1]
|
268
|
+
else
|
269
|
+
return nil
|
270
|
+
end
|
228
271
|
end
|
229
272
|
|
230
273
|
# Copy in clipboard the login and password
|
231
274
|
# @args: item -> the item
|
232
|
-
|
275
|
+
# clipboard -> enable clipboard
|
276
|
+
def clipboard(item, clipboard=true)
|
233
277
|
pid = nil
|
234
278
|
|
235
279
|
# Security: force quit after 90s
|
@@ -246,21 +290,33 @@ class Cli
|
|
246
290
|
break
|
247
291
|
|
248
292
|
when 'l', 'login'
|
249
|
-
|
250
|
-
|
293
|
+
if clipboard
|
294
|
+
Clipboard.copy(item.user)
|
295
|
+
puts I18n.t('form.clipboard.login').green
|
296
|
+
else
|
297
|
+
puts item.user
|
298
|
+
end
|
251
299
|
|
252
300
|
when 'p', 'password'
|
253
|
-
|
254
|
-
|
301
|
+
if clipboard
|
302
|
+
Clipboard.copy(@mpw.get_password(item.id))
|
303
|
+
puts I18n.t('form.clipboard.password').yellow
|
255
304
|
|
256
|
-
|
257
|
-
|
305
|
+
Thread.new do
|
306
|
+
sleep 30
|
258
307
|
|
259
|
-
|
308
|
+
Clipboard.clear
|
309
|
+
end
|
310
|
+
else
|
311
|
+
puts @mpw.get_password(item.id)
|
260
312
|
end
|
261
313
|
|
262
314
|
when 'o', 'otp'
|
263
|
-
|
315
|
+
if clipboard
|
316
|
+
Clipboard.copy(@mpw.get_otp_code(item.id))
|
317
|
+
else
|
318
|
+
puts @mpw.get_otp_code(item.id)
|
319
|
+
end
|
264
320
|
puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow
|
265
321
|
|
266
322
|
else
|
@@ -268,6 +324,7 @@ class Cli
|
|
268
324
|
puts I18n.t('form.clipboard.help.login')
|
269
325
|
puts I18n.t('form.clipboard.help.password')
|
270
326
|
puts I18n.t('form.clipboard.help.otp_code')
|
327
|
+
puts I18n.t('form.clipboard.help.quit')
|
271
328
|
next
|
272
329
|
end
|
273
330
|
end
|
@@ -277,34 +334,30 @@ class Cli
|
|
277
334
|
Clipboard.clear
|
278
335
|
end
|
279
336
|
|
337
|
+
# List all wallets
|
338
|
+
def list_wallet
|
339
|
+
wallets = []
|
340
|
+
Dir.glob("#{@config.wallet_dir}/*.mpw").each do |f|
|
341
|
+
wallet = File.basename(f, '.mpw')
|
342
|
+
wallet += ' *'.green if wallet == @config.default_wallet
|
343
|
+
wallets << wallet
|
344
|
+
end
|
345
|
+
|
346
|
+
table_list('wallets', wallets)
|
347
|
+
end
|
348
|
+
|
280
349
|
# Display the wallet
|
281
350
|
# @args: wallet -> the wallet name
|
282
351
|
def get_wallet(wallet=nil)
|
283
352
|
if wallet.to_s.empty?
|
284
353
|
wallets = Dir.glob("#{@config.wallet_dir}/*.mpw")
|
285
354
|
|
286
|
-
|
287
|
-
when 0
|
288
|
-
puts I18n.t('display.nothing')
|
289
|
-
when 1
|
355
|
+
if wallets.length == 1
|
290
356
|
@wallet_file = wallets[0]
|
357
|
+
elsif not @config.default_wallet.to_s.empty?
|
358
|
+
@wallet_file = "#{@config.wallet_dir}/#{@config.default_wallet}.mpw"
|
291
359
|
else
|
292
|
-
|
293
|
-
wallets.each do |wallet|
|
294
|
-
print "#{i}: ".cyan
|
295
|
-
puts File.basename(wallet, '.mpw')
|
296
|
-
|
297
|
-
i += 1
|
298
|
-
end
|
299
|
-
|
300
|
-
choice = ask(I18n.t('form.select')).to_i
|
301
|
-
|
302
|
-
if choice >= 1 and choice < i
|
303
|
-
@wallet_file = wallets[choice-1]
|
304
|
-
else
|
305
|
-
puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
|
306
|
-
exit 2
|
307
|
-
end
|
360
|
+
@wallet_file = "#{@config.wallet_dir}/default.mpw"
|
308
361
|
end
|
309
362
|
else
|
310
363
|
@wallet_file = "#{@config.wallet_dir}/#{wallet}.mpw"
|
@@ -312,10 +365,9 @@ class Cli
|
|
312
365
|
end
|
313
366
|
|
314
367
|
# Add a new public key
|
315
|
-
# args: key -> the key name to add
|
316
|
-
|
317
|
-
|
318
|
-
@mpw.add_key(key, file)
|
368
|
+
# args: key -> the key name or key file to add
|
369
|
+
def add_key(key)
|
370
|
+
@mpw.add_key(key)
|
319
371
|
@mpw.write_data
|
320
372
|
@mpw.sync(true) if @sync
|
321
373
|
|
@@ -336,111 +388,154 @@ class Cli
|
|
336
388
|
puts "#{I18n.t('display.error')} #15: #{e}".red
|
337
389
|
end
|
338
390
|
|
339
|
-
#
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
options
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
391
|
+
# Text editor interface
|
392
|
+
# @args: template -> template name
|
393
|
+
# item -> the item to edit
|
394
|
+
# password -> disable field password
|
395
|
+
def text_editor(template_name, item=nil, password=false)
|
396
|
+
editor = ENV['EDITOR'] || 'nano'
|
397
|
+
options = {}
|
398
|
+
opts = {}
|
399
|
+
template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb"
|
400
|
+
template = ERB.new(IO.read(template_file))
|
401
|
+
|
402
|
+
Dir.mktmpdir do |dir|
|
403
|
+
tmp_file = "#{dir}/#{template_name}.yml"
|
404
|
+
|
405
|
+
File.open(tmp_file, 'w') do |f|
|
406
|
+
f << template.result(binding)
|
407
|
+
end
|
408
|
+
|
409
|
+
system("#{editor} #{tmp_file}")
|
410
|
+
|
411
|
+
opts = YAML::load_file(tmp_file)
|
412
|
+
end
|
413
|
+
|
414
|
+
opts.delete_if { |k,v| v.to_s.empty? }
|
415
|
+
|
416
|
+
opts.each do |k,v|
|
417
|
+
options[k.to_sym] = v
|
356
418
|
end
|
357
419
|
|
358
|
-
|
420
|
+
return options
|
421
|
+
end
|
359
422
|
|
423
|
+
# Form to add a new item
|
424
|
+
# @args: password -> generate a random password
|
425
|
+
def add(password=false)
|
426
|
+
options = text_editor('add_form', nil, password)
|
427
|
+
item = Item.new(options)
|
428
|
+
options[:password] = MPW::password(@config.password) if password
|
429
|
+
|
360
430
|
@mpw.add(item)
|
361
|
-
@mpw.set_password(item.id, password)
|
362
|
-
@mpw.set_otp_key(item.id, otp_key)
|
431
|
+
@mpw.set_password(item.id, options[:password]) if options.has_key?(:password)
|
432
|
+
@mpw.set_otp_key(item.id, options[:otp_key]) if options.has_key?(:otp_key)
|
363
433
|
@mpw.write_data
|
364
434
|
@mpw.sync(true) if @sync
|
365
435
|
|
366
436
|
puts "#{I18n.t('form.add_item.valid')}".green
|
437
|
+
rescue Exception => e
|
438
|
+
puts "#{I18n.t('display.error')} #13: #{e}".red
|
367
439
|
end
|
368
440
|
|
369
441
|
# Update an item
|
370
|
-
# @args:
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
options[:name] = ask(I18n.t('form.update_item.name' , name: item.name)).to_s
|
380
|
-
options[:group] = ask(I18n.t('form.update_item.group' , group: item.group)).to_s
|
381
|
-
options[:host] = ask(I18n.t('form.update_item.server' , server: item.host)).to_s
|
382
|
-
options[:protocol] = ask(I18n.t('form.update_item.protocol', protocol: item.protocol)).to_s
|
383
|
-
options[:user] = ask(I18n.t('form.update_item.login' , login: item.user)).to_s
|
384
|
-
password = ask(I18n.t('form.update_item.password')).to_s
|
385
|
-
options[:port] = ask(I18n.t('form.update_item.port' , port: item.port)).to_s
|
386
|
-
options[:comment] = ask(I18n.t('form.update_item.comment' , comment: item.comment)).to_s
|
387
|
-
|
388
|
-
if @otp
|
389
|
-
otp_key = ask(I18n.t('form.update_item.otp_key')).to_s
|
390
|
-
end
|
442
|
+
# @args: password -> generate a random password
|
443
|
+
# options -> the option to search
|
444
|
+
def update(password=false, options={})
|
445
|
+
items = @mpw.list(options)
|
446
|
+
|
447
|
+
if items.length == 0
|
448
|
+
puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
|
449
|
+
else
|
450
|
+
table_items(items) if items.length > 1
|
391
451
|
|
392
|
-
|
452
|
+
item = get_item(items)
|
453
|
+
options = text_editor('update_form', item, password)
|
454
|
+
options[:password] = MPW::password(@config.password) if password
|
393
455
|
|
394
456
|
item.update(options)
|
395
|
-
@mpw.set_password(item.id, password) if
|
396
|
-
@mpw.set_otp_key(item.id, otp_key) if
|
457
|
+
@mpw.set_password(item.id, options[:password]) if options.has_key?(:password)
|
458
|
+
@mpw.set_otp_key(item.id, options[:otp_key]) if options.has_key?(:otp_key)
|
397
459
|
@mpw.write_data
|
398
460
|
@mpw.sync(true) if @sync
|
399
461
|
|
400
462
|
puts "#{I18n.t('form.update_item.valid')}".green
|
401
|
-
else
|
402
|
-
puts I18n.t('display.nothing')
|
403
463
|
end
|
404
464
|
rescue Exception => e
|
405
465
|
puts "#{I18n.t('display.error')} #14: #{e}".red
|
406
466
|
end
|
407
467
|
|
408
468
|
# Remove an item
|
409
|
-
# @args:
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
return
|
418
|
-
end
|
419
|
-
|
420
|
-
if not force
|
421
|
-
display_item(item)
|
469
|
+
# @args: options -> the option to search
|
470
|
+
def delete(options={})
|
471
|
+
items = @mpw.list(options)
|
472
|
+
|
473
|
+
if items.length == 0
|
474
|
+
puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
|
475
|
+
else
|
476
|
+
table_items(items)
|
422
477
|
|
423
|
-
|
478
|
+
item = get_item(items)
|
479
|
+
confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s
|
480
|
+
|
424
481
|
if not confirm =~ /^(y|yes|YES|Yes|Y)$/
|
425
|
-
return
|
482
|
+
return false
|
426
483
|
end
|
484
|
+
|
485
|
+
item.delete
|
486
|
+
@mpw.write_data
|
487
|
+
@mpw.sync(true) if @sync
|
488
|
+
|
489
|
+
puts "#{I18n.t('form.delete_item.valid')}".green
|
427
490
|
end
|
491
|
+
rescue Exception => e
|
492
|
+
puts "#{I18n.t('display.error')} #16: #{e}".red
|
493
|
+
end
|
428
494
|
|
429
|
-
|
430
|
-
|
431
|
-
|
495
|
+
# Copy a password, otp, login
|
496
|
+
# @args: clipboard -> enable clipboard
|
497
|
+
# options -> the option to search
|
498
|
+
def copy(clipboard=true, options={})
|
499
|
+
items = @mpw.list(options)
|
500
|
+
|
501
|
+
if items.length == 0
|
502
|
+
puts I18n.t('display.nothing')
|
503
|
+
else
|
504
|
+
table_items(items)
|
432
505
|
|
433
|
-
|
506
|
+
item = get_item(items)
|
507
|
+
clipboard(item, clipboard)
|
508
|
+
end
|
434
509
|
rescue Exception => e
|
435
|
-
puts "#{I18n.t('display.error')} #
|
510
|
+
puts "#{I18n.t('display.error')} #14: #{e}".red
|
436
511
|
end
|
437
512
|
|
438
513
|
# Export the items in a CSV file
|
439
514
|
# @args: file -> the destination file
|
440
|
-
|
441
|
-
|
515
|
+
# options -> option to search
|
516
|
+
def export(file, options)
|
517
|
+
file = 'export-mpw.yml' if file.to_s.empty?
|
518
|
+
items = @mpw.list(options)
|
519
|
+
data = {}
|
520
|
+
|
521
|
+
items.each do |item|
|
522
|
+
data.merge!(item.id => { 'host' => item.host,
|
523
|
+
'user' => item.user,
|
524
|
+
'group' => item.group,
|
525
|
+
'password' => @mpw.get_password(item.id),
|
526
|
+
'protocol' => item.protocol,
|
527
|
+
'port' => item.port,
|
528
|
+
'otp_key' => @mpw.get_otp_key(item.id),
|
529
|
+
'comment' => item.comment,
|
530
|
+
'last_edit' => item.last_edit,
|
531
|
+
'created' => item.created,
|
532
|
+
}
|
533
|
+
)
|
534
|
+
end
|
442
535
|
|
443
|
-
|
536
|
+
File.open(file, 'w') {|f| f << data.to_yaml}
|
537
|
+
|
538
|
+
puts "#{I18n.t('form.export.valid', file: file)}".green
|
444
539
|
rescue Exception => e
|
445
540
|
puts "#{I18n.t('display.error')} #17: #{e}".red
|
446
541
|
end
|
@@ -448,7 +543,25 @@ class Cli
|
|
448
543
|
# Import items from a YAML file
|
449
544
|
# @args: file -> the import file
|
450
545
|
def import(file)
|
451
|
-
|
546
|
+
raise I18n.t('form.import.file_empty') if file.to_s.empty?
|
547
|
+
raise I18n.t('form.import.file_not_exist') if not File.exist?(file)
|
548
|
+
|
549
|
+
YAML::load_file(file).each_value do |row|
|
550
|
+
item = Item.new(group: row['group'],
|
551
|
+
host: row['host'],
|
552
|
+
protocol: row['protocol'],
|
553
|
+
user: row['user'],
|
554
|
+
port: row['port'],
|
555
|
+
comment: row['comment'],
|
556
|
+
)
|
557
|
+
|
558
|
+
next if item.empty?
|
559
|
+
|
560
|
+
@mpw.add(item)
|
561
|
+
@mpw.set_password(item.id, row['password']) if not row['password'].to_s.empty?
|
562
|
+
@mpw.set_otp_key(item.id, row['otp_key']) if not row['otp_key'].to_s.empty?
|
563
|
+
end
|
564
|
+
|
452
565
|
@mpw.write_data
|
453
566
|
|
454
567
|
puts "#{I18n.t('form.import.valid')}".green
|