Class: MPW::Cli

Inherits:
Object
  • Object
show all
Defined in:
lib/mpw/cli.rb

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Cli

Returns a new instance of Cli.

Parameters:



32
33
34
# File 'lib/mpw/cli.rb', line 32

def initialize(config)
  @config = config
end

Instance Method Details

#add(password = false, text_editor = false, **values) ⇒ Object

Form to add a new item

Parameters:

  • password (Boolean) (defaults to: false)

    generate a random password

  • text_editor (Boolean) (defaults to: false)

    enable text editor mode

  • values (Hash)

    multiples value to set the item



470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/mpw/cli.rb', line 470

def add(password = false, text_editor = false, **values)
  options            = text_editor('add_form', password, nil, values) if text_editor
  item               = Item.new(options)
  options[:password] = MPW.password(@config.password) if password

  @mpw.add(item)
  @mpw.set_password(item.id, options[:password]) if options.key?(:password)
  @mpw.set_otp_key(item.id, options[:otp_key])   if options.key?(:otp_key)
  @mpw.write_data

  puts I18n.t('form.add_item.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #13: #{e}".red
end

#add_key(key) ⇒ Object

Add a new public key

Parameters:

  • key (String)

    key name or key file to add



414
415
416
417
418
419
420
421
# File 'lib/mpw/cli.rb', line 414

def add_key(key)
  @mpw.add_key(key)
  @mpw.write_data

  puts I18n.t('form.add_key.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #13: #{e}".red
end

#clipboard(item, clipboard = true) ⇒ Object

Copy in clipboard the login and password

Parameters:

  • item (Item)
  • clipboard (Boolean) (defaults to: true)

    enable clipboard



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/mpw/cli.rb', line 310

def clipboard(item, clipboard = true)
  # Security: force quit after 90s
  Thread.new do
    sleep 90
    exit
  end

  Kernel.loop do
    choice = ask(I18n.t('form.clipboard.choice')).to_s

    case choice
    when 'q', 'quit'
      break

    when 'u', 'url'
      if clipboard
        Clipboard.copy(item.url)
        puts I18n.t('form.clipboard.url').green
      else
        puts item.url
      end

    when 'l', 'login'
      if clipboard
        Clipboard.copy(item.user)
        puts I18n.t('form.clipboard.login').green
      else
        puts item.user
      end

    when 'p', 'password'
      if clipboard
        Clipboard.copy(@mpw.get_password(item.id))
        puts I18n.t('form.clipboard.password').yellow

        Thread.new do
          sleep 30

          Clipboard.clear
        end
      else
        puts @mpw.get_password(item.id)
      end

    when 'o', 'otp'
      if !item.otp
        clipboard_help(item)
        next
      elsif clipboard
        Clipboard.copy(@mpw.get_otp_code(item.id))
      else
        puts @mpw.get_otp_code(item.id)
      end
      puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow

    else
      clipboard_help(item)
    end
  end

  Clipboard.clear
rescue SystemExit, Interrupt
  Clipboard.clear
end

#clipboard_help(item) ⇒ Object

Print help message for clipboard mode

Parameters:



298
299
300
301
302
303
304
305
# File 'lib/mpw/cli.rb', line 298

def clipboard_help(item)
  puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan
  puts I18n.t('form.clipboard.help.url')
  puts I18n.t('form.clipboard.help.login')
  puts I18n.t('form.clipboard.help.password')
  puts I18n.t('form.clipboard.help.otp_code') if item.otp
  puts I18n.t('form.clipboard.help.quit')
end

#copy(clipboard = true, **options) ⇒ Object

Copy a password, otp, login

Parameters:

  • clipboard (Boolean) (defaults to: true)

    enable clipboard

  • options (Hash)

    the options to search



540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/mpw/cli.rb', line 540

def copy(clipboard = true, **options)
  items = @mpw.list(options)

  if items.empty?
    puts I18n.t('display.nothing')
  else
    table_items(items)

    item = get_item(items)
    clipboard(item, clipboard)
  end
rescue => e
  puts "#{I18n.t('display.error')} #14: #{e}".red
end

#decryptObject

Request the GPG password and decrypt the file



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/mpw/cli.rb', line 128

def decrypt
  if defined?(@mpw)
    @mpw.read_data
  else
    begin
      @mpw = MPW.new(@config.gpg_key, @wallet_file, nil, @config.gpg_exe, @config.pinmode)

      @mpw.read_data
    rescue
      @password = ask(I18n.t('display.gpg_password')) { |q| q.echo = false }
      @mpw      = MPW.new(@config.gpg_key, @wallet_file, @password, @config.gpg_exe, @config.pinmode)

      @mpw.read_data
    end
  end
rescue => e
  puts "#{I18n.t('display.error')} #11: #{e}".red
  exit 2
end

#delete(**options) ⇒ Object

Remove an item

Parameters:

  • options (Hash)

    the options to search



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/mpw/cli.rb', line 515

def delete(**options)
  items = @mpw.list(options)

  if items.empty?
    puts I18n.t('display.nothing')
  else
    table_items(items)

    item    = get_item(items)
    confirm = ask("#{I18n.t('form.delete_item.ask')} (y/N) ").to_s

    return unless confirm =~ /^(y|yes|YES|Yes|Y)$/

    item.delete
    @mpw.write_data

    puts I18n.t('form.delete_item.valid').to_s.green
  end
rescue => e
  puts "#{I18n.t('display.error')} #16: #{e}".red
end

#delete_key(key) ⇒ Object

Add new public key

Parameters:

  • key (String)

    key name to delete



425
426
427
428
429
430
431
432
# File 'lib/mpw/cli.rb', line 425

def delete_key(key)
  @mpw.delete_key(key)
  @mpw.write_data

  puts I18n.t('form.delete_key.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #15: #{e}".red
end

#export(file, options) ⇒ Object

Export the items in an yaml file

Parameters:

  • file (String)

    the path of destination file

  • options (Hash)

    options to search



558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/mpw/cli.rb', line 558

def export(file, options)
  file  = 'export-mpw.yml' if file.to_s.empty?
  items = @mpw.list(options)
  data  = {}

  items.each do |item|
    data.merge!(
      item.id => {
        'host'      => item.host,
        'user'      => item.user,
        'group'     => item.group,
        'password'  => @mpw.get_password(item.id),
        'protocol'  => item.protocol,
        'port'      => item.port,
        'otp_key'   => @mpw.get_otp_key(item.id),
        'comment'   => item.comment,
        'last_edit' => item.last_edit,
        'created'   => item.created,
      }
    )
  end

  File.open(file, 'w') { |f| f << data.to_yaml }

  puts I18n.t('form.export.valid', file: file).to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #17: #{e}".red
end

#get_item(items) ⇒ Item

Get an item when multiple choice

Parameters:

  • items (Array)

    list of items

Returns:

  • (Item)

    an item



285
286
287
288
289
290
291
292
293
294
# File 'lib/mpw/cli.rb', line 285

def get_item(items)
  return items[0] if items.length == 1

  items.sort! { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }
  choice = ask(I18n.t('form.select.choice')).to_i

  raise I18n.t('form.select.error') unless choice >= 1 && choice <= items.length

  items[choice - 1]
end

#get_wallet(wallet = nil) ⇒ Object

Display the wallet

Parameters:

  • wallet (String) (defaults to: nil)

    wallet name



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/mpw/cli.rb', line 389

def get_wallet(wallet = nil)
  @wallet =
    if wallet.to_s.empty?
      wallets = Dir.glob("#{@config.wallet_dir}/*.mpw")
      if wallets.length == 1
        File.basename(wallets[0], '.mpw')
      elsif !@config.default_wallet.to_s.empty?
        @config.default_wallet
      else
        'default'
      end
    else
      wallet
    end

  @wallet_file =
    if @config.wallet_paths.key?(@wallet)
      "#{@config.wallet_paths[@wallet]}/#{@wallet}.mpw"
    else
      "#{@config.wallet_dir}/#{@wallet}.mpw"
    end
end

#import(file) ⇒ Object

Import items from an yaml file

Parameters:

  • file (String)

    path of import file



589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/mpw/cli.rb', line 589

def import(file)
  raise I18n.t('form.import.file_empty')     if file.to_s.empty?
  raise I18n.t('form.import.file_not_exist') unless File.exist?(file)

  YAML.load_file(file).each_value do |row|
    item = Item.new(group:    row['group'],
                    host:     row['host'],
                    protocol: row['protocol'],
                    user:     row['user'],
                    port:     row['port'],
                    comment:  row['comment'])

    next if item.empty?

    @mpw.add(item)
    @mpw.set_password(item.id, row['password']) unless row['password'].to_s.empty?
    @mpw.set_otp_key(item.id, row['otp_key'])   unless row['otp_key'].to_s.empty?
  end

  @mpw.write_data

  puts I18n.t('form.import.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #18: #{e}".red
end

#list(**options) ⇒ Object

Display the query’s result

Parameters:

  • options (Hash)

    the options to search



272
273
274
275
276
277
278
279
280
# File 'lib/mpw/cli.rb', line 272

def list(**options)
  result = @mpw.list(options)

  if result.empty?
    puts I18n.t('display.nothing')
  else
    table_items(result)
  end
end

#list_configObject

List config



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mpw/cli.rb', line 103

def list_config
  config = {
    'lang'           => @config.lang,
    'gpg_key'        => @config.gpg_key,
    'default_wallet' => @config.default_wallet,
    'wallet_dir'     => @config.wallet_dir,
    'pinmode'        => @config.pinmode,
    'gpg_exe'        => @config.gpg_exe
  }

  @config.wallet_paths.each { |k, v| config["path_wallet_#{k}"] = "#{v}/#{k}.mpw" }
  @config.password.each     { |k, v| config["password_#{k}"] = v }

  table_list('config', config)
end

#list_keysObject

List gpg keys in wallet



98
99
100
# File 'lib/mpw/cli.rb', line 98

def list_keys
  table_list('keys', @mpw.list_keys)
end

#list_walletObject

List all wallets



376
377
378
379
380
381
382
383
384
385
# File 'lib/mpw/cli.rb', line 376

def list_wallet
  wallets = []
  Dir.glob("#{@config.wallet_dir}/*.mpw").each do |f|
    wallet = File.basename(f, '.mpw')
    wallet += ' *'.green if wallet == @config.default_wallet
    wallets << wallet
  end

  table_list('wallets', wallets)
end

#load_configObject

Load config



120
121
122
123
124
125
# File 'lib/mpw/cli.rb', line 120

def load_config
  @config.load_config
rescue => e
  puts "#{I18n.t('display.error')} #10: #{e}".red
  exit 2
end

#set_config(options) ⇒ Object

Change a parameter int the config after init

Parameters:

  • options (Hash)

    param to change



38
39
40
41
42
43
44
45
# File 'lib/mpw/cli.rb', line 38

def set_config(options)
  @config.setup(options)

  puts I18n.t('form.set_config.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #15: #{e}".red
  exit 2
end

#set_wallet_path(path) ⇒ Object

Change the wallet path

Parameters:

  • path (String)

    new path



49
50
51
52
53
54
55
56
# File 'lib/mpw/cli.rb', line 49

def set_wallet_path(path)
  @config.set_wallet_path(path, @wallet)

  puts I18n.t('form.set_wallet_path.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #19: #{e}".red
  exit 2
end

#setup(options) ⇒ Object

Create a new config file

Parameters:

  • options (Hash)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/mpw/cli.rb', line 60

def setup(options)
  options[:lang] = options[:lang] || Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]

  I18n.locale = options[:lang].to_sym

  @config.setup(options)

  load_config

  puts I18n.t('form.setup_config.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #8: #{e}".red
  exit 2
end

#setup_gpg_key(gpg_key) ⇒ Object

Setup a new GPG key

Parameters:

  • gpg_key (String)

    gpg key name



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/mpw/cli.rb', line 77

def setup_gpg_key(gpg_key)
  return if @config.check_gpg_key?

  password = ask(I18n.t('form.setup_gpg_key.password')) { |q| q.echo = false }
  confirm  = ask(I18n.t('form.setup_gpg_key.confirm_password')) { |q| q.echo = false }

  raise I18n.t('form.setup_gpg_key.error_password') if password != confirm

  @password = password.to_s

  puts I18n.t('form.setup_gpg_key.wait')

  @config.setup_gpg_key(@password, gpg_key)

  puts I18n.t('form.setup_gpg_key.valid').to_s.green
rescue => e
  puts "#{I18n.t('display.error')} #8: #{e}".red
  exit 2
end

#table_items(items = []) ⇒ Object

Format items on a table

Parameters:

  • items (Array) (defaults to: [])


183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/mpw/cli.rb', line 183

def table_items(items = [])
  group        = '.'
  i            = 1
  length_total = 10
  data         = { id:       { length: 3,  color: 'cyan' },
                   host:     { length: 9,  color: 'yellow' },
                   user:     { length: 7,  color: 'green' },
                   otp:      { length: 4,  color: 'white' },
                   comment:  { length: 14, color: 'magenta' } }

  items.each do |item|
    data.each do |k, v|
      case k
      when :id, :otp
        next
      when :host
        v[:length] = item.url.length + 3 if item.url.length >= v[:length]
      else
        v[:length] = item.send(k.to_s).to_s.length + 3 if item.send(k.to_s).to_s.length >= v[:length]
      end
    end
  end
  data[:id][:length] = items.length.to_s.length + 2 if items.length.to_s.length > data[:id][:length]

  data.each_value { |v| length_total += v[:length] }
  items.sort!     { |a, b| a.group.to_s.downcase <=> b.group.to_s.downcase }

  items.each do |item|
    if group != item.group
      group = item.group

      if group.to_s.empty?
        puts "\n#{I18n.t('display.no_group')}".red
      else
        puts "\n#{group}".red
      end

      print ' '
      length_total.times { print '=' }
      print "\n "
      data.each do |k, v|
        case k
        when :id
          print ' ID'
        when :otp
          print '| OTP'
        else
          print "| #{k.to_s.capitalize}"
        end

        (v[:length] - k.to_s.length).times { print ' ' }
      end
      print "\n "
      length_total.times { print '=' }
      print "\n"
    end

    print "  #{i}".send(data[:id][:color])
    (data[:id][:length] - i.to_s.length).times { print ' ' }
    data.each do |k, v|
      next if k == :id

      print '| '

      case k
      when :otp
        item.otp ? (print ' X  ') : 4.times { print ' ' }

      when :host
        print "#{item.protocol}://".light_black if item.protocol
        print item.host.send(v[:color])
        print ":#{item.port}".light_black if item.port
        (v[:length] - item.url.to_s.length).times { print ' ' }

      else
        print item.send(k.to_s).to_s.send(v[:color])
        (v[:length] - item.send(k.to_s).to_s.length).times { print ' ' }
      end
    end
    print "\n"

    i += 1
  end

  print "\n"
end

#table_list(title, list) ⇒ Object

Format list on a table

Parameters:

  • title (String)

    name of table

  • list

    an array or hash



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/mpw/cli.rb', line 151

def table_list(title, list)
  length = { k: 0, v: 0 }

  if list.is_a?(Array)
    i    = 0
    list = list.map do |item|
      i += 1
      [i, item]
    end.to_h
  end

  list.each do |k, v|
    length[:k] = k.to_s.length if length[:k] < k.to_s.length
    length[:v] = v.to_s.length if length[:v] < v.to_s.length
  end

  puts "\n#{I18n.t("display.#{title}")}".red
  print ' '
  (length[:k] + length[:v] + 5).times { print '=' }
  print "\n"

  list.each do |k, v|
    print "  #{k}".cyan
    (length[:k] - k.to_s.length + 1).times { print ' ' }
    puts "| #{v}"
  end

  print "\n"
end

#text_editor(template_name, password = false, item = nil, **options) ⇒ Hash

Text editor interface

Parameters:

  • template_name (String)

    template name

  • item (Item) (defaults to: nil)

    the item to edit

  • password (Boolean) (defaults to: false)

    disable field password

Returns:

  • (Hash)

    the values for an item



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/mpw/cli.rb', line 439

def text_editor(template_name, password = false, item = nil, **options)
  editor        = ENV['EDITOR'] || 'nano'
  opts          = {}
  template_file = "#{File.expand_path('../../../templates', __FILE__)}/#{template_name}.erb"
  template      = ERB.new(IO.read(template_file))

  Dir.mktmpdir do |dir|
    tmp_file = "#{dir}/#{template_name}.yml"

    File.open(tmp_file, 'w') do |f|
      f << template.result(binding)
    end

    system("#{editor} #{tmp_file}")

    opts = YAML.load_file(tmp_file)
  end

  opts.delete_if { |_, v| v.to_s.empty? }

  opts.each do |k, v|
    options[k.to_sym] = v
  end

  options
end

#update(password = false, text_editor = false, options = {}, **values) ⇒ Object

Update an item

Parameters:

  • password (Boolean) (defaults to: false)

    generate a random password

  • text_editor (Boolean) (defaults to: false)

    enable text editor mode

  • options (Hash) (defaults to: {})

    the options to search

  • values (Hash)

    multiples value to set the item



490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/mpw/cli.rb', line 490

def update(password = false, text_editor = false, options = {}, **values)
  items = @mpw.list(options)

  if items.empty?
    puts I18n.t('display.nothing')
  else
    table_items(items) if items.length > 1

    item              = get_item(items)
    values            = text_editor('update_form', password, item, values) if text_editor
    values[:password] = MPW.password(@config.password) if password

    item.update(values)
    @mpw.set_password(item.id, values[:password]) if values.key?(:password)
    @mpw.set_otp_key(item.id, values[:otp_key])   if values.key?(:otp_key)
    @mpw.write_data

    puts I18n.t('form.update_item.valid').to_s.green
  end
rescue => e
  puts "#{I18n.t('display.error')} #14: #{e}".red
end