conjur-rack 1.4.0 → 5.0.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 +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
|