certmeister 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +20 -0
- data/README.md +32 -0
- data/Rakefile +6 -0
- data/certmeister.gemspec +26 -0
- data/fixtures/ca.crt +15 -0
- data/fixtures/ca.csr +12 -0
- data/fixtures/ca.key +15 -0
- data/fixtures/client.crt +15 -0
- data/fixtures/client.csr +12 -0
- data/fixtures/client.key +15 -0
- data/lib/certmeister.rb +14 -0
- data/lib/certmeister/base.rb +92 -0
- data/lib/certmeister/config.rb +129 -0
- data/lib/certmeister/in_memory_store.rb +43 -0
- data/lib/certmeister/policy.rb +21 -0
- data/lib/certmeister/policy/blackhole.rb +15 -0
- data/lib/certmeister/policy/chain_all.rb +36 -0
- data/lib/certmeister/policy/domain.rb +37 -0
- data/lib/certmeister/policy/existing.rb +32 -0
- data/lib/certmeister/policy/fcrdns.rb +40 -0
- data/lib/certmeister/policy/noop.rb +15 -0
- data/lib/certmeister/policy/psk.rb +37 -0
- data/lib/certmeister/policy/response.rb +24 -0
- data/lib/certmeister/response.rb +47 -0
- data/lib/certmeister/store_error.rb +6 -0
- data/lib/certmeister/test/memory_store_interface.rb +54 -0
- data/lib/certmeister/version.rb +5 -0
- data/signit.rb +39 -0
- data/spec/certmeister/base_spec.rb +205 -0
- data/spec/certmeister/config_spec.rb +170 -0
- data/spec/certmeister/in_memory_store_spec.rb +40 -0
- data/spec/certmeister/policy/blackhole_spec.rb +19 -0
- data/spec/certmeister/policy/chain_all_spec.rb +40 -0
- data/spec/certmeister/policy/domain_spec.rb +38 -0
- data/spec/certmeister/policy/existing_spec.rb +39 -0
- data/spec/certmeister/policy/fcrdns_spec.rb +45 -0
- data/spec/certmeister/policy/noop_spec.rb +17 -0
- data/spec/certmeister/policy/psk_spec.rb +38 -0
- data/spec/certmeister/policy/response_spec.rb +35 -0
- data/spec/certmeister/response_spec.rb +73 -0
- data/spec/helpers/certmeister_config_helper.rb +21 -0
- data/spec/helpers/certmeister_fetching_request_helper.rb +9 -0
- data/spec/helpers/certmeister_policy_helper.rb +14 -0
- data/spec/helpers/certmeister_removing_request_helper.rb +9 -0
- data/spec/helpers/certmeister_signing_request_helper.rb +10 -0
- data/spec/spec_helper.rb +20 -0
- metadata +159 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/certmeister_config_helper'
|
3
|
+
require 'helpers/certmeister_signing_request_helper'
|
4
|
+
|
5
|
+
require 'certmeister'
|
6
|
+
require 'openssl'
|
7
|
+
|
8
|
+
describe Certmeister do
|
9
|
+
|
10
|
+
it "is configured at instantiation" do
|
11
|
+
expect { Certmeister.new(CertmeisterConfigHelper::valid_config) }.to_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "cannot be instantiated with an invalid config" do
|
15
|
+
expect { Certmeister.new(Certmeister::Config.new({})) }.to raise_error(RuntimeError, /invalid config/)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#sign(request)" do
|
19
|
+
|
20
|
+
let(:valid_request) { CertmeisterSigningRequestHelper::valid_request }
|
21
|
+
|
22
|
+
describe "refuses" do
|
23
|
+
|
24
|
+
it "refuses the request if it has no cn" do
|
25
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
26
|
+
response = ca.sign({})
|
27
|
+
invalid_request = valid_request.tap { |o| o.delete(:cn) }
|
28
|
+
response = ca.sign(invalid_request)
|
29
|
+
expect(response.error).to match /CN/
|
30
|
+
end
|
31
|
+
|
32
|
+
it "refuses the request if the sign policy declines it" do
|
33
|
+
options = CertmeisterConfigHelper::valid_config_options
|
34
|
+
options[:sign_policy] = Certmeister::Policy::Blackhole.new
|
35
|
+
ca = Certmeister.new(Certmeister::Config.new(options))
|
36
|
+
response = ca.sign(valid_request)
|
37
|
+
expect(response.error).to eql "request refused (blackholed)"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "refuses to sign an invalid CSR" do
|
41
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
42
|
+
invalid_request = valid_request.tap { |o| o[:csr] = "a terrible misunderstanding" }
|
43
|
+
response = ca.sign(invalid_request)
|
44
|
+
expect(response.error).to eql "invalid CSR (not enough data)"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "refuses to sign a CSR if the subject does not agree with the request CN" do
|
48
|
+
request = valid_request.tap { |r| r[:cn] = "monkeyface.example.com" }
|
49
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
50
|
+
response = ca.sign(request)
|
51
|
+
expect(response.error).to eql "CSR subject (axl.hetzner.africa) disagrees with request CN (monkeyface.example.com)"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "signing" do
|
57
|
+
|
58
|
+
def sign_valid_request
|
59
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
60
|
+
ca.sign(valid_request)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "signs a CSR if the sign policy passes the request" do
|
64
|
+
response = sign_valid_request
|
65
|
+
expect(response).to be_hit
|
66
|
+
end
|
67
|
+
|
68
|
+
it "sets the issuer to the subject of the CA certificate" do
|
69
|
+
response = sign_valid_request
|
70
|
+
cert = OpenSSL::X509::Certificate.new(response.pem)
|
71
|
+
expect(cert.issuer.to_s).to match /CN=Certmeister Test CA/
|
72
|
+
end
|
73
|
+
|
74
|
+
it "sets the subject to the subject of the CSR" do
|
75
|
+
response = sign_valid_request
|
76
|
+
cert = OpenSSL::X509::Certificate.new(response.pem)
|
77
|
+
expect(cert.subject.to_s).to match /CN=axl.hetzner.africa/
|
78
|
+
end
|
79
|
+
|
80
|
+
it "sets validity to 5 years from now" do
|
81
|
+
now = (DateTime.now.to_time - 1)
|
82
|
+
response = sign_valid_request
|
83
|
+
cert = OpenSSL::X509::Certificate.new(response.pem)
|
84
|
+
expect(cert.not_before).to be >= now
|
85
|
+
expect(cert.not_after - cert.not_before).to be < (5 * 365 * 24 * 60 * 60 + 2)
|
86
|
+
expect(cert.not_after - cert.not_before).to be >= (5 * 365 * 24 * 60 * 60)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "stores the signed certificate, indexed on request CN" do
|
90
|
+
config = CertmeisterConfigHelper::valid_config
|
91
|
+
ca = Certmeister.new(config)
|
92
|
+
response = ca.sign(valid_request)
|
93
|
+
stored = config.store.fetch('axl.hetzner.africa')
|
94
|
+
expect(stored).to eql response.pem
|
95
|
+
end
|
96
|
+
|
97
|
+
it "does not capture errors from the store" do
|
98
|
+
config = CertmeisterConfigHelper::valid_config
|
99
|
+
config.store.send(:break!)
|
100
|
+
ca = Certmeister.new(config)
|
101
|
+
expect { ca.sign(valid_request) }.to raise_error(Certmeister::StoreError)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#fetch(request)" do
|
109
|
+
|
110
|
+
describe "refuses" do
|
111
|
+
|
112
|
+
it "refuses the request if it has no cn" do
|
113
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
114
|
+
response = ca.fetch({})
|
115
|
+
expect(response.error).to match /CN/
|
116
|
+
end
|
117
|
+
|
118
|
+
it "refuses the request if the fetch policy declines it" do
|
119
|
+
options = CertmeisterConfigHelper::valid_config_options
|
120
|
+
options[:fetch_policy] = Certmeister::Policy::Blackhole.new
|
121
|
+
ca = Certmeister.new(Certmeister::Config.new(options))
|
122
|
+
response = ca.fetch(cn: 'axl.starjuice.net')
|
123
|
+
expect(response.error).to eql "request refused (blackholed)"
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns a miss if the store has no certificate for the cn" do
|
129
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
130
|
+
expect(ca.fetch(cn: 'axl.starjuice.net')).to be_miss
|
131
|
+
end
|
132
|
+
|
133
|
+
it "returns the certificate as a PEM-encoded string when the store has a certificate for the cn" do
|
134
|
+
config = CertmeisterConfigHelper::valid_config
|
135
|
+
config.store.store('axl.starjuice.net', '...')
|
136
|
+
ca = Certmeister.new(config)
|
137
|
+
expect(ca.fetch(cn: 'axl.starjuice.net').pem).to eql '...'
|
138
|
+
end
|
139
|
+
|
140
|
+
class StoreWithBrokenFetch
|
141
|
+
def store(cn, cert); end
|
142
|
+
def fetch(cn); raise Certmeister::StoreError.new("simulated error"); end
|
143
|
+
def health_check; end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "does not capture errors from the store" do
|
147
|
+
config = CertmeisterConfigHelper::valid_config
|
148
|
+
config.store.send(:break!)
|
149
|
+
ca = Certmeister.new(config)
|
150
|
+
expect { ca.fetch(cn: 'axl.starjuice.net') }.to raise_error(Certmeister::StoreError)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#remove(cn)" do
|
156
|
+
|
157
|
+
describe "refuses" do
|
158
|
+
|
159
|
+
it "refuses the request if it has no cn" do
|
160
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
161
|
+
response = ca.remove({})
|
162
|
+
expect(response.error).to match /CN/
|
163
|
+
end
|
164
|
+
|
165
|
+
it "refuses the request if the fetch policy declines it" do
|
166
|
+
options = CertmeisterConfigHelper::valid_config_options
|
167
|
+
options[:remove_policy] = Certmeister::Policy::Blackhole.new
|
168
|
+
ca = Certmeister.new(Certmeister::Config.new(options))
|
169
|
+
response = ca.remove(cn: 'axl.starjuice.net')
|
170
|
+
expect(response.error).to eql "request refused (blackholed)"
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
it "returns a hit if the certificate existed in the store" do
|
176
|
+
config = CertmeisterConfigHelper::valid_config
|
177
|
+
config.store.store('axl.starjuice.net', '...')
|
178
|
+
ca = Certmeister.new(config)
|
179
|
+
expect(ca.remove(cn: 'axl.starjuice.net')).to be_hit
|
180
|
+
end
|
181
|
+
|
182
|
+
it "returns a miss if the certificate did not exist in the store" do
|
183
|
+
ca = Certmeister.new(CertmeisterConfigHelper::valid_config)
|
184
|
+
expect(ca.remove(cn: 'axl.starjuice.net')).to be_miss
|
185
|
+
end
|
186
|
+
|
187
|
+
it "removes the certificate from the store" do
|
188
|
+
config = CertmeisterConfigHelper::valid_config
|
189
|
+
config.store.store('axl.starjuice.net', '...')
|
190
|
+
ca = Certmeister.new(config)
|
191
|
+
ca.remove(cn: 'axl.starjuice.net')
|
192
|
+
expect(config.store.fetch('axl.starjuice.net')).to be_nil
|
193
|
+
end
|
194
|
+
|
195
|
+
it "does not capture errors from the store" do
|
196
|
+
config = CertmeisterConfigHelper::valid_config
|
197
|
+
config.store.send(:break!)
|
198
|
+
ca = Certmeister.new(config)
|
199
|
+
expect { ca.remove(cn: 'axl.starjuice.net') }.to raise_error(Certmeister::StoreError)
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/certmeister_config_helper'
|
3
|
+
require 'helpers/certmeister_policy_helper'
|
4
|
+
|
5
|
+
require 'certmeister'
|
6
|
+
|
7
|
+
describe Certmeister::Config do
|
8
|
+
|
9
|
+
let(:options) { CertmeisterConfigHelper::valid_config_options }
|
10
|
+
|
11
|
+
def config_option_is_required(option)
|
12
|
+
options.delete(option)
|
13
|
+
config = Certmeister::Config.new(options)
|
14
|
+
expect(config).to_not be_valid
|
15
|
+
expect(config.errors[option]).to eql "is required"
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_option_provides_method_with_arity(option, method, arity)
|
19
|
+
arity_name = case arity
|
20
|
+
when 0 then "nullary"
|
21
|
+
when 1 then "unary"
|
22
|
+
when 2 then "binary"
|
23
|
+
when 3 then "ternary"
|
24
|
+
else
|
25
|
+
raise "broken test helper does not support arity #{4}"
|
26
|
+
end
|
27
|
+
options[option].send(:define_singleton_method, method) { |wrong, number, of, arguments| }
|
28
|
+
|
29
|
+
config = Certmeister::Config.new(options)
|
30
|
+
expect(config).to_not be_valid
|
31
|
+
expect(config.errors[option]).to eql "must provide a #{arity_name} #{method} method"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "does not allow unknown options" do
|
35
|
+
options[:unknown] = 1
|
36
|
+
config = Certmeister::Config.new(options)
|
37
|
+
expect(config).to_not be_valid
|
38
|
+
expect(config.errors[:unknown]).to eql "is not a supported option"
|
39
|
+
end
|
40
|
+
|
41
|
+
describe ":ca_cert" do
|
42
|
+
|
43
|
+
it "is required" do
|
44
|
+
config_option_is_required(:ca_cert)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "must be a PEM-encoded x509 certificate" do
|
48
|
+
options[:ca_cert] = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n"
|
49
|
+
config = Certmeister::Config.new(options)
|
50
|
+
expect(config).to_not be_valid
|
51
|
+
expect(config.errors[:ca_cert]).to eql "must be a PEM-encoded x509 certificate (nested asn1 error)"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is accessible as an OpenSSL::X509::Certificate object" do
|
55
|
+
config = Certmeister::Config.new(options)
|
56
|
+
expect(config.ca_cert).to be_a(OpenSSL::X509::Certificate)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ":ca_key" do
|
62
|
+
|
63
|
+
it "is required" do
|
64
|
+
config_option_is_required(:ca_key)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "must be a string containing an x509 certificate in PEM encoding" do
|
68
|
+
options[:ca_key] = "-----BEGIN RSA PRIVATE KEY-----\n-----END RSA PRIVATE KEY-----\n"
|
69
|
+
config = Certmeister::Config.new(options)
|
70
|
+
expect(config).to_not be_valid
|
71
|
+
expect(config.errors[:ca_key]).to eql "must be a PEM-encoded private key (Could not parse PKey)"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "is accessible as an OpenSSL::PKey::PKey object" do
|
75
|
+
config = Certmeister::Config.new(options)
|
76
|
+
expect(config.ca_key).to be_a(OpenSSL::PKey::PKey)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe ":store" do
|
82
|
+
|
83
|
+
it "is required" do
|
84
|
+
config_option_is_required(:store)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "must provide a binary store method" do
|
88
|
+
config_option_provides_method_with_arity(:store, :store, 2)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "must provide a unary fetch method" do
|
92
|
+
config_option_provides_method_with_arity(:store, :fetch, 1)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "must provide a unary remove method" do
|
96
|
+
config_option_provides_method_with_arity(:store, :remove, 1)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "must provide a nullary health_check method" do
|
100
|
+
config_option_provides_method_with_arity(:store, :health_check, 0)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "is accessible" do
|
104
|
+
config = Certmeister::Config.new(options)
|
105
|
+
expect(config.store).to eql options[:store]
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
[:sign_policy, :fetch_policy, :remove_policy].each do |policy|
|
111
|
+
describe ":#{policy}" do
|
112
|
+
|
113
|
+
it "is required" do
|
114
|
+
config_option_is_required(policy)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "must provide a unary authenticate method" do
|
118
|
+
config_option_provides_method_with_arity(policy, :authenticate, 1)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "must return a Certmeister::Policy::Response from the authenticate method" do
|
122
|
+
options[policy] = CertmeisterPolicyHelper::BrokenPolicy.new
|
123
|
+
config = Certmeister::Config.new(options)
|
124
|
+
expect(config).to_not be_valid
|
125
|
+
expect(config.errors[policy]).to eql "must return a policy response"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "is accessible" do
|
129
|
+
config = Certmeister::Config.new(options)
|
130
|
+
expect(config.send(policy)).to eql options[policy]
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "error_list" do
|
137
|
+
|
138
|
+
it "is empty if the config has no errors" do
|
139
|
+
config = Certmeister::Config.new(options)
|
140
|
+
config.valid?
|
141
|
+
expect(config.error_list).to be_empty
|
142
|
+
end
|
143
|
+
|
144
|
+
it "includes one string (option and message) per error if the config has errors" do
|
145
|
+
options.delete(:ca_cert)
|
146
|
+
options.delete(:ca_key)
|
147
|
+
config = Certmeister::Config.new(options)
|
148
|
+
config.valid?
|
149
|
+
expect(config.error_list).to match_array ["ca_cert is required", "ca_key is required"]
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "openssl_digest" do
|
155
|
+
|
156
|
+
it "causes a validation failure if the OpenSSL library doesn't provide one" do
|
157
|
+
expect(OpenSSL::Digest).to receive(:const_defined?).twice.and_return(nil)
|
158
|
+
config = Certmeister::Config.new(options)
|
159
|
+
expect(config).to_not be_valid
|
160
|
+
expect(config.errors[:openssl_digest]).to eql "can't find FIPS 140-2 compliant algorithm in OpenSSL::Digest"
|
161
|
+
end
|
162
|
+
|
163
|
+
it "is accessible without being supplied" do
|
164
|
+
config = Certmeister::Config.new(options)
|
165
|
+
expect([OpenSSL::Digest::SHA256, OpenSSL::Digest::SHA1]).to include(config.openssl_digest)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'certmeister/test/memory_store_interface'
|
3
|
+
|
4
|
+
require 'certmeister/in_memory_store'
|
5
|
+
|
6
|
+
describe Certmeister::InMemoryStore do
|
7
|
+
|
8
|
+
class << self
|
9
|
+
include Certmeister::Test::MemoryStoreInterface
|
10
|
+
end
|
11
|
+
|
12
|
+
it_behaves_like_a_certmeister_store
|
13
|
+
|
14
|
+
describe "for use in testing" do
|
15
|
+
|
16
|
+
it "can be initialized with an existing data set" do
|
17
|
+
existing = {'axl.hetzner.africa' => '...cert...'}
|
18
|
+
store = Certmeister::InMemoryStore.new(existing)
|
19
|
+
expect(store.fetch('axl.hetzner.africa')).to eql '...cert...'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "store raises Certmeister::StoreError when broken" do
|
23
|
+
subject.send(:break!)
|
24
|
+
expect { subject.store('axl.hetzner.africa', "first") }.to raise_error(Certmeister::StoreError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "fetch raises Certmeister::StoreError when broken" do
|
28
|
+
subject.send(:break!)
|
29
|
+
expect { subject.fetch('axl.hetzner.africa') }.to raise_error(Certmeister::StoreError, "in-memory store is broken")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "remove raises Certmeister::StoreError when broken" do
|
33
|
+
subject.send(:break!)
|
34
|
+
expect { subject.remove('axl.hetzner.africa') }.to raise_error(Certmeister::StoreError, "in-memory store is broken")
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/certmeister_signing_request_helper'
|
3
|
+
|
4
|
+
require 'certmeister/policy/blackhole'
|
5
|
+
|
6
|
+
describe Certmeister::Policy::Blackhole do
|
7
|
+
|
8
|
+
it "demands a request" do
|
9
|
+
expect { subject.authenticate }.to raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "refuses any request" do
|
13
|
+
response = subject.authenticate(CertmeisterSigningRequestHelper::valid_request)
|
14
|
+
expect(response).to_not be_authenticated
|
15
|
+
expect(response.error).to eql "blackholed"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'certmeister/policy/blackhole'
|
3
|
+
require 'certmeister/policy/noop'
|
4
|
+
|
5
|
+
require 'certmeister/policy/chain_all'
|
6
|
+
|
7
|
+
describe Certmeister::Policy::ChainAll do
|
8
|
+
|
9
|
+
it "must be configured with a list of policys" do
|
10
|
+
expected_error = "enumerable collection of policys required"
|
11
|
+
expect { Certmeister::Policy::ChainAll.new }.to raise_error(ArgumentError)
|
12
|
+
expect { Certmeister::Policy::ChainAll.new(Certmeister::Policy::Noop.new) }.to raise_error(ArgumentError, expected_error)
|
13
|
+
expect { Certmeister::Policy::ChainAll.new([]) }.to raise_error(ArgumentError, expected_error)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "demands a request" do
|
17
|
+
policy = Certmeister::Policy::ChainAll.new([Certmeister::Policy::Noop.new])
|
18
|
+
expect { policy.authenticate }.to raise_error(ArgumentError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "authenticates a request that all its chained policys authenticate" do
|
22
|
+
policy = Certmeister::Policy::ChainAll.new([Certmeister::Policy::Noop.new, Certmeister::Policy::Noop.new])
|
23
|
+
response = policy.authenticate({anything: 'something'})
|
24
|
+
expect(response).to be_authenticated
|
25
|
+
end
|
26
|
+
|
27
|
+
it "refuses a request that any one of its chained policys refuses" do
|
28
|
+
refuse_last = Certmeister::Policy::ChainAll.new([ Certmeister::Policy::Noop.new, Certmeister::Policy::Blackhole.new])
|
29
|
+
refuse_first = Certmeister::Policy::ChainAll.new([ Certmeister::Policy::Blackhole.new, Certmeister::Policy::Noop.new])
|
30
|
+
policys = [refuse_last, refuse_first]
|
31
|
+
|
32
|
+
policys.each do |policy|
|
33
|
+
response = policy.authenticate({anything: 'something'})
|
34
|
+
expect(response).to_not be_authenticated
|
35
|
+
expect(response.error).to eql "blackholed"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|