mpw 2.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 +7 -0
- data/.gitignore +1 -0
- data/CHANGELOG +13 -0
- data/Gemfile +10 -0
- data/LICENSE +339 -0
- data/README.md +14 -0
- data/VERSION +1 -0
- data/bin/mpw +165 -0
- data/bin/mpw-server +78 -0
- data/bin/mpw-ssh +89 -0
- data/i18n/cli/en.yml +149 -0
- data/i18n/cli/fr.yml +149 -0
- data/i18n/server/en.yml +26 -0
- data/i18n/server/fr.yml +26 -0
- data/lib/mpw/config.rb +231 -0
- data/lib/mpw/item.rb +109 -0
- data/lib/mpw/mpw.rb +332 -0
- data/lib/mpw/server.rb +343 -0
- data/lib/mpw/sync/ftp.rb +81 -0
- data/lib/mpw/sync/mpw.rb +124 -0
- data/lib/mpw/sync/ssh.rb +80 -0
- data/lib/mpw/sync.rb +138 -0
- data/lib/mpw/ui/cli.rb +324 -0
- data/lib/mpw/ui/clissh.rb +42 -0
- data/mpw.gemspec +19 -0
- data/test/files/fixtures.yml +32 -0
- data/test/files/test_import.csv +3 -0
- data/test/files/test_import.yml +23 -0
- data/test/test_item.rb +218 -0
- data/test/test_mpw.rb +191 -0
- data/test/tests.rb +4 -0
- metadata +82 -0
data/lib/mpw/item.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# author: nishiki
|
3
|
+
# mail: nishiki@yaegashi.fr
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'i18n'
|
7
|
+
|
8
|
+
module MPW
|
9
|
+
class Item
|
10
|
+
|
11
|
+
attr_accessor :error_msg
|
12
|
+
|
13
|
+
attr_accessor :id
|
14
|
+
attr_accessor :name
|
15
|
+
attr_accessor :group
|
16
|
+
attr_accessor :host
|
17
|
+
attr_accessor :protocol
|
18
|
+
attr_accessor :user
|
19
|
+
attr_accessor :password
|
20
|
+
attr_accessor :port
|
21
|
+
attr_accessor :comment
|
22
|
+
attr_accessor :last_edit
|
23
|
+
attr_accessor :last_sync
|
24
|
+
attr_accessor :created
|
25
|
+
|
26
|
+
# Constructor
|
27
|
+
# Create a new item
|
28
|
+
# @args: options -> a hash of parameter
|
29
|
+
# raise an error if the hash hasn't the key name
|
30
|
+
def initialize(options={})
|
31
|
+
if not options.has_key?(:name) or options[:name].to_s.empty?
|
32
|
+
@error_msg = I18n.t('error.update.name_empty')
|
33
|
+
raise @error_msg
|
34
|
+
end
|
35
|
+
|
36
|
+
if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty?
|
37
|
+
@id = generate_id
|
38
|
+
@created = Time.now.to_i
|
39
|
+
else
|
40
|
+
@id = options[:id]
|
41
|
+
@created = options[:created]
|
42
|
+
@last_edit = options[:last_edit]
|
43
|
+
options[:no_update_last_edit] = true
|
44
|
+
end
|
45
|
+
|
46
|
+
update(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Update the item
|
50
|
+
# @args: options -> a hash of parameter
|
51
|
+
# @rtrn: true if the item is update
|
52
|
+
def update(options={})
|
53
|
+
if options.has_key?(:name) and options[:name].to_s.empty?
|
54
|
+
@error_msg = I18n.t('error.update.name_empty')
|
55
|
+
return false
|
56
|
+
end
|
57
|
+
|
58
|
+
@name = options[:name] if options.has_key?(:name)
|
59
|
+
@group = options[:group] if options.has_key?(:group)
|
60
|
+
@host = options[:host] if options.has_key?(:host)
|
61
|
+
@protocol = options[:protocol] if options.has_key?(:protocol)
|
62
|
+
@user = options[:user] if options.has_key?(:user)
|
63
|
+
@password = options[:password] if options.has_key?(:password)
|
64
|
+
@port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty?
|
65
|
+
@comment = options[:comment] if options.has_key?(:comment)
|
66
|
+
@last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit)
|
67
|
+
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
# Update last_sync
|
72
|
+
def set_last_sync
|
73
|
+
@last_sync = Time.now.to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
# Delete all data
|
77
|
+
# @rtrn: true
|
78
|
+
def delete
|
79
|
+
@id = nil
|
80
|
+
@name = nil
|
81
|
+
@group = nil
|
82
|
+
@host = nil
|
83
|
+
@protocol = nil
|
84
|
+
@user = nil
|
85
|
+
@password = nil
|
86
|
+
@port = nil
|
87
|
+
@comment = nil
|
88
|
+
@created = nil
|
89
|
+
@last_edit = nil
|
90
|
+
@last_sync = nil
|
91
|
+
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
|
95
|
+
def empty?
|
96
|
+
return @name.to_s.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def nil?
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
# Generate an random id
|
104
|
+
private
|
105
|
+
def generate_id
|
106
|
+
return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/mpw/mpw.rb
ADDED
@@ -0,0 +1,332 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# author: nishiki
|
3
|
+
# mail: nishiki@yaegashi.fr
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'gpgme'
|
7
|
+
require 'csv'
|
8
|
+
require 'i18n'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'yaml'
|
11
|
+
require 'mpw/item'
|
12
|
+
|
13
|
+
module MPW
|
14
|
+
class MPW
|
15
|
+
|
16
|
+
attr_accessor :error_msg
|
17
|
+
|
18
|
+
# Constructor
|
19
|
+
def initialize(file_gpg, key, share_keys='')
|
20
|
+
@error_msg = nil
|
21
|
+
@file_gpg = file_gpg
|
22
|
+
@key = key
|
23
|
+
@share_keys = share_keys
|
24
|
+
@data = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Decrypt a gpg file
|
28
|
+
# @args: password -> the GPG key password
|
29
|
+
# @rtrn: true if data has been decrypted
|
30
|
+
def decrypt(password=nil)
|
31
|
+
@data = []
|
32
|
+
|
33
|
+
if File.exist?(@file_gpg) and not File.zero?(@file_gpg)
|
34
|
+
crypto = GPGME::Crypto.new(armor: true)
|
35
|
+
data_decrypt = crypto.decrypt(IO.read(@file_gpg), password: password).read.force_encoding('utf-8')
|
36
|
+
|
37
|
+
if not data_decrypt.to_s.empty?
|
38
|
+
YAML.load(data_decrypt).each_value do |d|
|
39
|
+
@data.push(Item.new(id: d['id'],
|
40
|
+
name: d['name'],
|
41
|
+
group: d['group'],
|
42
|
+
host: d['host'],
|
43
|
+
protocol: d['protocol'],
|
44
|
+
user: d['user'],
|
45
|
+
password: d['password'],
|
46
|
+
port: d['port'],
|
47
|
+
comment: d['comment'],
|
48
|
+
last_edit: d['last_edit'],
|
49
|
+
created: d['created'],
|
50
|
+
)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return true
|
57
|
+
rescue Exception => e
|
58
|
+
@error_msg = "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
|
59
|
+
return false
|
60
|
+
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
|
+
|
67
|
+
data_to_encrypt = {}
|
68
|
+
|
69
|
+
@data.each do |item|
|
70
|
+
next if item.empty?
|
71
|
+
|
72
|
+
data_to_encrypt.merge!(item.id => {'id' => item.id,
|
73
|
+
'name' => item.name,
|
74
|
+
'group' => item.group,
|
75
|
+
'host' => item.host,
|
76
|
+
'protocol' => item.protocol,
|
77
|
+
'user' => item.user,
|
78
|
+
'password' => item.password,
|
79
|
+
'port' => item.port,
|
80
|
+
'comment' => item.comment,
|
81
|
+
'last_edit' => item.last_edit,
|
82
|
+
'created' => item.created,
|
83
|
+
}
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
recipients = []
|
88
|
+
recipients.push(@key)
|
89
|
+
if not @share_keys.nil?
|
90
|
+
@share_keys.split.each { |k| recipients.push(k) }
|
91
|
+
end
|
92
|
+
|
93
|
+
crypto = GPGME::Crypto.new(armor: true)
|
94
|
+
file_gpg = File.open(@file_gpg, 'w+')
|
95
|
+
crypto.encrypt(data_to_encrypt.to_yaml, recipients: recipients, output: file_gpg)
|
96
|
+
file_gpg.close
|
97
|
+
|
98
|
+
FileUtils.rm("#{@file_gpg}.bk") if File.exist?("#{@file_gpg}.bk")
|
99
|
+
return true
|
100
|
+
rescue Exception => e
|
101
|
+
@error_msg = "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
|
102
|
+
FileUtils.mv("#{@file_gpg}.bk", @file_gpg) if File.exist?("#{@file_gpg}.bk")
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add a new item
|
107
|
+
# @args: item -> Object MPW::Item
|
108
|
+
# @rtrn: true if add item
|
109
|
+
def add(item)
|
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
|
120
|
+
end
|
121
|
+
|
122
|
+
# Search in some csv data
|
123
|
+
# @args: options -> a hash with paramaters
|
124
|
+
# @rtrn: a list with the resultat of the search
|
125
|
+
def list(options={})
|
126
|
+
result = []
|
127
|
+
|
128
|
+
search = options[:search].to_s.downcase
|
129
|
+
group = options[:group].to_s.downcase
|
130
|
+
protocol = options[:protocol].to_s.downcase
|
131
|
+
|
132
|
+
@data.each do |item|
|
133
|
+
next if item.empty?
|
134
|
+
|
135
|
+
next if not group.empty? and not group.eql?(item.group.downcase)
|
136
|
+
next if not protocol.empty? and not protocol.eql?(item.protocol.downcase)
|
137
|
+
|
138
|
+
name = item.name.to_s.downcase
|
139
|
+
host = item.host.to_s.downcase
|
140
|
+
comment = item.comment.to_s.downcase
|
141
|
+
|
142
|
+
if not name =~ /^.*#{search}.*$/ and not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
|
143
|
+
next
|
144
|
+
end
|
145
|
+
|
146
|
+
result.push(item)
|
147
|
+
end
|
148
|
+
|
149
|
+
return result
|
150
|
+
end
|
151
|
+
|
152
|
+
# Search in some csv data
|
153
|
+
# @args: id -> the id item
|
154
|
+
# @rtrn: a row with the result of the search
|
155
|
+
def search_by_id(id)
|
156
|
+
@data.each do |item|
|
157
|
+
return item if item.id == id
|
158
|
+
end
|
159
|
+
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Export to csv
|
164
|
+
# @args: file -> file where you export the data
|
165
|
+
# type -> udata type
|
166
|
+
# @rtrn: true if export work
|
167
|
+
def export(file, type=:yaml)
|
168
|
+
case type
|
169
|
+
when :csv
|
170
|
+
CSV.open(file, 'w', write_headers: true,
|
171
|
+
headers: ['name', 'group', 'protocol', 'host', 'user', 'password', 'port', 'comment']) do |csv|
|
172
|
+
@data.each do |item|
|
173
|
+
csv << [item.name, item.group, item.protocol, item.host, item.user, item.password, item.port, item.comment]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
when :yaml
|
178
|
+
data = {}
|
179
|
+
@data.each do |item|
|
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
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
File.open(file, 'w') {|f| f << data.to_yaml}
|
196
|
+
|
197
|
+
else
|
198
|
+
@error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
|
199
|
+
return false
|
200
|
+
end
|
201
|
+
|
202
|
+
return true
|
203
|
+
rescue Exception => e
|
204
|
+
@error_msg = "#{I18n.t('error.export.write', file: file)}\n#{e}"
|
205
|
+
return false
|
206
|
+
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
|
+
|
231
|
+
when :yaml
|
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
|
247
|
+
|
248
|
+
else
|
249
|
+
@error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
|
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
|
257
|
+
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
|
+
|
283
|
+
when :yaml
|
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
|
299
|
+
|
300
|
+
else
|
301
|
+
@error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
|
302
|
+
return false
|
303
|
+
end
|
304
|
+
|
305
|
+
return data
|
306
|
+
rescue Exception => e
|
307
|
+
@error_msg = "#{I18n.t('error.import.read', file: file)}\n#{e}"
|
308
|
+
return false
|
309
|
+
end
|
310
|
+
|
311
|
+
# Generate a random password
|
312
|
+
# @args: length -> the length password
|
313
|
+
# @rtrn: a random string
|
314
|
+
def self.password(length=8)
|
315
|
+
if length.to_i <= 0
|
316
|
+
length = 8
|
317
|
+
else
|
318
|
+
length = length.to_i
|
319
|
+
end
|
320
|
+
|
321
|
+
result = ''
|
322
|
+
while length > 62 do
|
323
|
+
result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(62).join
|
324
|
+
length -= 62
|
325
|
+
end
|
326
|
+
result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
|
327
|
+
|
328
|
+
return result
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|