conjur-rack 1.4.0 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90340dd3a8dbc2f410a43b66ef10d010e76a9ffd
4
- data.tar.gz: 9cc5f13b006ac102df5a28b65da08e7f3a7ef6dd
3
+ metadata.gz: 0ee85c177c59da73e229825170b3b8c34230e4e1
4
+ data.tar.gz: 7758e75373c0ccdfcb61539a33325795d5394600
5
5
  SHA512:
6
- metadata.gz: 9223bfe5621080f8b9dba63e2de5593e2f6e44d2ad34d3beb60c8447a0ff8340eae3d1530420975052cfcbe6d26a66e2bbc5c8a1737f0019a2ee437c0fe458a3
7
- data.tar.gz: 58042ba2ee61e535da72b1395ab7f48e1a873d8fba78fdc0ac1ab1a01a15b3dda63e7ba78fe83a550876c180238fbe5d38917d50f5c0485cf8dba1f42bfdbb67
6
+ metadata.gz: ccec8cadd0827fbf5b298f57fa78aa32a7792a58d985c0ff54ee05b80c256e55c0a9c868a1432be4908d7314c9becefdcf812ed95aad28ec756e989ad250def2
7
+ data.tar.gz: 36e507e1714480f2c5e4f5ff9d3af63cca883f855fc2259e272c3ce45420dadc36e0cd64cbe926f6396d0d3793968f802df147950ac0db86c7b69bba3abd0b8d
@@ -1,3 +1,28 @@
1
+ # v3.1.0
2
+
3
+ * Support for JWT Slosilo tokens.
4
+
5
+ # v3.0.0.pre
6
+
7
+ * Initial support for Conjur 5.
8
+
9
+ # v2.3.0
10
+
11
+ * Add TRUSTED_PROXIES support
12
+
13
+ # v2.2.0
14
+
15
+ * resolve 'own' token to CONJUR_ACCOUNT env var
16
+ * add #optional paths to Conjur::Rack authenticator
17
+
18
+ # v2.1.0
19
+
20
+ * Add handling for `Conjur-Audit-Roles` and `Conjur-Audit-Resources`
21
+
22
+ # v2.0.0
23
+
24
+ * Change `global_sudo?` to `global_elevate?`
25
+
1
26
  # v1.4.0
2
27
 
3
28
  * Add `validated_global_privilege` helper function to get the global privilege, if any, which has been submitted with the request and verified by the Conjur server.
data/Gemfile CHANGED
@@ -1,6 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ # make sure github uses TLS
4
+ git_source(:github) { |name| "https://github.com/#{name}.git" }
5
+
6
+ #ruby=ruby-2.3.4
7
+ #ruby-gemset=conjur-rack
8
+
3
9
  # Specify your gem's dependencies in conjur-rack.gemspec
4
10
  gemspec
5
11
 
6
- gem 'conjur-api', github: 'conjurinc/api-ruby', branch: 'master'
12
+ # gem 'conjur-api', github: 'cyberark/conjur-api-ruby', branch: 'master'
@@ -18,12 +18,15 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "slosilo"
22
- spec.add_dependency "conjur-api", ">= 4.17"
23
- spec.add_dependency "rack"
21
+ spec.add_dependency "slosilo", "~> 2.1"
22
+ spec.add_dependency "conjur-api", "< 6"
23
+ spec.add_dependency "rack", '~> 1'
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 1.3"
26
26
  spec.add_development_dependency "rake"
27
- spec.add_development_dependency "rspec", ">=2.9", "<3.0"
27
+ spec.add_development_dependency "rspec"
28
28
  spec.add_development_dependency 'ci_reporter_rspec'
29
+ spec.add_development_dependency 'pry-byebug'
30
+ spec.add_development_dependency 'rspec-its'
31
+
29
32
  end
@@ -0,0 +1,12 @@
1
+ #!/bin/bash -e
2
+
3
+ TEST_IMAGE='ruby:2.3.4'
4
+
5
+ rm -f Gemfile.lock
6
+
7
+ docker run --rm \
8
+ -v "$PWD:/usr/src/app" \
9
+ -w /usr/src/app \
10
+ -e CONJUR_ENV=ci \
11
+ $TEST_IMAGE \
12
+ bash -c "bundle update && bundle exec rake spec"
@@ -1,3 +1,26 @@
1
1
  require "conjur/rack/version"
2
2
  require "conjur/rack/authenticator"
3
3
  require "conjur/rack/path_prefix"
4
+ require 'ipaddr'
5
+ require 'set'
6
+
7
+ module TrustedProxies
8
+
9
+ def trusted_proxy?(ip)
10
+ trusted_proxies ? trusted_proxies.any? { |cidr| cidr.include?(ip) } : super
11
+ end
12
+
13
+ def trusted_proxies
14
+ @trusted_proxies || ENV['TRUSTED_PROXIES'].try do |proxies|
15
+ cidrs = Set.new(proxies.split(',') + ['127.0.0.1'])
16
+ @trusted_proxies = cidrs.collect {|cidr| IPAddr.new(cidr) }
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ module Rack
23
+ class Request
24
+ prepend TrustedProxies
25
+ end
26
+ end
@@ -2,36 +2,51 @@ require "conjur/rack/user"
2
2
 
3
3
  module Conjur
4
4
  module Rack
5
- def self.identity?
6
- !Thread.current[:conjur_rack_identity].nil?
7
- end
8
-
9
- def self.user
10
- User.new(identity[0], identity[1], privilege, remote_ip)
11
- end
12
-
13
- def self.identity
14
- Thread.current[:conjur_rack_identity] or raise "No Conjur identity for current request"
15
- end
16
-
17
- def self.privilege
18
- Thread.current[:conjur_rack_privilege]
19
- end
20
-
21
- def self.remote_ip
22
- Thread.current[:conjur_rack_remote_ip]
5
+
6
+ class << self
7
+ def conjur_rack
8
+ Thread.current[:conjur_rack] ||= {}
9
+ end
10
+
11
+ def identity?
12
+ !conjur_rack[:identity].nil?
13
+ end
14
+
15
+ def user
16
+ User.new(identity[0], identity[1],
17
+ :privilege => privilege,
18
+ :remote_ip => remote_ip,
19
+ :audit_roles => audit_roles,
20
+ :audit_resources => audit_resources
21
+ )
22
+ end
23
+
24
+ def identity
25
+ conjur_rack[:identity] or raise "No Conjur identity for current request"
26
+ end
27
+
28
+ # class attributes
29
+ [:privilege, :remote_ip, :audit_roles, :audit_resources].each do |a|
30
+ define_method(a) do
31
+ conjur_rack[a]
32
+ end
33
+ end
23
34
  end
35
+
24
36
 
25
37
  class Authenticator
26
38
  class AuthorizationError < SecurityError
27
39
  end
28
40
  class SignatureError < SecurityError
29
41
  end
42
+ class Forbidden < SecurityError
43
+ end
30
44
 
31
45
  attr_reader :app, :options
32
46
 
33
47
  # +options+:
34
- # :except :: a list of request path patterns for which to skip authentication
48
+ # :except :: a list of request path patterns for which to skip authentication.
49
+ # :optional :: request path patterns for which authentication is optional.
35
50
  def initialize app, options = {}
36
51
  @app = app
37
52
  @options = options
@@ -39,10 +54,13 @@ module Conjur
39
54
 
40
55
  # threadsafe accessors, values are established explicitly below
41
56
  def env; Thread.current[:rack_env] ; end
42
- def token; Thread.current[:conjur_rack_token] ; end
43
- def account; Thread.current[:conjur_rack_account]; end
44
- def privilege; Thread.current[:conjur_rack_privilege]; end
45
- def remote_ip; Thread.current[:conjur_rack_remote_ip]; end
57
+
58
+ # instance attributes
59
+ [:token, :account, :privilege, :remote_ip, :audit_roles, :audit_resources].each do |a|
60
+ define_method(a) do
61
+ conjur_rack[a]
62
+ end
63
+ end
46
64
 
47
65
  def call rackenv
48
66
  # never store request-specific variables as application attributes
@@ -50,13 +68,19 @@ module Conjur
50
68
  if authenticate?
51
69
  begin
52
70
  identity = verify_authorization_and_get_identity # [token, account]
53
-
54
- Thread.current[:conjur_rack_token] = identity[0]
55
- Thread.current[:conjur_rack_account] = identity[1]
56
- Thread.current[:conjur_rack_identity] = identity
57
- Thread.current[:conjur_rack_privilege] = conjur_privilege
58
- Thread.current[:conjur_rack_remote_ip] = remote_ip
71
+
72
+ if identity
73
+ conjur_rack[:token] = identity[0]
74
+ conjur_rack[:account] = identity[1]
75
+ conjur_rack[:identity] = identity
76
+ conjur_rack[:privilege] = http_privilege
77
+ conjur_rack[:remote_ip] = http_remote_ip
78
+ conjur_rack[:audit_roles] = http_audit_roles
79
+ conjur_rack[:audit_resources] = http_audit_resources
80
+ end
59
81
 
82
+ rescue Forbidden
83
+ return error 403, $!.message
60
84
  rescue SecurityError, RestClient::Exception
61
85
  return error 401, $!.message
62
86
  end
@@ -65,61 +89,108 @@ module Conjur
65
89
  @app.call rackenv
66
90
  ensure
67
91
  Thread.current[:rack_env] = nil
68
- Thread.current[:conjur_rack_identity] = nil
69
- Thread.current[:conjur_rack_token] = nil
70
- Thread.current[:conjur_rack_account] = nil
71
- Thread.current[:conjur_rack_privilege] = nil
72
- Thread.current[:conjur_rack_remote_ip] = nil
92
+ Thread.current[:conjur_rack] = {}
73
93
  end
74
94
  end
75
95
 
76
96
  protected
77
97
 
98
+ def conjur_rack
99
+ Conjur::Rack.conjur_rack
100
+ end
101
+
78
102
  def validate_token_and_get_account token
79
- failure = SignatureError.new("Unathorized: Invalid token")
103
+ failure = SignatureError.new("Unauthorized: Invalid token")
80
104
  raise failure unless (signer = Slosilo.token_signer token)
81
- raise failure unless signer =~ /\Aauthn:(.+)\z/
82
- return $1
105
+ if signer == 'own'
106
+ ENV['CONJUR_ACCOUNT'] or raise failure
107
+ else
108
+ raise failure unless signer =~ /\Aauthn:(.+)\z/
109
+ $1
110
+ end
83
111
  end
84
112
 
85
113
  def error status, message
86
114
  [status, { 'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s }, [message] ]
87
115
  end
116
+
117
+ def parsed_token
118
+ token = http_authorization.to_s[/^Token token="(.*)"/, 1]
119
+ token = token && JSON.parse(Base64.decode64(token))
120
+ token = Slosilo::JWT token rescue token
121
+ rescue JSON::ParserError
122
+ raise AuthorizationError.new("Malformed authorization token")
123
+ end
88
124
 
125
+ RECOGNIZED_CLAIMS = [
126
+ 'iat', 'exp', # recognized by Slosilo
127
+ 'cidr', 'sub',
128
+ 'iss', 'aud', 'jti' # RFC 7519, not handled but recognized
129
+ ].freeze
130
+
89
131
  def verify_authorization_and_get_identity
90
- if authorization.to_s[/^Token token="(.*)"/]
91
- token = JSON.parse(Base64.decode64($1))
92
- account = validate_token_and_get_account(token)
93
- return [token, account]
132
+ if token = parsed_token
133
+ begin
134
+ account = validate_token_and_get_account token
135
+ if token.respond_to?(:claims)
136
+ claims = token.claims
137
+ raise AuthorizationError, "token contains unrecognized claims" unless \
138
+ (claims.keys.map(&:to_s) - RECOGNIZED_CLAIMS).empty?
139
+ if (cidr = claims['cidr'])
140
+ raise Forbidden, "IP address rejected" unless \
141
+ cidr.map(&IPAddr.method(:new)).any? { |c| c.include? http_remote_ip }
142
+ end
143
+ end
144
+ return [token, account]
145
+ end
94
146
  else
95
- raise AuthorizationError.new("Authorization missing")
147
+ path = http_path
148
+ if optional_paths.find{|p| p.match(path)}.nil?
149
+ raise AuthorizationError.new("Authorization missing")
150
+ else
151
+ nil
152
+ end
96
153
  end
97
154
  end
98
155
 
99
156
  def authenticate?
157
+ path = http_path
100
158
  if options[:except]
101
159
  options[:except].find{|p| p.match(path)}.nil?
102
160
  else
103
161
  true
104
162
  end
105
163
  end
106
-
107
- def conjur_privilege
108
- env['HTTP_X_CONJUR_PRIVILEGE']
109
- end
110
164
 
111
- def authorization
165
+ def optional_paths
166
+ options[:optional] || []
167
+ end
168
+
169
+ def http_authorization
112
170
  env['HTTP_AUTHORIZATION']
113
171
  end
114
-
115
- def remote_ip
172
+
173
+ def http_privilege
174
+ env['HTTP_X_CONJUR_PRIVILEGE']
175
+ end
176
+
177
+ def http_remote_ip
116
178
  require 'rack/request'
117
179
  ::Rack::Request.new(env).ip
118
180
  end
119
-
120
- def path
181
+
182
+ def http_audit_roles
183
+ env['HTTP_CONJUR_AUDIT_ROLES']
184
+ end
185
+
186
+ def http_audit_resources
187
+ env['HTTP_CONJUR_AUDIT_RESOURCES']
188
+ end
189
+
190
+ def http_path
121
191
  [ env['SCRIPT_NAME'], env['PATH_INFO'] ].join
122
192
  end
193
+
123
194
  end
124
195
  end
125
196
  end
@@ -2,25 +2,32 @@ require 'conjur/api'
2
2
 
3
3
  module Conjur
4
4
  module Rack
5
+ # Token data can be a string (which is the user login), or a Hash.
6
+ # If it's a hash, it should contain the user login keyed by the string 'login'.
7
+ # The rest of the payload is available as +attributes+.
5
8
  class User
6
- attr_accessor :token, :account, :privilege, :remote_ip
9
+ attr_reader :token, :account, :privilege, :remote_ip, :audit_roles, :audit_resources
7
10
 
8
- def initialize(token, account, privilege = nil, remote_ip = nil)
11
+ def initialize(token, account, options = {})
9
12
  @token = token
10
13
  @account = account
11
- @privilege = privilege
12
- @remote_ip = remote_ip
14
+ # Third argument used to be the name of privilege, be
15
+ # backwards compatible:
16
+ if options.respond_to?(:to_str)
17
+ @privilege = options
18
+ else
19
+ @privilege = options[:privilege]
20
+ @remote_ip = options[:remote_ip]
21
+ @audit_roles = options[:audit_roles]
22
+ @audit_resources = options[:audit_resources]
23
+ end
13
24
  end
14
25
 
15
26
  # This file was accidently calling account conjur_account,
16
27
  # I'm adding an alias in case that's going on anywhere else.
17
28
  # -- Jon
18
29
  alias :conjur_account :account
19
- alias :conjur_account= :account=
20
-
21
- def new_association(cls, params = {})
22
- cls.new params.merge({userid: login})
23
- end
30
+ # alias :conjur_account= :account=
24
31
 
25
32
  # Returns the global privilege which was present on the request, if and only
26
33
  # if the user actually has that privilege.
@@ -41,13 +48,21 @@ module Conjur
41
48
  validated_global_privilege == "reveal"
42
49
  end
43
50
 
44
- # True if and only if the user has valid global 'sudo' privilege.
45
- def global_sudo?
46
- validated_global_privilege == "sudo"
51
+ # True if and only if the user has valid global 'elevate' privilege.
52
+ def global_elevate?
53
+ validated_global_privilege == "elevate"
47
54
  end
48
55
 
49
56
  def login
50
- token["data"] or raise "No data field in token"
57
+ parse_token
58
+
59
+ @login
60
+ end
61
+
62
+ def attributes
63
+ parse_token
64
+
65
+ @attributes || {}
51
66
  end
52
67
 
53
68
  def roleid
@@ -63,17 +78,56 @@ module Conjur
63
78
  def role
64
79
  api.role(roleid)
65
80
  end
66
-
81
+
82
+ def audit_resources
83
+ Conjur::API.decode_audit_ids(@audit_resources) if @audit_resources
84
+ end
85
+
86
+ def audit_roles
87
+ Conjur::API.decode_audit_ids(@audit_roles) if @audit_roles
88
+ end
89
+
67
90
  def api(cls = Conjur::API)
68
91
  args = [ token ]
69
92
  args.push remote_ip if remote_ip
70
93
  api = cls.new_from_token(*args)
71
- if privilege
72
- api.with_privilege(privilege)
94
+ api = api.with_privilege(privilege) if privilege
95
+ api = api.with_audit_resources(audit_resources) if audit_resources
96
+ api = api.with_audit_roles(audit_roles) if audit_roles
97
+
98
+ api
99
+ end
100
+
101
+ protected
102
+
103
+ def parse_token
104
+ return if @login
105
+
106
+ @token = Slosilo::JWT token
107
+ load_jwt token
108
+ rescue ArgumentError
109
+ if data = token['data']
110
+ return load_legacy data
73
111
  else
74
- api
112
+ raise "malformed token"
75
113
  end
76
114
  end
115
+
116
+ def load_legacy data
117
+ if data.is_a?(String)
118
+ @login = token['data']
119
+ elsif data.is_a?(Hash)
120
+ @attributes = token['data'].clone
121
+ @login = @attributes.delete('login') or raise "No 'login' field in token data"
122
+ else
123
+ raise "Expecting String or Hash token data, got #{data.class.name}"
124
+ end
125
+ end
126
+
127
+ def load_jwt jwt
128
+ @attributes = jwt.claims.merge (jwt.header || {}) # just pass all the info
129
+ @login = jwt.claims['sub'] or raise "No 'sub' field in claims"
130
+ end
77
131
  end
78
132
  end
79
133
  end
@@ -1,5 +1,5 @@
1
1
  module Conjur
2
2
  module Rack
3
- VERSION = "1.4.0"
3
+ VERSION = "3.1.0"
4
4
  end
5
5
  end
@@ -3,107 +3,179 @@ require 'spec_helper'
3
3
  require 'conjur/rack/authenticator'
4
4
 
5
5
  describe Conjur::Rack::Authenticator do
6
- let(:app) { double(:app) }
7
- let(:options) { {} }
8
- let(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
9
- let(:call) { authenticator.call env }
10
-
11
- context "#call" do
12
- context "with Conjur authorization" do
13
- before{ stub_const 'Slosilo', Module.new }
14
- let(:env) {
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
- let(:basic_64) { Base64.strict_encode64(token.to_json) }
23
- let(:token) { { "data" => "foobar" } }
24
- let(:sample_account) { "someacc" }
25
- let(:privilege) { nil }
26
- let(:remote_ip) { nil }
27
-
28
- context "of a valid token" do
29
-
30
- before(:each) {
31
- Slosilo.stub token_signer: 'authn:'+sample_account
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
- context 'Authable provides module method conjur_user' do
40
- let(:stubuser) { "some value" }
41
- before {
42
- app.stub(:call) { Conjur::Rack.user }
43
- }
44
-
45
- context 'when called in app context' do
46
- let(:invoke) {
47
- Conjur::Rack::User.should_receive(:new).
48
- with(token, sample_account, privilege, remote_ip).
49
- and_return(stubuser)
50
- Conjur::Rack.should_receive(:user).and_call_original
51
- call
52
- }
53
-
54
- shared_examples_for 'returns User built from token' do
55
- specify {
56
- invoke.should == stubuser
57
- }
58
- end
59
-
60
- it_should_behave_like 'returns User built from token'
61
-
62
- context 'with X-Conjur-Privilege' do
63
- let(:privilege) { "sudo" }
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
- context 'with X-Forwarded-For' do
68
- let(:remote_ip) { "66.0.0.1" }
69
- it_should_behave_like 'returns User built from token'
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
- context 'called out of app context' do
74
- it { lambda { Conjur::Rack.user }.should raise_error }
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
- context "of an invalid token" do
79
- it "returns a 401 error" do
80
- Slosilo.stub token_signer: nil
81
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"26"}, ["Unathorized: Invalid token"]]
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
- context "of a token invalid for authn" do
85
- it "returns a 401 error" do
86
- Slosilo.stub token_signer: 'a-totally-different-key'
87
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"26"}, ["Unathorized: Invalid token"]]
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
- end
92
- context "without authorization" do
93
- context "to a protected path" do
94
- let(:env) { { 'SCRIPT_NAME' => '/pathname' } }
95
- it "returns a 401 error" do
96
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"21"}, ["Authorization missing"]]
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
- context "to an unprotected path" do
100
- let(:except) { [ /^\/foo/ ] }
101
- let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
102
- it "proceeds" do
103
- options[:except] = except
104
- app.should_receive(:call).with(env).and_return app
105
- call.should == app
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.should_receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return 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.should_receive(:call).with({ 'PATH_INFO' => '/' }).and_return 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.should_receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
34
+ expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
35
35
  call
36
36
  end
37
37
  end
@@ -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{ described_class.new token, account, privilege, remote_ip }
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
- its(:token){ should == token }
13
- its(:account){ should == account }
14
- its(:conjur_account){ should == account }
15
- its(:login){ should == token['data'] }
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
- context "when login contains one token 'foobar'" do
34
- let(:tokens){ ['foobar'] }
35
- its(:roleid){ should == "#{account}:user:#{login}" }
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
- context "when login contains tokens ['foo', 'bar']" do
38
- let(:tokens){ ["foos", "bar"] }
39
- its(:roleid){ should == "#{account}:#{tokens[0]}:#{tokens[1]}"}
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
- context "when login contains tokens ['foo','bar','baz']" do
42
- let(:tokens){ ['foo', 'bar', 'baz'] }
43
- its(:roleid){ should == "#{account}:#{tokens[0]}:#{tokens[1]}/#{tokens[2]}" }
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,13 +58,13 @@ 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.stub(:roleid).and_return roleid
52
- subject.stub(:api).and_return api
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.should_receive(:role).with(roleid).and_return 'the role'
57
- subject.role.should == 'the 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
 
@@ -63,19 +73,19 @@ describe Conjur::Rack::User do
63
73
  let(:privilege) { "reveal" }
64
74
  let(:api){ Conjur::API.new_from_token "the-token" }
65
75
  before do
66
- subject.stub(:api).and_return api
76
+ allow(subject).to receive(:api).and_return(api)
67
77
  end
68
78
  it "checks the API function global_privilege_permitted?" do
69
- api.should_receive(:resource).with("!:!:conjur").and_return resource = double(:resource)
70
- resource.should_receive(:permitted?).with("reveal").and_return true
71
- expect(subject.global_reveal?).to be_true
79
+ expect(api).to receive(:resource).with("!:!:conjur").and_return(resource = double(:resource))
80
+ expect(resource).to receive(:permitted?).with("reveal").and_return(true)
81
+ expect(subject.global_reveal?).to be true
72
82
  # The result is cached
73
83
  subject.global_reveal?
74
84
  end
75
85
  end
76
86
  context "without a global privilege" do
77
87
  it "simply returns nil" do
78
- expect(subject.global_reveal?).to be_false
88
+ expect(subject.global_reveal?).to be false
79
89
  end
80
90
  end
81
91
  end
@@ -84,38 +94,96 @@ describe Conjur::Rack::User do
84
94
  context "when given a class" do
85
95
  let(:cls){ double('API class') }
86
96
  it "calls cls.new_from_token with its token" do
87
- cls.should_receive(:new_from_token).with(token).and_return 'the api'
88
- subject.api(cls).should == 'the api'
97
+ expect(cls).to receive(:new_from_token).with(token).and_return 'the api'
98
+ expect(subject.api(cls)).to eq('the api')
89
99
  end
90
100
  end
91
101
  context 'when not given args' do
92
102
  shared_examples_for "builds the api" do
93
- specify {
94
- subject.api.should == 'the api'
95
- }
103
+ its(:api) { should == 'the api' }
96
104
  end
97
105
 
98
106
  context "with no extra args" do
99
107
  before {
100
- Conjur::API.should_receive(:new_from_token).with(token).and_return 'the api'
108
+ expect(Conjur::API).to receive(:new_from_token).with(token).and_return('the api')
101
109
  }
102
110
  it_should_behave_like "builds the api"
103
111
  end
104
112
  context "with remote_ip" do
105
113
  let(:remote_ip) { "the-ip" }
106
114
  before {
107
- Conjur::API.should_receive(:new_from_token).with(token, 'the-ip').and_return 'the api'
115
+ expect(Conjur::API).to receive(:new_from_token).with(token, 'the-ip').and_return('the api')
108
116
  }
109
117
  it_should_behave_like "builds the api"
110
118
  end
111
119
  context "with privilege" do
112
- let(:privilege) { "sudo" }
120
+ let(:privilege) { "elevate" }
121
+ before {
122
+ expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
123
+ expect(api).to receive(:with_privilege).with("elevate").and_return('the api')
124
+ }
125
+ it_should_behave_like "builds the api"
126
+ end
127
+
128
+ context "with audit resource" do
129
+ let (:audit_resources) { 'food:bacon' }
113
130
  before {
114
- Conjur::API.should_receive(:new_from_token).with(token).and_return api = double(:api)
115
- expect(api).to receive(:with_privilege).with("sudo").and_return('the api')
131
+ expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
132
+ expect(api).to receive(:with_audit_resources).with(['food:bacon']).and_return('the api')
116
133
  }
117
134
  it_should_behave_like "builds the api"
118
135
  end
136
+
137
+ context "with audit roles" do
138
+ let (:audit_roles) { 'user:cook' }
139
+ before {
140
+ expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
141
+ expect(api).to receive(:with_audit_roles).with(['user:cook']).and_return('the api')
142
+ }
143
+ it_should_behave_like "builds the api"
144
+ end
145
+
146
+ end
147
+ end
148
+
149
+ context "with invalid type payload" do
150
+ let(:token){ { "data" => :alice } }
151
+ it "raises an error on trying to access the content" do
152
+ expect{ subject.login }.to raise_error("Expecting String or Hash token data, got Symbol")
153
+ end
154
+ end
155
+
156
+ context "with hash payload" do
157
+ let(:token){ { "data" => { "login" => "alice", "capabilities" => { "fry" => "bacon" } } } }
158
+
159
+ it "processes the login and attributes" do
160
+ original_token = token.deep_dup
161
+
162
+ expect(subject.login).to eq('alice')
163
+ expect(subject.attributes).to eq({"capabilities" => { "fry" => "bacon" }})
164
+
165
+ expect(token).to eq original_token
166
+ end
167
+ end
168
+
169
+ context "with JWT token" do
170
+ let(:token) { {"protected"=>"eyJhbGciOiJ0ZXN0IiwidHlwIjoiSldUIn0=",
171
+ "payload"=>"eyJzdWIiOiJhbGljZSIsImlhdCI6MTUwNDU1NDI2NX0=",
172
+ "signature"=>"dGVzdHNpZw=="} }
173
+
174
+ it "processes the login and attributes" do
175
+ original_token = token.deep_dup
176
+
177
+ expect(subject.login).to eq('alice')
178
+
179
+ # TODO: should we only pass unrecognized attrs here?
180
+ expect(subject.attributes).to eq \
181
+ 'alg' => 'test',
182
+ 'iat' => 1504554265,
183
+ 'sub' => 'alice',
184
+ 'typ' => 'JWT'
185
+
186
+ expect(token).to eq original_token
119
187
  end
120
188
  end
121
- end
189
+ end
@@ -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
@@ -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
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-20 00:00:00.000000000 Z
11
+ date: 2017-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slosilo
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: conjur-api
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '4.17'
33
+ version: '6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "<"
39
39
  - !ruby/object:Gem::Version
40
- version: '4.17'
40
+ version: '6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rack
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '1'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '1'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,22 +86,44 @@ dependencies:
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '2.9'
90
- - - "<"
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
91
95
  - !ruby/object:Gem::Version
92
- version: '3.0'
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ci_reporter_rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
93
104
  type: :development
94
105
  prerelease: false
95
106
  version_requirements: !ruby/object:Gem::Requirement
96
107
  requirements:
97
108
  - - ">="
98
109
  - !ruby/object:Gem::Version
99
- version: '2.9'
100
- - - "<"
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
101
116
  - !ruby/object:Gem::Version
102
- version: '3.0'
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
103
125
  - !ruby/object:Gem::Dependency
104
- name: ci_reporter_rspec
126
+ name: rspec-its
105
127
  requirement: !ruby/object:Gem::Requirement
106
128
  requirements:
107
129
  - - ">="
@@ -123,13 +145,13 @@ extra_rdoc_files: []
123
145
  files:
124
146
  - ".gitignore"
125
147
  - ".project"
126
- - ".rvmrc"
127
148
  - CHANGELOG.md
128
149
  - Gemfile
129
150
  - LICENSE.txt
130
151
  - README.md
131
152
  - Rakefile
132
153
  - conjur-rack.gemspec
154
+ - jenkins.sh
133
155
  - lib/conjur/rack.rb
134
156
  - lib/conjur/rack/authenticator.rb
135
157
  - lib/conjur/rack/path_prefix.rb
@@ -138,6 +160,7 @@ files:
138
160
  - spec/rack/authenticator_spec.rb
139
161
  - spec/rack/path_prefix_spec.rb
140
162
  - spec/rack/user_spec.rb
163
+ - spec/rack_spec.rb
141
164
  - spec/spec_helper.rb
142
165
  homepage: http://github.com/conjurinc/conjur-rack
143
166
  licenses:
@@ -159,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
182
  version: '0'
160
183
  requirements: []
161
184
  rubyforge_project:
162
- rubygems_version: 2.4.8
185
+ rubygems_version: 2.6.13
163
186
  signing_key:
164
187
  specification_version: 4
165
188
  summary: Rack authenticator and basic User struct
@@ -167,4 +190,5 @@ test_files:
167
190
  - spec/rack/authenticator_spec.rb
168
191
  - spec/rack/path_prefix_spec.rb
169
192
  - spec/rack/user_spec.rb
193
+ - spec/rack_spec.rb
170
194
  - spec/spec_helper.rb
data/.rvmrc DELETED
@@ -1 +0,0 @@
1
- rvm use --create 2.0.0@conjur-rack