mpw 3.1.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef1f27beb1e85561895eeaec053e65339d404a18
4
- data.tar.gz: b69ea026f373f5b3039a71776e4acc9ec4d44e3c
3
+ metadata.gz: 38a2af169d14523a873d9ca733b19ce2f7856830
4
+ data.tar.gz: 7d3365270ad43f53316ce9519e7b2feb01ee73ca
5
5
  SHA512:
6
- metadata.gz: 46c34128083ac6a70fcbb1ef3b9e0fb29b9bb58419a51d0b3623d0dbc34dd8b6371c8bed9c13b4dc61ffd7c9d0a2de06f37a61477e296b78934878edcf62959c
7
- data.tar.gz: 0ff807e8c938d31abec7bce1f4a18b4f795eacf87065c8166430ffc9f86e3a9533f9098032db6ca94455345761099ea89c006072017868cac29aff6e1a381443
6
+ metadata.gz: 3d8931b9d43a244cae00039e66cc9575d21cb6ab4b7448104eccbaa70bc95bf0e3ebbec3c2297504fd5d59b2f6610764777e94e59750e2d7020b71bc9d2449c3
7
+ data.tar.gz: eea9b9f36d27dc035e1c18a04a0ef6c3cdda0b71fb1709f72dc30739373c088c06b40b0464fa42d196fda2020d1f58ff38489ed0f24caf22479dcfce0df8e9fe
data/Gemfile CHANGED
@@ -7,3 +7,4 @@ gem 'colorize'
7
7
  gem 'net-ssh'
8
8
  gem 'net-scp'
9
9
  gem 'clipboard'
10
+ gem 'rotp'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0
1
+ 3.2.0
data/bin/mpw CHANGED
@@ -16,11 +16,15 @@
16
16
  # along with this program; if not, write to the Free Software
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
 
19
+ $: << File.expand_path('../../lib', __FILE__)
20
+
19
21
  require 'optparse'
20
- require 'pathname'
21
22
  require 'locale'
22
23
  require 'set'
23
24
  require 'i18n'
25
+ require 'mpw/mpw'
26
+ require 'mpw/config'
27
+ require 'mpw/cli'
24
28
 
25
29
  # --------------------------------------------------------- #
26
30
  # Set local
@@ -32,15 +36,8 @@ if defined?(I18n.enforce_available_locales)
32
36
  I18n.enforce_available_locales = true
33
37
  end
34
38
 
35
- APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath)
36
-
37
- # TODO
38
- require "#{APP_ROOT}/../lib/mpw/mpw.rb"
39
- require "#{APP_ROOT}/../lib/mpw/config.rb"
40
- require "#{APP_ROOT}/../lib/mpw/ui/cli.rb"
41
-
42
39
  I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
43
- I18n.load_path = Dir["#{APP_ROOT}/../i18n/*.yml"]
40
+ I18n.load_path = Dir["#{File.expand_path('../../i18n', __FILE__)}/*.yml"]
44
41
  I18n.default_locale = :en
45
42
  I18n.locale = lang.to_sym
46
43
 
@@ -48,8 +45,10 @@ I18n.locale = lang.to_sym
48
45
  # Options
49
46
  # --------------------------------------------------------- #
50
47
 
48
+ options_password = {}
51
49
  options = {}
52
50
  options[:force] = false
51
+ options[:otp] = false
53
52
  options[:sync] = true
54
53
  options[:clipboard] = true
55
54
  options[:group] = nil
@@ -97,8 +96,7 @@ OptionParser.new do |opts|
97
96
  end
98
97
 
99
98
  opts.on('-G', '--generate-password [LENGTH]', I18n.t('option.generate_password')) do |length|
100
- puts MPW::MPW::password(length)
101
- exit 0
99
+ options_password[:length] = length
102
100
  end
103
101
 
104
102
  opts.on('-h', '--help', I18n.t('option.help')) do
@@ -118,10 +116,18 @@ OptionParser.new do |opts|
118
116
  options[:key] = key
119
117
  end
120
118
 
119
+ opts.on('-n', '--numeric', I18n.t('option.numeric')) do
120
+ options_password[:numeric] = true
121
+ end
122
+
121
123
  opts.on('-N', '--no-sync', I18n.t('option.no_sync')) do
122
124
  options[:sync] = false
123
125
  end
124
126
 
127
+ opts.on('-O', '--otp', I18n.t('option.otp')) do
128
+ options[:otp] = true
129
+ end
130
+
125
131
  opts.on('-s', '--show [SEARCH]', I18n.t('option.show')) do |search|
126
132
  search.nil? ? (options[:show] = '') : (options[:show] = search)
127
133
  end
@@ -141,15 +147,29 @@ OptionParser.new do |opts|
141
147
  opts.on('-W', '--setup-wallet', I18n.t('option.setup_wallet')) do
142
148
  options[:setup_wallet] = true
143
149
  end
150
+
151
+ opts.on('-x', '--special-chars', I18n.t('option.special_chars')) do
152
+ options_password[:special] = true
153
+ end
154
+
155
+ opts.on('-y', '--alpha', I18n.t('option.alpha')) do
156
+ options_password[:alpha] = true
157
+ end
144
158
  end.parse!
145
159
 
146
160
  # --------------------------------------------------------- #
147
161
  # Main
148
162
  # --------------------------------------------------------- #
149
163
 
164
+ # Generate password
165
+ if not options_password.empty?
166
+ puts MPW::MPW::password(options_password)
167
+ exit 0
168
+ end
169
+
150
170
  begin
151
171
  config = MPW::Config.new(options[:config])
152
- cli = MPW::Cli.new(config, options[:clipboard], options[:sync])
172
+ cli = MPW::Cli.new(config, options[:clipboard], options[:sync], options[:otp])
153
173
 
154
174
  # Setup a new config
155
175
  if not options[:setup].nil?
data/i18n/en.yml CHANGED
@@ -37,6 +37,7 @@ en:
37
37
 
38
38
  option:
39
39
  add: "Add an item or key"
40
+ alpha: "Use letter to generate a password"
40
41
  config: "Specify the configuration file to use"
41
42
  clipboard: "Disable the clipboard feature"
42
43
  export: "Export a wallet in an yaml file"
@@ -49,8 +50,10 @@ en:
49
50
  import: "Import item since a yaml file"
50
51
  key: "Specify the key name, to use with the options [--add | --delete | --update]"
51
52
  no_sync: "Disable synchronization with the server"
53
+ numeric: "Use number to generate a password"
52
54
  setup: "Create a new configuration file"
53
55
  setup_wallet: "Create a new configuration file for a wallet"
56
+ special_chars: "Use special char to generate a password"
54
57
  show: "Search and show the items"
55
58
  show_all: "List all items"
56
59
  remove: "Delete an item"
@@ -72,11 +75,19 @@ en:
72
75
  password: "Enter the the password: "
73
76
  port: "Enter the connection port (optional): "
74
77
  comment: "Enter a comment (optional): "
78
+ otp_key: "Enter the otp secret (base32 secret key): "
75
79
  valid: "Item has been added!"
76
80
  clipboard:
81
+ choice: "What do you want to copy ? [q = quit, p = password, l = login]: "
77
82
  clean: "The clipboard has been cleaned."
78
- login: "The login has been copied in clipboard, press any key to continue."
83
+ login: "The login has been copied in clipboard."
79
84
  password: "The password has been copied in clipboard for 30s!"
85
+ otp: "The OTP code has been copied for %{time}s!"
86
+ help:
87
+ name: "Help"
88
+ login: "Press <l> to copy the login"
89
+ password: "Press <p> to copy the password"
90
+ otp_code: "Press <o> to copy the otp code"
80
91
  delete_key:
81
92
  valid: "Key has been deleted!"
82
93
  delete_item:
@@ -125,6 +136,7 @@ en:
125
136
  password: "Enter the the password: "
126
137
  port: "Enter the connection port [%{port}]: "
127
138
  comment: "Enter a comment [%{comment}]: "
139
+ otp_key: "Enter the otp secret (base32 secret key): "
128
140
  valid: "Item has been updated!"
129
141
  export:
130
142
  valid: "The export in %{file} is succesfull!"
@@ -136,7 +148,9 @@ en:
136
148
  group: "Group"
137
149
  login: "Login"
138
150
  name: "Name"
151
+ no_group: "Without group"
139
152
  nothing: "No matches!"
153
+ otp_code: "OTP code"
140
154
  password: "Password"
141
155
  port: "Port"
142
156
  protocol: "Protocol"
data/i18n/fr.yml CHANGED
@@ -37,6 +37,7 @@ fr:
37
37
 
38
38
  option:
39
39
  add: "Ajoute un élément ou une clé"
40
+ alpha: "Utilise des lettres dans la génération d'un mot de passe"
40
41
  config: "Spécifie le fichier de configuration à utiliser"
41
42
  clipboard: "Désactive la fonction presse papier"
42
43
  export: "Exporte un portefeuille dans un fichier yaml"
@@ -49,8 +50,10 @@ fr:
49
50
  import: "Importe des éléments depuis un fichier yaml"
50
51
  key: "Spécifie le nom d'une clé, à utiliser avec les options [--add | --delete | --update]"
51
52
  no_sync: "Désactive la synchronisation avec le serveur"
53
+ numeric: "Utilise des chiffre dans la génération d'un mot de passe"
52
54
  setup: "Création d'un nouveau fichier de configuration"
53
55
  setup_wallet: "Création d'un nouveau fichier de configuration pour un portefeuille"
56
+ special_chars: "Utilise des charactères speciaux dans la génération d'un mot de passe"
54
57
  show: "Recherche et affiche les éléments"
55
58
  show_all: "Liste tous les éléments"
56
59
  remove: "Supprime un élément"
@@ -72,11 +75,19 @@ fr:
72
75
  password: "Entrez le mot de passe: "
73
76
  port: "Entrez le port de connexion (optionnel): "
74
77
  comment: "Entrez un commentaire (optionnel): "
78
+ otp_key: "Entrez le secret OTP: "
75
79
  valid: "L'élément a bien été ajouté!"
76
80
  clipboard:
81
+ choice: "Que voulez-vous copier ? : "
77
82
  clean: "Le presse papier a été nettoyé."
78
- login: "L'identifiant a été copié dans le presse papier, pressez n'importe quelle touche pour continuer."
83
+ login: "L'identifiant a été copié dans le presse papier"
79
84
  password: "Le mot de passe a été copié dans le presse papier pour 30s!"
85
+ otp: "Le code OTP a été copié dans le presse papier il est valable %{time}s!"
86
+ help:
87
+ name: "Aide"
88
+ login: "Pressez <l> pour copier l'identifiant"
89
+ password: "Pressez <p> pour copier le mot de passe"
90
+ otp_code: "Pressez <o> pour copier le code OTP"
80
91
  delete_key:
81
92
  valid: "La clé a bien été supprimée!"
82
93
  delete_item:
@@ -125,6 +136,7 @@ fr:
125
136
  password: "Entrez le mot de passe: "
126
137
  port: "Entrez un port de connexion [%{port}]: "
127
138
  comment: "Entrez un commentaire [%{comment}]: "
139
+ otp_key: "Entrez le secret OTP: "
128
140
  valid: "L'élément a bien été mis à jour!"
129
141
  export:
130
142
  valid: "L'export dans %{file} est un succès!"
@@ -136,7 +148,9 @@ fr:
136
148
  group: "Groupe"
137
149
  login: "Identifiant"
138
150
  name: "Nom"
151
+ no_group: "Sans groupe"
139
152
  nothing: "Aucun résultat!"
153
+ otp_code: "Code OTP"
140
154
  password: "Mot de passe"
141
155
  port: "Port"
142
156
  protocol: "Protocol"
@@ -21,10 +21,8 @@ require 'i18n'
21
21
  require 'colorize'
22
22
  require 'highline/import'
23
23
  require 'clipboard'
24
-
25
- #TODO
26
- require "#{APP_ROOT}/../lib/mpw/item.rb"
27
- require "#{APP_ROOT}/../lib/mpw/mpw.rb"
24
+ require 'mpw/item'
25
+ require 'mpw/mpw'
28
26
 
29
27
  module MPW
30
28
  class Cli
@@ -32,10 +30,13 @@ class Cli
32
30
  # Constructor
33
31
  # @args: config -> the config
34
32
  # sync -> boolean for sync or not
35
- def initialize(config, clipboard=true, sync=true)
33
+ # clipboard -> enable clopboard
34
+ # otp -> enable otp
35
+ def initialize(config, clipboard=true, sync=true, otp=false)
36
36
  @config = config
37
37
  @clipboard = clipboard
38
38
  @sync = sync
39
+ @otp = otp
39
40
  end
40
41
 
41
42
  # Create a new config file
@@ -160,7 +161,12 @@ class Cli
160
161
  result.each do |item|
161
162
  if group != item.group
162
163
  group = item.group
163
- puts "#{I18n.t('display.group')}: #{group}".yellow
164
+
165
+ if group.empty?
166
+ puts I18n.t('display.no_group').yellow
167
+ else
168
+ puts "\n#{group}".yellow
169
+ end
164
170
  end
165
171
 
166
172
  print " |_ ".yellow
@@ -171,6 +177,8 @@ class Cli
171
177
 
172
178
  i += 1
173
179
  end
180
+
181
+ print "\n"
174
182
  choice = ask(I18n.t('form.select')).to_i
175
183
 
176
184
  if choice >= 1 and choice < i
@@ -197,12 +205,20 @@ class Cli
197
205
  puts item.protocol
198
206
  print "#{I18n.t('display.login')}: ".cyan
199
207
  puts item.user
200
- print "#{I18n.t('display.password')}: ".cyan
208
+
201
209
  if @clipboard
210
+ print "#{I18n.t('display.password')}: ".cyan
202
211
  puts '***********'
203
212
  else
213
+ print "#{I18n.t('display.password')}: ".cyan
204
214
  puts @mpw.get_password(item.id)
215
+
216
+ if @mpw.get_otp_code(item.id) > 0
217
+ print "#{I18n.t('display.otp_code')}: ".cyan
218
+ puts "#{@mpw.get_otp_code(item.id)} (#{@mpw.get_otp_remaining_time}s)"
219
+ end
205
220
  end
221
+
206
222
  print "#{I18n.t('display.port')}: ".cyan
207
223
  puts item.port
208
224
  print "#{I18n.t('display.comment')}: ".cyan
@@ -212,18 +228,51 @@ class Cli
212
228
  end
213
229
 
214
230
  # Copy in clipboard the login and password
231
+ # @args: item -> the item
215
232
  def clipboard(item)
216
- Clipboard.copy(item.user)
217
- print "\n#{I18n.t('form.clipboard.login')}".green
218
- gets
233
+ pid = nil
234
+
235
+ # Security: force quit after 90s
236
+ Thread.new do
237
+ sleep 90
238
+ exit
239
+ end
240
+
241
+ while true
242
+ choice = ask(I18n.t('form.clipboard.choice')).to_s
243
+
244
+ case choice
245
+ when 'q', 'quit'
246
+ break
247
+
248
+ when 'l', 'login'
249
+ Clipboard.copy(item.user)
250
+ puts I18n.t('form.clipboard.login').green
219
251
 
220
- Clipboard.copy(@mpw.get_password(item.id))
221
- puts I18n.t('form.clipboard.password').yellow
252
+ when 'p', 'password'
253
+ Clipboard.copy(@mpw.get_password(item.id))
254
+ puts I18n.t('form.clipboard.password').yellow
222
255
 
223
- sleep(30)
256
+ Thread.new do
257
+ sleep 30
258
+
259
+ Clipboard.clear
260
+ end
261
+
262
+ when 'o', 'otp'
263
+ Clipboard.copy(@mpw.get_otp_code(item.id))
264
+ puts I18n.t('form.clipboard.otp', time: @mpw.get_otp_remaining_time).yellow
265
+
266
+ else
267
+ puts "----- #{I18n.t('form.clipboard.help.name')} -----".cyan
268
+ puts I18n.t('form.clipboard.help.login')
269
+ puts I18n.t('form.clipboard.help.password')
270
+ puts I18n.t('form.clipboard.help.otp_code')
271
+ next
272
+ end
273
+ end
224
274
 
225
275
  Clipboard.clear
226
- puts I18n.t('form.clipboard.clean').green
227
276
  rescue SystemExit, Interrupt
228
277
  Clipboard.clear
229
278
  end
@@ -254,6 +303,7 @@ class Cli
254
303
  @wallet_file = wallets[choice-1]
255
304
  else
256
305
  puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
306
+ exit 2
257
307
  end
258
308
  end
259
309
  else
@@ -301,10 +351,15 @@ class Cli
301
351
  options[:port] = ask(I18n.t('form.add_item.port')).to_s
302
352
  options[:comment] = ask(I18n.t('form.add_item.comment')).to_s
303
353
 
354
+ if @otp
355
+ otp_key = ask(I18n.t('form.add_item.otp_key')).to_s
356
+ end
357
+
304
358
  item = Item.new(options)
305
359
 
306
360
  @mpw.add(item)
307
361
  @mpw.set_password(item.id, password)
362
+ @mpw.set_otp_key(item.id, otp_key)
308
363
  @mpw.write_data
309
364
  @mpw.sync(true) if @sync
310
365
 
@@ -330,10 +385,15 @@ class Cli
330
385
  options[:port] = ask(I18n.t('form.update_item.port' , port: item.port)).to_s
331
386
  options[:comment] = ask(I18n.t('form.update_item.comment' , comment: item.comment)).to_s
332
387
 
388
+ if @otp
389
+ otp_key = ask(I18n.t('form.update_item.otp_key')).to_s
390
+ end
391
+
333
392
  options.delete_if { |k,v| v.empty? }
334
-
393
+
335
394
  item.update(options)
336
395
  @mpw.set_password(item.id, password) if not password.empty?
396
+ @mpw.set_otp_key(item.id, otp_key) if not otp_key.to_s.empty?
337
397
  @mpw.write_data
338
398
  @mpw.sync(true) if @sync
339
399
 
data/lib/mpw/item.rb CHANGED
@@ -21,8 +21,6 @@ require 'i18n'
21
21
  module MPW
22
22
  class Item
23
23
 
24
- attr_accessor :error_msg
25
-
26
24
  attr_accessor :id
27
25
  attr_accessor :name
28
26
  attr_accessor :group
@@ -41,8 +39,7 @@ class Item
41
39
  # raise an error if the hash hasn't the key name
42
40
  def initialize(options={})
43
41
  if not options.has_key?(:name) or options[:name].to_s.empty?
44
- @error_msg = I18n.t('error.update.name_empty')
45
- raise @error_msg
42
+ raise I18n.t('error.update.name_empty')
46
43
  end
47
44
 
48
45
  if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty?
@@ -60,11 +57,9 @@ class Item
60
57
 
61
58
  # Update the item
62
59
  # @args: options -> a hash of parameter
63
- # @rtrn: true if the item is update
64
60
  def update(options={})
65
61
  if options.has_key?(:name) and options[:name].to_s.empty?
66
- @error_msg = I18n.t('error.update.name_empty')
67
- return false
62
+ raise I18n.t('error.update.name_empty')
68
63
  end
69
64
 
70
65
  @name = options[:name] if options.has_key?(:name)
@@ -75,8 +70,6 @@ class Item
75
70
  @port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty?
76
71
  @comment = options[:comment] if options.has_key?(:comment)
77
72
  @last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit)
78
-
79
- return true
80
73
  end
81
74
 
82
75
  # Update last_sync
@@ -85,7 +78,6 @@ class Item
85
78
  end
86
79
 
87
80
  # Delete all data
88
- # @rtrn: true
89
81
  def delete
90
82
  @id = nil
91
83
  @name = nil
@@ -98,8 +90,6 @@ class Item
98
90
  @created = nil
99
91
  @last_edit = nil
100
92
  @last_sync = nil
101
-
102
- return true
103
93
  end
104
94
 
105
95
  def empty?
data/lib/mpw/mpw.rb CHANGED
@@ -20,9 +20,8 @@ require 'rubygems/package'
20
20
  require 'gpgme'
21
21
  require 'i18n'
22
22
  require 'yaml'
23
-
24
- #TODO
25
- require "#{APP_ROOT}/../lib/mpw/item.rb"
23
+ require 'rotp'
24
+ require 'mpw/item'
26
25
 
27
26
  module MPW
28
27
  class MPW
@@ -45,6 +44,7 @@ class MPW
45
44
  @data = []
46
45
  @keys = {}
47
46
  @passwords = {}
47
+ @otp_keys = {}
48
48
 
49
49
  data = nil
50
50
 
@@ -70,6 +70,10 @@ class MPW
70
70
 
71
71
  when /^wallet\/passwords\/(?<id>[a-zA-Z0-9]+)\.gpg$/
72
72
  @passwords[Regexp.last_match('id')] = f.read
73
+
74
+ when /^wallet\/otp_keys\/(?<id>[a-zA-Z0-9]+)\.gpg$/
75
+ @otp_keys[Regexp.last_match('id')] = f.read
76
+
73
77
  else
74
78
  next
75
79
  end
@@ -120,6 +124,8 @@ class MPW
120
124
  )
121
125
  end
122
126
 
127
+ @config['last_update'] = Time.now.to_i
128
+
123
129
  Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
124
130
  data_encrypt = encrypt(data.to_yaml)
125
131
  tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
@@ -137,6 +143,12 @@ class MPW
137
143
  end
138
144
  end
139
145
 
146
+ @otp_keys.each do |id, key|
147
+ tar.add_file_simple("wallet/otp_keys/#{id}.gpg", 0400, key.length) do |io|
148
+ io.write(key)
149
+ end
150
+ end
151
+
140
152
  @keys.each do |id, key|
141
153
  tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
142
154
  io.write(key)
@@ -167,7 +179,7 @@ class MPW
167
179
  # args: id -> the item id
168
180
  # password -> the new password
169
181
  def set_password(id, password)
170
- salt = MPW::password(Random.rand(4..9))
182
+ salt = MPW::password(length: Random.rand(4..9))
171
183
  password = "$#{salt}::#{password}"
172
184
 
173
185
  @passwords[id] = encrypt(password)
@@ -319,13 +331,13 @@ class MPW
319
331
  # @args: force -> force the sync
320
332
  def sync(force=false)
321
333
  return if @config.empty? or @config['sync']['type'].to_s.empty?
322
- return if get_last_sync < Time.now.to_i + 300 and not force
323
-
334
+ return if get_last_sync + 300 > Time.now.to_i and not force
335
+
324
336
  tmp_file = "#{@wallet_file}.sync"
325
337
 
326
338
  case @config['sync']['type']
327
339
  when 'sftp', 'scp', 'ssh'
328
- require "#{APP_ROOT}/../lib/mpw/sync/ssh.rb"
340
+ require "mpw/sync/ssh"
329
341
  sync = SyncSSH.new(@config['sync'])
330
342
  when 'ftp'
331
343
  require 'mpw/sync/ftp'
@@ -342,7 +354,7 @@ class MPW
342
354
 
343
355
  File.unlink(tmp_file) if File.exist?(tmp_file)
344
356
 
345
- return if remote.get_last_sync == get_last_sync
357
+ return if remote.get_last_sync == @config['last_update']
346
358
 
347
359
  if not remote.to_s.empty?
348
360
  @data.each do |item|
@@ -413,22 +425,54 @@ class MPW
413
425
  raise "#{I18n.t('error.sync.general')}\n#{e}"
414
426
  end
415
427
 
428
+ # Set an opt key
429
+ # args: id -> the item id
430
+ # key -> the new key
431
+ def set_otp_key(id, key)
432
+ @otp_keys[id] = encrypt(key)
433
+ end
434
+
435
+ # Get an otp code
436
+ # @args: id -> the item id
437
+ # @rtrn: an otp code
438
+ def get_otp_code(id)
439
+ if not @otp_keys.has_key?(id)
440
+ return 0
441
+ else
442
+ return ROTP::TOTP.new(decrypt(@otp_keys[id])).now
443
+ end
444
+ end
445
+
446
+ # Get remaining time before expire otp code
447
+ # @rtrn: return time in seconde
448
+ def get_otp_remaining_time
449
+ return (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
450
+ end
451
+
416
452
  # Generate a random password
417
- # @args: length -> the length password
453
+ # @args: options -> :length, :special, :alpha, :numeric
418
454
  # @rtrn: a random string
419
- def self.password(length=8)
420
- if length.to_i <= 0
455
+ def self.password(options={})
456
+ if not options.include?(:length) or options[:length].to_i <= 0
421
457
  length = 8
458
+ elsif options[:length].to_i >= 32768
459
+ length = 32768
422
460
  else
423
- length = length.to_i
461
+ length = options[:length].to_i
424
462
  end
425
463
 
464
+ chars = []
465
+ chars += [*('!'..'?')] - [*('0'..'9')] if options.include?(:special)
466
+ chars += [*('A'..'Z'),*('a'..'z')] if options.include?(:alpha)
467
+ chars += [*('0'..'9')] if options.include?(:numeric)
468
+ chars = [*('A'..'Z'),*('a'..'z'),*('0'..'9')] if chars.empty?
469
+
426
470
  result = ''
427
471
  while length > 62 do
428
- result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(62).join
472
+ result << chars.sample(62).join
429
473
  length -= 62
430
474
  end
431
- result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
475
+ result << chars.sample(length).join
432
476
 
433
477
  return result
434
478
  end
data/mpw.gemspec CHANGED
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "net-ssh"
26
26
  spec.add_dependency "net-scp"
27
27
  spec.add_dependency "clipboard"
28
+ spec.add_dependency "rotp"
28
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mpw
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrien Waksberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-09 00:00:00.000000000 Z
11
+ date: 2016-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rotp
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  description: Manage your passwords in all security with MPW, we use GPG to encrypt
126
140
  your passwords
127
141
  email:
@@ -140,12 +154,12 @@ files:
140
154
  - bin/mpw
141
155
  - i18n/en.yml
142
156
  - i18n/fr.yml
157
+ - lib/mpw/cli.rb
143
158
  - lib/mpw/config.rb
144
159
  - lib/mpw/item.rb
145
160
  - lib/mpw/mpw.rb
146
161
  - lib/mpw/sync/ftp.rb
147
162
  - lib/mpw/sync/ssh.rb
148
- - lib/mpw/ui/cli.rb
149
163
  - mpw.gemspec
150
164
  - test/files/fixtures.yml
151
165
  - test/files/test_import.csv