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.
data/lib/mpw/sync.rb ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/ruby
2
+ # author: nishiki
3
+ # mail: nishiki@yaegashi.fr
4
+ # info: a simple script who manage your passwords
5
+
6
+ require 'rubygems'
7
+ require 'i18n'
8
+ require 'yaml'
9
+ require 'tempfile'
10
+ require 'mpw/mpw'
11
+ require 'mpw/item'
12
+
13
+ module MPW
14
+ class Sync
15
+
16
+ attr_accessor :error_msg
17
+
18
+ # Constructor
19
+ # raise an exception if there is a bad parameter
20
+ def initialize(config, local, password=nil)
21
+ @error_msg = nil
22
+ @config = config
23
+ @local = local
24
+ @password = password
25
+
26
+ raise I18n.t('error.class') if not @local.instance_of?(MPW)
27
+ end
28
+
29
+ # Get the data on remote host
30
+ # @rtrn: true if get the date, else false
31
+ def get_remote
32
+ case @config.sync_type
33
+ when 'mpw'
34
+ require 'mpw/sync/mpw'
35
+ @sync = SyncMPW.new(@config.sync_host, @config.sync_user, @config.sync_pwd, @config.sync_path, @config.sync_port)
36
+ when 'sftp', 'scp', 'ssh'
37
+ require 'mpw/sync/ssh'
38
+ @sync = SyncSSH.new(@config.sync_host, @config.sync_user, @config.sync_pwd, @config.sync_path, @config.sync_port)
39
+ when 'ftp'
40
+ require 'mpw/sync/ftp'
41
+ @sync = SyncFTP.new(@config.sync_host, @config.sync_user, @config.sync_pwd, @config.sync_path, @config.sync_port)
42
+ else
43
+ @error_msg = I18n.t('error.unknown_type')
44
+ return false
45
+ end
46
+
47
+ if not @sync.connect
48
+ @error_msg = @sync.error_msg
49
+ return false
50
+ end
51
+
52
+
53
+ file_tmp = Tempfile.new('mpw-')
54
+ raise @sync.error_msg if not @sync.get(file_tmp.path)
55
+
56
+ @remote = MPW.new(file_tmp.path, @config.key)
57
+ raise @remote.error_msg if not @remote.decrypt(@password)
58
+
59
+ file_tmp.close(true)
60
+ return true
61
+ rescue Exception => e
62
+ @error_msg = "#{I18n.t('error.sync.download')} #{e}"
63
+ file_tmp.close(true)
64
+ return false
65
+ end
66
+
67
+ # Sync remote data and local data
68
+ # raise an exception if there is a problem
69
+ def sync
70
+
71
+ if not @remote.to_s.empty?
72
+ @local.list.each do |item|
73
+ update = false
74
+ @remote.list.each do |r|
75
+
76
+ # Update item
77
+ if item.id == r.id
78
+ if item.last_edit < r.last_edit
79
+ raise item.error_msg if not item.update(name: r.name,
80
+ group: r.group,
81
+ host: r.host,
82
+ protocol: r.protocol,
83
+ user: r.user,
84
+ password: r.password,
85
+ port: r.port,
86
+ comment: r.comment
87
+ )
88
+ end
89
+
90
+ r.delete
91
+ update = true
92
+
93
+ break
94
+ end
95
+ end
96
+
97
+ # Remove an old item
98
+ if not update and item.last_sync.to_i < @config.last_sync and item.last_edit < @config.last_sync
99
+ item.delete
100
+ end
101
+ end
102
+ end
103
+
104
+ # Add item
105
+ @remote.list.each do |r|
106
+ if r.last_edit > @config.last_sync
107
+ item = Item.new(id: r.id,
108
+ name: r.name,
109
+ group: r.group,
110
+ host: r.host,
111
+ protocol: r.protocol,
112
+ user: r.user,
113
+ password: r.password,
114
+ port: r.port,
115
+ comment: r.comment,
116
+ created: r.created,
117
+ last_edit: r.last_edit
118
+ )
119
+ raise @local.error_msg if not @local.add(item)
120
+ end
121
+ end
122
+
123
+ @local.list.each do |item|
124
+ item.set_last_sync
125
+ end
126
+
127
+ raise @mpw.error_msg if not @local.encrypt
128
+ raise @sync.error_msg if not @sync.update(@config.file_gpg)
129
+
130
+ @config.set_last_sync
131
+
132
+ return true
133
+ rescue Exception => e
134
+ @error_msg = "#{I18n.t('error.sync.unknown')} #{e}"
135
+ return false
136
+ end
137
+ end
138
+ end
data/lib/mpw/ui/cli.rb ADDED
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/ruby
2
+ # author: nishiki
3
+ # mail: nishiki@yaegashi.fr
4
+ # info: a simple script who m your passwords
5
+
6
+ require 'rubygems'
7
+ require 'highline/import'
8
+ require 'pathname'
9
+ require 'readline'
10
+ require 'i18n'
11
+ require 'colorize'
12
+ require 'mpw/sync'
13
+ require 'mpw/mpw'
14
+ require 'mpw/item'
15
+
16
+ class Cli
17
+
18
+ # Constructor
19
+ # @args: lang -> the operating system language
20
+ # config_file -> a specify config file
21
+ def initialize(config)
22
+ @config = config
23
+ end
24
+
25
+ # Sync the data with the server
26
+ # @rtnr: true if the synchro is finish
27
+ def sync
28
+ @sync = MPW::Sync.new(@config, @mpw, @password)
29
+
30
+ raise(@sync.error_msg) if not @sync.get_remote
31
+ raise(@sync.error_msg) if not @sync.sync
32
+
33
+ return true
34
+ rescue Exception => e
35
+ puts "#{I18n.t('display.error')} #7: #{e}".red
36
+ return false
37
+ end
38
+
39
+ # Create a new config file
40
+ # @args: lang -> the software language
41
+ def setup(lang)
42
+ puts I18n.t('form.setup.title')
43
+ puts '--------------------'
44
+ language = ask(I18n.t('form.setup.lang', lang: lang)).to_s
45
+ key = ask(I18n.t('form.setup.gpg_key')).to_s
46
+ share_keys = ask(I18n.t('form.setup.share_gpg_keys')).to_s
47
+ file_gpg = ask(I18n.t('form.setup.gpg_file', home: @conf.dir_config)).to_s
48
+ sync_type = ask(I18n.t('form.setup.sync_type')).to_s
49
+
50
+ if ['ssh', 'ftp', 'mpw'].include?(sync_type)
51
+ sync_host = ask(I18n.t('form.setup.sync_host')).to_s
52
+ sync_port = ask(I18n.t('form.setup.sync_port')).to_s
53
+ sync_user = ask(I18n.t('form.setup.sync_user')).to_s
54
+ sync_pwd = ask(I18n.t('form.setup.sync_pwd')).to_s
55
+ sync_path = ask(I18n.t('form.setup.sync_path')).to_s
56
+ end
57
+
58
+ if language.nil? or language.empty?
59
+ language = lang
60
+ end
61
+ I18n.locale = language.to_sym
62
+
63
+ sync_type = sync_type.nil? or sync_type.empty? ? nil : sync_type
64
+ sync_host = sync_host.nil? or sync_host.empty? ? nil : sync_host
65
+ sync_port = sync_port.nil? or sync_port.empty? ? nil : sync_port.to_i
66
+ sync_user = sync_user.nil? or sync_user.empty? ? nil : sync_user
67
+ sync_pwd = sync_pwd.nil? or sync_pwd.empty? ? nil : sync_pwd
68
+ sync_path = sync_path.nil? or sync_path.empty? ? nil : sync_path
69
+
70
+ if @config.setup(key, share_keys, language, file_gpg, sync_type, sync_host, sync_port, sync_user, sync_pwd, sync_path)
71
+ puts "#{I18n.t('form.setup.valid')}".green
72
+ else
73
+ puts "#{I18n.t('display.error')} #8: #{@config.error_msg}".red
74
+ exit 2
75
+ end
76
+
77
+ if not @config.checkconfig
78
+ puts "#{I18n.t('display.error')} #9: #{@config.error_msg}".red
79
+ exit 2
80
+ end
81
+ end
82
+
83
+ # Setup a new GPG key
84
+ def setup_gpg_key
85
+ puts I18n.t('form.setup_gpg_key.title')
86
+ puts '--------------------'
87
+ ask = ask(I18n.t('form.setup_gpg_key.ask')).to_s
88
+
89
+ if not ['Y', 'y', 'O', 'o'].include?(ask)
90
+ puts I18n.t('form.setup_gpg_key.no_create')
91
+ exit 2
92
+ end
93
+
94
+ name = ask(I18n.t('form.setup_gpg_key.name')).to_s
95
+ password = ask(I18n.t('form.setup_gpg_key.password')) {|q| q.echo = false}
96
+ confirm = ask(I18n.t('form.setup_gpg_key.confirm_password')) {|q| q.echo = false}
97
+
98
+ if password != confirm
99
+ puts I18n.t('form.setup_gpg_key.error_password')
100
+ exit 2
101
+ end
102
+
103
+ length = ask(I18n.t('form.setup_gpg_key.length')).to_s
104
+ expire = ask(I18n.t('form.setup_gpg_key.expire')).to_s
105
+ password = password.to_s
106
+
107
+ length = length.nil? or length.empty? ? 2048 : length.to_i
108
+ expire = expire.nil? or expire.empty? ? 0 : expire.to_i
109
+
110
+ puts I18n.t('form.setup_gpg_key.wait')
111
+
112
+ if @config.setup_gpg_key(password, name, length, expire)
113
+ puts "#{I18n.t('form.setup_gpg_key.valid')}".green
114
+ else
115
+ puts "#{I18n.t('display.error')} #10: #{@config.error_msg}".red
116
+ exit 2
117
+ end
118
+ end
119
+
120
+ # Request the GPG password and decrypt the file
121
+ def decrypt
122
+ if not defined?(@mpw)
123
+ @mpw = MPW::MPW.new(@config.file_gpg, @config.key, @config.share_keys)
124
+ end
125
+
126
+ @password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
127
+ if not @mpw.decrypt(@password)
128
+ puts "#{I18n.t('display.error')} #11: #{@mpw.error_msg}".red
129
+ exit 2
130
+ end
131
+ end
132
+
133
+ # Display the query's result
134
+ # @args: search -> the string to search
135
+ # protocol -> search from a particular protocol
136
+ def display(options={})
137
+ result = @mpw.list(options)
138
+
139
+ case result.length
140
+ when 0
141
+ puts I18n.t('display.nothing')
142
+ when 1
143
+ display_item(result.first)
144
+ else
145
+ i = 1
146
+ result.each do |item|
147
+ print "#{i}: ".cyan
148
+ print item.name
149
+ print " -> #{item.comment}".magenta if not item.comment.to_s.empty?
150
+ print "\n"
151
+
152
+ i += 1
153
+ end
154
+ choice = ask(I18n.t('form.select')).to_i
155
+
156
+ if choice >= 1 and choice < i
157
+ display_item(result[choice-1])
158
+ else
159
+ puts "#{I18n.t('display.warning')}: #{I18n.t('warning.select')}".yellow
160
+ end
161
+ end
162
+ end
163
+
164
+ # Display an item in the default format
165
+ # @args: item -> an array with the item information
166
+ def display_item(item)
167
+ puts '--------------------'.cyan
168
+ print 'Id: '.cyan
169
+ puts item.id
170
+ print "#{I18n.t('display.name')}: ".cyan
171
+ puts item.name
172
+ print "#{I18n.t('display.group')}: ".cyan
173
+ puts item.group
174
+ print "#{I18n.t('display.server')}: ".cyan
175
+ puts item.host
176
+ print "#{I18n.t('display.protocol')}: ".cyan
177
+ puts item.protocol
178
+ print "#{I18n.t('display.login')}: ".cyan
179
+ puts item.user
180
+ print "#{I18n.t('display.password')}: ".cyan
181
+ puts item.password
182
+ print "#{I18n.t('display.port')}: ".cyan
183
+ puts item.port
184
+ print "#{I18n.t('display.comment')}: ".cyan
185
+ puts item.comment
186
+ end
187
+
188
+ # Form to add a new item
189
+ def add
190
+ options = {}
191
+
192
+ puts I18n.t('form.add.title')
193
+ puts '--------------------'
194
+ options[:name] = ask(I18n.t('form.add.name')).to_s
195
+ options[:group] = ask(I18n.t('form.add.group')).to_s
196
+ options[:host] = ask(I18n.t('form.add.server')).to_s
197
+ options[:protocol] = ask(I18n.t('form.add.protocol')).to_s
198
+ options[:user] = ask(I18n.t('form.add.login')).to_s
199
+ options[:password] = ask(I18n.t('form.add.password')).to_s
200
+ options[:port] = ask(I18n.t('form.add.port')).to_s
201
+ options[:comment] = ask(I18n.t('form.add.comment')).to_s
202
+
203
+ item = MPW::Item.new(options)
204
+ if @mpw.add(item)
205
+ if @mpw.encrypt
206
+ sync
207
+ puts "#{I18n.t('form.add.valid')}".green
208
+ else
209
+ puts "#{I18n.t('display.error')} #12: #{@mpw.error_msg}".red
210
+ end
211
+ else
212
+ puts "#{I18n.t('display.error')} #13: #{item.error_msg}".red
213
+ end
214
+ end
215
+
216
+ # Update an item
217
+ # @args: id -> the item's id
218
+ def update(id)
219
+ item = @mpw.search_by_id(id)
220
+
221
+ if not item.nil?
222
+ options = {}
223
+
224
+ puts I18n.t('form.update.title')
225
+ puts '--------------------'
226
+ options[:name] = ask(I18n.t('form.update.name' , name: item.name)).to_s
227
+ options[:group] = ask(I18n.t('form.update.group' , group: item.group)).to_s
228
+ options[:host] = ask(I18n.t('form.update.server' , server: item.host)).to_s
229
+ options[:protocol] = ask(I18n.t('form.update.protocol', protocol: item.protocol)).to_s
230
+ options[:user] = ask(I18n.t('form.update.login' , login: item.user)).to_s
231
+ options[:password] = ask(I18n.t('form.update.password')).to_s
232
+ options[:port] = ask(I18n.t('form.update.port' , port: item.port)).to_s
233
+ options[:comment] = ask(I18n.t('form.update.comment' , comment: item.comment)).to_s
234
+
235
+ options.delete_if { |k,v| v.empty? }
236
+
237
+ if item.update(options)
238
+ if @mpw.encrypt
239
+ sync
240
+ puts "#{I18n.t('form.update.valid')}".green
241
+ else
242
+ puts "#{I18n.t('display.error')} #14: #{@mpw.error_msg}".red
243
+ end
244
+ else
245
+ puts "#{I18n.t('display.error')} #15: #{item.error_msg}".red
246
+ end
247
+ else
248
+ puts I18n.t('display.nothing')
249
+ end
250
+ end
251
+
252
+ # Remove an item
253
+ # @args: id -> the item's id
254
+ # force -> no resquest a validation
255
+ def delete(id, force=false)
256
+ if not force
257
+ item = @mpw.search_by_id(id)
258
+
259
+ if not item.nil?
260
+ display_item(item)
261
+
262
+ confirm = ask("#{I18n.t('form.delete.ask', id: id)} (y/N) ").to_s
263
+ if confirm =~ /^(y|yes|YES|Yes|Y)$/
264
+ force = true
265
+ end
266
+ else
267
+ puts I18n.t('display.nothing')
268
+ end
269
+ end
270
+
271
+ if force
272
+ item.delete
273
+
274
+ if @mpw.encrypt
275
+ sync
276
+ puts "#{I18n.t('form.delete.valid', id: id)}".green
277
+ else
278
+ puts "#{I18n.t('display.error')} #16: #{@mpw.error_msg}".red
279
+ end
280
+ end
281
+ end
282
+
283
+ # Export the items in a CSV file
284
+ # @args: file -> the destination file
285
+ def export(file, type=:yaml)
286
+ if @mpw.export(file, type)
287
+ puts "#{I18n.t('export.valid', file)}".green
288
+ else
289
+ puts "#{I18n.t('display.error')} #17: #{@mpw.error_msg}".red
290
+ end
291
+ end
292
+
293
+ # Import items from a CSV file
294
+ # @args: file -> the import file
295
+ # force -> no resquest a validation
296
+ def import(file, type=:yaml, force=false)
297
+
298
+ if not force
299
+ result = @mpw.import_preview(file, type)
300
+ if result.is_a?(Array) and not result.empty?
301
+ result.each do |r|
302
+ display_item(r)
303
+ end
304
+
305
+ confirm = ask("#{I18n.t('form.import.ask', file: file)} (y/N) ").to_s
306
+ if confirm =~ /^(y|yes|YES|Yes|Y)$/
307
+ force = true
308
+ end
309
+ else
310
+ puts I18n.t('form.import.not_valid')
311
+ end
312
+ end
313
+
314
+ if force
315
+ if @mpw.import(file, type) and @mpw.encrypt
316
+ sync
317
+ puts "#{I18n.t('form.import.valid')}".green
318
+ else
319
+ puts "#{I18n.t('display.error')} #18: #{@mpw.error_msg}".red
320
+ end
321
+ end
322
+ end
323
+
324
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/ruby
2
+ # author: nishiki
3
+ # mail: nishiki@yaegashi.fr
4
+ # info: a simple script who manage your passwords
5
+
6
+ require 'mpw/ui/cli'
7
+
8
+ class CliSSH < Cli
9
+
10
+ attr_accessor :server, :port, :login
11
+
12
+ # Connect to SSH
13
+ # args: search -> string to search
14
+ def ssh(search)
15
+ result = @mpw.search(search, nil, 'ssh')
16
+
17
+ if result.length > 0
18
+ result.each do |r|
19
+ server = @server.nil? ? r[:host] : @server
20
+ port = @port.nil? ? r[:port] : @port
21
+ login = @login.nil? ? r[:login] : @login
22
+
23
+ passwd = r[:password]
24
+
25
+ if port.nil? and port.empty?
26
+ port = 22
27
+ end
28
+
29
+ puts "#{I18n.t('ssh.display.connect')} ssh #{login}@#{server} -p #{port}"
30
+ if passwd.empty?
31
+ system("ssh #{login}@#{server} -p #{port}")
32
+ else
33
+ system("sshpass -p '#{passwd}' ssh #{login}@#{server} -p #{port}")
34
+ end
35
+ end
36
+
37
+ else
38
+ puts I18n.t('ssh.display.nothing')
39
+ end
40
+ end
41
+ end
42
+
data/mpw.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mpw'
7
+ spec.version = File.open('VERSION').read
8
+ spec.authors = ['nishiki']
9
+ spec.email = ['gems@yae.im']
10
+ spec.summary = 'Manage your password'
11
+ spec.description = 'Save and read your password with gpg'
12
+ spec.homepage = 'https://github.com/nishiki/manage-password'
13
+ spec.license = 'GPL'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = ['mpw', 'mpw-server']
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+ end
@@ -0,0 +1,32 @@
1
+ add_new:
2
+ name: 'test_name'
3
+ group: 'test_group'
4
+ host: 'test_host'
5
+ protocol: 'test_protocol'
6
+ user: 'test_user'
7
+ password: 'test_password'
8
+ port: '42'
9
+ comment: 'test_comment'
10
+
11
+ add_existing:
12
+ id: 'TEST-ID-XXXXX'
13
+ name: 'test_name_existing'
14
+ group: 'test_group_existing'
15
+ host: 'test_host_existing'
16
+ protocol: 'test_protocol_existing'
17
+ user: 'test_user_existing'
18
+ password: 'test_password_existing'
19
+ port: '44'
20
+ comment: 'test_comment_existing'
21
+ created: 1386752948
22
+
23
+ update:
24
+ name: 'test_name_update'
25
+ group: 'test_group_update'
26
+ host: 'test_host_update'
27
+ protocol: 'test_protocol_update'
28
+ user: 'test_user_update'
29
+ password: 'test_password_update'
30
+ port: '43'
31
+ comment: 'test_comment_update'
32
+
@@ -0,0 +1,3 @@
1
+ name,group,protocol,host,user,password,port,comment
2
+ test_name,test_group,test_protocol,test_host,test_user,test_password,42,test_comment
3
+ test_name_update,test_group_update,test_protocol_update,test_host_update,test_user_update,test_password_update,43,test_comment_update
@@ -0,0 +1,23 @@
1
+ ---
2
+ XWas7vpy0HerhOYd:
3
+ id: XWas7vpy0HerhOYd
4
+ name: test_name
5
+ group: test_group
6
+ host: test_host
7
+ protocol: test_protocol
8
+ user: test_user
9
+ password: test_password
10
+ port: 42
11
+ comment: test_comment
12
+ date: 1419858983
13
+ D7URyJENLa91jt0b:
14
+ id: D7URyJENLa91jt0b
15
+ name: test_name_update
16
+ group: test_group_update
17
+ host: test_host_update
18
+ protocol: test_protocol_update
19
+ user: test_user_update
20
+ password: test_password_update
21
+ port: 43
22
+ comment: test_comment_update
23
+ date: 1419858983