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 +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
|