mpw 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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