trocla-ruby2 0.4.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 +7 -0
- data/.document +4 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +51 -0
- data/LICENSE.txt +15 -0
- data/README.md +351 -0
- data/Rakefile +53 -0
- data/bin/trocla +148 -0
- data/ext/redhat/rubygem-trocla.spec +120 -0
- data/lib/VERSION +4 -0
- data/lib/trocla.rb +162 -0
- data/lib/trocla/default_config.yaml +47 -0
- data/lib/trocla/encryptions.rb +54 -0
- data/lib/trocla/encryptions/none.rb +10 -0
- data/lib/trocla/encryptions/ssl.rb +51 -0
- data/lib/trocla/formats.rb +54 -0
- data/lib/trocla/formats/bcrypt.rb +7 -0
- data/lib/trocla/formats/md5crypt.rb +6 -0
- data/lib/trocla/formats/mysql.rb +6 -0
- data/lib/trocla/formats/pgsql.rb +7 -0
- data/lib/trocla/formats/plain.rb +7 -0
- data/lib/trocla/formats/sha1.rb +7 -0
- data/lib/trocla/formats/sha256crypt.rb +6 -0
- data/lib/trocla/formats/sha512crypt.rb +6 -0
- data/lib/trocla/formats/ssha.rb +9 -0
- data/lib/trocla/formats/sshkey.rb +46 -0
- data/lib/trocla/formats/x509.rb +197 -0
- data/lib/trocla/store.rb +80 -0
- data/lib/trocla/stores.rb +39 -0
- data/lib/trocla/stores/memory.rb +56 -0
- data/lib/trocla/stores/moneta.rb +58 -0
- data/lib/trocla/util.rb +71 -0
- data/lib/trocla/version.rb +22 -0
- data/spec/data/.keep +0 -0
- data/spec/spec_helper.rb +290 -0
- data/spec/trocla/encryptions/none_spec.rb +22 -0
- data/spec/trocla/encryptions/ssl_spec.rb +26 -0
- data/spec/trocla/formats/x509_spec.rb +375 -0
- data/spec/trocla/store/memory_spec.rb +6 -0
- data/spec/trocla/store/moneta_spec.rb +6 -0
- data/spec/trocla/util_spec.rb +54 -0
- data/spec/trocla_spec.rb +248 -0
- data/trocla-ruby2.gemspec +104 -0
- metadata +202 -0
data/lib/trocla/store.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# implements the default store behavior
|
2
|
+
class Trocla::Store
|
3
|
+
attr_reader :store_config, :trocla
|
4
|
+
def initialize(config,trocla)
|
5
|
+
@store_config = config
|
6
|
+
@trocla = trocla
|
7
|
+
end
|
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
|
+
|
15
|
+
# should return value for key & format
|
16
|
+
# returns nil if nothing or a nil value
|
17
|
+
# was found.
|
18
|
+
# If a key is expired it must return nil.
|
19
|
+
def get(key,format)
|
20
|
+
raise 'not implemented'
|
21
|
+
end
|
22
|
+
|
23
|
+
# sets value for key & format
|
24
|
+
# setting the plain format must invalidate
|
25
|
+
# all other formats as they should either
|
26
|
+
# be derived from plain or set directly.
|
27
|
+
# options is a hash containing further
|
28
|
+
# information for the store. e.g. expiration
|
29
|
+
# of a key. Keys can have an expiration /
|
30
|
+
# timeout by setting `expires` within
|
31
|
+
# the options hashs. Value of `expires`
|
32
|
+
# must be an integer indicating the
|
33
|
+
# amount of seconds a key can live with.
|
34
|
+
# This mechanism is expected to be
|
35
|
+
# be implemented by the backend.
|
36
|
+
def set(key,format,value,options={})
|
37
|
+
if format == 'plain'
|
38
|
+
set_plain(key,value,options)
|
39
|
+
else
|
40
|
+
set_format(key,format,value,options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# deletes the value for format
|
45
|
+
# if format is nil everything is deleted
|
46
|
+
# returns value of format or hash of
|
47
|
+
# format => value # if everything is
|
48
|
+
# deleted.
|
49
|
+
def delete(key,format=nil)
|
50
|
+
format.nil? ? (delete_all(key)||{}) : delete_format(key,format)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
# sets a new plain value
|
55
|
+
# *must* invalidate all
|
56
|
+
# other formats
|
57
|
+
def set_plain(key,value,options)
|
58
|
+
raise 'not implemented'
|
59
|
+
end
|
60
|
+
|
61
|
+
# sets a value of a format
|
62
|
+
def set_format(key,format,value,options)
|
63
|
+
raise 'not implemented'
|
64
|
+
end
|
65
|
+
|
66
|
+
# deletes all entries of this key
|
67
|
+
# and returns a hash with all
|
68
|
+
# formats and values
|
69
|
+
# or nil if nothing is found
|
70
|
+
def delete_all(key)
|
71
|
+
raise 'not implemented'
|
72
|
+
end
|
73
|
+
|
74
|
+
# deletes the value of the passed
|
75
|
+
# key & format and returns the
|
76
|
+
# value.
|
77
|
+
def delete_format(key,format)
|
78
|
+
raise 'not implemented'
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'trocla/store'
|
2
|
+
# store management
|
3
|
+
class Trocla::Stores
|
4
|
+
class << self
|
5
|
+
def [](store)
|
6
|
+
stores[store.to_s.downcase]
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
@all ||= Dir[ path '*' ].collect do |store|
|
11
|
+
File.basename(store, '.rb').downcase
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def available?(store)
|
16
|
+
all.include?(store.to_s.downcase)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def stores
|
21
|
+
@@stores ||= Hash.new do |hash, store|
|
22
|
+
store = store.to_s.downcase
|
23
|
+
if File.exists?(path(store))
|
24
|
+
require "trocla/stores/#{store}"
|
25
|
+
class_name = "Trocla::Stores::#{store.capitalize}"
|
26
|
+
hash[store] = (eval class_name)
|
27
|
+
else
|
28
|
+
raise "Store #{store} is not supported!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def path(store)
|
34
|
+
File.expand_path(
|
35
|
+
File.join(File.dirname(__FILE__), 'stores', "#{store}.rb")
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# a simple in memory store just as an example
|
2
|
+
class Trocla::Stores::Memory < Trocla::Store
|
3
|
+
attr_reader :memory
|
4
|
+
def initialize(config,trocla)
|
5
|
+
super(config,trocla)
|
6
|
+
@memory = Hash.new({})
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(key,format)
|
10
|
+
unless expired?(key)
|
11
|
+
memory[key][format]
|
12
|
+
else
|
13
|
+
delete_all(key)
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def set(key,format,value,options={})
|
18
|
+
super(key,format,value,options)
|
19
|
+
set_expires(key,options['expires'])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def set_plain(key,value,options)
|
24
|
+
memory[key] = { 'plain' => value }
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_format(key,format,value,options)
|
28
|
+
memory[key].merge!({ format => value })
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_all(key)
|
32
|
+
memory.delete(key)
|
33
|
+
end
|
34
|
+
def delete_format(key,format)
|
35
|
+
old_val = (h = memory[key]).delete(format)
|
36
|
+
h.empty? ? memory.delete(key) : memory[key] = h
|
37
|
+
set_expires(key,nil)
|
38
|
+
old_val
|
39
|
+
end
|
40
|
+
private
|
41
|
+
def set_expires(key,expires)
|
42
|
+
expires = memory[key]['_expires'] if expires.nil?
|
43
|
+
if expires && expires > 0
|
44
|
+
memory[key]['_expires'] = expires
|
45
|
+
memory[key]['_expires_at'] = Time.now + expires
|
46
|
+
else
|
47
|
+
memory[key].delete('_expires')
|
48
|
+
memory[key].delete('_expires_at')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def expired?(key)
|
52
|
+
memory.key?(key) &&
|
53
|
+
(a = memory[key]['_expires_at']).is_a?(Time) && \
|
54
|
+
(a < Time.now)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# the default moneta based store
|
2
|
+
class Trocla::Stores::Moneta < Trocla::Store
|
3
|
+
attr_reader :moneta
|
4
|
+
def initialize(config,trocla)
|
5
|
+
super(config,trocla)
|
6
|
+
require 'moneta'
|
7
|
+
# load expire support by default
|
8
|
+
adapter_options = { :expires => true }.merge(
|
9
|
+
store_config['adapter_options']||{})
|
10
|
+
@moneta = Moneta.new(store_config['adapter'],adapter_options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
moneta.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key,format)
|
18
|
+
moneta.fetch(key, {})[format]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def set_plain(key,value,options)
|
23
|
+
h = { 'plain' => value }
|
24
|
+
mo = moneta_options(key,options)
|
25
|
+
if options['expires'] && options['expires'] > 0
|
26
|
+
h['_expires'] = options['expires']
|
27
|
+
else
|
28
|
+
# be sure that we disable the existing
|
29
|
+
# expires if nothing is set.
|
30
|
+
mo[:expires] = false
|
31
|
+
end
|
32
|
+
moneta.store(key,h,mo)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_format(key,format,value,options)
|
36
|
+
moneta.store(key,
|
37
|
+
moneta.fetch(key,{}).merge({ format => value }),
|
38
|
+
moneta_options(key,options))
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_all(key)
|
42
|
+
moneta.delete(key)
|
43
|
+
end
|
44
|
+
def delete_format(key,format)
|
45
|
+
old_val = (h = moneta.fetch(key,{})).delete(format)
|
46
|
+
h.empty? ? moneta.delete(key) : moneta.store(key,h,moneta_options(key,{}))
|
47
|
+
old_val
|
48
|
+
end
|
49
|
+
def moneta_options(key,options)
|
50
|
+
res = {}
|
51
|
+
if options.key?('expires')
|
52
|
+
res[:expires] = options['expires']
|
53
|
+
elsif e = moneta.fetch(key, {})['_expires']
|
54
|
+
res[:expires] = e
|
55
|
+
end
|
56
|
+
res
|
57
|
+
end
|
58
|
+
end
|
data/lib/trocla/util.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
class Trocla
|
3
|
+
class Util
|
4
|
+
class << self
|
5
|
+
def random_str(length=12, charset='default')
|
6
|
+
_charsets = charsets[charset] || charsets['default']
|
7
|
+
(1..length).collect{|a| _charsets[SecureRandom.random_number(_charsets.size)] }.join.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def salt(length=8)
|
11
|
+
alphanumeric_size = alphanumeric.size
|
12
|
+
(1..length).collect{|a| alphanumeric[SecureRandom.random_number(alphanumeric_size)] }.join.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def charsets
|
18
|
+
@charsets ||= begin
|
19
|
+
h = {
|
20
|
+
'default' => chars,
|
21
|
+
'alphanumeric' => alphanumeric,
|
22
|
+
'shellsafe' => shellsafe,
|
23
|
+
'windowssafe' => windowssafe,
|
24
|
+
'numeric' => numeric,
|
25
|
+
'hexadecimal' => hexadecimal,
|
26
|
+
'consolesafe' => consolesafe,
|
27
|
+
'typesafe' => typesafe,
|
28
|
+
}
|
29
|
+
h.each { |k, v| h[k] = v.uniq }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def chars
|
34
|
+
@chars ||= shellsafe + special_chars
|
35
|
+
end
|
36
|
+
def shellsafe
|
37
|
+
@shellsafe ||= alphanumeric + shellsafe_chars
|
38
|
+
end
|
39
|
+
def windowssafe
|
40
|
+
@windowssafe ||= alphanumeric + windowssafe_chars
|
41
|
+
end
|
42
|
+
def consolesafe
|
43
|
+
@consolesafe ||= alphanumeric + consolesafe_chars
|
44
|
+
end
|
45
|
+
def hexadecimal
|
46
|
+
@hexadecimal ||= numeric + ('a'..'f').to_a
|
47
|
+
end
|
48
|
+
def alphanumeric
|
49
|
+
@alphanumeric ||= ('a'..'z').to_a + ('A'..'Z').to_a + numeric
|
50
|
+
end
|
51
|
+
def numeric
|
52
|
+
@numeric ||= ('0'..'9').to_a
|
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
|
57
|
+
def special_chars
|
58
|
+
@special_chars ||= "*()&![]{}-".split(//)
|
59
|
+
end
|
60
|
+
def shellsafe_chars
|
61
|
+
@shellsafe_chars ||= "+%/@=?_.,:".split(//)
|
62
|
+
end
|
63
|
+
def windowssafe_chars
|
64
|
+
@windowssafe_chars ||= "+%/@=?_.,".split(//)
|
65
|
+
end
|
66
|
+
def consolesafe_chars
|
67
|
+
@consolesafe_chars ||= '+.-,_'.split(//)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
class Trocla
|
3
|
+
class VERSION
|
4
|
+
version = {}
|
5
|
+
File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
|
6
|
+
type, value = line.chomp.split(":")
|
7
|
+
next if type =~ /^\s+$/ || value =~ /^\s+$/
|
8
|
+
version[type] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
MAJOR = version['major']
|
12
|
+
MINOR = version['minor']
|
13
|
+
PATCH = version['patch']
|
14
|
+
BUILD = version['build']
|
15
|
+
|
16
|
+
STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
|
17
|
+
|
18
|
+
def self.version
|
19
|
+
STRING
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/data/.keep
ADDED
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/pending_for'
|
5
|
+
require 'yaml'
|
6
|
+
require 'trocla'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.shared_examples "encryption_basics" do
|
13
|
+
describe 'storing' do
|
14
|
+
it "random passwords" do
|
15
|
+
expect(@trocla.password('random1', 'plain').length).to eql(16)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "long random passwords" do
|
19
|
+
expect(@trocla.set_password('random1_long','plain',4096.times.collect{|s| 'x' }.join('')).length).to eql(4096)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'retrieve' do
|
24
|
+
it "random passwords" do
|
25
|
+
stored = @trocla.password('random1', 'plain')
|
26
|
+
retrieved = @trocla.password('random1', 'plain')
|
27
|
+
retrieved_again = @trocla.password('random1', 'plain')
|
28
|
+
expect(retrieved).to eql(stored)
|
29
|
+
expect(retrieved_again).to eql(stored)
|
30
|
+
expect(retrieved_again).to eql(retrieved)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "encrypted passwords" do
|
34
|
+
@trocla.set_password('some_pass', 'plain', 'super secret')
|
35
|
+
expect(@trocla.get_password('some_pass', 'plain')).to eql('super secret')
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
describe 'deleting' do
|
40
|
+
it "plain" do
|
41
|
+
@trocla.set_password('some_pass', 'plain', 'super secret')
|
42
|
+
expect(@trocla.delete_password('some_pass', 'plain')).to eql('super secret')
|
43
|
+
end
|
44
|
+
it "delete formats" do
|
45
|
+
plain = @trocla.password('some_mysqlpass', 'plain')
|
46
|
+
mysql = @trocla.password('some_mysqlpass', 'mysql')
|
47
|
+
expect(@trocla.delete_password('some_mysqlpass', 'mysql')).to eql(mysql)
|
48
|
+
expect(@trocla.delete_password('some_mysqlpass', 'plain')).to eql(plain)
|
49
|
+
expect(@trocla.get_password('some_mysqlpass','plain')).to be_nil
|
50
|
+
expect(@trocla.get_password('some_mysqlpass','mysql')).to be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "all passwords" do
|
54
|
+
plain = @trocla.password('some_mysqlpass', 'plain')
|
55
|
+
mysql = @trocla.password('some_mysqlpass', 'mysql')
|
56
|
+
deleted = @trocla.delete_password('some_mysqlpass')
|
57
|
+
expect(deleted).to be_a_kind_of(Hash)
|
58
|
+
expect(deleted['plain']).to eql(plain)
|
59
|
+
expect(deleted['mysql']).to eql(mysql)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
RSpec.shared_examples "verify_encryption" do
|
64
|
+
it "does not store plaintext passwords" do
|
65
|
+
@trocla.set_password('noplain', 'plain', 'plaintext_password')
|
66
|
+
expect(File.readlines(trocla_yaml_file).grep(/plaintext_password/)).to be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "makes sure identical passwords do not match when stored" do
|
70
|
+
@trocla.set_password('one_key', 'plain', 'super secret')
|
71
|
+
@trocla.set_password('another_key', 'plain', 'super secret')
|
72
|
+
yaml = YAML.load_file(trocla_yaml_file)
|
73
|
+
expect(yaml['one_key']['plain']).not_to eq(yaml['another_key']['plain'])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
RSpec.shared_examples 'store_validation' do |store|
|
78
|
+
describe '.get' do
|
79
|
+
it { expect(store.get('some_key','plain')).to be_nil }
|
80
|
+
end
|
81
|
+
describe '.set' do
|
82
|
+
it 'stores nil values' do
|
83
|
+
store.set('some_nil_value','plain',nil)
|
84
|
+
expect(store.get('some_nil_value','plain')).to be_nil
|
85
|
+
end
|
86
|
+
it 'stores plain format' do
|
87
|
+
store.set('some_value','plain','value')
|
88
|
+
expect(store.get('some_value','plain')).to eql('value')
|
89
|
+
end
|
90
|
+
it 'stores other formats' do
|
91
|
+
store.set('some_value','foo','bla')
|
92
|
+
expect(store.get('some_value','foo')).to eql('bla')
|
93
|
+
end
|
94
|
+
it 'resets other formats on setting plain' do
|
95
|
+
store.set('some_value','foo','bla')
|
96
|
+
store.set('some_value','plain','value')
|
97
|
+
expect(store.get('some_value','plain')).to eql('value')
|
98
|
+
expect(store.get('some_value','foo')).to be_nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
describe '.delete' do
|
102
|
+
it { expect(store.delete('something','foo')).to be_nil }
|
103
|
+
it { expect(store.delete('something')).to be_empty }
|
104
|
+
it 'deletes the value of a format' do
|
105
|
+
store.set('some_value','foo','bla')
|
106
|
+
expect(store.delete('some_value','foo')).to eql('bla')
|
107
|
+
expect(store.get('some_value','foo')).to be_nil
|
108
|
+
end
|
109
|
+
it 'deletes only the value of a format' do
|
110
|
+
store.set('some_value','plain','value')
|
111
|
+
store.set('some_value','foo','bla')
|
112
|
+
expect(store.delete('some_value','plain')).to eql('value')
|
113
|
+
expect(store.get('some_value','plain')).to be_nil
|
114
|
+
expect(store.get('some_value','foo')).to eql('bla')
|
115
|
+
end
|
116
|
+
it 'deletes all values without a format' do
|
117
|
+
store.set('some_value','plain','value')
|
118
|
+
store.set('some_value','foo','bla')
|
119
|
+
hash = store.delete('some_value')
|
120
|
+
expect(hash).to be_a_kind_of(Hash)
|
121
|
+
expect(hash['plain']).to eql('value')
|
122
|
+
expect(hash['foo']).to eql('bla')
|
123
|
+
expect(store.get('some_value','plain')).to be_nil
|
124
|
+
expect(store.get('some_value','foo')).to be_nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
describe 'expiration' do
|
128
|
+
it 'will not return an expired key' do
|
129
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
|
130
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
131
|
+
sleep 3
|
132
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
133
|
+
end
|
134
|
+
it 'increases expiration when setting anything for that key' do
|
135
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
|
136
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
137
|
+
sleep 1
|
138
|
+
store.set('some_expiring_value','bla','bla_to_be_expired',{ 'expires' => 3 })
|
139
|
+
sleep 2
|
140
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
141
|
+
sleep 2
|
142
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
143
|
+
end
|
144
|
+
it 'keeps expiration when setting another value' do
|
145
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
|
146
|
+
store.set('some_expiring_value','foo','to_be_expired_foo')
|
147
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
148
|
+
sleep 3
|
149
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
150
|
+
expect(store.get('some_expiring_value','foo')).to be_nil
|
151
|
+
end
|
152
|
+
it 'setting plain clears everything including expiration' do
|
153
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
|
154
|
+
sleep 1
|
155
|
+
store.set('some_expiring_value','plain','to_be_expired2')
|
156
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
157
|
+
sleep 3
|
158
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
159
|
+
end
|
160
|
+
it 'extends expiration when setting another value' do
|
161
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 4 })
|
162
|
+
sleep 2
|
163
|
+
store.set('some_expiring_value','foo','to_be_expired_foo')
|
164
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
165
|
+
sleep 3
|
166
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
167
|
+
sleep 2
|
168
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
169
|
+
end
|
170
|
+
it 'extends expiration when deleting a format' do
|
171
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 4 })
|
172
|
+
store.set('some_expiring_value','foo','to_be_expired2')
|
173
|
+
sleep 2
|
174
|
+
expect(store.delete('some_expiring_value','foo')).to eql('to_be_expired2')
|
175
|
+
sleep 3
|
176
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
177
|
+
sleep 2
|
178
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
179
|
+
end
|
180
|
+
it 'keeps expiration although we\'re fetching a value' do
|
181
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 3 })
|
182
|
+
sleep 2
|
183
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
184
|
+
sleep 2
|
185
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
186
|
+
end
|
187
|
+
it 'readding a value with an expiration makes it expiring in the future' do
|
188
|
+
store.set('some_expiring_value','plain','to_be_expired')
|
189
|
+
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
|
190
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
191
|
+
sleep 3
|
192
|
+
expect(store.get('some_expiring_value','plain')).to be_nil
|
193
|
+
end
|
194
|
+
it 'setting an expires of false removes expiration' do
|
195
|
+
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
|
196
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
197
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => false })
|
198
|
+
sleep 3
|
199
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
200
|
+
end
|
201
|
+
it 'setting an expires of 0 removes expiration' do
|
202
|
+
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
|
203
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
204
|
+
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 0 })
|
205
|
+
sleep 3
|
206
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
|
207
|
+
end
|
208
|
+
it 'setting an expires of false removes expiration even if it\'s for a different format' do
|
209
|
+
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
|
210
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
211
|
+
store.set('some_expiring_value','foo','to_be_expired_foo',{ 'expires' => false })
|
212
|
+
sleep 3
|
213
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
214
|
+
expect(store.get('some_expiring_value','foo')).to eql('to_be_expired_foo')
|
215
|
+
end
|
216
|
+
it 'setting an expires of 0 removes expiration even if it\'s for a different format' do
|
217
|
+
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
|
218
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
219
|
+
store.set('some_expiring_value','foo','to_be_expired_foo',{ 'expires' => 0 })
|
220
|
+
sleep 3
|
221
|
+
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
|
222
|
+
expect(store.get('some_expiring_value','foo')).to eql('to_be_expired_foo')
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def default_config
|
228
|
+
@default_config ||= YAML.load(File.read(File.expand_path(base_dir+'/lib/trocla/default_config.yaml')))
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_config
|
232
|
+
@config ||= default_config.merge({
|
233
|
+
'store' => :memory,
|
234
|
+
})
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_config_persistent
|
238
|
+
@config ||= default_config.merge({
|
239
|
+
'store_options' => {
|
240
|
+
'adapter' => :YAML,
|
241
|
+
'adapter_options' => {
|
242
|
+
:file => trocla_yaml_file
|
243
|
+
},
|
244
|
+
},
|
245
|
+
})
|
246
|
+
end
|
247
|
+
|
248
|
+
def ssl_test_config
|
249
|
+
@ssl_config ||= test_config_persistent.merge({
|
250
|
+
'encryption' => :ssl,
|
251
|
+
'encryption_options' => {
|
252
|
+
:private_key => data_dir('trocla.key'),
|
253
|
+
:public_key => data_dir('trocla.pub'),
|
254
|
+
},
|
255
|
+
})
|
256
|
+
end
|
257
|
+
|
258
|
+
def base_dir
|
259
|
+
File.dirname(__FILE__)+'/../'
|
260
|
+
end
|
261
|
+
|
262
|
+
def data_dir(file = nil)
|
263
|
+
File.expand_path(File.join(base_dir, 'spec/data', file))
|
264
|
+
end
|
265
|
+
|
266
|
+
def trocla_yaml_file
|
267
|
+
data_dir('trocla_store.yaml')
|
268
|
+
end
|
269
|
+
|
270
|
+
def generate_ssl_keys
|
271
|
+
require 'openssl'
|
272
|
+
rsa_key = OpenSSL::PKey::RSA.new(4096)
|
273
|
+
File.open(data_dir('trocla.key'), 'w') { |f| f.write(rsa_key.to_pem) }
|
274
|
+
File.open(data_dir('trocla.pub'), 'w') { |f| f.write(rsa_key.public_key.to_pem) }
|
275
|
+
end
|
276
|
+
|
277
|
+
def remove_ssl_keys
|
278
|
+
File.unlink(data_dir('trocla.key'))
|
279
|
+
File.unlink(data_dir('trocla.pub'))
|
280
|
+
end
|
281
|
+
|
282
|
+
def remove_yaml_store
|
283
|
+
File.unlink(trocla_yaml_file)
|
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
|