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 +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile +18 -1
- data/README.md +5 -0
- data/bin/trocla +16 -12
- data/ext/redhat/rubygem-trocla.spec +6 -4
- data/lib/VERSION +2 -2
- data/lib/trocla.rb +24 -4
- data/lib/trocla/formats.rb +11 -0
- data/lib/trocla/formats/bcrypt.rb +2 -1
- data/lib/trocla/formats/x509.rb +1 -0
- data/lib/trocla/store.rb +6 -0
- data/lib/trocla/stores/moneta.rb +4 -0
- data/lib/trocla/util.rb +4 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/trocla/formats/x509_spec.rb +6 -4
- data/spec/trocla/util_spec.rb +8 -0
- data/spec/trocla_spec.rb +190 -103
- data/trocla.gemspec +35 -35
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fe321c3e5f61203499ad978f32fef9b9617bf54
|
4
|
+
data.tar.gz: 276a6191e0342e7d26c185a06b383f357483b300
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cda445d5bab3d8b96a56990b2b9e447869a44e02bb4f28ca9d29ffce8578aa1e1cb4d5dedb7bec0e0102cc42416fed6361f1c9ea276c2b9fa2b1b7e1ab99698a
|
7
|
+
data.tar.gz: a59f1905c9877eda6ff3614b84f50e6598c75be420ba78c4e324f0b5c6c72584efda35a711477c35571aeac804de4e8f6ff1a3de80e3f2f220a825ce0d52056d
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
78
|
-
|
79
|
-
|
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
|
data/lib/VERSION
CHANGED
data/lib/trocla.rb
CHANGED
@@ -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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/trocla/formats.rb
CHANGED
@@ -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
|
data/lib/trocla/formats/x509.rb
CHANGED
@@ -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)
|
data/lib/trocla/store.rb
CHANGED
@@ -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.
|
data/lib/trocla/stores/moneta.rb
CHANGED
data/lib/trocla/util.rb
CHANGED
@@ -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
|
data/spec/spec_helper.rb
CHANGED
@@ -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}
|
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
|
-
|
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
|
data/spec/trocla/util_spec.rb
CHANGED
@@ -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
|
data/spec/trocla_spec.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
26
|
-
|
27
|
-
expect(
|
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
|
31
|
-
pwd = @trocla.password('
|
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
|
-
|
39
|
-
it "
|
40
|
-
expect
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
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
|
51
|
-
|
52
|
-
expect(
|
53
|
-
expect(
|
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
|
-
|
58
|
-
|
59
|
-
expect(
|
60
|
-
expect(
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
119
|
+
expect(@trocla.get_password('reset_pwd2','mysql')).to eq(mysql)
|
120
|
+
end
|
106
121
|
end
|
107
122
|
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
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
|
|
data/trocla.gemspec
CHANGED
@@ -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.
|
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.
|
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 = "
|
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.
|
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
|
77
|
-
s.add_runtime_dependency(%q<highline
|
78
|
-
s.add_runtime_dependency(%q<bcrypt
|
79
|
-
s.add_development_dependency(%q<rspec
|
80
|
-
s.add_development_dependency(%q<rdoc
|
81
|
-
s.add_development_dependency(%q<jeweler
|
82
|
-
s.add_development_dependency(%q<rspec-pending_for
|
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
|
85
|
-
s.add_dependency(%q<highline
|
86
|
-
s.add_dependency(%q<bcrypt
|
87
|
-
s.add_dependency(%q<rspec
|
88
|
-
s.add_dependency(%q<rdoc
|
89
|
-
s.add_dependency(%q<jeweler
|
90
|
-
s.add_dependency(%q<rspec-pending_for
|
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
|
94
|
-
s.add_dependency(%q<highline
|
95
|
-
s.add_dependency(%q<bcrypt
|
96
|
-
s.add_dependency(%q<rspec
|
97
|
-
s.add_dependency(%q<rdoc
|
98
|
-
s.add_dependency(%q<jeweler
|
99
|
-
s.add_dependency(%q<rspec-pending_for
|
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.
|
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:
|
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.
|
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
|