mixlib-authentication 1.1.4 → 1.3.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
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 'spec/rake/spectask'
5
+ require 'rspec/core/rake_task'
6
6
 
7
7
  GEM = "mixlib-authentication"
8
- GEM_VERSION = "1.1.4"
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
- Spec::Rake::SpecTask.new do |t|
38
- t.spec_files = FileList['spec/**/*_spec.rb']
39
- t.spec_opts = %w(-fs --color)
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
- Rake::GemPackageTask.new(spec) do |pkg|
43
- pkg.gem_spec = spec
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 :auth_request, :signing_description
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=sha256;version=1.0;
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
- @auth_request
86
-
87
- #BUGBUG Not doing anything with the signing description yet [cb]
88
- parse_signing_description
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
- SIGNING_DESCRIPTION = 'version=1.0'
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" => SIGNING_DESCRIPTION,
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
- "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:#{user_id}"
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
- auth_req = Mixlib::Authentication::HTTPAuthenticationRequest.new(@request)
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
- it "should generate the correct string to sign and signature" do
70
- # fix the timestamp, private key and body so we get the same answer back
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
- signing_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(args)
84
-
85
- expected_string_to_sign = <<EOS
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
- res = signing_obj.sign(private_key)
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 not choke when signing a request for a resource with a long name" do
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
- private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
112
-
113
- signing_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(args)
114
-
115
- lambda { signing_obj.sign(private_key) }.should_not raise_error
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 = OpenSSL::PKey::RSA.new(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, MERB_HEADERS, "")
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, MERB_HEADERS, BODY)
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 File-containing request - Passenger" do
148
- request_params = PASSENGER_REQUEST_PARAMS.clone
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
- auth_req = Mixlib::Authentication::SignatureVerification.new
155
- res = auth_req.authenticate_user_request(mock_request, @user_private_key)
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 = MERB_HEADERS.clone
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 = MERB_HEADERS.clone
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, MERB_HEADERS, BODY)
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 = MERB_HEADERS.dup
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
- "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
237
- "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
238
- "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
239
- "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
240
- "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
241
- "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="
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
- EXPECTED_SIGN_RESULT = {
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.0",
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
- MERB_HEADERS = {
267
- # These are used by signatureverification. An arbitrary sampling of non-HTTP_*
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.0",
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
- # Random sampling
283
- "REMOTE_ADDR"=>"127.0.0.1",
284
- "PATH_INFO"=>"/organizations/local-test-org/cookbooks",
285
- "REQUEST_PATH"=>"/organizations/local-test-org/cookbooks",
286
- "CONTENT_TYPE"=>"multipart/form-data; boundary=----RubyMultipartClient6792ZZZZZ",
287
- "CONTENT_LENGTH"=>"394",
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
- PASSENGER_HEADERS = {
298
- # These are used by signatureverification. An arbitrary sampling of non-HTTP_*
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.0",
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
- # Random set of other headers to exercirse the non- HTTP_ code path
314
- "HTTP_ACCEPT"=>"application/json",
315
- "SERVER_SOFTWARE"=>"Apache",
316
- "SCRIPT_URI"=>"http://com-stg.opscode.com/api/v1/cookbooks",
317
- "SCRIPT_NAME"=>"",
318
- "SERVER_ADDR"=>"10.242.197.174",
319
- "SERVER_NAME"=>"com-stg.opscode.com",
320
- "DOCUMENT_ROOT"=>"/srv/opscode-community/current/public",
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
- PUBLIC_KEY = <<EOS
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
- PRIVATE_KEY = <<EOS
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
- prerelease: false
4
+ hash: 62196403
5
+ prerelease: 6
5
6
  segments:
6
7
  - 1
7
- - 1
8
- - 4
9
- version: 1.1.4
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: mixlib-authentication
16
+ autorequire:
14
17
  bindir: bin
15
18
  cert_chain: []
16
19
 
17
- date: 2010-07-23 00:00:00 -07:00
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/digester.rb
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
- - spec/mixlib/authentication/http_authentication_request_spec.rb
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
- has_rdoc: true
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
- - 0
81
- version: "0"
83
+ - 1
84
+ - 3
85
+ - 1
86
+ version: 1.3.1
82
87
  requirements: []
83
88
 
84
89
  rubyforge_project:
85
- rubygems_version: 1.3.7
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
@@ -1,4 +0,0 @@
1
- --colour
2
- --format specdoc
3
- --loadby mtime
4
- --reverse