mpw 2.0.3 → 3.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/Gemfile +3 -5
- data/README.md +115 -6
- data/VERSION +1 -1
- data/bin/mpw +90 -58
- data/i18n/{cli/en.yml → en.yml} +43 -49
- data/i18n/{cli/fr.yml → fr.yml} +45 -51
- data/lib/mpw/config.rb +117 -206
- data/lib/mpw/item.rb +98 -88
- data/lib/mpw/mpw.rb +416 -288
- data/lib/mpw/sync/ftp.rb +55 -68
- data/lib/mpw/sync/ssh.rb +52 -65
- data/lib/mpw/ui/cli.rb +201 -175
- data/mpw.gemspec +9 -8
- metadata +28 -29
- data/bin/mpw-server +0 -78
- data/bin/mpw-ssh +0 -89
- data/i18n/server/en.yml +0 -26
- data/i18n/server/fr.yml +0 -26
- data/lib/mpw/server.rb +0 -343
- data/lib/mpw/sync/mpw.rb +0 -124
- data/lib/mpw/sync.rb +0 -138
- data/lib/mpw/ui/clissh.rb +0 -42
data/lib/mpw/mpw.rb
CHANGED
@@ -1,332 +1,460 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# MPW is a software to crypt and manage your passwords
|
3
|
+
# Copyright (C) 2016 Adrien Waksberg <mpw@yae.im>
|
4
|
+
#
|
5
|
+
# This program is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU General Public License
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
8
|
+
# of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
4
18
|
|
5
|
-
require 'rubygems'
|
19
|
+
require 'rubygems/package'
|
6
20
|
require 'gpgme'
|
7
|
-
require 'csv'
|
8
21
|
require 'i18n'
|
9
|
-
require 'fileutils'
|
10
22
|
require 'yaml'
|
11
|
-
|
23
|
+
|
24
|
+
#TODO
|
25
|
+
require "#{APP_ROOT}/../lib/mpw/item.rb"
|
12
26
|
|
13
27
|
module MPW
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
28
|
+
class MPW
|
29
|
+
|
30
|
+
# Constructor
|
31
|
+
def initialize(key, wallet_file, gpg_pass=nil)
|
32
|
+
@key = key
|
33
|
+
@gpg_pass = gpg_pass
|
34
|
+
@wallet_file = wallet_file
|
35
|
+
end
|
36
|
+
|
37
|
+
# Read mpw file
|
38
|
+
def read_data
|
39
|
+
@config = {}
|
40
|
+
@data = []
|
41
|
+
@keys = {}
|
42
|
+
@passwords = {}
|
43
|
+
|
44
|
+
data = nil
|
45
|
+
|
46
|
+
return if not File.exists?(@wallet_file)
|
47
|
+
|
48
|
+
Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
|
49
|
+
tar.each do |f|
|
50
|
+
case f.full_name
|
51
|
+
when 'wallet/config.gpg'
|
52
|
+
@config = YAML.load(decrypt(f.read))
|
53
|
+
|
54
|
+
when 'wallet/meta.gpg'
|
55
|
+
data = decrypt(f.read)
|
56
|
+
|
57
|
+
when /^wallet\/keys\/(?<key>.+)\.pub$/
|
58
|
+
key = Regexp.last_match('key')
|
59
|
+
|
60
|
+
if GPGME::Key.find(:public, key).length == 0
|
61
|
+
GPGME::Key.import(f.read, armor: true)
|
62
|
+
end
|
63
|
+
|
64
|
+
@keys[key] = f.read
|
65
|
+
|
66
|
+
when /^wallet\/passwords\/(?<id>[a-zA-Z0-9]+)\.gpg$/
|
67
|
+
@passwords[Regexp.last_match('id')] = f.read
|
68
|
+
else
|
69
|
+
next
|
53
70
|
end
|
54
71
|
end
|
55
|
-
|
56
|
-
return true
|
57
|
-
rescue Exception => e
|
58
|
-
@error_msg = "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
|
59
|
-
return false
|
60
72
|
end
|
61
|
-
|
62
|
-
# Encrypt a file
|
63
|
-
# @rtrn: true if the file has been encrypted
|
64
|
-
def encrypt
|
65
|
-
FileUtils.cp(@file_gpg, "#{@file_gpg}.bk") if File.exist?(@file_gpg)
|
66
73
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
'last_edit' => item.last_edit,
|
82
|
-
'created' => item.created,
|
83
|
-
}
|
84
|
-
)
|
74
|
+
if not data.nil? and not data.empty?
|
75
|
+
YAML.load(data).each_value do |d|
|
76
|
+
@data.push(Item.new(id: d['id'],
|
77
|
+
name: d['name'],
|
78
|
+
group: d['group'],
|
79
|
+
host: d['host'],
|
80
|
+
protocol: d['protocol'],
|
81
|
+
user: d['user'],
|
82
|
+
port: d['port'],
|
83
|
+
comment: d['comment'],
|
84
|
+
last_edit: d['last_edit'],
|
85
|
+
created: d['created'],
|
86
|
+
)
|
87
|
+
)
|
85
88
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
89
|
+
end
|
90
|
+
|
91
|
+
add_key(@key) if @keys[@key].nil?
|
92
|
+
rescue Exception => e
|
93
|
+
raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Encrypt a file
|
97
|
+
def write_data
|
98
|
+
data = {}
|
99
|
+
tmp_file = "#{@wallet_file}.tmp"
|
100
|
+
|
101
|
+
@data.each do |item|
|
102
|
+
next if item.empty?
|
103
|
+
|
104
|
+
data.merge!(item.id => {'id' => item.id,
|
105
|
+
'name' => item.name,
|
106
|
+
'group' => item.group,
|
107
|
+
'host' => item.host,
|
108
|
+
'protocol' => item.protocol,
|
109
|
+
'user' => item.user,
|
110
|
+
'port' => item.port,
|
111
|
+
'comment' => item.comment,
|
112
|
+
'last_edit' => item.last_edit,
|
113
|
+
'created' => item.created,
|
114
|
+
}
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
Gem::Package::TarWriter.new(File.open(tmp_file, 'w+')) do |tar|
|
119
|
+
data_encrypt = encrypt(data.to_yaml)
|
120
|
+
tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
|
121
|
+
io.write(data_encrypt)
|
91
122
|
end
|
92
123
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
124
|
+
config = encrypt(@config.to_yaml)
|
125
|
+
tar.add_file_simple('wallet/config.gpg', 0400, config.length) do |io|
|
126
|
+
io.write(config)
|
127
|
+
end
|
128
|
+
|
129
|
+
@passwords.each do |id, password|
|
130
|
+
tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
|
131
|
+
io.write(password)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
@keys.each do |id, key|
|
136
|
+
tar.add_file_simple("wallet/keys/#{id}.pub", 0400, key.length) do |io|
|
137
|
+
io.write(key)
|
138
|
+
end
|
139
|
+
end
|
104
140
|
end
|
141
|
+
|
142
|
+
File.rename(tmp_file, @wallet_file)
|
143
|
+
rescue Exception => e
|
144
|
+
File.unlink(tmp_file)
|
145
|
+
|
146
|
+
raise "#{I18n.t('error.mpw_file.write_data')}\n#{e}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Get a password
|
150
|
+
# args: id -> the item id
|
151
|
+
def get_password(id)
|
152
|
+
password = decrypt(@passwords[id])
|
105
153
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if not item.instance_of?(Item)
|
111
|
-
@error_msg = I18n.t('error.bad_class')
|
112
|
-
return false
|
113
|
-
elsif item.empty?
|
114
|
-
@error_msg = I18n.t('error.add.empty')
|
115
|
-
return false
|
116
|
-
else
|
117
|
-
@data.push(item)
|
118
|
-
return true
|
119
|
-
end
|
154
|
+
if /^\$[a-zA-Z0-9]{4,9}::(?<password>.+)$/ =~ password
|
155
|
+
return Regexp.last_match('password')
|
156
|
+
else
|
157
|
+
return password
|
120
158
|
end
|
159
|
+
end
|
121
160
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
search = options[:search].to_s.downcase
|
129
|
-
group = options[:group].to_s.downcase
|
130
|
-
protocol = options[:protocol].to_s.downcase
|
161
|
+
# Set a password
|
162
|
+
# args: id -> the item id
|
163
|
+
# password -> the new password
|
164
|
+
def set_password(id, password)
|
165
|
+
salt = MPW::password(Random.rand(4..9))
|
166
|
+
password = "$#{salt}::#{password}"
|
131
167
|
|
132
|
-
|
133
|
-
|
168
|
+
@passwords[id] = encrypt(password)
|
169
|
+
end
|
134
170
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
171
|
+
# Add a public key
|
172
|
+
# args: key -> new public key
|
173
|
+
# file -> public gpg file to import
|
174
|
+
def add_key(key, file=nil)
|
175
|
+
if not file.nil? and File.exists?(file)
|
176
|
+
data = File.open(file).read
|
177
|
+
GPGME::Key.import(data, armor: true)
|
178
|
+
else
|
179
|
+
data = GPGME::Key.export(key, armor: true).read
|
180
|
+
end
|
141
181
|
|
142
|
-
|
143
|
-
|
144
|
-
|
182
|
+
if data.to_s.empty?
|
183
|
+
raise I18n.t('error.export_key')
|
184
|
+
end
|
145
185
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
186
|
+
@keys[key] = data
|
187
|
+
end
|
188
|
+
|
189
|
+
# Delete a public key
|
190
|
+
# args: key -> public key to delete
|
191
|
+
def delete_key(key)
|
192
|
+
@keys.delete(key)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Set config
|
196
|
+
# args: config -> a hash with config options
|
197
|
+
def set_config(config)
|
198
|
+
@config['sync'] = {} if @config['sync'].nil?
|
199
|
+
|
200
|
+
@config['sync']['type'] = config['sync']['type']
|
201
|
+
@config['sync']['host'] = config['sync']['host']
|
202
|
+
@config['sync']['port'] = config['sync']['port']
|
203
|
+
@config['sync']['user'] = config['sync']['user']
|
204
|
+
@config['sync']['password'] = config['sync']['password']
|
205
|
+
@config['sync']['path'] = config['sync']['path']
|
206
|
+
@config['sync']['last_sync'] = @config['sync']['last_sync'].nil? ? 0 : @config['sync']['last_sync']
|
207
|
+
end
|
208
|
+
|
209
|
+
# Add a new item
|
210
|
+
# @args: item -> Object MPW::Item
|
211
|
+
def add(item)
|
212
|
+
if not item.instance_of?(Item)
|
213
|
+
raise I18n.t('error.bad_class')
|
214
|
+
elsif item.empty?
|
215
|
+
raise I18n.t('error.add.empty')
|
216
|
+
else
|
217
|
+
@data.push(item)
|
150
218
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
219
|
+
end
|
220
|
+
|
221
|
+
# Search in some csv data
|
222
|
+
# @args: options -> a hash with paramaters
|
223
|
+
# @rtrn: a list with the resultat of the search
|
224
|
+
def list(options={})
|
225
|
+
result = []
|
226
|
+
|
227
|
+
search = options[:search].to_s.downcase
|
228
|
+
group = options[:group].to_s.downcase
|
229
|
+
|
230
|
+
@data.each do |item|
|
231
|
+
next if item.empty?
|
232
|
+
next if not group.empty? and not group.eql?(item.group.downcase)
|
233
|
+
|
234
|
+
name = item.name.to_s.downcase
|
235
|
+
host = item.host.to_s.downcase
|
236
|
+
comment = item.comment.to_s.downcase
|
237
|
+
|
238
|
+
if not name =~ /^.*#{search}.*$/ and not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
|
239
|
+
next
|
158
240
|
end
|
159
|
-
|
160
|
-
|
241
|
+
|
242
|
+
result.push(item)
|
161
243
|
end
|
244
|
+
|
245
|
+
return result
|
246
|
+
end
|
247
|
+
|
248
|
+
# Search in some csv data
|
249
|
+
# @args: id -> the id item
|
250
|
+
# @rtrn: a row with the result of the search
|
251
|
+
def search_by_id(id)
|
252
|
+
@data.each do |item|
|
253
|
+
return item if item.id == id
|
254
|
+
end
|
255
|
+
|
256
|
+
return nil
|
257
|
+
end
|
258
|
+
|
259
|
+
# Export to yaml
|
260
|
+
# @args: file -> file where you export the data
|
261
|
+
def export(file)
|
262
|
+
data = {}
|
263
|
+
@data.each do |item|
|
264
|
+
data.merge!(item.id => {'id' => item.id,
|
265
|
+
'name' => item.name,
|
266
|
+
'group' => item.group,
|
267
|
+
'host' => item.host,
|
268
|
+
'protocol' => item.protocol,
|
269
|
+
'user' => item.user,
|
270
|
+
'password' => get_password(item.id),
|
271
|
+
'port' => item.port,
|
272
|
+
'comment' => item.comment,
|
273
|
+
'last_edit' => item.last_edit,
|
274
|
+
'created' => item.created,
|
275
|
+
}
|
276
|
+
)
|
277
|
+
end
|
278
|
+
|
279
|
+
File.open(file, 'w') {|f| f << data.to_yaml}
|
280
|
+
rescue Exception => e
|
281
|
+
raise "#{I18n.t('error.export', file: file)}\n#{e}"
|
282
|
+
end
|
283
|
+
|
284
|
+
# Import to yaml
|
285
|
+
# @args: file -> path to file import
|
286
|
+
def import(file)
|
287
|
+
YAML::load_file(file).each_value do |row|
|
288
|
+
item = Item.new(name: row['name'],
|
289
|
+
group: row['group'],
|
290
|
+
host: row['host'],
|
291
|
+
protocol: row['protocol'],
|
292
|
+
user: row['user'],
|
293
|
+
port: row['port'],
|
294
|
+
comment: row['comment'],
|
295
|
+
)
|
296
|
+
|
297
|
+
raise 'Item is empty' if item.empty?
|
298
|
+
|
299
|
+
@data.push(item)
|
300
|
+
set_password(item.id, row['password'])
|
301
|
+
end
|
302
|
+
rescue Exception => e
|
303
|
+
raise "#{I18n.t('error.import', file: file)}\n#{e}"
|
304
|
+
end
|
305
|
+
|
306
|
+
# Get last sync
|
307
|
+
def get_last_sync
|
308
|
+
return @config['sync']['last_sync'].to_i
|
309
|
+
rescue
|
310
|
+
return 0
|
311
|
+
end
|
312
|
+
|
313
|
+
# Sync data with remote file
|
314
|
+
def sync
|
315
|
+
return if @config.empty? or @config['sync'].nil?
|
162
316
|
|
163
|
-
#
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
317
|
+
tmp_file = "#{@wallet_file}.sync"
|
318
|
+
last_sync = @config['last_sync'].to_i
|
319
|
+
|
320
|
+
case @config['sync']['type']
|
321
|
+
when 'sftp', 'scp', 'ssh'
|
322
|
+
require "#{APP_ROOT}/../lib/mpw/sync/ssh.rb"
|
323
|
+
sync = SyncSSH.new(@config['sync'])
|
324
|
+
when 'ftp'
|
325
|
+
require 'mpw/sync/ftp'
|
326
|
+
sync = SyncFTP.new(@config['sync'])
|
327
|
+
else
|
328
|
+
raise I18n.t('error.sync.unknown_type')
|
329
|
+
end
|
330
|
+
|
331
|
+
sync.connect
|
332
|
+
sync.get(tmp_file)
|
333
|
+
|
334
|
+
remote = MPW.new(@key, tmp_file, @gpg_pass)
|
335
|
+
remote.read_data
|
336
|
+
|
337
|
+
File.unlink(tmp_file) if File.exist?(tmp_file)
|
338
|
+
|
339
|
+
return if remote.get_last_sync == get_last_sync
|
340
|
+
|
341
|
+
if not remote.to_s.empty?
|
342
|
+
@data.each do |item|
|
343
|
+
update = false
|
344
|
+
|
345
|
+
remote.list.each do |r|
|
346
|
+
next if item.id != r.id
|
347
|
+
|
348
|
+
# Update item
|
349
|
+
if item.last_edit < r.last_edit
|
350
|
+
item.update(name: r.name,
|
351
|
+
group: r.group,
|
352
|
+
host: r.host,
|
353
|
+
protocol: r.protocol,
|
354
|
+
user: r.user,
|
355
|
+
port: r.port,
|
356
|
+
comment: r.comment
|
357
|
+
)
|
358
|
+
set_password(item.id, remote.get_password(item.id))
|
174
359
|
end
|
360
|
+
|
361
|
+
r.delete
|
362
|
+
update = true
|
363
|
+
|
364
|
+
break
|
175
365
|
end
|
176
366
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
data.merge!(item.id => {'id' => item.id,
|
181
|
-
'name' => item.name,
|
182
|
-
'group' => item.group,
|
183
|
-
'host' => item.host,
|
184
|
-
'protocol' => item.protocol,
|
185
|
-
'user' => item.user,
|
186
|
-
'password' => item.password,
|
187
|
-
'port' => item.port,
|
188
|
-
'comment' => item.comment,
|
189
|
-
'last_edit' => item.last_edit,
|
190
|
-
'created' => item.created,
|
191
|
-
}
|
192
|
-
)
|
367
|
+
# Remove an old item
|
368
|
+
if not update and item.last_sync.to_i < last_sync and item.last_edit < last_sync
|
369
|
+
item.delete
|
193
370
|
end
|
371
|
+
end
|
372
|
+
end
|
194
373
|
|
195
|
-
|
374
|
+
# Add item
|
375
|
+
remote.list.each do |r|
|
376
|
+
next if r.last_edit <= last_sync
|
196
377
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
378
|
+
item = Item.new(id: r.id,
|
379
|
+
name: r.name,
|
380
|
+
group: r.group,
|
381
|
+
host: r.host,
|
382
|
+
protocol: r.protocol,
|
383
|
+
user: r.user,
|
384
|
+
port: r.port,
|
385
|
+
comment: r.comment,
|
386
|
+
created: r.created,
|
387
|
+
last_edit: r.last_edit
|
388
|
+
)
|
201
389
|
|
202
|
-
|
203
|
-
|
204
|
-
@error_msg = "#{I18n.t('error.export.write', file: file)}\n#{e}"
|
205
|
-
return false
|
390
|
+
set_password(item.id, remote.get_password(item.id))
|
391
|
+
add(item)
|
206
392
|
end
|
207
|
-
|
208
|
-
# Import to csv
|
209
|
-
# @args: file -> path to file import
|
210
|
-
# type -> udata type
|
211
|
-
# @rtrn: true if the import work
|
212
|
-
def import(file, type=:yaml)
|
213
|
-
case type
|
214
|
-
when :csv
|
215
|
-
CSV.foreach(file, {headers: true}) do |row|
|
216
|
-
item = Item.new(name: row['name'],
|
217
|
-
group: row['group'],
|
218
|
-
host: row['host'],
|
219
|
-
protocol: row['protocol'],
|
220
|
-
user: row['user'],
|
221
|
-
password: row['password'],
|
222
|
-
port: row['port'],
|
223
|
-
comment: row['comment'],
|
224
|
-
)
|
225
|
-
|
226
|
-
return false if item.empty?
|
227
|
-
|
228
|
-
@data.push(item)
|
229
|
-
end
|
230
393
|
|
231
|
-
|
232
|
-
YAML::load_file(file).each_value do |row|
|
233
|
-
item = Item.new(name: row['name'],
|
234
|
-
group: row['group'],
|
235
|
-
host: row['host'],
|
236
|
-
protocol: row['protocol'],
|
237
|
-
user: row['user'],
|
238
|
-
password: row['password'],
|
239
|
-
port: row['port'],
|
240
|
-
comment: row['comment'],
|
241
|
-
)
|
242
|
-
|
243
|
-
return false if item.empty?
|
244
|
-
|
245
|
-
@data.push(item)
|
246
|
-
end
|
394
|
+
remote = nil
|
247
395
|
|
248
|
-
|
249
|
-
|
250
|
-
return false
|
251
|
-
end
|
252
|
-
|
253
|
-
return true
|
254
|
-
rescue Exception => e
|
255
|
-
@error_msg = "#{I18n.t('error.import.read', file: file)}\n#{e}"
|
256
|
-
return false
|
396
|
+
@data.each do |item|
|
397
|
+
item.set_last_sync
|
257
398
|
end
|
258
|
-
|
259
|
-
# Return a preview import
|
260
|
-
# @args: file -> path to file import
|
261
|
-
# @rtrn: a hash with the items to import, if there is an error return false
|
262
|
-
def import_preview(file, type=:yaml)
|
263
|
-
data = []
|
264
|
-
|
265
|
-
case type
|
266
|
-
when :csv
|
267
|
-
CSV.foreach(file, {headers: true}) do |row|
|
268
|
-
item = Item.new(name: row['name'],
|
269
|
-
group: row['group'],
|
270
|
-
host: row['host'],
|
271
|
-
protocol: row['protocol'],
|
272
|
-
user: row['user'],
|
273
|
-
password: row['password'],
|
274
|
-
port: row['port'],
|
275
|
-
comment: row['comment'],
|
276
|
-
)
|
277
|
-
|
278
|
-
return false if item.empty?
|
279
|
-
|
280
|
-
data.push(item)
|
281
|
-
end
|
282
399
|
|
283
|
-
|
284
|
-
YAML::load_file(file).each_value do |row|
|
285
|
-
item = Item.new(name: row['name'],
|
286
|
-
group: row['group'],
|
287
|
-
host: row['host'],
|
288
|
-
protocol: row['protocol'],
|
289
|
-
user: row['user'],
|
290
|
-
password: row['password'],
|
291
|
-
port: row['port'],
|
292
|
-
comment: row['comment'],
|
293
|
-
)
|
294
|
-
|
295
|
-
return false if item.empty?
|
296
|
-
|
297
|
-
data.push(item)
|
298
|
-
end
|
400
|
+
@config['sync']['last_sync'] = Time.now.to_i
|
299
401
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
402
|
+
write_data
|
403
|
+
sync.update(@wallet_file)
|
404
|
+
rescue Exception => e
|
405
|
+
File.unlink(tmp_file) if File.exist?(tmp_file)
|
406
|
+
|
407
|
+
raise "#{I18n.t('error.sync.general')}\n#{e}"
|
408
|
+
end
|
409
|
+
|
410
|
+
# Generate a random password
|
411
|
+
# @args: length -> the length password
|
412
|
+
# @rtrn: a random string
|
413
|
+
def self.password(length=8)
|
414
|
+
if length.to_i <= 0
|
415
|
+
length = 8
|
416
|
+
else
|
417
|
+
length = length.to_i
|
309
418
|
end
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
419
|
+
|
420
|
+
result = ''
|
421
|
+
while length > 62 do
|
422
|
+
result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(62).join
|
423
|
+
length -= 62
|
424
|
+
end
|
425
|
+
result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
|
426
|
+
|
427
|
+
return result
|
428
|
+
end
|
429
|
+
|
430
|
+
# Decrypt a gpg file
|
431
|
+
# @args: data -> string to decrypt
|
432
|
+
private
|
433
|
+
def decrypt(data)
|
434
|
+
crypto = GPGME::Crypto.new(armor: true)
|
435
|
+
|
436
|
+
return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8')
|
437
|
+
rescue Exception => e
|
438
|
+
raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
|
439
|
+
end
|
440
|
+
|
441
|
+
# Encrypt a file
|
442
|
+
# args: data -> string to encrypt
|
443
|
+
private
|
444
|
+
def encrypt(data)
|
445
|
+
recipients = []
|
446
|
+
crypto = GPGME::Crypto.new(armor: true, always_trust: true)
|
447
|
+
|
448
|
+
@keys.each_key do |key|
|
449
|
+
recipients.push(key)
|
329
450
|
end
|
330
451
|
|
452
|
+
recipients.push(@key) if not recipients.index(@key).nil?
|
453
|
+
|
454
|
+
return crypto.encrypt(data, recipients: recipients).read
|
455
|
+
rescue Exception => e
|
456
|
+
raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
|
331
457
|
end
|
458
|
+
|
459
|
+
end
|
332
460
|
end
|