mpw 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/VERSION +1 -1
- data/bin/mpw +32 -12
- data/i18n/en.yml +15 -1
- data/i18n/fr.yml +15 -1
- data/lib/mpw/{ui/cli.rb → cli.rb} +75 -15
- data/lib/mpw/item.rb +2 -12
- data/lib/mpw/mpw.rb +58 -14
- data/mpw.gemspec +1 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38a2af169d14523a873d9ca733b19ce2f7856830
|
4
|
+
data.tar.gz: 7d3365270ad43f53316ce9519e7b2feb01ee73ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d8931b9d43a244cae00039e66cc9575d21cb6ab4b7448104eccbaa70bc95bf0e3ebbec3c2297504fd5d59b2f6610764777e94e59750e2d7020b71bc9d2449c3
|
7
|
+
data.tar.gz: eea9b9f36d27dc035e1c18a04a0ef6c3cdda0b71fb1709f72dc30739373c088c06b40b0464fa42d196fda2020d1f58ff38489ed0f24caf22479dcfce0df8e9fe
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
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["#{
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
221
|
-
|
252
|
+
when 'p', 'password'
|
253
|
+
Clipboard.copy(@mpw.get_password(item.id))
|
254
|
+
puts I18n.t('form.clipboard.password').yellow
|
222
255
|
|
223
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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 "
|
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 ==
|
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:
|
453
|
+
# @args: options -> :length, :special, :alpha, :numeric
|
418
454
|
# @rtrn: a random string
|
419
|
-
def self.password(
|
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 <<
|
472
|
+
result << chars.sample(62).join
|
429
473
|
length -= 62
|
430
474
|
end
|
431
|
-
result <<
|
475
|
+
result << chars.sample(length).join
|
432
476
|
|
433
477
|
return result
|
434
478
|
end
|
data/mpw.gemspec
CHANGED
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.
|
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-
|
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
|