trocla 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: edc9de388cf60d7294f3d350f9e147dbb51d3d75
4
- data.tar.gz: 571e88bacaabda8a8e20a0297ad10e9eb5de67a8
3
+ metadata.gz: 0fe321c3e5f61203499ad978f32fef9b9617bf54
4
+ data.tar.gz: 276a6191e0342e7d26c185a06b383f357483b300
5
5
  SHA512:
6
- metadata.gz: f250ac0166aee34d55830d21d519023770b30add412e357b48849aa33add62a79507d0c4fe870ec7511f34f5b61fdaf14de37bd437bb0b0d9ffb1eeed0f63a06
7
- data.tar.gz: 72d47d4291ab1875b8c376068838bb6b5c86ce58ae1ddd267c8ce9863dfb1c1eee8981d4bf71aca54b25b74c3f3cc564b700d6001bf8e29546c87d69bd6fd992
6
+ metadata.gz: cda445d5bab3d8b96a56990b2b9e447869a44e02bb4f28ca9d29ffce8578aa1e1cb4d5dedb7bec0e0102cc42416fed6361f1c9ea276c2b9fa2b1b7e1ab99698a
7
+ data.tar.gz: a59f1905c9877eda6ff3614b84f50e6598c75be420ba78c4e324f0b5c6c72584efda35a711477c35571aeac804de4e8f6ff1a3de80e3f2f220a825ce0d52056d
@@ -1,10 +1,11 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
+ - jruby
4
5
  - jruby-18mode
5
6
  - jruby-19mode
7
+ - 2.4.0
6
8
  - 2.2.0
7
- - 2.1.0
8
9
  - 2.0.0
9
10
  - 1.9.3
10
11
  - 1.8.7
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## to 0.3.0
4
+
5
+ * Add open method to be able to immediately close a trocla store after using it - thanks martinpfeiffer
6
+ * Add typesafe charset - thanks hggh
7
+ * Support cost option for bcrypt
8
+ * address concurrency corner cases, when 2 concurrent threads or even processes
9
+ are currently calculating the same (expensive) format.
10
+ * parse additional options on cli (#39 & #46) - thanks fe80
11
+
3
12
  ## to 0.2.3
4
13
 
5
14
  1. Add extended CA validity profiles
data/Gemfile CHANGED
@@ -3,12 +3,22 @@ source "http://rubygems.org"
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
 
6
+ if RUBY_VERSION.to_f <= 2.2
7
+ gem 'rack', '< 2.0'
8
+ end
9
+
10
+ if RUBY_VERSION.to_f < 2.1
11
+ gem 'nokogiri', '< 1.7'
12
+ end
13
+
6
14
  if RUBY_VERSION.to_f > 1.8
7
15
  gem "moneta"
8
16
  gem "highline"
9
17
  else
10
18
  gem "moneta", "~> 0.7.20"
11
19
  gem "highline", "~> 1.6.2"
20
+ gem 'rake', '< 11'
21
+ gem 'git', '< 1.3'
12
22
  end
13
23
 
14
24
  if defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby')
@@ -22,7 +32,14 @@ group :development do
22
32
  if RUBY_VERSION.to_f > 1.8
23
33
  gem "rspec"
24
34
  gem "rdoc"
25
- gem "jeweler"
35
+ if RUBY_VERSION.to_f < 2.2
36
+ gem 'jeweler', '< 2.2'
37
+ else
38
+ gem "jeweler"
39
+ end
40
+ if RUBY_VERSION.to_f < 2.0
41
+ gem 'public_suffix', '~> 1.4.6'
42
+ end
26
43
  else
27
44
  gem "rspec", "~> 2.4"
28
45
  gem "rdoc", "~> 3.8"
data/README.md CHANGED
@@ -168,6 +168,11 @@ options to work properly. These are documented here:
168
168
  Password hashes for PostgreSQL servers. Requires the option `username` to be set
169
169
  to the username to which the password will be assigned.
170
170
 
171
+ ### bcrypt
172
+
173
+ You are able to tune the [cost factor of bcrypt](https://github.com/codahale/bcrypt-ruby#cost-factors) by passing the option `cost`.
174
+ Note: ruby bcrypt does not support a [cost > 31](https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L45).
175
+
171
176
  ### x509
172
177
 
173
178
  This format takes a set of additional options. Required are:
data/bin/trocla CHANGED
@@ -47,18 +47,20 @@ OptionParser.new do |opts|
47
47
  end.parse!
48
48
 
49
49
  def create(options)
50
- Trocla.new(options.delete(:config_file)).password(
50
+ [ Trocla.new(options.delete(:config_file)).password(
51
51
  options.delete(:trocla_key),
52
52
  options.delete(:trocla_format),
53
53
  options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
54
- )
54
+ ) , 0 ]
55
55
  end
56
56
 
57
57
  def get(options)
58
- Trocla.new(options.delete(:config_file)).get_password(
58
+ res = Trocla.new(options.delete(:config_file)).get_password(
59
59
  options.delete(:trocla_key),
60
- options.delete(:trocla_format)
60
+ options.delete(:trocla_format),
61
+ options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
61
62
  )
63
+ [ res, res.nil? ? 1 : 0 ]
62
64
  end
63
65
  def set(options)
64
66
  if options.delete(:ask_password)
@@ -67,7 +69,7 @@ def set(options)
67
69
  pwd2 = ask('Repeat password: ') { |q| q.echo = 'x' }.to_s
68
70
  unless password == pwd2
69
71
  STDERR.puts 'Passwords did not match, exiting!'
70
- exit 1
72
+ return [ nil, 1 ]
71
73
  end
72
74
  else
73
75
  password = options.delete(:password) || STDIN.read.chomp
@@ -78,29 +80,29 @@ def set(options)
78
80
  value = if no_format
79
81
  password
80
82
  else
81
- trocla.formats(format).format(password, options.delete(:other_options).shift.to_s)
83
+ trocla.formats(format).format(password, (YAML.load(options.delete(:other_options).shift.to_s)||{}))
82
84
  end
83
85
  trocla.set_password(
84
86
  options.delete(:trocla_key),
85
87
  format,
86
88
  value
87
89
  )
88
- ''
90
+ [ '', 0 ]
89
91
  end
90
92
 
91
93
  def reset(options)
92
- Trocla.new(options.delete(:config_file)).reset_password(
94
+ [ Trocla.new(options.delete(:config_file)).reset_password(
93
95
  options.delete(:trocla_key),
94
96
  options.delete(:trocla_format),
95
97
  options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
96
- )
98
+ ), 0 ]
97
99
  end
98
100
 
99
101
  def delete(options)
100
- Trocla.new(options.delete(:config_file)).delete_password(
102
+ [ Trocla.new(options.delete(:config_file)).delete_password(
101
103
  options.delete(:trocla_key),
102
104
  options.delete(:trocla_format)
103
- )
105
+ ), 0 ]
104
106
  end
105
107
 
106
108
  def formats(options)
@@ -125,7 +127,8 @@ if (action=ARGV.shift) && actions.include?(action)
125
127
  options[:other_options] = ARGV
126
128
  check_format(options[:trocla_format]) unless ['delete','formats'].include?(action)
127
129
  begin
128
- if result = send(action,options)
130
+ result, excode = send(action,options)
131
+ if result
129
132
  puts result.is_a?(String) ? result : result.inspect
130
133
  end
131
134
  rescue Exception => e
@@ -136,6 +139,7 @@ if (action=ARGV.shift) && actions.include?(action)
136
139
  raise e if options[:trace]
137
140
  exit 1
138
141
  end
142
+ exit excode.nil? ? 0 : excode
139
143
  else
140
144
  STDERR.puts "Please supply one of the following actions: #{actions.join(', ')}"
141
145
  STDERR.puts "Use #{$0} --help to get a list of options for these actions"
@@ -2,7 +2,7 @@
2
2
  %global gem_name trocla
3
3
 
4
4
  Name: rubygem-%{gem_name}
5
- Version: 0.2.2
5
+ Version: 0.3.0
6
6
  Release: 1%{?dist}
7
7
  Summary: Trocla a simple password generator and storage
8
8
  Group: Development/Languages
@@ -74,9 +74,11 @@ chmod a+x %{buildroot}%{gem_instdir}/bin/%{gem_name}
74
74
 
75
75
  cat <<EOF > %{buildroot}/%{_sysconfdir}/%{gem_name}rc.yaml
76
76
  ---
77
- adapter: :YAML
78
- adapter_options:
79
- :file: '%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml'
77
+ store: :moneta
78
+ store_options:
79
+ adapter: :YAML
80
+ adapter_options:
81
+ :file: '%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml'
80
82
  EOF
81
83
 
82
84
  # Run the test suite
@@ -1,4 +1,4 @@
1
1
  major:0
2
- minor:2
3
- patch:3
2
+ minor:3
3
+ patch:0
4
4
  build:
@@ -13,6 +13,17 @@ class Trocla
13
13
  end
14
14
  end
15
15
 
16
+ def self.open(config_file=nil)
17
+ trocla = Trocla.new(config_file)
18
+
19
+ if block_given?
20
+ yield trocla
21
+ trocla.close
22
+ else
23
+ trocla
24
+ end
25
+ end
26
+
16
27
  def password(key,format,options={})
17
28
  # respect a default profile, but let the
18
29
  # profiles win over the default options
@@ -35,10 +46,15 @@ class Trocla
35
46
  elsif !options['random'] && plain_pwd.nil?
36
47
  raise "Password must be present as plaintext if you don't want a random password"
37
48
  end
38
- set_password(key,
39
- format,
40
- self.formats(format).format(plain_pwd,options),
41
- options)
49
+ pwd = self.formats(format).format(plain_pwd,options)
50
+ # it's possible that meanwhile another thread/process was faster in
51
+ # formating the password. But we want todo that second lookup
52
+ # only for expensive formats
53
+ if self.formats(format).expensive?
54
+ get_password(key,format,options) || set_password(key, format, pwd, options)
55
+ else
56
+ set_password(key, format, pwd, options)
57
+ end
42
58
  end
43
59
 
44
60
  def get_password(key, format, options={})
@@ -78,6 +94,10 @@ class Trocla
78
94
  @config ||= read_config
79
95
  end
80
96
 
97
+ def close
98
+ store.close
99
+ end
100
+
81
101
  private
82
102
  def store
83
103
  @store ||= build_store
@@ -8,6 +8,17 @@ class Trocla::Formats
8
8
  def render(output,render_options={})
9
9
  output
10
10
  end
11
+ def expensive?
12
+ self.class.expensive?
13
+ end
14
+ class << self
15
+ def expensive(is_expensive)
16
+ @expensive = is_expensive
17
+ end
18
+ def expensive?
19
+ @expensive == true
20
+ end
21
+ end
11
22
  end
12
23
 
13
24
  class << self
@@ -1,6 +1,7 @@
1
1
  class Trocla::Formats::Bcrypt < Trocla::Formats::Base
2
+ expensive true
2
3
  require 'bcrypt'
3
4
  def format(plain_password,options={})
4
- BCrypt::Password.create(plain_password).to_s
5
+ BCrypt::Password.create(plain_password, :cost => options['cost']||BCrypt::Engine.cost).to_s
5
6
  end
6
7
  end
@@ -1,5 +1,6 @@
1
1
  require 'openssl'
2
2
  class Trocla::Formats::X509 < Trocla::Formats::Base
3
+ expensive true
3
4
  def format(plain_password,options={})
4
5
 
5
6
  if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/m)
@@ -6,6 +6,12 @@ class Trocla::Store
6
6
  @trocla = trocla
7
7
  end
8
8
 
9
+ # closes the store
10
+ # when called do whatever "closes" your
11
+ # store, e.g. close database connections.
12
+ def close
13
+ end
14
+
9
15
  # should return value for key & format
10
16
  # returns nil if nothing or a nil value
11
17
  # was found.
@@ -10,6 +10,10 @@ class Trocla::Stores::Moneta < Trocla::Store
10
10
  @moneta = Moneta.new(store_config['adapter'],adapter_options)
11
11
  end
12
12
 
13
+ def close
14
+ moneta.close
15
+ end
16
+
13
17
  def get(key,format)
14
18
  moneta.fetch(key, {})[format]
15
19
  end
@@ -24,6 +24,7 @@ class Trocla
24
24
  'numeric' => numeric,
25
25
  'hexadecimal' => hexadecimal,
26
26
  'consolesafe' => consolesafe,
27
+ 'typesafe' => typesafe,
27
28
  }
28
29
  h.each { |k, v| h[k] = v.uniq }
29
30
  end
@@ -50,6 +51,9 @@ class Trocla
50
51
  def numeric
51
52
  @numeric ||= ('0'..'9').to_a
52
53
  end
54
+ def typesafe
55
+ @typesafe ||= ('a'..'x').to_a - ['i'] - ['l'] + ('A'..'X').to_a - ['I'] - ['L'] + ('1'..'9').to_a
56
+ end
53
57
  def special_chars
54
58
  @special_chars ||= "*()&![]{}-".split(//)
55
59
  end
@@ -282,3 +282,9 @@ end
282
282
  def remove_yaml_store
283
283
  File.unlink(trocla_yaml_file)
284
284
  end
285
+ class Trocla::Formats::Sleep < Trocla::Formats::Base
286
+ def format(plain_password,options={})
287
+ sleep options['sleep'] ||= 0
288
+ (options['sleep'] + 1 ).times.collect{ plain_password }.join(' ')
289
+ end
290
+ end
@@ -242,12 +242,12 @@ describe "Trocla::Format::X509" do
242
242
  expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
243
243
  expect((Date.parse(valid_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
244
244
  # workaround broken openssl
245
- if %x{openssl version} =~ /1\.0\.[2-9]/
246
- expect(verify([@ca,ca2],valid_cert)).to be true
247
- else
245
+ if Gem::Version.new(%x{openssl version}.split(' ')[1]) < Gem::Version.new('1.0.2')
248
246
  skip_for(:engine => 'ruby',:reason => 'NameConstraints verification is broken on older openssl versions https://rt.openssl.org/Ticket/Display.html?id=3562') do
249
247
  expect(verify([@ca,ca2],valid_cert)).to be true
250
248
  end
249
+ else
250
+ expect(verify([@ca,ca2],valid_cert)).to be true
251
251
  end
252
252
 
253
253
  false_cert_str = @trocla.password('myfalseexamplecert','x509', {
@@ -311,7 +311,9 @@ describe "Trocla::Format::X509" do
311
311
  # https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
312
312
  expect(cert.public_key.n.num_bytes * 8).to eq(2048)
313
313
  expect(verify(@ca,cert)).to be true
314
- expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }.value).to eq('DNS:www.test, DNS:test, DNS:test1, DNS:test2, DNS:test3')
314
+ skip_for(:engine => 'jruby',:reason => 'subjectAltName represenation is broken in jruby-openssl -> https://github.com/jruby/jruby-openssl/pull/123') do
315
+ expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }.value).to eq('DNS:www.test, DNS:test, DNS:test1, DNS:test2, DNS:test3')
316
+ end
315
317
 
316
318
  expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:FALSE')
317
319
  ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
@@ -36,6 +36,14 @@ describe "Trocla::Util" do
36
36
  end
37
37
  end
38
38
 
39
+ describe :typesafe_generator do
40
+ 10.times.each do |i|
41
+ it "creates random typesafe password #{i}" do
42
+ expect(Trocla::Util.random_str(12, 'typesafe')).to match(/^[1-9a-hj-km-xA-HJ-KM-X]{12}$/)
43
+ end
44
+ end
45
+ end
46
+
39
47
  describe :salt do
40
48
  10.times.each do |i|
41
49
  it "contains only characters and numbers #{i}" do
@@ -4,144 +4,231 @@ describe "Trocla" do
4
4
 
5
5
  before(:each) do
6
6
  expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
7
- @trocla = Trocla.new
8
7
  end
9
-
10
- describe "password" do
11
- it "generates random passwords by default" do
12
- expect(@trocla.password('random1','plain')).not_to eq(@trocla.password('random2','plain'))
8
+ context 'in normal usage with' do
9
+ before(:each) do
10
+ @trocla = Trocla.new
11
+ @trocla.password('init','plain')
13
12
  end
14
13
 
15
- it "generates passwords of length #{default_config['options']['length']}" do
16
- expect(@trocla.password('random1','plain').length).to eq(default_config['options']['length'])
17
- end
14
+ describe "password" do
15
+ it "generates random passwords by default" do
16
+ expect(@trocla.password('random1','plain')).not_to eq(@trocla.password('random2','plain'))
17
+ end
18
+
19
+ it "generates passwords of length #{default_config['options']['length']}" do
20
+ expect(@trocla.password('random1','plain').length).to eq(default_config['options']['length'])
21
+ end
22
+
23
+ Trocla::Formats.all.each do |format|
24
+ describe "#{format} password format" do
25
+ it "retursn a password hashed in the #{format} format" do
26
+ expect(@trocla.password('some_test',format,format_options[format])).not_to be_empty
27
+ end
28
+
29
+ it "returns the same hashed for the #{format} format on multiple invocations" do
30
+ expect(round1=@trocla.password('some_test',format,format_options[format])).not_to be_empty
31
+ expect(@trocla.password('some_test',format,format_options[format])).to eq(round1)
32
+ end
33
+
34
+ it "also stores the plain password by default" do
35
+ pwd = @trocla.password('some_test','plain')
36
+ expect(pwd).not_to be_empty
37
+ expect(pwd.length).to eq(16)
38
+ end
39
+ end
40
+ end
41
+
42
+ Trocla::Formats.all.reject{|f| f == 'plain' }.each do |format|
43
+ it "raises an exception if not a random password is asked but plain password is not present for format #{format}" do
44
+ expect{@trocla.password('not_random',format, 'random' => false)}.to raise_error(/Password must be present as plaintext/)
45
+ end
46
+ end
18
47
 
19
- Trocla::Formats.all.each do |format|
20
- describe "#{format} password format" do
21
- it "retursn a password hashed in the #{format} format" do
22
- expect(@trocla.password('some_test',format,format_options[format])).not_to be_empty
48
+ describe 'with profiles' do
49
+ it 'raises an exception on unknown profile' do
50
+ expect{@trocla.password('no profile known','plain',
51
+ 'profiles' => 'unknown_profile') }.to raise_error(/No such profile unknown_profile defined/)
23
52
  end
24
53
 
25
- it "returns the same hashed for the #{format} format on multiple invocations" do
26
- expect(round1=@trocla.password('some_test',format,format_options[format])).not_to be_empty
27
- expect(@trocla.password('some_test',format,format_options[format])).to eq(round1)
54
+ it 'takes a profile and merge its options' do
55
+ pwd = @trocla.password('some_test','plain', 'profiles' => 'rootpw')
56
+ expect(pwd).not_to be_empty
57
+ expect(pwd.length).to eq(32)
58
+ expect(pwd).to_not match(/[={}\[\]\?%\*()&!]+/)
28
59
  end
29
60
 
30
- it "also stores the plain password by default" do
31
- pwd = @trocla.password('some_test','plain')
61
+ it 'is possible to combine profiles but first profile wins' do
62
+ pwd = @trocla.password('some_test1','plain', 'profiles' => ['rootpw','login'])
63
+ expect(pwd).not_to be_empty
64
+ expect(pwd.length).to eq(32)
65
+ expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
66
+ end
67
+ it 'is possible to combine profiles but first profile wins 2' do
68
+ pwd = @trocla.password('some_test2','plain', 'profiles' => ['login','mysql'])
32
69
  expect(pwd).not_to be_empty
33
70
  expect(pwd.length).to eq(16)
71
+ expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
72
+ end
73
+ it 'is possible to combine profiles but first profile wins 3' do
74
+ pwd = @trocla.password('some_test3','plain', 'profiles' => ['mysql','login'])
75
+ expect(pwd).not_to be_empty
76
+ expect(pwd.length).to eq(32)
77
+ expect(pwd).to match(/[+%\/@=\?_.,:]+/)
34
78
  end
35
79
  end
36
80
  end
37
81
 
38
- Trocla::Formats.all.reject{|f| f == 'plain' }.each do |format|
39
- it "raises an exception if not a random password is asked but plain password is not present for format #{format}" do
40
- expect{@trocla.password('not_random',format, 'random' => false)}.to raise_error(/Password must be present as plaintext/)
41
- end
42
- end
82
+ describe "set_password" do
83
+ it "resets hashed passwords on a new plain password" do
84
+ expect(@trocla.password('set_test','mysql')).not_to be_empty
85
+ expect(@trocla.get_password('set_test','mysql')).not_to be_nil
86
+ expect(old_plain=@trocla.password('set_test','mysql')).not_to be_empty
43
87
 
44
- describe 'with profiles' do
45
- it 'raises an exception on unknown profile' do
46
- expect{@trocla.password('no profile known','plain',
47
- 'profiles' => 'unknown_profile') }.to raise_error(/No such profile unknown_profile defined/)
88
+ expect(@trocla.set_password('set_test','plain','foobar')).not_to eq(old_plain)
89
+ expect(@trocla.get_password('set_test','mysql')).to be_nil
48
90
  end
49
91
 
50
- it 'takes a profile and merge its options' do
51
- pwd = @trocla.password('some_test','plain', 'profiles' => 'rootpw')
52
- expect(pwd).not_to be_empty
53
- expect(pwd.length).to eq(32)
54
- expect(pwd).to_not match(/[={}\[\]\?%\*()&!]+/)
55
- end
92
+ it "otherwise updates only the hash" do
93
+ expect(mysql = @trocla.password('set_test2','mysql')).not_to be_empty
94
+ expect(md5crypt = @trocla.password('set_test2','md5crypt')).not_to be_empty
95
+ expect(plain = @trocla.get_password('set_test2','plain')).not_to be_empty
56
96
 
57
- it 'is possible to combine profiles but first profile wins' do
58
- pwd = @trocla.password('some_test','plain', 'profiles' => ['rootpw','login'])
59
- expect(pwd).not_to be_empty
60
- expect(pwd.length).to eq(32)
61
- expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
62
- end
63
- it 'is possible to combine profiles but first profile wins 2' do
64
- pwd = @trocla.password('some_test','plain', 'profiles' => ['login','mysql'])
65
- expect(pwd).not_to be_empty
66
- expect(pwd.length).to eq(16)
67
- expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
68
- end
69
- it 'is possible to combine profiles but first profile wins 3' do
70
- pwd = @trocla.password('some_test','plain', 'profiles' => ['mysql','login'])
71
- expect(pwd).not_to be_empty
72
- expect(pwd.length).to eq(32)
73
- expect(pwd).to match(/[+%\/@=\?_.,:]+/)
97
+ expect(new_mysql = @trocla.set_password('set_test2','mysql','foo')).not_to eql(mysql)
98
+ expect(@trocla.get_password('set_test2','mysql')).to eq(new_mysql)
99
+ expect(@trocla.get_password('set_test2','md5crypt')).to eq(md5crypt)
100
+ expect(@trocla.get_password('set_test2','plain')).to eq(plain)
74
101
  end
75
102
  end
76
- end
77
-
78
- describe "set_password" do
79
- it "resets hashed passwords on a new plain password" do
80
- expect(@trocla.password('set_test','mysql')).not_to be_empty
81
- expect(@trocla.get_password('set_test','mysql')).not_to be_nil
82
- expect(old_plain=@trocla.password('set_test','mysql')).not_to be_empty
83
103
 
84
- expect(@trocla.set_password('set_test','plain','foobar')).not_to eq(old_plain)
85
- expect(@trocla.get_password('set_test','mysql')).to be_nil
86
- end
104
+ describe "reset_password" do
105
+ it "resets a password" do
106
+ plain1 = @trocla.password('reset_pwd','plain')
107
+ plain2 = @trocla.reset_password('reset_pwd','plain')
87
108
 
88
- it "otherwise updates only the hash" do
89
- expect(mysql = @trocla.password('set_test2','mysql')).not_to be_empty
90
- expect(md5crypt = @trocla.password('set_test2','md5crypt')).not_to be_empty
91
- expect(plain = @trocla.get_password('set_test2','plain')).not_to be_empty
109
+ expect(plain1).not_to eq(plain2)
110
+ end
92
111
 
93
- expect(new_mysql = @trocla.set_password('set_test2','mysql','foo')).not_to eql(mysql)
94
- expect(@trocla.get_password('set_test2','mysql')).to eq(new_mysql)
95
- expect(@trocla.get_password('set_test2','md5crypt')).to eq(md5crypt)
96
- expect(@trocla.get_password('set_test2','plain')).to eq(plain)
97
- end
98
- end
112
+ it "does not reset other formats" do
113
+ expect(mysql = @trocla.password('reset_pwd2','mysql')).not_to be_empty
114
+ expect(md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).not_to be_empty
99
115
 
100
- describe "reset_password" do
101
- it "resets a password" do
102
- plain1 = @trocla.password('reset_pwd','plain')
103
- plain2 = @trocla.reset_password('reset_pwd','plain')
116
+ expect(md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).not_to be_empty
117
+ expect(md5crypt2).not_to eq(md5crypt1)
104
118
 
105
- expect(plain1).not_to eq(plain2)
119
+ expect(@trocla.get_password('reset_pwd2','mysql')).to eq(mysql)
120
+ end
106
121
  end
107
122
 
108
- it "does not reset other formats" do
109
- expect(mysql = @trocla.password('reset_pwd2','mysql')).not_to be_empty
110
- expect(md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).not_to be_empty
111
-
112
- expect(md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).not_to be_empty
113
- expect(md5crypt2).not_to eq(md5crypt1)
123
+ describe "delete_password" do
124
+ it "deletes all passwords if no format is given" do
125
+ expect(@trocla.password('delete_test1','mysql')).not_to be_nil
126
+ expect(@trocla.get_password('delete_test1','plain')).not_to be_nil
114
127
 
115
- expect(@trocla.get_password('reset_pwd2','mysql')).to eq(mysql)
116
- end
117
- end
128
+ @trocla.delete_password('delete_test1')
129
+ expect(@trocla.get_password('delete_test1','plain')).to be_nil
130
+ expect(@trocla.get_password('delete_test1','mysql')).to be_nil
131
+ end
118
132
 
119
- describe "delete_password" do
120
- it "deletes all passwords if no format is given" do
121
- expect(@trocla.password('delete_test1','mysql')).not_to be_nil
122
- expect(@trocla.get_password('delete_test1','plain')).not_to be_nil
133
+ it "deletes only a given format" do
134
+ expect(@trocla.password('delete_test2','mysql')).not_to be_nil
135
+ expect(@trocla.get_password('delete_test2','plain')).not_to be_nil
123
136
 
124
- @trocla.delete_password('delete_test1')
125
- expect(@trocla.get_password('delete_test1','plain')).to be_nil
126
- expect(@trocla.get_password('delete_test1','mysql')).to be_nil
127
- end
137
+ @trocla.delete_password('delete_test2','plain')
138
+ expect(@trocla.get_password('delete_test2','plain')).to be_nil
139
+ expect(@trocla.get_password('delete_test2','mysql')).not_to be_nil
140
+ end
128
141
 
129
- it "deletes only a given format" do
130
- expect(@trocla.password('delete_test2','mysql')).not_to be_nil
131
- expect(@trocla.get_password('delete_test2','plain')).not_to be_nil
142
+ it "deletes only a given non-plain format" do
143
+ expect(@trocla.password('delete_test3','mysql')).not_to be_nil
144
+ expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
132
145
 
133
- @trocla.delete_password('delete_test2','plain')
134
- expect(@trocla.get_password('delete_test2','plain')).to be_nil
135
- expect(@trocla.get_password('delete_test2','mysql')).not_to be_nil
146
+ @trocla.delete_password('delete_test3','mysql')
147
+ expect(@trocla.get_password('delete_test3','mysql')).to be_nil
148
+ expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
149
+ end
136
150
  end
137
151
 
138
- it "deletes only a given non-plain format" do
139
- expect(@trocla.password('delete_test3','mysql')).not_to be_nil
140
- expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
152
+ context 'concurrent access' do
153
+ context 'on expensive flagged formats' do
154
+ before(:each) do
155
+ expect(Trocla::Formats).to receive(:[]).with('sleep').at_least(:once).and_return(Trocla::Formats::Sleep)
156
+ expect(Trocla::Formats::Sleep).to receive(:expensive?).at_least(:once).and_return(true)
157
+ expect(Trocla::Formats).to receive(:available?).with('sleep').at_least(:once).and_return(true)
158
+ end
159
+ it 'should not overwrite a value if it takes longer' do
160
+ t1 = Thread.new{ @trocla.password('threadpwd','sleep','sleep' => 4) }
161
+ t2 = Thread.new{ @trocla.password('threadpwd','sleep','sleep' => 1) }
162
+ pwd1 = t1.value
163
+ pwd2 = t2.value
164
+ real_value = @trocla.password('threadpwd','sleep')
165
+ # as t2 finished first this should win
166
+ expect(pwd1).to eql(pwd2)
167
+ expect(real_value).to eql(pwd1)
168
+ expect(real_value).to eql(pwd2)
169
+ end
170
+ end
171
+ context 'on inexpensive flagged formats' do
172
+ before(:each) do
173
+ expect(Trocla::Formats).to receive(:[]).with('sleep').at_least(:once).and_return(Trocla::Formats::Sleep)
174
+ expect(Trocla::Formats::Sleep).to receive(:expensive?).at_least(:once).and_return(false)
175
+ expect(Trocla::Formats).to receive(:available?).with('sleep').at_least(:once).and_return(true)
176
+ end
177
+ it 'should not overwrite a value if it takes longer' do
178
+ t1 = Thread.new{ @trocla.password('threadpwd_inexp','sleep','sleep' => 4) }
179
+ t2 = Thread.new{ @trocla.password('threadpwd_inexp','sleep','sleep' => 1) }
180
+ pwd1 = t1.value
181
+ pwd2 = t2.value
182
+ real_value = @trocla.password('threadpwd_inexp','sleep')
183
+ # as t2 finished first but the format is inexpensive it gets overwritten
184
+ expect(pwd1).not_to eql(pwd2)
185
+ expect(real_value).to eql(pwd1)
186
+ expect(real_value).not_to eql(pwd2)
187
+ end
188
+ end
189
+ context 'real world example' do
190
+ it 'should store the quicker one' do
191
+ t1 = Thread.new{ @trocla.password('threadpwd_real','bcrypt','cost' => 17) }
192
+ t2 = Thread.new{ @trocla.password('threadpwd_real','bcrypt') }
193
+ pwd1 = t1.value
194
+ pwd2 = t2.value
195
+ real_value = @trocla.password('threadpwd_real','bcrypt')
196
+ # t2 should still win but both should be the same
197
+ expect(pwd1).to eql(pwd2)
198
+ expect(real_value).to eql(pwd1)
199
+ expect(real_value).to eql(pwd2)
200
+ end
201
+ it 'should store the quicker one test 2' do
202
+ t1 = Thread.new{ @trocla.password('my_shiny_selfsigned_ca', 'x509', {
203
+ 'CN' => 'This is my self-signed certificate',
204
+ 'become_ca' => false,
205
+ }) }
206
+ t2 = Thread.new{ @trocla.password('my_shiny_selfsigned_ca', 'x509', {
207
+ 'CN' => 'This is my self-signed certificate',
208
+ 'become_ca' => false,
209
+ }) }
210
+ cert1 = t1.value
211
+ cert2 = t2.value
212
+ real_value = @trocla.password('my_shiny_selfsigned_ca','x509')
213
+ # t2 should still win but both should be the same
214
+ expect(cert1).to eql(cert2)
215
+ expect(real_value).to eql(cert1)
216
+ expect(real_value).to eql(cert2)
217
+ end
218
+ end
219
+ end
141
220
 
142
- @trocla.delete_password('delete_test3','mysql')
143
- expect(@trocla.get_password('delete_test3','mysql')).to be_nil
144
- expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
221
+ end
222
+ context 'with .open' do
223
+ it 'closes the connection with a block' do
224
+ expect_any_instance_of(Trocla::Stores::Memory).to receive(:close)
225
+ Trocla.open{|t|
226
+ t.password('plain_open','plain')
227
+ }
228
+ end
229
+ it 'keeps the connection without a block' do
230
+ expect_any_instance_of(Trocla::Stores::Memory).not_to receive(:close)
231
+ Trocla.open.password('plain_open','plain')
145
232
  end
146
233
  end
147
234
 
@@ -2,19 +2,19 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: trocla 0.2.3 ruby lib
5
+ # stub: trocla 0.3.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
- s.name = "trocla"
9
- s.version = "0.2.3"
8
+ s.name = "trocla".freeze
9
+ s.version = "0.3.0"
10
10
 
11
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
- s.require_paths = ["lib"]
13
- s.authors = ["mh"]
14
- s.date = "2016-02-15"
15
- s.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival."
16
- s.email = "mh+trocla@immerda.ch"
17
- s.executables = ["trocla"]
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib".freeze]
13
+ s.authors = ["mh".freeze]
14
+ s.date = "2017-08-04"
15
+ s.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival.".freeze
16
+ s.email = "mh+trocla@immerda.ch".freeze
17
+ s.executables = ["trocla".freeze]
18
18
  s.extra_rdoc_files = [
19
19
  "LICENSE.txt",
20
20
  "README.md"
@@ -64,39 +64,39 @@ Gem::Specification.new do |s|
64
64
  "spec/trocla_spec.rb",
65
65
  "trocla.gemspec"
66
66
  ]
67
- s.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/"
68
- s.licenses = ["GPLv3"]
69
- s.rubygems_version = "2.2.2"
70
- s.summary = "Trocla a simple password generator and storage"
67
+ s.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/".freeze
68
+ s.licenses = ["GPLv3".freeze]
69
+ s.rubygems_version = "2.6.11".freeze
70
+ s.summary = "Trocla a simple password generator and storage".freeze
71
71
 
72
72
  if s.respond_to? :specification_version then
73
73
  s.specification_version = 4
74
74
 
75
75
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
76
- s.add_runtime_dependency(%q<moneta>, [">= 0"])
77
- s.add_runtime_dependency(%q<highline>, [">= 0"])
78
- s.add_runtime_dependency(%q<bcrypt>, [">= 0"])
79
- s.add_development_dependency(%q<rspec>, [">= 0"])
80
- s.add_development_dependency(%q<rdoc>, [">= 0"])
81
- s.add_development_dependency(%q<jeweler>, [">= 0"])
82
- s.add_development_dependency(%q<rspec-pending_for>, [">= 0"])
76
+ s.add_runtime_dependency(%q<moneta>.freeze, [">= 0"])
77
+ s.add_runtime_dependency(%q<highline>.freeze, [">= 0"])
78
+ s.add_runtime_dependency(%q<bcrypt>.freeze, [">= 0"])
79
+ s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
80
+ s.add_development_dependency(%q<rdoc>.freeze, [">= 0"])
81
+ s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
82
+ s.add_development_dependency(%q<rspec-pending_for>.freeze, [">= 0"])
83
83
  else
84
- s.add_dependency(%q<moneta>, [">= 0"])
85
- s.add_dependency(%q<highline>, [">= 0"])
86
- s.add_dependency(%q<bcrypt>, [">= 0"])
87
- s.add_dependency(%q<rspec>, [">= 0"])
88
- s.add_dependency(%q<rdoc>, [">= 0"])
89
- s.add_dependency(%q<jeweler>, [">= 0"])
90
- s.add_dependency(%q<rspec-pending_for>, [">= 0"])
84
+ s.add_dependency(%q<moneta>.freeze, [">= 0"])
85
+ s.add_dependency(%q<highline>.freeze, [">= 0"])
86
+ s.add_dependency(%q<bcrypt>.freeze, [">= 0"])
87
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
88
+ s.add_dependency(%q<rdoc>.freeze, [">= 0"])
89
+ s.add_dependency(%q<jeweler>.freeze, [">= 0"])
90
+ s.add_dependency(%q<rspec-pending_for>.freeze, [">= 0"])
91
91
  end
92
92
  else
93
- s.add_dependency(%q<moneta>, [">= 0"])
94
- s.add_dependency(%q<highline>, [">= 0"])
95
- s.add_dependency(%q<bcrypt>, [">= 0"])
96
- s.add_dependency(%q<rspec>, [">= 0"])
97
- s.add_dependency(%q<rdoc>, [">= 0"])
98
- s.add_dependency(%q<jeweler>, [">= 0"])
99
- s.add_dependency(%q<rspec-pending_for>, [">= 0"])
93
+ s.add_dependency(%q<moneta>.freeze, [">= 0"])
94
+ s.add_dependency(%q<highline>.freeze, [">= 0"])
95
+ s.add_dependency(%q<bcrypt>.freeze, [">= 0"])
96
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
97
+ s.add_dependency(%q<rdoc>.freeze, [">= 0"])
98
+ s.add_dependency(%q<jeweler>.freeze, [">= 0"])
99
+ s.add_dependency(%q<rspec-pending_for>.freeze, [">= 0"])
100
100
  end
101
101
  end
102
102
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trocla
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-15 00:00:00.000000000 Z
11
+ date: 2017-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: moneta
@@ -181,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
181
  version: '0'
182
182
  requirements: []
183
183
  rubyforge_project:
184
- rubygems_version: 2.2.2
184
+ rubygems_version: 2.6.11
185
185
  signing_key:
186
186
  specification_version: 4
187
187
  summary: Trocla a simple password generator and storage