mixlib-authentication 1.1.4 → 1.3.0.beta.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.
- data/README.rdoc +2 -0
- data/Rakefile +9 -27
- data/lib/mixlib/authentication/signatureverification.rb +11 -11
- data/lib/mixlib/authentication/signedheaderauth.rb +19 -7
- data/spec/mixlib/authentication/http_authentication_request_spec.rb +2 -3
- data/spec/mixlib/authentication/mixlib_authentication_spec.rb +189 -88
- metadata +23 -18
- data/spec/spec.opts +0 -4
data/README.rdoc
CHANGED
@@ -20,4 +20,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
20
|
See the License for the specific language governing permissions and
|
21
21
|
limitations under the License.
|
22
22
|
|
23
|
+
== Issue Tracker
|
23
24
|
|
25
|
+
Report any issues via Opscode Jira instance at http://tickets.opscode.com against Chef project.
|
data/Rakefile
CHANGED
@@ -2,45 +2,27 @@ require 'rubygems'
|
|
2
2
|
require 'rake/gempackagetask'
|
3
3
|
require 'rubygems/specification'
|
4
4
|
require 'date'
|
5
|
-
require '
|
5
|
+
require 'rspec/core/rake_task'
|
6
6
|
|
7
7
|
GEM = "mixlib-authentication"
|
8
|
-
GEM_VERSION = "1.1
|
8
|
+
GEM_VERSION = "1.2.1"
|
9
9
|
AUTHOR = "Opscode, Inc."
|
10
10
|
EMAIL = "info@opscode.com"
|
11
11
|
HOMEPAGE = "http://www.opscode.com"
|
12
12
|
SUMMARY = "Mixes in simple per-request authentication"
|
13
13
|
|
14
|
-
spec = Gem::Specification.new do |s|
|
15
|
-
s.name = GEM
|
16
|
-
s.version = GEM_VERSION
|
17
|
-
s.platform = Gem::Platform::RUBY
|
18
|
-
s.has_rdoc = true
|
19
|
-
s.extra_rdoc_files = ["README.rdoc", "LICENSE", 'NOTICE']
|
20
|
-
s.summary = SUMMARY
|
21
|
-
s.description = s.summary
|
22
|
-
s.author = AUTHOR
|
23
|
-
s.email = EMAIL
|
24
|
-
s.homepage = HOMEPAGE
|
25
|
-
|
26
|
-
# Uncomment this to add a dependency
|
27
|
-
s.add_dependency "mixlib-log"
|
28
|
-
|
29
|
-
s.require_path = 'lib'
|
30
|
-
s.autorequire = GEM
|
31
|
-
s.files = %w(LICENSE README.rdoc Rakefile NOTICE) + Dir.glob("{lib,spec,features}/**/*")
|
32
|
-
end
|
33
|
-
|
34
14
|
task :default => :spec
|
35
15
|
|
36
16
|
desc "Run specs"
|
37
|
-
|
38
|
-
t.
|
39
|
-
t.
|
17
|
+
RSpec::Core::RakeTask.new do |t|
|
18
|
+
t.pattern = 'spec/**/*_spec.rb'
|
19
|
+
t.rspec_opts = %w(-fs --color)
|
40
20
|
end
|
41
21
|
|
42
|
-
|
43
|
-
|
22
|
+
gem_spec = eval(File.read("mixlib-authentication.gemspec"))
|
23
|
+
|
24
|
+
Rake::GemPackageTask.new(gem_spec) do |pkg|
|
25
|
+
pkg.gem_spec = gem_spec
|
44
26
|
end
|
45
27
|
|
46
28
|
desc "install the gem locally"
|
@@ -34,7 +34,7 @@ module Mixlib
|
|
34
34
|
|
35
35
|
def_delegator :@auth_request, :path
|
36
36
|
|
37
|
-
def_delegator
|
37
|
+
def_delegator :@auth_request, :signing_description
|
38
38
|
|
39
39
|
def_delegator :@auth_request, :user_id
|
40
40
|
|
@@ -50,8 +50,6 @@ module Mixlib
|
|
50
50
|
|
51
51
|
include Mixlib::Authentication::SignedHeaderAuth
|
52
52
|
|
53
|
-
attr_reader :auth_request
|
54
|
-
|
55
53
|
def initialize(request=nil)
|
56
54
|
@auth_request = HTTPAuthenticationRequest.new(request) if request
|
57
55
|
|
@@ -65,12 +63,13 @@ module Mixlib
|
|
65
63
|
@auth_request = HTTPAuthenticationRequest.new(request)
|
66
64
|
authenticate_request(user_lookup, time_skew)
|
67
65
|
end
|
66
|
+
|
68
67
|
# Takes the request, boils down the pieces we are interested in,
|
69
68
|
# looks up the user, generates a signature, and compares to
|
70
69
|
# the signature in the request
|
71
70
|
# ====Headers
|
72
71
|
#
|
73
|
-
# X-Ops-Sign: algorithm=
|
72
|
+
# X-Ops-Sign: algorithm=sha1;version=1.0;
|
74
73
|
# X-Ops-UserId: <user_id>
|
75
74
|
# X-Ops-Timestamp:
|
76
75
|
# X-Ops-Content-Hash:
|
@@ -82,12 +81,13 @@ module Mixlib
|
|
82
81
|
@allowed_time_skew = time_skew # in seconds
|
83
82
|
|
84
83
|
begin
|
85
|
-
|
86
|
-
|
87
|
-
#
|
88
|
-
|
84
|
+
parts = parse_signing_description
|
85
|
+
|
86
|
+
# version 1.0 clients don't include their algorithm in the
|
87
|
+
# signing description, so default to sha1
|
88
|
+
parts[:algorithm] ||= 'sha1'
|
89
89
|
|
90
|
-
verify_signature
|
90
|
+
verify_signature(parts[:algorithm], parts[:version])
|
91
91
|
verify_timestamp
|
92
92
|
verify_content_hash
|
93
93
|
|
@@ -136,8 +136,8 @@ module Mixlib
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
-
def verify_signature
|
140
|
-
candidate_block = canonicalize_request
|
139
|
+
def verify_signature(algorithm, version)
|
140
|
+
candidate_block = canonicalize_request(algorithm, version)
|
141
141
|
request_decrypted_block = @user_secret.public_decrypt(Base64.decode64(request_signature))
|
142
142
|
@valid_signature = (request_decrypted_block == candidate_block)
|
143
143
|
|
@@ -27,8 +27,9 @@ module Mixlib
|
|
27
27
|
module Authentication
|
28
28
|
|
29
29
|
module SignedHeaderAuth
|
30
|
-
|
31
|
-
|
30
|
+
|
31
|
+
SUPPORTED_ALGORITHMS = ['sha1']
|
32
|
+
SUPPORTED_VERSIONS = ['1.0', '1.1']
|
32
33
|
|
33
34
|
# This is a module meant to be mixed in but can be used standalone
|
34
35
|
# with the simple OpenStruct extended with the auth functions
|
@@ -42,17 +43,17 @@ module Mixlib
|
|
42
43
|
# compute the signature from the request, using the looked-up user secret
|
43
44
|
# ====Parameters
|
44
45
|
# private_key<OpenSSL::PKey::RSA>:: user's RSA private key.
|
45
|
-
def sign(private_key)
|
46
|
+
def sign(private_key, algorithm='sha1', version='1.1')
|
46
47
|
# Our multiline hash for authorization will be encoded in multiple header
|
47
48
|
# lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
|
48
49
|
header_hash = {
|
49
|
-
"X-Ops-Sign" =>
|
50
|
+
"X-Ops-Sign" => "algorithm=#{algorithm};version=#{version};",
|
50
51
|
"X-Ops-Userid" => user_id,
|
51
52
|
"X-Ops-Timestamp" => canonical_time,
|
52
53
|
"X-Ops-Content-Hash" => hashed_body,
|
53
54
|
}
|
54
55
|
|
55
|
-
string_to_sign = canonicalize_request
|
56
|
+
string_to_sign = canonicalize_request(algorithm, version)
|
56
57
|
signature = Base64.encode64(private_key.private_encrypt(string_to_sign)).chomp
|
57
58
|
signature_lines = signature.split(/\n/)
|
58
59
|
signature_lines.each_index do |idx|
|
@@ -98,8 +99,18 @@ module Mixlib
|
|
98
99
|
# ====Parameters
|
99
100
|
#
|
100
101
|
#
|
101
|
-
def canonicalize_request
|
102
|
-
"
|
102
|
+
def canonicalize_request(algorithm='sha1', version='1.1')
|
103
|
+
raise AuthenticationError, "Bad algorithm '#{algorithm}' or version '#{version}'" unless SUPPORTED_ALGORITHMS.include?(algorithm) && SUPPORTED_VERSIONS.include?(version)
|
104
|
+
|
105
|
+
canonical_x_ops_user_id = case
|
106
|
+
when version == "1.1"
|
107
|
+
digester.hash_string(user_id)
|
108
|
+
when version == "1.0"
|
109
|
+
user_id
|
110
|
+
else
|
111
|
+
user_id
|
112
|
+
end
|
113
|
+
"Method:#{http_method.to_s.upcase}\nHashed Path:#{digester.hash_string(canonical_path)}\nX-Ops-Content-Hash:#{hashed_body}\nX-Ops-Timestamp:#{canonical_time}\nX-Ops-UserId:#{canonical_x_ops_user_id}"
|
103
114
|
end
|
104
115
|
|
105
116
|
# Parses signature version information, algorithm used, etc.
|
@@ -113,6 +124,7 @@ module Mixlib
|
|
113
124
|
memo
|
114
125
|
end
|
115
126
|
Mixlib::Authentication::Log.debug "Parsed signing description: #{parts.inspect}"
|
127
|
+
parts
|
116
128
|
end
|
117
129
|
|
118
130
|
def digester
|
@@ -82,8 +82,7 @@ describe Mixlib::Authentication::HTTPAuthenticationRequest do
|
|
82
82
|
it "raises an error when not all required headers are given" do
|
83
83
|
@merb_headers.delete("HTTP_X_OPS_SIGN")
|
84
84
|
exception = Mixlib::Authentication::MissingAuthenticationHeader
|
85
|
-
|
86
|
-
lambda {auth_req.validate_headers!}.should raise_error(exception)
|
85
|
+
lambda{ Mixlib::Authentication::HTTPAuthenticationRequest.new(@request) }.should raise_error(exception)
|
87
86
|
end
|
88
87
|
|
89
88
|
it "extracts the path from the request" do
|
@@ -126,4 +125,4 @@ SIG
|
|
126
125
|
@http_authentication_request.request_signature.should == expected.chomp
|
127
126
|
end
|
128
127
|
|
129
|
-
end
|
128
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Tim Hinderliter (<tim@opscode.com>)
|
3
3
|
# Author:: Christopher Walters (<cw@opscode.com>)
|
4
|
+
# Author:: Christopher Brown (<cb@opscode.com>)
|
4
5
|
# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
|
5
6
|
# License:: Apache License, Version 2.0
|
6
7
|
#
|
@@ -66,60 +67,51 @@ end
|
|
66
67
|
#Mixlib::Authentication::Log.level :debug
|
67
68
|
|
68
69
|
describe "Mixlib::Authentication::SignedHeaderAuth" do
|
69
|
-
|
70
|
-
|
71
|
-
# every time.
|
72
|
-
args = {
|
73
|
-
:body => BODY,
|
74
|
-
:user_id => USER_ID,
|
75
|
-
:http_method => :post,
|
76
|
-
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
77
|
-
:file => MockFile.new,
|
78
|
-
:path => PATH,
|
79
|
-
}
|
80
|
-
|
81
|
-
private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
|
70
|
+
|
71
|
+
it "should generate the correct string to sign and signature, version 1.0" do
|
82
72
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
Method:POST
|
87
|
-
Hashed Path:#{HASHED_CANONICAL_PATH}
|
88
|
-
X-Ops-Content-Hash:#{HASHED_BODY}
|
89
|
-
X-Ops-Timestamp:#{TIMESTAMP_ISO8601}
|
90
|
-
X-Ops-UserId:#{USER_ID}
|
91
|
-
EOS
|
92
|
-
signing_obj.canonicalize_request.should == expected_string_to_sign.chomp
|
73
|
+
algorithm = 'sha1'
|
74
|
+
version = '1.0'
|
75
|
+
V1_0_SIGNING_OBJECT.canonicalize_request(algorithm, version).should == V1_0_CANONICAL_REQUEST
|
93
76
|
|
94
77
|
# If you need to regenerate the constants in this test spec, print out
|
95
78
|
# the results of res.inspect and copy them as appropriate into the
|
96
79
|
# the constants in this file.
|
97
|
-
|
98
|
-
#$stderr.puts "res.inspect = #{res.inspect}"
|
99
|
-
res.should == EXPECTED_SIGN_RESULT
|
80
|
+
V1_0_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version).should == EXPECTED_SIGN_RESULT_V1_0
|
100
81
|
end
|
101
82
|
|
102
|
-
it "should
|
103
|
-
args = {
|
104
|
-
:body => BODY,
|
105
|
-
:user_id => USER_ID,
|
106
|
-
:http_method => :put,
|
107
|
-
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
108
|
-
:file => MockFile.new,
|
109
|
-
:path => PATH + "/nodes/#{"A" * 100}"}
|
83
|
+
it "should generate the correct string to sign and signature, version 1.1" do
|
110
84
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
85
|
+
V1_1_SIGNING_OBJECT.canonicalize_request.should == V1_1_CANONICAL_REQUEST
|
86
|
+
|
87
|
+
# If you need to regenerate the constants in this test spec, print out
|
88
|
+
# the results of res.inspect and copy them as appropriate into the
|
89
|
+
# the constants in this file.
|
90
|
+
V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY).should == EXPECTED_SIGN_RESULT_V1_1
|
116
91
|
end
|
92
|
+
|
93
|
+
it "should not choke when signing a request for a long user id with version 1.1" do
|
94
|
+
lambda { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', '1.1') }.should_not raise_error
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should choke when signing a request for a long user id with version 1.0" do
|
98
|
+
lambda { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', '1.0') }.should raise_error
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should choke when signing a request with a bad version" do
|
102
|
+
lambda { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', 'poo') }.should raise_error
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should choke when signing a request with a bad algorithm" do
|
106
|
+
lambda { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha_poo', '1.1') }.should raise_error
|
107
|
+
end
|
108
|
+
|
117
109
|
end
|
118
110
|
|
119
111
|
describe "Mixlib::Authentication::SignatureVerification" do
|
120
112
|
|
121
113
|
before(:each) do
|
122
|
-
@user_private_key =
|
114
|
+
@user_private_key = PRIVATE_KEY
|
123
115
|
end
|
124
116
|
|
125
117
|
it "should authenticate a File-containing request - Merb" do
|
@@ -127,7 +119,7 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
127
119
|
request_params["file"] =
|
128
120
|
{ "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new }
|
129
121
|
|
130
|
-
mock_request = MockRequest.new(PATH, request_params,
|
122
|
+
mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_1, "")
|
131
123
|
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
132
124
|
|
133
125
|
service = Mixlib::Authentication::SignatureVerification.new
|
@@ -135,8 +127,20 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
135
127
|
res.should_not be_nil
|
136
128
|
end
|
137
129
|
|
130
|
+
it "should authenticate a File-containing request from a v1.0 client - Passenger" do
|
131
|
+
request_params = PASSENGER_REQUEST_PARAMS.clone
|
132
|
+
request_params["tarball"] = MockFile.new
|
133
|
+
|
134
|
+
mock_request = MockRequest.new(PATH, request_params, PASSENGER_HEADERS_V1_0, "")
|
135
|
+
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
136
|
+
|
137
|
+
auth_req = Mixlib::Authentication::SignatureVerification.new
|
138
|
+
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
139
|
+
res.should_not be_nil
|
140
|
+
end
|
141
|
+
|
138
142
|
it "should authenticate a normal (post body) request - Merb" do
|
139
|
-
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS,
|
143
|
+
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_1, BODY)
|
140
144
|
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
141
145
|
|
142
146
|
service = Mixlib::Authentication::SignatureVerification.new
|
@@ -144,20 +148,17 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
144
148
|
res.should_not be_nil
|
145
149
|
end
|
146
150
|
|
147
|
-
it "should authenticate a
|
148
|
-
|
149
|
-
request_params["tarball"] = MockFile.new
|
150
|
-
|
151
|
-
mock_request = MockRequest.new(PATH, request_params, PASSENGER_HEADERS, "")
|
151
|
+
it "should authenticate a normal (post body) request from a v1.0 client - Merb" do
|
152
|
+
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_0, BODY)
|
152
153
|
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
153
154
|
|
154
|
-
|
155
|
-
res =
|
155
|
+
service = Mixlib::Authentication::SignatureVerification.new
|
156
|
+
res = service.authenticate_user_request(mock_request, @user_private_key)
|
156
157
|
res.should_not be_nil
|
157
158
|
end
|
158
159
|
|
159
160
|
it "shouldn't authenticate if an Authorization header is missing" do
|
160
|
-
headers =
|
161
|
+
headers = MERB_HEADERS_V1_1.clone
|
161
162
|
headers.delete("HTTP_X_OPS_SIGN")
|
162
163
|
|
163
164
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
@@ -174,7 +175,7 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
174
175
|
|
175
176
|
|
176
177
|
it "shouldn't authenticate if Authorization header is wrong" do
|
177
|
-
headers =
|
178
|
+
headers = MERB_HEADERS_V1_1.clone
|
178
179
|
headers["HTTP_X_OPS_CONTENT_HASH"] += "_"
|
179
180
|
|
180
181
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
@@ -191,7 +192,7 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
191
192
|
end
|
192
193
|
|
193
194
|
it "shouldn't authenticate if the timestamp is not within bounds" do
|
194
|
-
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS,
|
195
|
+
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_1, BODY)
|
195
196
|
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ - 1000)
|
196
197
|
|
197
198
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
@@ -204,7 +205,7 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
204
205
|
end
|
205
206
|
|
206
207
|
it "shouldn't authenticate if the signature is wrong" do
|
207
|
-
headers =
|
208
|
+
headers = MERB_HEADERS_V1_1.dup
|
208
209
|
headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail"
|
209
210
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
210
211
|
Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
@@ -221,6 +222,7 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
221
222
|
end
|
222
223
|
|
223
224
|
USER_ID = "spec-user"
|
225
|
+
DIGESTED_USER_ID = Base64.encode64(Digest::SHA1.new.digest(USER_ID)).chomp
|
224
226
|
BODY = "Spec Body"
|
225
227
|
HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=" # Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
|
226
228
|
TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z"
|
@@ -228,25 +230,75 @@ TIMESTAMP_OBJ = Time.parse("Thu Jan 01 12:00:00 -0000 2009")
|
|
228
230
|
PATH = "/organizations/clownco"
|
229
231
|
HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=" # Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
|
230
232
|
|
233
|
+
V1_0_ARGS = {
|
234
|
+
:body => BODY,
|
235
|
+
:user_id => USER_ID,
|
236
|
+
:http_method => :post,
|
237
|
+
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
238
|
+
:file => MockFile.new,
|
239
|
+
:path => PATH
|
240
|
+
}
|
241
|
+
|
242
|
+
V1_1_ARGS = {
|
243
|
+
:body => BODY,
|
244
|
+
:user_id => USER_ID,
|
245
|
+
:http_method => :post,
|
246
|
+
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
247
|
+
:file => MockFile.new,
|
248
|
+
:path => PATH
|
249
|
+
}
|
250
|
+
|
251
|
+
LONG_PATH_LONG_USER_ARGS = {
|
252
|
+
:body => BODY,
|
253
|
+
:user_id => "A" * 200,
|
254
|
+
:http_method => :put,
|
255
|
+
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
256
|
+
:file => MockFile.new,
|
257
|
+
:path => PATH + "/nodes/#{"A" * 250}"
|
258
|
+
}
|
259
|
+
|
231
260
|
REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa"
|
232
261
|
|
233
262
|
# Content hash is ???TODO
|
234
263
|
X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs="
|
264
|
+
X_OPS_AUTHORIZATION_LINES_V1_0 = [
|
265
|
+
"jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
|
266
|
+
"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
|
267
|
+
"3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
|
268
|
+
"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
|
269
|
+
"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
|
270
|
+
"utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="
|
271
|
+
]
|
272
|
+
|
235
273
|
X_OPS_AUTHORIZATION_LINES = [
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
274
|
+
"UfZD9dRz6rFu6LbP5Mo1oNHcWYxpNIcUfFCffJS1FQa0GtfU/vkt3/O5HuCM",
|
275
|
+
"1wIFl/U0f5faH9EWpXWY5NwKR031Myxcabw4t4ZLO69CIh/3qx1XnjcZvt2w",
|
276
|
+
"c2R9bx/43IWA/r8w8Q6decuu0f6ZlNheJeJhaYPI8piX/aH+uHBH8zTACZu8",
|
277
|
+
"vMnl5MF3/OIlsZc8cemq6eKYstp8a8KYq9OmkB5IXIX6qVMJHA6fRvQEB/7j",
|
278
|
+
"281Q7oI/O+lE8AmVyBbwruPb7Mp6s4839eYiOdjbDwFjYtbS3XgAjrHlaD7W",
|
279
|
+
"FDlbAG7H8Dmvo+wBxmtNkszhzbBnEYtuwQqT8nM/8A=="
|
242
280
|
]
|
243
281
|
|
244
282
|
# We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this
|
245
|
-
# if passed the BODY above
|
246
|
-
|
283
|
+
# if passed the BODY above, based on version
|
284
|
+
|
285
|
+
EXPECTED_SIGN_RESULT_V1_0 = {
|
286
|
+
"X-Ops-Content-Hash"=>X_OPS_CONTENT_HASH,
|
287
|
+
"X-Ops-Userid"=>USER_ID,
|
288
|
+
"X-Ops-Sign"=>"algorithm=sha1;version=1.0;",
|
289
|
+
"X-Ops-Authorization-1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
|
290
|
+
"X-Ops-Authorization-2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
|
291
|
+
"X-Ops-Authorization-3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
|
292
|
+
"X-Ops-Authorization-4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
|
293
|
+
"X-Ops-Authorization-5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
|
294
|
+
"X-Ops-Authorization-6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
|
295
|
+
"X-Ops-Timestamp"=>TIMESTAMP_ISO8601
|
296
|
+
}
|
297
|
+
|
298
|
+
EXPECTED_SIGN_RESULT_V1_1 = {
|
247
299
|
"X-Ops-Content-Hash"=>X_OPS_CONTENT_HASH,
|
248
300
|
"X-Ops-Userid"=>USER_ID,
|
249
|
-
"X-Ops-Sign"=>"version=1.
|
301
|
+
"X-Ops-Sign"=>"algorithm=sha1;version=1.1;",
|
250
302
|
"X-Ops-Authorization-1"=>X_OPS_AUTHORIZATION_LINES[0],
|
251
303
|
"X-Ops-Authorization-2"=>X_OPS_AUTHORIZATION_LINES[1],
|
252
304
|
"X-Ops-Authorization-3"=>X_OPS_AUTHORIZATION_LINES[2],
|
@@ -256,6 +308,16 @@ EXPECTED_SIGN_RESULT = {
|
|
256
308
|
"X-Ops-Timestamp"=>TIMESTAMP_ISO8601
|
257
309
|
}
|
258
310
|
|
311
|
+
OTHER_HEADERS = {
|
312
|
+
# An arbitrary sampling of non-HTTP_* headers are in here to
|
313
|
+
# exercise that code path.
|
314
|
+
"REMOTE_ADDR"=>"127.0.0.1",
|
315
|
+
"PATH_INFO"=>"/organizations/local-test-org/cookbooks",
|
316
|
+
"REQUEST_PATH"=>"/organizations/local-test-org/cookbooks",
|
317
|
+
"CONTENT_TYPE"=>"multipart/form-data; boundary=----RubyMultipartClient6792ZZZZZ",
|
318
|
+
"CONTENT_LENGTH"=>"394",
|
319
|
+
}
|
320
|
+
|
259
321
|
# This is what will be in request.params for the Merb case.
|
260
322
|
MERB_REQUEST_PARAMS = {
|
261
323
|
"name"=>"zsh", "action"=>"create", "controller"=>"chef_server_api/cookbooks",
|
@@ -263,11 +325,10 @@ MERB_REQUEST_PARAMS = {
|
|
263
325
|
}
|
264
326
|
|
265
327
|
# Tis is what will be in request.env for the Merb case.
|
266
|
-
|
267
|
-
# These are used by signatureverification.
|
268
|
-
# headers are in here to exercise that code path.
|
328
|
+
MERB_HEADERS_V1_1 = {
|
329
|
+
# These are used by signatureverification.
|
269
330
|
"HTTP_HOST"=>"127.0.0.1",
|
270
|
-
"HTTP_X_OPS_SIGN"=>"version=1.
|
331
|
+
"HTTP_X_OPS_SIGN"=>"algorithm=sha1;version=1.1;",
|
271
332
|
"HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
|
272
333
|
"HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
|
273
334
|
"HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
|
@@ -278,14 +339,24 @@ MERB_HEADERS = {
|
|
278
339
|
"HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
|
279
340
|
"HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
|
280
341
|
"HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
|
342
|
+
}.merge(OTHER_HEADERS)
|
281
343
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
"
|
286
|
-
"
|
287
|
-
"
|
288
|
-
|
344
|
+
# Tis is what will be in request.env for the Merb case.
|
345
|
+
MERB_HEADERS_V1_0 = {
|
346
|
+
# These are used by signatureverification.
|
347
|
+
"HTTP_HOST"=>"127.0.0.1",
|
348
|
+
"HTTP_X_OPS_SIGN"=>"version=1.0",
|
349
|
+
"HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
|
350
|
+
"HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
|
351
|
+
"HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
|
352
|
+
"HTTP_X_OPS_USERID"=>USER_ID,
|
353
|
+
"HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
|
354
|
+
"HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
|
355
|
+
"HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
|
356
|
+
"HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
|
357
|
+
"HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
|
358
|
+
"HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
|
359
|
+
}.merge(OTHER_HEADERS)
|
289
360
|
|
290
361
|
PASSENGER_REQUEST_PARAMS = {
|
291
362
|
"action"=>"create",
|
@@ -294,11 +365,10 @@ PASSENGER_REQUEST_PARAMS = {
|
|
294
365
|
"cookbook"=>"{\"category\":\"databases\"}",
|
295
366
|
}
|
296
367
|
|
297
|
-
|
298
|
-
# These are used by signatureverification.
|
299
|
-
# headers are in here to exercise that code path.
|
368
|
+
PASSENGER_HEADERS_V1_1 = {
|
369
|
+
# These are used by signatureverification.
|
300
370
|
"HTTP_HOST"=>"127.0.0.1",
|
301
|
-
"HTTP_X_OPS_SIGN"=>"version=1.
|
371
|
+
"HTTP_X_OPS_SIGN"=>"algorithm=sha1;version=1.1;",
|
302
372
|
"HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
|
303
373
|
"HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
|
304
374
|
"HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
|
@@ -309,21 +379,28 @@ PASSENGER_HEADERS = {
|
|
309
379
|
"HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
|
310
380
|
"HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
|
311
381
|
"HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
|
382
|
+
}.merge(OTHER_HEADERS)
|
312
383
|
|
313
|
-
|
314
|
-
|
315
|
-
"
|
316
|
-
"
|
317
|
-
"
|
318
|
-
"
|
319
|
-
"
|
320
|
-
"
|
321
|
-
|
384
|
+
PASSENGER_HEADERS_V1_0 = {
|
385
|
+
# These are used by signatureverification.
|
386
|
+
"HTTP_HOST"=>"127.0.0.1",
|
387
|
+
"HTTP_X_OPS_SIGN"=>"version=1.0",
|
388
|
+
"HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
|
389
|
+
"HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
|
390
|
+
"HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
|
391
|
+
"HTTP_X_OPS_USERID"=>USER_ID,
|
392
|
+
"HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
|
393
|
+
"HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
|
394
|
+
"HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
|
395
|
+
"HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
|
396
|
+
"HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
|
397
|
+
"HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
|
398
|
+
}.merge(OTHER_HEADERS)
|
322
399
|
|
323
400
|
# generated with
|
324
401
|
# openssl genrsa -out private.pem 2048
|
325
402
|
# openssl rsa -in private.pem -out public.pem -pubout
|
326
|
-
|
403
|
+
PUBLIC_KEY_DATA = <<EOS
|
327
404
|
-----BEGIN PUBLIC KEY-----
|
328
405
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ueqo76MXuP6XqZBILFz
|
329
406
|
iH/9AI7C6PaN5W0dSvkr9yInyGHSz/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUB
|
@@ -335,7 +412,7 @@ YQIDAQAB
|
|
335
412
|
-----END PUBLIC KEY-----
|
336
413
|
EOS
|
337
414
|
|
338
|
-
|
415
|
+
PRIVATE_KEY_DATA = <<EOS
|
339
416
|
-----BEGIN RSA PRIVATE KEY-----
|
340
417
|
MIIEpAIBAAKCAQEA0ueqo76MXuP6XqZBILFziH/9AI7C6PaN5W0dSvkr9yInyGHS
|
341
418
|
z/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUBuAsnlAVQ1z0exDtIFFOyQCdR7iXm
|
@@ -364,3 +441,27 @@ YlkUQYXhy9JixmUUKtH+NXkKX7Lyc8XYw5ETr7JBT3ifs+G7HruDjVG78EJVojbd
|
|
364
441
|
8uLA+DdQm5mg4vd1GTiSK65q/3EeoBlUaVor3HhLFki+i9qpT8CBsg==
|
365
442
|
-----END RSA PRIVATE KEY-----
|
366
443
|
EOS
|
444
|
+
|
445
|
+
PRIVATE_KEY = OpenSSL::PKey::RSA.new(PRIVATE_KEY_DATA)
|
446
|
+
|
447
|
+
V1_0_CANONICAL_REQUEST_DATA = <<EOS
|
448
|
+
Method:POST
|
449
|
+
Hashed Path:#{HASHED_CANONICAL_PATH}
|
450
|
+
X-Ops-Content-Hash:#{HASHED_BODY}
|
451
|
+
X-Ops-Timestamp:#{TIMESTAMP_ISO8601}
|
452
|
+
X-Ops-UserId:#{USER_ID}
|
453
|
+
EOS
|
454
|
+
V1_0_CANONICAL_REQUEST = V1_0_CANONICAL_REQUEST_DATA.chomp
|
455
|
+
|
456
|
+
V1_1_CANONICAL_REQUEST_DATA = <<EOS
|
457
|
+
Method:POST
|
458
|
+
Hashed Path:#{HASHED_CANONICAL_PATH}
|
459
|
+
X-Ops-Content-Hash:#{HASHED_BODY}
|
460
|
+
X-Ops-Timestamp:#{TIMESTAMP_ISO8601}
|
461
|
+
X-Ops-UserId:#{DIGESTED_USER_ID}
|
462
|
+
EOS
|
463
|
+
V1_1_CANONICAL_REQUEST = V1_1_CANONICAL_REQUEST_DATA.chomp
|
464
|
+
|
465
|
+
V1_1_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_1_ARGS)
|
466
|
+
V1_0_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_0_ARGS)
|
467
|
+
LONG_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(LONG_PATH_LONG_USER_ARGS)
|
metadata
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixlib-authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 62196403
|
5
|
+
prerelease: 6
|
5
6
|
segments:
|
6
7
|
- 1
|
7
|
-
-
|
8
|
-
-
|
9
|
-
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
- beta
|
11
|
+
- 0
|
12
|
+
version: 1.3.0.beta.0
|
10
13
|
platform: ruby
|
11
14
|
authors:
|
12
15
|
- Opscode, Inc.
|
13
|
-
autorequire:
|
16
|
+
autorequire:
|
14
17
|
bindir: bin
|
15
18
|
cert_chain: []
|
16
19
|
|
17
|
-
date:
|
18
|
-
default_executable:
|
20
|
+
date: 2012-06-05 00:00:00 Z
|
19
21
|
dependencies:
|
20
22
|
- !ruby/object:Gem::Dependency
|
21
23
|
name: mixlib-log
|
@@ -25,6 +27,7 @@ dependencies:
|
|
25
27
|
requirements:
|
26
28
|
- - ">="
|
27
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
28
31
|
segments:
|
29
32
|
- 0
|
30
33
|
version: "0"
|
@@ -45,16 +48,14 @@ files:
|
|
45
48
|
- README.rdoc
|
46
49
|
- Rakefile
|
47
50
|
- NOTICE
|
48
|
-
- lib/mixlib/authentication
|
49
|
-
- lib/mixlib/authentication/http_authentication_request.rb
|
51
|
+
- lib/mixlib/authentication.rb
|
50
52
|
- lib/mixlib/authentication/signatureverification.rb
|
51
53
|
- lib/mixlib/authentication/signedheaderauth.rb
|
52
|
-
- lib/mixlib/authentication.rb
|
53
|
-
-
|
54
|
-
- spec/mixlib/authentication/mixlib_authentication_spec.rb
|
55
|
-
- spec/spec.opts
|
54
|
+
- lib/mixlib/authentication/digester.rb
|
55
|
+
- lib/mixlib/authentication/http_authentication_request.rb
|
56
56
|
- spec/spec_helper.rb
|
57
|
-
|
57
|
+
- spec/mixlib/authentication/mixlib_authentication_spec.rb
|
58
|
+
- spec/mixlib/authentication/http_authentication_request_spec.rb
|
58
59
|
homepage: http://www.opscode.com
|
59
60
|
licenses: []
|
60
61
|
|
@@ -68,21 +69,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
69
|
requirements:
|
69
70
|
- - ">="
|
70
71
|
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
71
73
|
segments:
|
72
74
|
- 0
|
73
75
|
version: "0"
|
74
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
77
|
none: false
|
76
78
|
requirements:
|
77
|
-
- - "
|
79
|
+
- - ">"
|
78
80
|
- !ruby/object:Gem::Version
|
81
|
+
hash: 25
|
79
82
|
segments:
|
80
|
-
-
|
81
|
-
|
83
|
+
- 1
|
84
|
+
- 3
|
85
|
+
- 1
|
86
|
+
version: 1.3.1
|
82
87
|
requirements: []
|
83
88
|
|
84
89
|
rubyforge_project:
|
85
|
-
rubygems_version: 1.
|
90
|
+
rubygems_version: 1.8.10
|
86
91
|
signing_key:
|
87
92
|
specification_version: 3
|
88
93
|
summary: Mixes in simple per-request authentication
|
data/spec/spec.opts
DELETED