mpw 4.1.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mpw/mpw.rb CHANGED
@@ -25,7 +25,11 @@ require 'mpw/item'
25
25
 
26
26
  module MPW
27
27
  class MPW
28
- # Constructor
28
+ # @param key [String] gpg key name
29
+ # @param wallet_file [String] path of the wallet file
30
+ # @param gpg_pass [String] password of the gpg key
31
+ # @param gpg_exe [String] path of the gpg executable
32
+ # @param pinmode [Boolean] enable the gpg pinmode
29
33
  def initialize(key, wallet_file, gpg_pass = nil, gpg_exe = nil, pinmode = false)
30
34
  @key = key
31
35
  @gpg_pass = gpg_pass
@@ -98,7 +102,7 @@ module MPW
98
102
  raise "#{I18n.t('error.mpw_file.read_data')}\n#{e}"
99
103
  end
100
104
 
101
- # Encrypt a file
105
+ # Encrypt all data in tarball
102
106
  def write_data
103
107
  data = {}
104
108
  tmp_file = "#{@wallet_file}.tmp"
@@ -154,7 +158,7 @@ module MPW
154
158
  end
155
159
 
156
160
  # Get a password
157
- # args: id -> the item id
161
+ # @param id [String] the item id
158
162
  def get_password(id)
159
163
  password = decrypt(@passwords[id])
160
164
 
@@ -165,9 +169,9 @@ module MPW
165
169
  end
166
170
  end
167
171
 
168
- # Set a password
169
- # args: id -> the item id
170
- # password -> the new password
172
+ # Set a new password for an item
173
+ # @param id [String] the item id
174
+ # @param password [String] the new password
171
175
  def set_password(id, password)
172
176
  salt = MPW.password(length: Random.rand(4..9))
173
177
  password = "$#{salt}::#{password}"
@@ -176,13 +180,13 @@ module MPW
176
180
  end
177
181
 
178
182
  # Return the list of all gpg keys
179
- # rtrn: an array with the gpg keys name
183
+ # @return [Array] the gpg keys name
180
184
  def list_keys
181
185
  @keys.keys
182
186
  end
183
187
 
184
188
  # Add a public key
185
- # args: key -> new public key file or name
189
+ # @param key [String] new public key file or name
186
190
  def add_key(key)
187
191
  if File.exist?(key)
188
192
  data = File.open(key).read
@@ -200,7 +204,7 @@ module MPW
200
204
  end
201
205
 
202
206
  # Delete a public key
203
- # args: key -> public key to delete
207
+ # @param key [String] public key to delete
204
208
  def delete_key(key)
205
209
  @keys.delete(key)
206
210
  @passwords.each_key { |id| set_password(id, get_password(id)) }
@@ -208,7 +212,7 @@ module MPW
208
212
  end
209
213
 
210
214
  # Add a new item
211
- # @args: item -> Object MPW::Item
215
+ # @param item [Item]
212
216
  def add(item)
213
217
  raise I18n.t('error.bad_class') unless item.instance_of?(Item)
214
218
  raise I18n.t('error.empty') if item.empty?
@@ -217,8 +221,8 @@ module MPW
217
221
  end
218
222
 
219
223
  # Search in some csv data
220
- # @args: options -> a hash with paramaters
221
- # @rtrn: a list with the resultat of the search
224
+ # @param options [Hash]
225
+ # @return [Array] a list with the resultat of the search
222
226
  def list(**options)
223
227
  result = []
224
228
 
@@ -240,9 +244,9 @@ module MPW
240
244
  result
241
245
  end
242
246
 
243
- # Search in some csv data
244
- # @args: id -> the id item
245
- # @rtrn: a row with the result of the search
247
+ # Search an item with an id
248
+ # @param id [String]the id item
249
+ # @return [Item] an item or nil
246
250
  def search_by_id(id)
247
251
  @data.each do |item|
248
252
  return item if item.id == id
@@ -251,36 +255,35 @@ module MPW
251
255
  nil
252
256
  end
253
257
 
254
- # Set an opt key
255
- # args: id -> the item id
256
- # key -> the new key
258
+ # Set a new opt key
259
+ # @param id [String] the item id
260
+ # @param key [String] the new key
257
261
  def set_otp_key(id, key)
258
262
  @otp_keys[id] = encrypt(key.to_s) unless key.to_s.empty?
259
263
  end
260
264
 
261
265
  # Get an opt key
262
- # args: id -> the item id
263
- # key -> the new key
266
+ # @param id [String] the item id
264
267
  def get_otp_key(id)
265
268
  @otp_keys.key?(id) ? decrypt(@otp_keys[id]) : nil
266
269
  end
267
270
 
268
271
  # Get an otp code
269
- # @args: id -> the item id
270
- # @rtrn: an otp code
272
+ # @param id [String] the item id
273
+ # @return [String] an otp code
271
274
  def get_otp_code(id)
272
275
  @otp_keys.key?(id) ? 0 : ROTP::TOTP.new(decrypt(@otp_keys[id])).now
273
276
  end
274
277
 
275
278
  # Get remaining time before expire otp code
276
- # @rtrn: return time in seconde
279
+ # @return [Integer] time in seconde
277
280
  def get_otp_remaining_time
278
281
  (Time.now.utc.to_i / 30 + 1) * 30 - Time.now.utc.to_i
279
282
  end
280
283
 
281
284
  # Generate a random password
282
- # @args: options -> :length, :special, :alpha, :numeric
283
- # @rtrn: a random string
285
+ # @param options [Hash] :length, :special, :alpha, :numeric
286
+ # @return [String] a random string
284
287
  def self.password(**options)
285
288
  length =
286
289
  if !options.include?(:length) || options[:length].to_i <= 0
@@ -298,11 +301,9 @@ module MPW
298
301
  chars = [*('A'..'Z'), *('a'..'z'), *('0'..'9')] if chars.empty?
299
302
 
300
303
  result = ''
301
- while length > 62
302
- result << chars.sample(62).join
303
- length -= 62
304
+ length.times do
305
+ result << chars.sample
304
306
  end
305
- result << chars.sample(length).join
306
307
 
307
308
  result
308
309
  end
@@ -310,7 +311,8 @@ module MPW
310
311
  private
311
312
 
312
313
  # Decrypt a gpg file
313
- # @args: data -> string to decrypt
314
+ # @param data [String] data to decrypt
315
+ # @return [String] data decrypted
314
316
  def decrypt(data)
315
317
  return nil if data.to_s.empty?
316
318
 
@@ -331,7 +333,8 @@ module MPW
331
333
  end
332
334
 
333
335
  # Encrypt a file
334
- # args: data -> string to encrypt
336
+ # @param data [String] data to encrypt
337
+ # @return [String] data encrypted
335
338
  def encrypt(data)
336
339
  recipients = []
337
340
  crypto = GPGME::Crypto.new(armor: true, always_trust: true)
@@ -0,0 +1,19 @@
1
+ ---
2
+ 1:
3
+ host: fric.com
4
+ user: 230403
5
+ group: Bank
6
+ password: 5XdiTQOubRDw9B0aJoMlcEyL
7
+ protocol: https
8
+ port:
9
+ otp_key: 330223432
10
+ comment: I love my bank
11
+ 2:
12
+ host: assurance.com
13
+ user: user_2132
14
+ group: Assurance
15
+ password: DMyK6B3v4bWO52VzU7aTHIem
16
+ protocol: https
17
+ port: 443
18
+ otp_key:
19
+ comment:
@@ -1,28 +1,28 @@
1
- add_new:
2
- group: 'test_group'
3
- host: 'test_host'
4
- protocol: 'test_protocol'
5
- user: 'test_user'
6
- password: 'test_password'
7
- port: '42'
8
- comment: 'test_comment'
1
+ add:
2
+ group: 'Bank'
3
+ host: 'example.com'
4
+ protocol: 'https'
5
+ user: 'admin'
6
+ password: 'VmfnCN6pPIqgRIbc'
7
+ port: '8080'
8
+ comment: 'the website'
9
9
 
10
- add_existing:
10
+ import:
11
11
  id: 'TEST-ID-XXXXX'
12
- group: 'test_group_existing'
13
- host: 'test_host_existing'
14
- protocol: 'test_protocol_existing'
15
- user: 'test_user_existing'
16
- password: 'test_password_existing'
17
- port: '44'
18
- comment: 'test_comment_existing'
12
+ group: 'Cloud'
13
+ host: 'gogole.com'
14
+ protocol: 'https'
15
+ user: 'gg-2304'
16
+ password: 'TITl0kV9CDDa9sVK'
17
+ port: '8081'
18
+ comment: 'My little servers'
19
19
  created: 1386752948
20
20
 
21
21
  update:
22
- group: 'test_group_update'
23
- host: 'test_host_update'
24
- protocol: 'test_protocol_update'
25
- user: 'test_user_update'
26
- password: 'test_password_update'
27
- port: '43'
28
- comment: 'test_comment_update'
22
+ group: 'Assurance'
23
+ host: 'example2.com'
24
+ protocol: 'ssh'
25
+ user: 'root'
26
+ password: 'kbSrbv4WlMaVxaZ7'
27
+ port: '2222'
28
+ comment: 'i love ssh'
data/test/init.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ require 'fileutils'
3
4
  require 'gpgme'
4
5
 
6
+ FileUtils.rm_rf("#{Dir.home}/.config/mpw")
7
+ FileUtils.rm_rf("#{Dir.home}/.gnupg")
8
+
5
9
  param = ''
6
10
  param << '<GnupgKeyParms format="internal">' + "\n"
7
11
  param << "Key-Type: RSA\n"
8
- param << "Key-Length: 2048\n"
12
+ param << "Key-Length: 512\n"
9
13
  param << "Subkey-Type: ELG-E\n"
10
- param << "Subkey-Length: 2048\n"
14
+ param << "Subkey-Length: 512\n"
11
15
  param << "Name-Real: test\n"
12
16
  param << "Name-Comment: test\n"
13
17
  param << "Name-Email: test2@example.com\n"
data/test/test_cli.rb ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'i18n'
4
+ require 'test/unit'
5
+
6
+ class TestConfig < Test::Unit::TestCase
7
+ def setup
8
+ if defined?(I18n.enforce_available_locales)
9
+ I18n.enforce_available_locales = true
10
+ end
11
+
12
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
13
+ I18n.load_path = ["#{File.expand_path('../../i18n', __FILE__)}/en.yml"]
14
+ I18n.locale = :en
15
+
16
+ @password = 'password'
17
+ @fixtures = YAML.load_file('./test/files/fixtures.yml')
18
+ @gpg_key = 'test@example.com'
19
+ end
20
+
21
+ def test_00_init_config
22
+ output = %x(echo "#{@password}\n#{@password}" | mpw config --init #{@gpg_key})
23
+ assert_match(I18n.t('form.setup_config.valid'), output)
24
+ assert_match(I18n.t('form.setup_gpg_key.valid'), output)
25
+ end
26
+
27
+ def test_01_add_item
28
+ data = @fixtures['add']
29
+
30
+ output = %x(
31
+ echo #{@password} | mpw add \
32
+ --host #{data['host']} \
33
+ --port #{data['port']} \
34
+ --protocol #{data['protocol']} \
35
+ --user #{data['user']} \
36
+ --comment '#{data['comment']}' \
37
+ --group #{data['group']} \
38
+ --random
39
+ )
40
+ puts output
41
+ assert_match(I18n.t('form.add_item.valid'), output)
42
+
43
+ output = %x(echo #{@password} | mpw list)
44
+ puts output
45
+ assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
46
+ assert_match(data['user'], output)
47
+ assert_match(data['comment'], output)
48
+ assert_match(data['group'], output)
49
+ end
50
+
51
+ def test_02_search
52
+ data = @fixtures['add']
53
+
54
+ output = %x(echo #{@password} | mpw list --group #{data['group']})
55
+ assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
56
+
57
+ output = %x(echo #{@password} | mpw list --pattern #{data['host']})
58
+ assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
59
+
60
+ output = %x(echo #{@password} | mpw list --pattern #{data['comment']})
61
+ assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
62
+
63
+ output = %x(echo #{@password} | mpw list --group R1Pmfbp626TFpjlr)
64
+ assert_match(I18n.t('display.nothing'), output)
65
+
66
+ output = %x(echo #{@password} | mpw list --pattern h1IfnKqamaGM9oEX)
67
+ assert_match(I18n.t('display.nothing'), output)
68
+ end
69
+
70
+ def test_03_update_item
71
+ data = @fixtures['update']
72
+
73
+ output = %x(
74
+ echo #{@password} | mpw update \
75
+ -p #{@fixtures['add']['host']} \
76
+ --host #{data['host']} \
77
+ --port #{data['port']} \
78
+ --protocol #{data['protocol']} \
79
+ --user #{data['user']} \
80
+ --comment '#{data['comment']}' \
81
+ --new-group #{data['group']}
82
+ )
83
+ puts output
84
+ assert_match(I18n.t('form.update_item.valid'), output)
85
+
86
+ output = %x(echo #{@password} | mpw list)
87
+ puts output
88
+ assert_match(%r{#{data['protocol']}://.+#{data['host']}.+:#{data['port']}}, output)
89
+ assert_match(data['user'], output)
90
+ assert_match(data['comment'], output)
91
+ assert_match(data['group'], output)
92
+ end
93
+
94
+ def test_04_delete_item
95
+ output = %x(echo "#{@password}\ny" | mpw delete -p #{@fixtures['update']['host']})
96
+ puts output
97
+ assert_match(I18n.t('form.delete_item.valid'), output)
98
+
99
+ output = %x(echo #{@password} | mpw list)
100
+ puts output
101
+ assert_match(I18n.t('display.nothing'), output)
102
+ end
103
+
104
+ def test_05_import_export
105
+ file_import = './test/files/fixtures-import.yml'
106
+ file_export = '/tmp/test-mpw.yml'
107
+
108
+ output = %x(echo #{@password} | mpw import --file #{file_import})
109
+ assert_match(I18n.t('form.import.valid', file: file_import), output)
110
+
111
+ output = %x(echo #{@password} | mpw export --file #{file_export})
112
+ assert_match(I18n.t('form.export.valid', file: file_export), output)
113
+ assert(File.exist?(file_export))
114
+ assert_equal(YAML.load_file(file_export).length, 2)
115
+
116
+ YAML.load_file(file_import).each_value do |import|
117
+ error = true
118
+
119
+ YAML.load_file(file_export).each_value do |export|
120
+ next if import['host'] != export['host']
121
+
122
+ %w[user group password protocol port otp_key comment].each do |key|
123
+ assert_equal(import[key].to_s, export[key].to_s)
124
+ end
125
+
126
+ error = false
127
+ end
128
+ assert(!error)
129
+ end
130
+ end
131
+
132
+ def test_06_copy
133
+ data = YAML.load_file('./test/files/fixtures-import.yml')[1]
134
+
135
+ output = %x(
136
+ echo "#{@password}\np\nq" | mpw copy \
137
+ --disable-clipboard \
138
+ -p #{data['host']}
139
+ )
140
+ puts output
141
+ assert_match(data['password'], output)
142
+ end
143
+
144
+ def test_07_setup_wallet
145
+ path = '/tmp/'
146
+ gpg_key = 'test2@example.com'
147
+
148
+ output = %x(echo #{@password} | mpw wallet --add-gpg-key #{gpg_key})
149
+ puts output
150
+ assert_match(I18n.t('form.add_key.valid'), output)
151
+
152
+ output = %x(echo #{@password} | mpw wallet --list-keys)
153
+ puts output
154
+ assert_match("| #{@gpg_key}", output)
155
+ assert_match("| #{gpg_key}", output)
156
+
157
+ output = %x(echo #{@password} | mpw wallet --delete-gpg-key #{gpg_key})
158
+ puts output
159
+ assert_match(I18n.t('form.delete_key.valid'), output)
160
+
161
+ output = %x(echo #{@password} | mpw wallet --list-keys)
162
+ puts output
163
+ assert_match("| #{@gpg_key}", output)
164
+ assert_no_match(/\| #{gpg_key}/, output)
165
+
166
+ output = %x(mpw wallet)
167
+ puts output
168
+ assert_match('| default', output)
169
+
170
+ output = %x(mpw wallet --path #{path})
171
+ puts output
172
+ assert_match(I18n.t('form.set_wallet_path.valid'), output)
173
+
174
+ output = %x(mpw config)
175
+ puts output
176
+ assert_match(%r{path_wallet_default.+\| #{path}/default.mpw}, output)
177
+ assert(File.exist?("#{path}/default.mpw"))
178
+
179
+ output = %x(mpw wallet --default-path)
180
+ puts output
181
+ assert_match(I18n.t('form.set_wallet_path.valid'), output)
182
+
183
+ output = %x(mpw config)
184
+ puts output
185
+ assert_no_match(/path_wallet_default/, output)
186
+ end
187
+
188
+ def test_08_setup_config
189
+ gpg_key = 'user@example2.com'
190
+ gpg_exe = '/usr/bin/gpg2'
191
+ wallet_dir = '/tmp/mpw'
192
+ length = 24
193
+ wallet = 'work'
194
+
195
+ output = %x(
196
+ mpw config \
197
+ --gpg-exe #{gpg_exe} \
198
+ --enable-pinmode \
199
+ --disable-alpha \
200
+ --disable-special-chars \
201
+ --disable-numeric \
202
+ --length #{length} \
203
+ --wallet-dir #{wallet_dir} \
204
+ --default-wallet #{wallet}
205
+ )
206
+ puts output
207
+ assert_match(I18n.t('form.set_config.valid'), output)
208
+
209
+ output = %x(mpw config)
210
+ puts output
211
+ assert_match(/gpg_key.+\| #{@gpg_key}/, output)
212
+ assert_match(/gpg_exe.+\| #{gpg_exe}/, output)
213
+ assert_match(/pinmode.+\| true/, output)
214
+ assert_match(/default_wallet.+\| #{wallet}/, output)
215
+ assert_match(/wallet_dir.+\| #{wallet_dir}/, output)
216
+ assert_match(/password_length.+\| #{length}/, output)
217
+ %w[numeric alpha special].each do |k|
218
+ assert_match(/password_#{k}.+\| false/, output)
219
+ end
220
+
221
+ output = %x(
222
+ mpw config \
223
+ --key #{gpg_key} \
224
+ --alpha \
225
+ --special-chars \
226
+ --numeric \
227
+ --disable-pinmode
228
+ )
229
+ puts output
230
+ assert_match(I18n.t('form.set_config.valid'), output)
231
+
232
+ output = %x(mpw config)
233
+ puts output
234
+ assert_match(/gpg_key.+\| #{gpg_key}/, output)
235
+ assert_match(/pinmode.+\| false/, output)
236
+ %w[numeric alpha special].each do |k|
237
+ assert_match(/password_#{k}.+\| true/, output)
238
+ end
239
+ end
240
+
241
+ def test_09_generate_password
242
+ length = 24
243
+
244
+ output = %x(
245
+ mpw genpwd \
246
+ --length #{length} \
247
+ --alpha
248
+ )
249
+ assert_match(/[a-zA-Z]{#{length}}/, output)
250
+
251
+ output = %x(
252
+ mpw genpwd \
253
+ --length #{length} \
254
+ --numeric
255
+ )
256
+ assert_match(/[0-9]{#{length}}/, output)
257
+
258
+ output = %x(
259
+ mpw genpwd \
260
+ --length #{length} \
261
+ --special-chars
262
+ )
263
+ assert_no_match(/[a-zA-Z0-9]/, output)
264
+ end
265
+ end