conjur-rack 1.4.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +44 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +7 -1
- data/Jenkinsfile +63 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -5
- data/conjur-rack.gemspec +9 -6
- data/lib/conjur/rack/authenticator.rb +122 -51
- data/lib/conjur/rack/user.rb +80 -18
- data/lib/conjur/rack/version.rb +1 -1
- data/lib/conjur/rack.rb +23 -0
- data/publish.sh +7 -0
- data/spec/rack/authenticator_spec.rb +155 -83
- data/spec/rack/path_prefix_spec.rb +3 -3
- data/spec/rack/user_spec.rb +165 -65
- data/spec/rack_spec.rb +49 -0
- data/spec/spec_helper.rb +36 -0
- data/test.sh +12 -0
- metadata +56 -30
- data/.rvmrc +0 -1
@@ -3,107 +3,179 @@ require 'spec_helper'
|
|
3
3
|
require 'conjur/rack/authenticator'
|
4
4
|
|
5
5
|
describe Conjur::Rack::Authenticator do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
{
|
16
|
-
'HTTP_AUTHORIZATION' => "Token token=\"#{basic_64}\""
|
17
|
-
}.tap do |e|
|
18
|
-
e['HTTP_X_CONJUR_PRIVILEGE'] = privilege if privilege
|
19
|
-
e['HTTP_X_FORWARDED_FOR'] = remote_ip if remote_ip
|
20
|
-
end
|
6
|
+
include_context "with authenticator"
|
7
|
+
|
8
|
+
describe "#call" do
|
9
|
+
context "to an unprotected path" do
|
10
|
+
let(:except) { [ /^\/foo/ ] }
|
11
|
+
let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
|
12
|
+
before {
|
13
|
+
options[:except] = except
|
14
|
+
expect(app).to receive(:call).with(env).and_return app
|
21
15
|
}
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
context "
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
it 'launches app' do
|
35
|
-
app.should_receive(:call).with(env).and_return app
|
36
|
-
call.should == app
|
16
|
+
context "without authorization" do
|
17
|
+
it "proceeds" do
|
18
|
+
expect(call).to eq(app)
|
19
|
+
expect(Conjur::Rack.identity?).to be(false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
context "with authorization" do
|
23
|
+
include_context "with authorization"
|
24
|
+
it "ignores the authorization" do
|
25
|
+
expect(call).to eq(app)
|
26
|
+
expect(Conjur::Rack.identity?).to be(false)
|
37
27
|
end
|
28
|
+
end
|
29
|
+
end
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
it_should_behave_like 'returns User built from token'
|
31
|
+
context "to a protected path" do
|
32
|
+
let(:env) { { 'SCRIPT_NAME' => '/pathname' } }
|
33
|
+
context "without authorization" do
|
34
|
+
it "returns a 401 error" do
|
35
|
+
expect(call).to return_http 401, "Authorization missing"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context "with Conjur authorization" do
|
39
|
+
include_context "with authorization"
|
40
|
+
|
41
|
+
context "with CIDR restriction" do
|
42
|
+
let(:claims) { { 'sub' => 'test-user', 'cidr' => %w(192.168.2.0/24 2001:db8::/32) } }
|
43
|
+
let(:token) { Slosilo::JWT.new(claims) }
|
44
|
+
before do
|
45
|
+
allow(subject).to receive_messages \
|
46
|
+
parsed_token: token,
|
47
|
+
http_remote_ip: remote_ip
|
48
|
+
end
|
49
|
+
|
50
|
+
%w(10.0.0.2 fdda:5cc1:23:4::1f).each do |addr|
|
51
|
+
context "with address #{addr} out of range" do
|
52
|
+
let(:remote_ip) { addr }
|
53
|
+
it "returns 403" do
|
54
|
+
expect(call).to return_http 403, "IP address rejected"
|
55
|
+
end
|
65
56
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
57
|
+
end
|
58
|
+
|
59
|
+
%w(192.168.2.3 2001:db8::22).each do |addr|
|
60
|
+
context "with address #{addr} in range" do
|
61
|
+
let(:remote_ip) { addr }
|
62
|
+
it "passes the request" do
|
63
|
+
expect(call.login).to eq 'test-user'
|
64
|
+
end
|
70
65
|
end
|
71
66
|
end
|
67
|
+
end
|
72
68
|
|
73
|
-
|
74
|
-
|
69
|
+
context "of a valid token" do
|
70
|
+
it 'launches app' do
|
71
|
+
expect(app).to receive(:call).with(env).and_return app
|
72
|
+
expect(call).to eq(app)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
context "of an invalid token" do
|
76
|
+
it "returns a 401 error" do
|
77
|
+
allow(Slosilo).to receive(:token_signer).and_return(nil)
|
78
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
context "of a token invalid for authn" do
|
82
|
+
it "returns a 401 error" do
|
83
|
+
allow(Slosilo).to receive(:token_signer).and_return('a-totally-different-key')
|
84
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
context "of 'own' token" do
|
88
|
+
it "returns ENV['CONJUR_ACCOUNT']" do
|
89
|
+
expect(ENV).to receive(:[]).with("CONJUR_ACCOUNT").and_return("test-account")
|
90
|
+
expect(app).to receive(:call) do |*args|
|
91
|
+
expect(Conjur::Rack.identity?).to be(true)
|
92
|
+
expect(Conjur::Rack.user.account).to eq('test-account')
|
93
|
+
:done
|
94
|
+
end
|
95
|
+
allow(Slosilo).to receive(:token_signer).and_return('own')
|
96
|
+
expect(call).to eq(:done)
|
97
|
+
end
|
98
|
+
it "requires ENV['CONJUR_ACCOUNT']" do
|
99
|
+
expect(ENV).to receive(:[]).with("CONJUR_ACCOUNT").and_return(nil)
|
100
|
+
allow(Slosilo).to receive(:token_signer).and_return('own')
|
101
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
75
102
|
end
|
76
103
|
end
|
77
104
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
105
|
+
|
106
|
+
context "with junk in token" do
|
107
|
+
let(:env) { { 'HTTP_AUTHORIZATION' => 'Token token="open sesame"' } }
|
108
|
+
it "returns 401" do
|
109
|
+
expect(call).to return_http 401, "Malformed authorization token"
|
82
110
|
end
|
83
111
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
112
|
+
|
113
|
+
context "with JSON junk in token" do
|
114
|
+
let(:env) { { 'HTTP_AUTHORIZATION' => 'Token token="eyJmb28iOiAiYmFyIn0="' } }
|
115
|
+
before do
|
116
|
+
allow(Slosilo).to receive(:token_signer).and_return(nil)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns 401" do
|
120
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
88
121
|
end
|
89
122
|
end
|
90
123
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
124
|
+
context "to an optional path" do
|
125
|
+
let(:optional) { [ /^\/foo/ ] }
|
126
|
+
let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
|
127
|
+
before {
|
128
|
+
options[:optional] = optional
|
129
|
+
}
|
130
|
+
context "without authorization" do
|
131
|
+
it "proceeds" do
|
132
|
+
expect(app).to receive(:call) do |*args|
|
133
|
+
expect(Conjur::Rack.identity?).to be(false)
|
134
|
+
:done
|
135
|
+
end
|
136
|
+
expect(call).to eq(:done)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
context "with authorization" do
|
140
|
+
include_context "with authorization"
|
141
|
+
it "processes the authorization" do
|
142
|
+
expect(app).to receive(:call) do |*args|
|
143
|
+
expect(Conjur::Rack.identity?).to be(true)
|
144
|
+
:done
|
145
|
+
end
|
146
|
+
expect(call).to eq(:done)
|
147
|
+
end
|
97
148
|
end
|
98
149
|
end
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
150
|
+
|
151
|
+
RSpec::Matchers.define :return_http do |status, message|
|
152
|
+
match do |actual|
|
153
|
+
status, headers, body = actual
|
154
|
+
expect(status).to eq status
|
155
|
+
expect(headers).to eq "Content-Type" => "text/plain", "Content-Length" => message.length.to_s
|
156
|
+
expect(body.join).to eq message
|
106
157
|
end
|
107
158
|
end
|
108
159
|
end
|
160
|
+
|
161
|
+
# protected internal methods
|
162
|
+
|
163
|
+
describe '#verify_authorization_and_get_identity' do
|
164
|
+
it "accepts JWT tokens without CIDR restrictions" do
|
165
|
+
mock_jwt sub: 'user'
|
166
|
+
expect { subject.send :verify_authorization_and_get_identity }.to_not raise_error
|
167
|
+
end
|
168
|
+
|
169
|
+
it "rejects JWT tokens with unrecognized claims" do
|
170
|
+
mock_jwt extra: 'field'
|
171
|
+
expect { subject.send :verify_authorization_and_get_identity }.to raise_error \
|
172
|
+
Conjur::Rack::Authenticator::AuthorizationError
|
173
|
+
end
|
174
|
+
|
175
|
+
def mock_jwt claims
|
176
|
+
token = Slosilo::JWT.new(claims).add_signature(alg: 'none') {}
|
177
|
+
allow(subject).to receive(:parsed_token) { token }
|
178
|
+
allow(Slosilo).to receive(:token_signer).with(token).and_return 'authn:test'
|
179
|
+
end
|
180
|
+
end
|
109
181
|
end
|
@@ -17,21 +17,21 @@ describe Conjur::Rack::PathPrefix do
|
|
17
17
|
context "/api/hosts" do
|
18
18
|
let(:path) { "/api/hosts" }
|
19
19
|
it "matches" do
|
20
|
-
app.
|
20
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
|
21
21
|
call
|
22
22
|
end
|
23
23
|
end
|
24
24
|
context "/api" do
|
25
25
|
let(:path) { "/api" }
|
26
26
|
it "doesn't erase the path completely" do
|
27
|
-
app.
|
27
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/' }).and_return app
|
28
28
|
call
|
29
29
|
end
|
30
30
|
end
|
31
31
|
context "with non-matching prefix" do
|
32
32
|
let(:path) { "/hosts" }
|
33
33
|
it "doesn't match" do
|
34
|
-
app.
|
34
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
|
35
35
|
call
|
36
36
|
end
|
37
37
|
end
|
data/spec/rack/user_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'conjur/rack/user'
|
2
3
|
|
3
4
|
describe Conjur::Rack::User do
|
4
5
|
let(:login){ 'admin' }
|
@@ -6,41 +7,50 @@ describe Conjur::Rack::User do
|
|
6
7
|
let(:account){ 'acct' }
|
7
8
|
let(:privilege) { nil }
|
8
9
|
let(:remote_ip) { nil }
|
10
|
+
let(:audit_roles) { nil }
|
11
|
+
let(:audit_resources) { nil }
|
9
12
|
|
10
|
-
subject{
|
13
|
+
subject(:user) {
|
14
|
+
described_class.new(token, account,
|
15
|
+
:privilege => privilege,
|
16
|
+
:remote_ip => remote_ip,
|
17
|
+
:audit_roles => audit_roles,
|
18
|
+
:audit_resources => audit_resources
|
19
|
+
)
|
20
|
+
}
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
it "aliases setter for account to conjur_account" do
|
18
|
-
subject.conjur_account = "changed!"
|
19
|
-
subject.account.should == "changed!"
|
20
|
-
end
|
21
|
-
|
22
|
-
describe '#new_assocation' do
|
23
|
-
let(:associate){ Class.new }
|
24
|
-
let(:params){{foo: 'bar'}}
|
25
|
-
it "calls cls.new with params including userid: login" do
|
26
|
-
associate.should_receive(:new).with(params.merge(userid: subject.login))
|
27
|
-
subject.new_association(associate, params)
|
28
|
-
end
|
22
|
+
it 'provides field accessors' do
|
23
|
+
expect(user.token).to eq token
|
24
|
+
expect(user.account).to eq account
|
25
|
+
expect(user.conjur_account).to eq account
|
26
|
+
expect(user.login).to eq login
|
29
27
|
end
|
30
28
|
|
31
29
|
describe '#roleid' do
|
32
30
|
let(:login){ tokens.join('/') }
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
|
32
|
+
context "when login contains one token" do
|
33
|
+
let(:tokens) { %w(foobar) }
|
34
|
+
|
35
|
+
it "is expanded to account:user:token" do
|
36
|
+
expect(subject.roleid).to eq "#{account}:user:foobar"
|
37
|
+
end
|
36
38
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
|
40
|
+
context "when login contains two tokens" do
|
41
|
+
let(:tokens) { %w(foo bar) }
|
42
|
+
|
43
|
+
it "is expanded to account:first:second" do
|
44
|
+
expect(subject.roleid).to eq "#{account}:foo:bar"
|
45
|
+
end
|
40
46
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
|
48
|
+
context "when login contains three tokens" do
|
49
|
+
let(:tokens) { %w(foo bar baz) }
|
50
|
+
|
51
|
+
it "is expanded to account:first:second/third" do
|
52
|
+
expect(subject.roleid).to eq "#{account}:foo:bar/baz"
|
53
|
+
end
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
@@ -48,34 +58,46 @@ describe Conjur::Rack::User do
|
|
48
58
|
let(:roleid){ 'the role id' }
|
49
59
|
let(:api){ double('conjur api') }
|
50
60
|
before do
|
51
|
-
subject.
|
52
|
-
subject.
|
61
|
+
allow(subject).to receive(:roleid).and_return roleid
|
62
|
+
allow(subject).to receive(:api).and_return api
|
53
63
|
end
|
54
64
|
|
55
65
|
it 'passes roleid to api.role' do
|
56
|
-
api.
|
57
|
-
subject.role.
|
66
|
+
expect(api).to receive(:role).with(roleid).and_return 'the role'
|
67
|
+
expect(subject.role).to eq('the role')
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
71
|
describe "#global_reveal?" do
|
72
|
+
let(:api){ double "conjur-api" }
|
73
|
+
before { allow(subject).to receive(:api).and_return(api) }
|
74
|
+
|
62
75
|
context "with global privilege" do
|
63
76
|
let(:privilege) { "reveal" }
|
64
|
-
|
65
|
-
|
66
|
-
|
77
|
+
|
78
|
+
context "when not supported" do
|
79
|
+
before { expect(api).not_to respond_to :global_privilege_permitted? }
|
80
|
+
it "simply returns false" do
|
81
|
+
expect(subject.global_reveal?).to be false
|
82
|
+
end
|
67
83
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
84
|
+
|
85
|
+
context "when supported" do
|
86
|
+
before do
|
87
|
+
allow(api).to receive(:global_privilege_permitted?).with('reveal') { true }
|
88
|
+
end
|
89
|
+
it "checks the API function global_privilege_permitted?" do
|
90
|
+
expect(subject.global_reveal?).to be true
|
91
|
+
# The result is cached
|
92
|
+
expect(api).not_to receive :global_privilege_permitted?
|
93
|
+
subject.global_reveal?
|
94
|
+
end
|
74
95
|
end
|
75
96
|
end
|
97
|
+
|
76
98
|
context "without a global privilege" do
|
77
|
-
it "simply returns
|
78
|
-
expect(subject.global_reveal?).to
|
99
|
+
it "simply returns false" do
|
100
|
+
expect(subject.global_reveal?).to be false
|
79
101
|
end
|
80
102
|
end
|
81
103
|
end
|
@@ -84,38 +106,116 @@ describe Conjur::Rack::User do
|
|
84
106
|
context "when given a class" do
|
85
107
|
let(:cls){ double('API class') }
|
86
108
|
it "calls cls.new_from_token with its token" do
|
87
|
-
cls.
|
88
|
-
subject.api(cls).
|
109
|
+
expect(cls).to receive(:new_from_token).with(token).and_return 'the api'
|
110
|
+
expect(subject.api(cls)).to eq('the api')
|
89
111
|
end
|
90
112
|
end
|
113
|
+
|
91
114
|
context 'when not given args' do
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
}
|
115
|
+
let(:api) { double :api }
|
116
|
+
before do
|
117
|
+
allow(Conjur::API).to receive(:new_from_token).with(token).and_return(api)
|
96
118
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
Conjur::API.should_receive(:new_from_token).with(token).and_return 'the api'
|
101
|
-
}
|
102
|
-
it_should_behave_like "builds the api"
|
119
|
+
|
120
|
+
it "builds the api from token" do
|
121
|
+
expect(subject.api).to eq api
|
103
122
|
end
|
123
|
+
|
104
124
|
context "with remote_ip" do
|
105
125
|
let(:remote_ip) { "the-ip" }
|
106
|
-
|
107
|
-
Conjur::API.
|
108
|
-
|
109
|
-
|
126
|
+
it "passes the IP to the API constructor" do
|
127
|
+
expect(Conjur::API).to receive(:new_from_token).with(token, 'the-ip').and_return(api)
|
128
|
+
expect(subject.api).to eq api
|
129
|
+
end
|
110
130
|
end
|
131
|
+
|
111
132
|
context "with privilege" do
|
112
|
-
let(:privilege) { "
|
113
|
-
|
114
|
-
|
115
|
-
expect(api).to
|
116
|
-
|
117
|
-
it_should_behave_like "builds the api"
|
133
|
+
let(:privilege) { "elevate" }
|
134
|
+
it "applies the privilege on the API object" do
|
135
|
+
expect(api).to receive(:with_privilege).with("elevate").and_return "privileged api"
|
136
|
+
expect(subject.api).to eq "privileged api"
|
137
|
+
end
|
118
138
|
end
|
139
|
+
|
140
|
+
context "when audit supported" do
|
141
|
+
before do
|
142
|
+
# If we're testing on an API version that doesn't
|
143
|
+
# support audit this method will be missing, so stub.
|
144
|
+
unless Conjur::API.respond_to? :decode_audit_ids
|
145
|
+
# not exactly a faithful reimplementation, but good enough for here
|
146
|
+
allow(Conjur::API).to receive(:decode_audit_ids) {|x|[x]}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with audit resource" do
|
151
|
+
let (:audit_resources) { 'food:bacon' }
|
152
|
+
it "applies the audit resource on the API object" do
|
153
|
+
expect(api).to receive(:with_audit_resources).with(['food:bacon']).and_return('the api')
|
154
|
+
expect(subject.api).to eq 'the api'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "with audit roles" do
|
159
|
+
let (:audit_roles) { 'user:cook' }
|
160
|
+
it "applies the audit role on the API object" do
|
161
|
+
expect(api).to receive(:with_audit_roles).with(['user:cook']).and_return('the api')
|
162
|
+
expect(subject.api).to eq 'the api'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "when audit not supported" do
|
168
|
+
before do
|
169
|
+
expect(api).not_to respond_to :with_audit_resources
|
170
|
+
expect(api).not_to respond_to :with_audit_roles
|
171
|
+
end
|
172
|
+
let (:audit_resources) { 'food:bacon' }
|
173
|
+
let (:audit_roles) { 'user:cook' }
|
174
|
+
it "ignores audit roles and resources" do
|
175
|
+
expect(subject.api).to eq api
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "with invalid type payload" do
|
182
|
+
let(:token){ { "data" => :alice } }
|
183
|
+
it "raises an error on trying to access the content" do
|
184
|
+
expect{ subject.login }.to raise_error("Expecting String or Hash token data, got Symbol")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "with hash payload" do
|
189
|
+
let(:token){ { "data" => { "login" => "alice", "capabilities" => { "fry" => "bacon" } } } }
|
190
|
+
|
191
|
+
it "processes the login and attributes" do
|
192
|
+
original_token = token.deep_dup
|
193
|
+
|
194
|
+
expect(subject.login).to eq('alice')
|
195
|
+
expect(subject.attributes).to eq({"capabilities" => { "fry" => "bacon" }})
|
196
|
+
|
197
|
+
expect(token).to eq original_token
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "with JWT token" do
|
202
|
+
let(:token) { {"protected"=>"eyJhbGciOiJ0ZXN0IiwidHlwIjoiSldUIn0=",
|
203
|
+
"payload"=>"eyJzdWIiOiJhbGljZSIsImlhdCI6MTUwNDU1NDI2NX0=",
|
204
|
+
"signature"=>"dGVzdHNpZw=="} }
|
205
|
+
|
206
|
+
it "processes the login and attributes" do
|
207
|
+
original_token = token.deep_dup
|
208
|
+
|
209
|
+
expect(subject.login).to eq('alice')
|
210
|
+
|
211
|
+
# TODO: should we only pass unrecognized attrs here?
|
212
|
+
expect(subject.attributes).to eq \
|
213
|
+
'alg' => 'test',
|
214
|
+
'iat' => 1504554265,
|
215
|
+
'sub' => 'alice',
|
216
|
+
'typ' => 'JWT'
|
217
|
+
|
218
|
+
expect(token).to eq original_token
|
119
219
|
end
|
120
220
|
end
|
121
|
-
end
|
221
|
+
end
|
data/spec/rack_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'conjur/rack'
|
3
|
+
|
4
|
+
describe Conjur::Rack do
|
5
|
+
describe '.user' do
|
6
|
+
include_context "with authorization"
|
7
|
+
let(:stubuser) { double :stubuser }
|
8
|
+
before do
|
9
|
+
allow(Conjur::Rack::User).to receive(:new)
|
10
|
+
.with(token, 'someacc', {:privilege => privilege, :remote_ip => remote_ip, :audit_roles => audit_roles, :audit_resources => audit_resources})
|
11
|
+
.and_return(stubuser)
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when called in app context' do
|
15
|
+
shared_examples_for :returns_user do
|
16
|
+
it "returns user built from token" do
|
17
|
+
expect(call).to eq stubuser
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include_examples :returns_user
|
22
|
+
|
23
|
+
context 'with X-Conjur-Privilege' do
|
24
|
+
let(:privilege) { "elevate" }
|
25
|
+
include_examples :returns_user
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with X-Forwarded-For' do
|
29
|
+
let(:remote_ip) { "66.0.0.1" }
|
30
|
+
include_examples :returns_user
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with Conjur-Audit-Roles' do
|
34
|
+
let (:audit_roles) { 'user%3Acook' }
|
35
|
+
include_examples :returns_user
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with Conjur-Audit-Resources' do
|
39
|
+
let (:audit_resources) { 'food%3Abacon' }
|
40
|
+
include_examples :returns_user
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises error if called out of app context" do
|
46
|
+
expect { Conjur::Rack.user }.to raise_error('No Conjur identity for current request')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,8 +4,44 @@ $:.unshift File.join(File.dirname(__FILE__), "lib")
|
|
4
4
|
|
5
5
|
# Allows loading of an environment config based on the environment
|
6
6
|
require 'rspec'
|
7
|
+
require 'rspec/its'
|
7
8
|
require 'securerandom'
|
9
|
+
require 'slosilo'
|
8
10
|
|
9
11
|
RSpec.configure do |config|
|
10
12
|
end
|
11
13
|
|
14
|
+
RSpec.shared_context "with authenticator" do
|
15
|
+
let(:options) { {} }
|
16
|
+
let(:app) { double(:app) }
|
17
|
+
subject(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
|
18
|
+
let(:call) { authenticator.call env }
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec.shared_context "with authorization" do
|
22
|
+
include_context "with authenticator"
|
23
|
+
let(:token_signer) { "authn:someacc" }
|
24
|
+
let(:audit_resources) { nil }
|
25
|
+
let(:privilege) { nil }
|
26
|
+
let(:remote_ip) { nil }
|
27
|
+
let(:audit_roles) { nil }
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(app).to receive(:call) { Conjur::Rack.user }
|
31
|
+
allow(Slosilo).to receive(:token_signer).and_return(token_signer)
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:env) do
|
35
|
+
{
|
36
|
+
'HTTP_AUTHORIZATION' => "Token token=\"#{basic_64}\""
|
37
|
+
}.tap do |e|
|
38
|
+
e['HTTP_X_CONJUR_PRIVILEGE'] = privilege if privilege
|
39
|
+
e['HTTP_X_FORWARDED_FOR'] = remote_ip if remote_ip
|
40
|
+
e['HTTP_CONJUR_AUDIT_ROLES'] = audit_roles if audit_roles
|
41
|
+
e['HTTP_CONJUR_AUDIT_RESOURCES'] = audit_resources if audit_resources
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:basic_64) { Base64.strict_encode64(token.to_json) }
|
46
|
+
let(:token) { { "data" => "foobar" } }
|
47
|
+
end
|