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/config.rb
CHANGED
@@ -26,11 +26,13 @@ class Config
|
|
26
26
|
|
27
27
|
attr_accessor :error_msg
|
28
28
|
|
29
|
-
attr_accessor :
|
29
|
+
attr_accessor :gpg_key
|
30
30
|
attr_accessor :lang
|
31
31
|
attr_accessor :config_dir
|
32
|
+
attr_accessor :default_wallet
|
32
33
|
attr_accessor :wallet_dir
|
33
34
|
attr_accessor :gpg_exe
|
35
|
+
attr_accessor :password
|
34
36
|
|
35
37
|
# Constructor
|
36
38
|
# @args: config_file -> the specify config file
|
@@ -45,39 +47,51 @@ class Config
|
|
45
47
|
@config_dir = "#{Dir.home}/.config/mpw"
|
46
48
|
end
|
47
49
|
|
48
|
-
if @config_file.nil? or @config_file.empty?
|
49
|
-
@config_file = "#{@config_dir}/mpw.cfg"
|
50
|
-
end
|
50
|
+
@config_file = "#{@config_dir}/mpw.cfg" if @config_file.nil? or @config_file.empty?
|
51
51
|
end
|
52
52
|
|
53
53
|
# Create a new config file
|
54
|
-
# @args:
|
55
|
-
# lang -> the software language
|
56
|
-
# wallet_dir -> the directory where are the wallets password
|
57
|
-
# gpg_exe -> the path of gpg executable
|
54
|
+
# @args: options -> hash with values
|
58
55
|
# @rtrn: true if le config file is create
|
59
|
-
def setup(
|
56
|
+
def setup(options)
|
57
|
+
gpg_key = options[:gpg_key] || @gpg_key
|
58
|
+
lang = options[:lang] || @lang
|
59
|
+
wallet_dir = options[:wallet_dir] || @wallet_dir
|
60
|
+
default_wallet = options[:default_wallet] || @default_wallet
|
61
|
+
gpg_exe = options[:gpg_exe] || @gpg_exe
|
62
|
+
password = { numeric: true,
|
63
|
+
alpha: true,
|
64
|
+
special: false,
|
65
|
+
length: 16,
|
66
|
+
}
|
67
|
+
|
68
|
+
['numeric', 'special', 'alpha', 'length'].each do |k|
|
69
|
+
if options.has_key?("pwd_#{k}".to_sym)
|
70
|
+
password[k.to_sym] = options["pwd_#{k}".to_sym]
|
71
|
+
elsif not @password.nil? and @password.has_key?(k.to_sym)
|
72
|
+
password[k.to_sym] = @password[k.to_sym]
|
73
|
+
end
|
74
|
+
end
|
60
75
|
|
61
|
-
if not
|
76
|
+
if not gpg_key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
|
62
77
|
raise I18n.t('error.config.key_bad_format')
|
63
78
|
end
|
64
79
|
|
65
|
-
if wallet_dir.empty?
|
66
|
-
|
67
|
-
|
80
|
+
wallet_dir = "#{@config_dir}/wallets" if wallet_dir.to_s.empty?
|
81
|
+
config = { 'gpg_key' => gpg_key,
|
82
|
+
'lang' => lang,
|
83
|
+
'wallet_dir' => wallet_dir,
|
84
|
+
'default_wallet' => default_wallet,
|
85
|
+
'gpg_exe' => gpg_exe,
|
86
|
+
'password' => password,
|
87
|
+
}
|
68
88
|
|
69
|
-
|
70
|
-
|
71
|
-
'wallet_dir' => wallet_dir,
|
72
|
-
'gpg_exe' => gpg_exe,
|
73
|
-
}
|
74
|
-
}
|
89
|
+
FileUtils.mkdir_p(@config_dir, mode: 0700)
|
90
|
+
FileUtils.mkdir_p(wallet_dir, mode: 0700)
|
75
91
|
|
76
|
-
FileUtils.mkdir_p(wallet_dir, mode: 0700)
|
77
92
|
File.open(@config_file, 'w') do |file|
|
78
93
|
file << config.to_yaml
|
79
94
|
end
|
80
|
-
|
81
95
|
rescue Exception => e
|
82
96
|
raise "#{I18n.t('error.config.write')}\n#{e}"
|
83
97
|
end
|
@@ -89,21 +103,21 @@ class Config
|
|
89
103
|
# expire -> the time of expire to GPG key
|
90
104
|
# @rtrn: true if the GPG key is create, else false
|
91
105
|
def setup_gpg_key(password, name, length = 4096, expire = 0)
|
92
|
-
if name.
|
106
|
+
if name.to_s.empty?
|
93
107
|
raise "#{I18n.t('error.config.genkey_gpg.name')}"
|
94
|
-
elsif password.
|
108
|
+
elsif password.to_s.empty?
|
95
109
|
raise "#{I18n.t('error.config.genkey_gpg.password')}"
|
96
110
|
end
|
97
111
|
|
98
112
|
param = ''
|
99
113
|
param << '<GnupgKeyParms format="internal">' + "\n"
|
100
|
-
param << "Key-Type:
|
114
|
+
param << "Key-Type: RSA\n"
|
101
115
|
param << "Key-Length: #{length}\n"
|
102
116
|
param << "Subkey-Type: ELG-E\n"
|
103
117
|
param << "Subkey-Length: #{length}\n"
|
104
118
|
param << "Name-Real: #{name}\n"
|
105
119
|
param << "Name-Comment: #{name}\n"
|
106
|
-
param << "Name-Email: #{@
|
120
|
+
param << "Name-Email: #{@gpg_key}\n"
|
107
121
|
param << "Expire-Date: #{expire}\n"
|
108
122
|
param << "Passphrase: #{password}\n"
|
109
123
|
param << "</GnupgKeyParms>\n"
|
@@ -114,29 +128,28 @@ class Config
|
|
114
128
|
raise "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}"
|
115
129
|
end
|
116
130
|
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@gpg_exe
|
125
|
-
|
126
|
-
|
131
|
+
# Load the config file
|
132
|
+
def load_config
|
133
|
+
config = YAML::load_file(@config_file)
|
134
|
+
@gpg_key = config['gpg_key']
|
135
|
+
@lang = config['lang']
|
136
|
+
@wallet_dir = config['wallet_dir']
|
137
|
+
@default_wallet = config['default_wallet']
|
138
|
+
@gpg_exe = config['gpg_exe']
|
139
|
+
@password = config['password'] || {}
|
140
|
+
|
141
|
+
raise if @gpg_key.empty? or @wallet_dir.empty?
|
127
142
|
|
128
143
|
I18n.locale = @lang.to_sym
|
129
|
-
|
130
|
-
|
131
|
-
rescue
|
132
|
-
return false
|
144
|
+
rescue Exception => e
|
145
|
+
raise "#{I18n.t('error.config.load')}\n#{e}"
|
133
146
|
end
|
134
147
|
|
135
148
|
# Check if private key exist
|
136
149
|
# @rtrn: true if the key exist, else false
|
137
150
|
def check_gpg_key?
|
138
151
|
ctx = GPGME::Ctx.new
|
139
|
-
ctx.each_key(@
|
152
|
+
ctx.each_key(@gpg_key, true) do
|
140
153
|
return true
|
141
154
|
end
|
142
155
|
|
data/lib/mpw/item.rb
CHANGED
@@ -22,12 +22,12 @@ module MPW
|
|
22
22
|
class Item
|
23
23
|
|
24
24
|
attr_accessor :id
|
25
|
-
attr_accessor :name
|
26
25
|
attr_accessor :group
|
27
26
|
attr_accessor :host
|
28
27
|
attr_accessor :protocol
|
29
28
|
attr_accessor :user
|
30
29
|
attr_accessor :port
|
30
|
+
attr_accessor :otp
|
31
31
|
attr_accessor :comment
|
32
32
|
attr_accessor :last_edit
|
33
33
|
attr_accessor :last_sync
|
@@ -38,15 +38,15 @@ class Item
|
|
38
38
|
# @args: options -> a hash of parameter
|
39
39
|
# raise an error if the hash hasn't the key name
|
40
40
|
def initialize(options={})
|
41
|
-
if not options.has_key?(:
|
42
|
-
raise I18n.t('error.update.
|
41
|
+
if not options.has_key?(:host) or options[:host].to_s.empty?
|
42
|
+
raise I18n.t('error.update.host_empty')
|
43
43
|
end
|
44
44
|
|
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?
|
46
|
-
@id
|
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?
|
46
|
+
@id = generate_id
|
47
47
|
@created = Time.now.to_i
|
48
48
|
else
|
49
|
-
@id
|
49
|
+
@id = options[:id]
|
50
50
|
@created = options[:created]
|
51
51
|
@last_edit = options[:last_edit]
|
52
52
|
options[:no_update_last_edit] = true
|
@@ -58,16 +58,16 @@ class Item
|
|
58
58
|
# Update the item
|
59
59
|
# @args: options -> a hash of parameter
|
60
60
|
def update(options={})
|
61
|
-
if options.has_key?(:
|
62
|
-
raise I18n.t('error.update.
|
61
|
+
if options.has_key?(:host) and options[:host].to_s.empty?
|
62
|
+
raise I18n.t('error.update.host_empty')
|
63
63
|
end
|
64
64
|
|
65
|
-
@name = options[:name] if options.has_key?(:name)
|
66
65
|
@group = options[:group] if options.has_key?(:group)
|
67
66
|
@host = options[:host] if options.has_key?(:host)
|
68
67
|
@protocol = options[:protocol] if options.has_key?(:protocol)
|
69
68
|
@user = options[:user] if options.has_key?(:user)
|
70
69
|
@port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty?
|
70
|
+
@otp = options[:otp] if options.has_key?(:otp)
|
71
71
|
@comment = options[:comment] if options.has_key?(:comment)
|
72
72
|
@last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit)
|
73
73
|
end
|
@@ -80,12 +80,12 @@ class Item
|
|
80
80
|
# Delete all data
|
81
81
|
def delete
|
82
82
|
@id = nil
|
83
|
-
@name = nil
|
84
83
|
@group = nil
|
85
84
|
@host = nil
|
86
85
|
@protocol = nil
|
87
86
|
@user = nil
|
88
87
|
@port = nil
|
88
|
+
@otp = nil
|
89
89
|
@comment = nil
|
90
90
|
@created = nil
|
91
91
|
@last_edit = nil
|
@@ -93,7 +93,7 @@ class Item
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def empty?
|
96
|
-
return @
|
96
|
+
return @id.to_s.empty?
|
97
97
|
end
|
98
98
|
|
99
99
|
def nil?
|
@@ -104,6 +104,6 @@ class Item
|
|
104
104
|
private
|
105
105
|
def generate_id
|
106
106
|
return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join
|
107
|
-
end
|
107
|
+
end
|
108
|
+
end
|
108
109
|
end
|
109
|
-
end
|
data/lib/mpw/mpw.rb
CHANGED
@@ -33,7 +33,7 @@ class MPW
|
|
33
33
|
@gpg_exe = gpg_exe
|
34
34
|
@wallet_file = wallet_file
|
35
35
|
|
36
|
-
if @gpg_exe
|
36
|
+
if not @gpg_exe.to_s.empty?
|
37
37
|
GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_exe, "#{Dir.home}/.gnupg")
|
38
38
|
end
|
39
39
|
end
|
@@ -83,12 +83,12 @@ class MPW
|
|
83
83
|
if not data.nil? and not data.empty?
|
84
84
|
YAML.load(data).each_value do |d|
|
85
85
|
@data.push(Item.new(id: d['id'],
|
86
|
-
name: d['name'],
|
87
86
|
group: d['group'],
|
88
87
|
host: d['host'],
|
89
88
|
protocol: d['protocol'],
|
90
89
|
user: d['user'],
|
91
90
|
port: d['port'],
|
91
|
+
otp: @otp_keys.has_key?(d['id']),
|
92
92
|
comment: d['comment'],
|
93
93
|
last_edit: d['last_edit'],
|
94
94
|
created: d['created'],
|
@@ -110,16 +110,15 @@ class MPW
|
|
110
110
|
@data.each do |item|
|
111
111
|
next if item.empty?
|
112
112
|
|
113
|
-
data.merge!(item.id => {'id' => item.id,
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
'created' => item.created,
|
113
|
+
data.merge!(item.id => { 'id' => item.id,
|
114
|
+
'group' => item.group,
|
115
|
+
'host' => item.host,
|
116
|
+
'protocol' => item.protocol,
|
117
|
+
'user' => item.user,
|
118
|
+
'port' => item.port,
|
119
|
+
'comment' => item.comment,
|
120
|
+
'last_edit' => item.last_edit,
|
121
|
+
'created' => item.created,
|
123
122
|
}
|
124
123
|
)
|
125
124
|
end
|
@@ -158,7 +157,7 @@ class MPW
|
|
158
157
|
|
159
158
|
File.rename(tmp_file, @wallet_file)
|
160
159
|
rescue Exception => e
|
161
|
-
File.unlink(tmp_file)
|
160
|
+
File.unlink(tmp_file) if File.exist?(tmp_file)
|
162
161
|
|
163
162
|
raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
|
164
163
|
end
|
@@ -185,13 +184,19 @@ class MPW
|
|
185
184
|
@passwords[id] = encrypt(password)
|
186
185
|
end
|
187
186
|
|
187
|
+
# Return the list of all gpg keys
|
188
|
+
# rtrn: an array with the gpg keys name
|
189
|
+
def list_keys
|
190
|
+
return @keys.keys
|
191
|
+
end
|
192
|
+
|
188
193
|
# Add a public key
|
189
|
-
# args: key -> new public key
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
GPGME::Key.
|
194
|
+
# args: key -> new public key file or name
|
195
|
+
def add_key(key)
|
196
|
+
if File.exists?(key)
|
197
|
+
data = File.open(key).read
|
198
|
+
key_import = GPGME::Key.import(data, armor: true)
|
199
|
+
key = GPGME::Key.get(key_import.imports[0].fpr).uids[0].email
|
195
200
|
else
|
196
201
|
data = GPGME::Key.export(key, armor: true).read
|
197
202
|
end
|
@@ -201,26 +206,30 @@ class MPW
|
|
201
206
|
end
|
202
207
|
|
203
208
|
@keys[key] = data
|
209
|
+
@passwords.each_key { |id| set_password(id, get_password(id)) }
|
210
|
+
@otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
|
204
211
|
end
|
205
212
|
|
206
213
|
# Delete a public key
|
207
214
|
# args: key -> public key to delete
|
208
215
|
def delete_key(key)
|
209
216
|
@keys.delete(key)
|
217
|
+
@passwords.each_key { |id| set_password(id, get_password(id)) }
|
218
|
+
@otp_keys.each_key { |id| set_otp_key(id, get_otp_key(id)) }
|
210
219
|
end
|
211
220
|
|
212
221
|
# Set config
|
213
222
|
# args: config -> a hash with config options
|
214
|
-
def set_config(
|
215
|
-
@config
|
216
|
-
|
217
|
-
@config['
|
218
|
-
@config['
|
219
|
-
@config['
|
220
|
-
@config['
|
221
|
-
@config['
|
222
|
-
@config['
|
223
|
-
@config['
|
223
|
+
def set_config(options={})
|
224
|
+
@config = {} if @config.nil?
|
225
|
+
|
226
|
+
@config['protocol'] = options[:protocol] if options.has_key?(:protocol)
|
227
|
+
@config['host'] = options[:host] if options.has_key?(:host)
|
228
|
+
@config['port'] = options[:port] if options.has_key?(:port)
|
229
|
+
@config['user'] = options[:user] if options.has_key?(:user)
|
230
|
+
@config['password'] = options[:password] if options.has_key?(:password)
|
231
|
+
@config['path'] = options[:path] if options.has_key?(:path)
|
232
|
+
@config['last_sync'] = @config['last_sync'].nil? ? 0 : @config['last_sync']
|
224
233
|
end
|
225
234
|
|
226
235
|
# Add a new item
|
@@ -229,7 +238,7 @@ class MPW
|
|
229
238
|
if not item.instance_of?(Item)
|
230
239
|
raise I18n.t('error.bad_class')
|
231
240
|
elsif item.empty?
|
232
|
-
raise I18n.t('error.
|
241
|
+
raise I18n.t('error.empty')
|
233
242
|
else
|
234
243
|
@data.push(item)
|
235
244
|
end
|
@@ -241,18 +250,17 @@ class MPW
|
|
241
250
|
def list(options={})
|
242
251
|
result = []
|
243
252
|
|
244
|
-
search
|
245
|
-
group
|
253
|
+
search = options[:pattern].to_s.downcase
|
254
|
+
group = options[:group].to_s.downcase
|
246
255
|
|
247
256
|
@data.each do |item|
|
248
257
|
next if item.empty?
|
249
|
-
next if not group.empty?
|
258
|
+
next if not group.empty? and not group.eql?(item.group.to_s.downcase)
|
250
259
|
|
251
|
-
name = item.name.to_s.downcase
|
252
260
|
host = item.host.to_s.downcase
|
253
261
|
comment = item.comment.to_s.downcase
|
254
262
|
|
255
|
-
if not
|
263
|
+
if not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
|
256
264
|
next
|
257
265
|
end
|
258
266
|
|
@@ -273,56 +281,9 @@ class MPW
|
|
273
281
|
return nil
|
274
282
|
end
|
275
283
|
|
276
|
-
# Export to yaml
|
277
|
-
# @args: file -> file where you export the data
|
278
|
-
def export(file)
|
279
|
-
data = {}
|
280
|
-
@data.each do |item|
|
281
|
-
data.merge!(item.id => {'id' => item.id,
|
282
|
-
'name' => item.name,
|
283
|
-
'group' => item.group,
|
284
|
-
'host' => item.host,
|
285
|
-
'protocol' => item.protocol,
|
286
|
-
'user' => item.user,
|
287
|
-
'password' => get_password(item.id),
|
288
|
-
'port' => item.port,
|
289
|
-
'comment' => item.comment,
|
290
|
-
'last_edit' => item.last_edit,
|
291
|
-
'created' => item.created,
|
292
|
-
}
|
293
|
-
)
|
294
|
-
end
|
295
|
-
|
296
|
-
File.open(file, 'w') {|f| f << data.to_yaml}
|
297
|
-
rescue Exception => e
|
298
|
-
raise "#{I18n.t('error.export', file: file)}\n#{e}"
|
299
|
-
end
|
300
|
-
|
301
|
-
# Import to yaml
|
302
|
-
# @args: file -> path to file import
|
303
|
-
def import(file)
|
304
|
-
YAML::load_file(file).each_value do |row|
|
305
|
-
item = Item.new(name: row['name'],
|
306
|
-
group: row['group'],
|
307
|
-
host: row['host'],
|
308
|
-
protocol: row['protocol'],
|
309
|
-
user: row['user'],
|
310
|
-
port: row['port'],
|
311
|
-
comment: row['comment'],
|
312
|
-
)
|
313
|
-
|
314
|
-
raise 'Item is empty' if item.empty?
|
315
|
-
|
316
|
-
@data.push(item)
|
317
|
-
set_password(item.id, row['password'])
|
318
|
-
end
|
319
|
-
rescue Exception => e
|
320
|
-
raise "#{I18n.t('error.import', file: file)}\n#{e}"
|
321
|
-
end
|
322
|
-
|
323
284
|
# Get last sync
|
324
285
|
def get_last_sync
|
325
|
-
return @config['
|
286
|
+
return @config['last_sync'].to_i
|
326
287
|
rescue
|
327
288
|
return 0
|
328
289
|
end
|
@@ -330,18 +291,18 @@ class MPW
|
|
330
291
|
# Sync data with remote file
|
331
292
|
# @args: force -> force the sync
|
332
293
|
def sync(force=false)
|
333
|
-
return if @config.empty? or @config['
|
294
|
+
return if @config.empty? or @config['protocol'].to_s.empty?
|
334
295
|
return if get_last_sync + 300 > Time.now.to_i and not force
|
335
296
|
|
336
297
|
tmp_file = "#{@wallet_file}.sync"
|
337
298
|
|
338
|
-
case @config['
|
299
|
+
case @config['protocol']
|
339
300
|
when 'sftp', 'scp', 'ssh'
|
340
301
|
require "mpw/sync/ssh"
|
341
|
-
sync = SyncSSH.new(@config
|
302
|
+
sync = SyncSSH.new(@config)
|
342
303
|
when 'ftp'
|
343
304
|
require 'mpw/sync/ftp'
|
344
|
-
sync = SyncFTP.new(@config
|
305
|
+
sync = SyncFTP.new(@config)
|
345
306
|
else
|
346
307
|
raise I18n.t('error.sync.unknown_type')
|
347
308
|
end
|
@@ -358,6 +319,8 @@ class MPW
|
|
358
319
|
|
359
320
|
if not remote.to_s.empty?
|
360
321
|
@data.each do |item|
|
322
|
+
next if item.empty?
|
323
|
+
|
361
324
|
update = false
|
362
325
|
|
363
326
|
remote.list.each do |r|
|
@@ -365,8 +328,7 @@ class MPW
|
|
365
328
|
|
366
329
|
# Update item
|
367
330
|
if item.last_edit < r.last_edit
|
368
|
-
item.update(
|
369
|
-
group: r.group,
|
331
|
+
item.update(group: r.group,
|
370
332
|
host: r.host,
|
371
333
|
protocol: r.protocol,
|
372
334
|
user: r.user,
|
@@ -394,7 +356,6 @@ class MPW
|
|
394
356
|
next if r.last_edit <= get_last_sync
|
395
357
|
|
396
358
|
item = Item.new(id: r.id,
|
397
|
-
name: r.name,
|
398
359
|
group: r.group,
|
399
360
|
host: r.host,
|
400
361
|
protocol: r.protocol,
|
@@ -415,7 +376,7 @@ class MPW
|
|
415
376
|
item.set_last_sync
|
416
377
|
end
|
417
378
|
|
418
|
-
@config['
|
379
|
+
@config['last_sync'] = Time.now.to_i
|
419
380
|
|
420
381
|
write_data
|
421
382
|
sync.update(@wallet_file)
|
@@ -429,13 +390,27 @@ class MPW
|
|
429
390
|
# args: id -> the item id
|
430
391
|
# key -> the new key
|
431
392
|
def set_otp_key(id, key)
|
432
|
-
|
393
|
+
if not key.to_s.empty?
|
394
|
+
@otp_keys[id] = encrypt(key.to_s)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Get an opt key
|
399
|
+
# args: id -> the item id
|
400
|
+
# key -> the new key
|
401
|
+
def get_otp_key(id)
|
402
|
+
if @otp_keys.has_key?(id)
|
403
|
+
return decrypt(@otp_keys[id])
|
404
|
+
else
|
405
|
+
return nil
|
406
|
+
end
|
433
407
|
end
|
434
408
|
|
409
|
+
|
435
410
|
# Get an otp code
|
436
411
|
# @args: id -> the item id
|
437
412
|
# @rtrn: an otp code
|
438
|
-
def
|
413
|
+
def get_otp_code(id)
|
439
414
|
if not @otp_keys.has_key?(id)
|
440
415
|
return 0
|
441
416
|
else
|
@@ -462,9 +437,9 @@ class MPW
|
|
462
437
|
end
|
463
438
|
|
464
439
|
chars = []
|
465
|
-
chars += [*('!'..'?')] - [*('0'..'9')] if options
|
466
|
-
chars += [*('A'..'Z'),*('a'..'z')] if options
|
467
|
-
chars += [*('0'..'9')] if options
|
440
|
+
chars += [*('!'..'?')] - [*('0'..'9')] if options[:special]
|
441
|
+
chars += [*('A'..'Z'),*('a'..'z')] if options[:alpha]
|
442
|
+
chars += [*('0'..'9')] if options[:numeric]
|
468
443
|
chars = [*('A'..'Z'),*('a'..'z'),*('0'..'9')] if chars.empty?
|
469
444
|
|
470
445
|
result = ''
|
@@ -481,6 +456,8 @@ class MPW
|
|
481
456
|
# @args: data -> string to decrypt
|
482
457
|
private
|
483
458
|
def decrypt(data)
|
459
|
+
return nil if data.to_s.empty?
|
460
|
+
|
484
461
|
crypto = GPGME::Crypto.new(armor: true)
|
485
462
|
|
486
463
|
return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8')
|
@@ -495,12 +472,12 @@ class MPW
|
|
495
472
|
recipients = []
|
496
473
|
crypto = GPGME::Crypto.new(armor: true, always_trust: true)
|
497
474
|
|
475
|
+
recipients.push(@key)
|
498
476
|
@keys.each_key do |key|
|
477
|
+
next if key == @key
|
499
478
|
recipients.push(key)
|
500
479
|
end
|
501
480
|
|
502
|
-
recipients.push(@key) if not recipients.index(@key).nil?
|
503
|
-
|
504
481
|
return crypto.encrypt(data, recipients: recipients).read
|
505
482
|
rescue Exception => e
|
506
483
|
raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
|
data/mpw.gemspec
CHANGED
@@ -17,13 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ['lib']
|
19
19
|
|
20
|
-
spec.add_dependency "i18n"
|
21
|
-
spec.add_dependency "gpgme"
|
22
|
-
spec.add_dependency "highline"
|
23
|
-
spec.add_dependency "locale"
|
24
|
-
spec.add_dependency "colorize"
|
25
|
-
spec.add_dependency "net-ssh"
|
26
|
-
spec.add_dependency "net-
|
27
|
-
spec.add_dependency "clipboard"
|
28
|
-
spec.add_dependency "rotp"
|
20
|
+
spec.add_dependency "i18n", "~> 0.7", ">= 0.7.0"
|
21
|
+
spec.add_dependency "gpgme", "~> 2.0", ">= 2.0.12"
|
22
|
+
spec.add_dependency "highline", "~> 1.7", ">= 1.7.8"
|
23
|
+
spec.add_dependency "locale", "~> 2.1", ">= 2.1.2"
|
24
|
+
spec.add_dependency "colorize", "~> 0.8", ">= 0.8.1"
|
25
|
+
spec.add_dependency "net-ssh", "~> 3.2", ">= 3.2.0"
|
26
|
+
spec.add_dependency "net-sftp", "~> 2.1", ">= 2.1.2"
|
27
|
+
spec.add_dependency "clipboard", "~> 1.1", ">= 1.1.1"
|
28
|
+
spec.add_dependency "rotp", "~> 3.1", ">= 3.1.0"
|
29
29
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
---
|
2
|
+
host: # <%= I18n.t('form.add_item.host') %>
|
3
|
+
user: # <%= I18n.t('form.add_item.login') %>
|
4
|
+
group: # <%= I18n.t('form.add_item.group') %>
|
5
|
+
protocol: # <%= I18n.t('form.add_item.protocol') %><% if not password %>
|
6
|
+
password: # <%= I18n.t('form.add_item.password') %><% end %>
|
7
|
+
port: # <%= I18n.t('form.add_item.port') %>
|
8
|
+
comment: # <%= I18n.t('form.add_item.comment') %>
|
9
|
+
otp_key: # <%= I18n.t('form.add_item.otp_key') %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
# <%= I18n.t('form.update_item.host') %>
|
3
|
+
host: <%= item.host %>
|
4
|
+
# <%= I18n.t('form.update_item.login') %>
|
5
|
+
user: <%= item.user %><% if not password %>
|
6
|
+
# <%= I18n.t('form.update_item.password') %>
|
7
|
+
password: <% end %>
|
8
|
+
# <%= I18n.t('form.update_item.group') %>
|
9
|
+
group: <%= item.group %>
|
10
|
+
# <%= I18n.t('form.update_item.protocol') %>
|
11
|
+
protocol: <%= item.protocol %>
|
12
|
+
# <%= I18n.t('form.update_item.port') %>
|
13
|
+
port: <%= item.port %>
|
14
|
+
# <%= I18n.t('form.update_item.otp_key') %>
|
15
|
+
otp_key:
|
16
|
+
# <%= I18n.t('form.update_item.comment') %>
|
17
|
+
comment: <%= item.comment %>
|
data/test/files/fixtures.yml
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
add_new:
|
2
|
-
name: 'test_name'
|
3
2
|
group: 'test_group'
|
4
3
|
host: 'test_host'
|
5
4
|
protocol: 'test_protocol'
|
@@ -10,7 +9,6 @@ add_new:
|
|
10
9
|
|
11
10
|
add_existing:
|
12
11
|
id: 'TEST-ID-XXXXX'
|
13
|
-
name: 'test_name_existing'
|
14
12
|
group: 'test_group_existing'
|
15
13
|
host: 'test_host_existing'
|
16
14
|
protocol: 'test_protocol_existing'
|
@@ -21,7 +19,6 @@ add_existing:
|
|
21
19
|
created: 1386752948
|
22
20
|
|
23
21
|
update:
|
24
|
-
name: 'test_name_update'
|
25
22
|
group: 'test_group_update'
|
26
23
|
host: 'test_host_update'
|
27
24
|
protocol: 'test_protocol_update'
|
@@ -29,4 +26,3 @@ update:
|
|
29
26
|
password: 'test_password_update'
|
30
27
|
port: '43'
|
31
28
|
comment: 'test_comment_update'
|
32
|
-
|
data/test/init.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'gpgme'
|
4
|
+
|
5
|
+
param = ''
|
6
|
+
param << '<GnupgKeyParms format="internal">' + "\n"
|
7
|
+
param << "Key-Type: RSA\n"
|
8
|
+
param << "Key-Length: 2048\n"
|
9
|
+
param << "Subkey-Type: ELG-E\n"
|
10
|
+
param << "Subkey-Length: 2048\n"
|
11
|
+
param << "Name-Real: test\n"
|
12
|
+
param << "Name-Comment: test\n"
|
13
|
+
param << "Name-Email: test2@example.com\n"
|
14
|
+
param << "Expire-Date: 0\n"
|
15
|
+
param << "Passphrase: password\n"
|
16
|
+
param << "</GnupgKeyParms>\n"
|
17
|
+
|
18
|
+
ctx = GPGME::Ctx.new
|
19
|
+
ctx.genkey(param, nil, nil)
|