trocla 0.2.3 → 0.3.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 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