mixlib-authentication 1.3.0 → 1.4.0.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Rakefile +3 -3
- data/lib/mixlib/authentication.rb +2 -0
- data/lib/mixlib/authentication/digester.rb +9 -10
- data/lib/mixlib/authentication/http_authentication_request.rb +4 -2
- data/lib/mixlib/authentication/signatureverification.rb +14 -5
- data/lib/mixlib/authentication/signedheaderauth.rb +114 -21
- data/lib/mixlib/authentication/version.rb +21 -0
- data/mixlib-authentication.gemspec +25 -0
- data/spec/mixlib/authentication/http_authentication_request_spec.rb +14 -10
- data/spec/mixlib/authentication/mixlib_authentication_spec.rb +163 -46
- metadata +96 -56
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4af0553c29d4d0ee53f281ab38ef8ed7097ba8f9
|
4
|
+
data.tar.gz: 198e0ae73d73b4d5c3f39e61dd30bab6822c1031
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5613f35e48e2142c25614b39c114b9ed7412a5039eea64141bed4087408c51f4242a11b954a9d0393a85b1ecdd850dc505d2267c21eb8c3737af6a795873d4c8
|
7
|
+
data.tar.gz: 071de33850bcf8120424ef0ee26e4d6013fd96fabff92c10dd69caf2da521b890d8433809080b8e86a0022f111c83edb6e47c334ed44517bb3811db856c6e8a0
|
data/Gemfile
ADDED
data/Rakefile
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'rubygems/package_task'
|
3
3
|
require 'rubygems/specification'
|
4
4
|
require 'date'
|
5
5
|
require 'rspec/core/rake_task'
|
@@ -16,12 +16,12 @@ task :default => :spec
|
|
16
16
|
desc "Run specs"
|
17
17
|
RSpec::Core::RakeTask.new do |t|
|
18
18
|
t.pattern = 'spec/**/*_spec.rb'
|
19
|
-
t.rspec_opts = %w(
|
19
|
+
t.rspec_opts = %w(--format documentation --color)
|
20
20
|
end
|
21
21
|
|
22
22
|
gem_spec = eval(File.read("mixlib-authentication.gemspec"))
|
23
23
|
|
24
|
-
|
24
|
+
Gem::PackageTask.new(gem_spec) do |pkg|
|
25
25
|
pkg.gem_spec = gem_spec
|
26
26
|
end
|
27
27
|
|
@@ -21,11 +21,10 @@ require 'mixlib/authentication'
|
|
21
21
|
module Mixlib
|
22
22
|
module Authentication
|
23
23
|
class Digester
|
24
|
-
|
25
24
|
class << self
|
26
|
-
|
27
|
-
def hash_file(f)
|
28
|
-
digester =
|
25
|
+
|
26
|
+
def hash_file(digest, f)
|
27
|
+
digester = digest.new
|
29
28
|
buf = ""
|
30
29
|
while f.read(16384, buf)
|
31
30
|
digester.update buf
|
@@ -34,15 +33,15 @@ module Mixlib
|
|
34
33
|
end
|
35
34
|
|
36
35
|
# Digests a string, base64's and chomps the end
|
37
|
-
#
|
36
|
+
#
|
38
37
|
# ====Parameters
|
39
|
-
#
|
40
|
-
def hash_string(str)
|
41
|
-
::Base64.encode64(
|
38
|
+
#
|
39
|
+
def hash_string(digest, str)
|
40
|
+
::Base64.encode64(digest.digest(str)).chomp
|
42
41
|
end
|
43
|
-
|
42
|
+
|
44
43
|
end
|
45
|
-
|
44
|
+
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
@@ -64,6 +64,10 @@ module Mixlib
|
|
64
64
|
headers[:x_ops_content_hash].chomp
|
65
65
|
end
|
66
66
|
|
67
|
+
def server_api_version
|
68
|
+
(headers[:x_ops_server_api_version] || DEFAULT_SERVER_API_VERSION).chomp
|
69
|
+
end
|
70
|
+
|
67
71
|
def request_signature
|
68
72
|
unless @request_signature
|
69
73
|
@request_signature = headers.find_all { |h| h[0].to_s =~ /^x_ops_authorization_/ }.sort { |x,y| x.to_s <=> y.to_s}.map { |i| i[1] }.join("\n")
|
@@ -80,8 +84,6 @@ module Mixlib
|
|
80
84
|
raise MissingAuthenticationHeader, "missing required authentication header(s) '#{missing_headers.join("', '")}'"
|
81
85
|
end
|
82
86
|
end
|
83
|
-
|
84
|
-
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
@@ -48,6 +48,8 @@ module Mixlib
|
|
48
48
|
|
49
49
|
def_delegator :@auth_request, :request
|
50
50
|
|
51
|
+
def_delegator :@auth_request, :server_api_version
|
52
|
+
|
51
53
|
include Mixlib::Authentication::SignedHeaderAuth
|
52
54
|
|
53
55
|
def initialize(request=nil)
|
@@ -138,8 +140,15 @@ module Mixlib
|
|
138
140
|
|
139
141
|
def verify_signature(algorithm, version)
|
140
142
|
candidate_block = canonicalize_request(algorithm, version)
|
141
|
-
|
142
|
-
@valid_signature =
|
143
|
+
signature = Base64.decode64(request_signature)
|
144
|
+
@valid_signature = case version
|
145
|
+
when '1.3'
|
146
|
+
digest = validate_sign_version_digest!(algorithm, version)
|
147
|
+
@user_secret.verify(digest.new, signature, candidate_block)
|
148
|
+
else
|
149
|
+
request_decrypted_block = @user_secret.public_decrypt(signature)
|
150
|
+
(request_decrypted_block == candidate_block)
|
151
|
+
end
|
143
152
|
|
144
153
|
# Keep the debug messages lined up so it's easy to scan them
|
145
154
|
Mixlib::Authentication::Log.debug("Verifying request signature:")
|
@@ -171,7 +180,7 @@ module Mixlib
|
|
171
180
|
|
172
181
|
# The request signature is based on any file attached, if any. Otherwise
|
173
182
|
# it's based on the body of the request.
|
174
|
-
def hashed_body
|
183
|
+
def hashed_body(digest=Digest::SHA1)
|
175
184
|
unless @hashed_body
|
176
185
|
# TODO: tim: 2009-112-28: It'd be nice to remove this special case, and
|
177
186
|
# always hash the entire request body. In the file case it would just be
|
@@ -205,11 +214,11 @@ module Mixlib
|
|
205
214
|
# we hash the body.
|
206
215
|
if file_param
|
207
216
|
Mixlib::Authentication::Log.debug "Digesting file_param: '#{file_param.inspect}'"
|
208
|
-
@hashed_body = digester.hash_file(file_param)
|
217
|
+
@hashed_body = digester.hash_file(digest, file_param)
|
209
218
|
else
|
210
219
|
body = request.raw_post
|
211
220
|
Mixlib::Authentication::Log.debug "Digesting body: '#{body}'"
|
212
|
-
@hashed_body = digester.hash_string(body)
|
221
|
+
@hashed_body = digester.hash_string(digest, body)
|
213
222
|
end
|
214
223
|
end
|
215
224
|
@hashed_body
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
require 'time'
|
21
21
|
require 'base64'
|
22
|
-
require 'digest
|
22
|
+
require 'openssl/digest'
|
23
23
|
require 'mixlib/authentication'
|
24
24
|
require 'mixlib/authentication/digester'
|
25
25
|
|
@@ -29,13 +29,21 @@ module Mixlib
|
|
29
29
|
module SignedHeaderAuth
|
30
30
|
|
31
31
|
NULL_ARG = Object.new
|
32
|
+
|
33
|
+
ALGORITHM_FOR_VERSION = {
|
34
|
+
'1.0' => 'sha1',
|
35
|
+
'1.1' => 'sha1',
|
36
|
+
'1.3' => 'sha256',
|
37
|
+
}.freeze()
|
38
|
+
|
39
|
+
# Use of SUPPORTED_ALGORITHMS and SUPPORTED_VERSIONS is deprecated. Use
|
40
|
+
# ALGORITHM_FOR_VERSION instead
|
32
41
|
SUPPORTED_ALGORITHMS = ['sha1'].freeze
|
33
42
|
SUPPORTED_VERSIONS = ['1.0', '1.1'].freeze
|
34
43
|
|
35
44
|
DEFAULT_SIGN_ALGORITHM = 'sha1'.freeze
|
36
45
|
DEFAULT_PROTO_VERSION = '1.0'.freeze
|
37
46
|
|
38
|
-
|
39
47
|
# === signing_object
|
40
48
|
# This is the intended interface for signing requests with the
|
41
49
|
# Opscode/Chef signed header protocol. This wraps the constructor for a
|
@@ -65,11 +73,20 @@ module Mixlib
|
|
65
73
|
# These parameters are accepted but not used in the computation of the signature.
|
66
74
|
# * `:host`: The host part of the URI
|
67
75
|
def self.signing_object(args={ })
|
68
|
-
SigningObject.new(args[:http_method],
|
76
|
+
SigningObject.new(args[:http_method],
|
77
|
+
args[:path],
|
78
|
+
args[:body],
|
79
|
+
args[:host],
|
80
|
+
args[:timestamp],
|
81
|
+
args[:user_id],
|
82
|
+
args[:file],
|
83
|
+
args[:proto_version],
|
84
|
+
args[:headers]
|
85
|
+
)
|
69
86
|
end
|
70
87
|
|
71
88
|
def algorithm
|
72
|
-
DEFAULT_SIGN_ALGORITHM
|
89
|
+
ALGORITHM_FOR_VERSION[proto_version] || DEFAULT_SIGN_ALGORITHM
|
73
90
|
end
|
74
91
|
|
75
92
|
def proto_version
|
@@ -81,28 +98,50 @@ module Mixlib
|
|
81
98
|
# ====Parameters
|
82
99
|
# private_key<OpenSSL::PKey::RSA>:: user's RSA private key.
|
83
100
|
def sign(private_key, sign_algorithm=algorithm, sign_version=proto_version)
|
101
|
+
digest = validate_sign_version_digest!(sign_algorithm, sign_version)
|
84
102
|
# Our multiline hash for authorization will be encoded in multiple header
|
85
103
|
# lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
|
86
104
|
header_hash = {
|
87
105
|
"X-Ops-Sign" => "algorithm=#{sign_algorithm};version=#{sign_version};",
|
88
106
|
"X-Ops-Userid" => user_id,
|
89
107
|
"X-Ops-Timestamp" => canonical_time,
|
90
|
-
"X-Ops-Content-Hash" => hashed_body,
|
108
|
+
"X-Ops-Content-Hash" => hashed_body(digest),
|
91
109
|
}
|
92
110
|
|
93
|
-
|
94
|
-
signature = Base64.encode64(private_key.private_encrypt(string_to_sign)).chomp
|
111
|
+
signature = Base64.encode64(do_sign(private_key, digest, sign_algorithm, sign_version)).chomp
|
95
112
|
signature_lines = signature.split(/\n/)
|
96
113
|
signature_lines.each_index do |idx|
|
97
114
|
key = "X-Ops-Authorization-#{idx + 1}"
|
98
115
|
header_hash[key] = signature_lines[idx]
|
99
116
|
end
|
100
117
|
|
101
|
-
Mixlib::Authentication::Log.debug "
|
118
|
+
Mixlib::Authentication::Log.debug "Header hash: #{header_hash.inspect}"
|
102
119
|
|
103
120
|
header_hash
|
104
121
|
end
|
105
122
|
|
123
|
+
def validate_sign_version_digest!(sign_algorithm, sign_version)
|
124
|
+
if ALGORITHM_FOR_VERSION[sign_version].nil?
|
125
|
+
raise AuthenticationError,
|
126
|
+
"Unsupported version '#{sign_version}'"
|
127
|
+
end
|
128
|
+
|
129
|
+
if ALGORITHM_FOR_VERSION[sign_version] != sign_algorithm
|
130
|
+
raise AuthenticationError,
|
131
|
+
"Unsupported algorithm #{sign_algorithm} for version '#{sign_version}'"
|
132
|
+
end
|
133
|
+
|
134
|
+
case sign_algorithm
|
135
|
+
when 'sha1'
|
136
|
+
OpenSSL::Digest::SHA1
|
137
|
+
when 'sha256'
|
138
|
+
OpenSSL::Digest::SHA256
|
139
|
+
else
|
140
|
+
# This case should never happen
|
141
|
+
raise "Unknown algorithm #{sign_algorithm}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
106
145
|
# Build the canonicalized time based on utc & iso8601
|
107
146
|
#
|
108
147
|
# ====Parameters
|
@@ -121,13 +160,27 @@ module Mixlib
|
|
121
160
|
p.length > 1 ? p.chomp('/') : p
|
122
161
|
end
|
123
162
|
|
124
|
-
def hashed_body
|
163
|
+
def hashed_body(digest=OpenSSL::Digest::SHA1)
|
164
|
+
# This is weird. sign() is called with the digest type and signing
|
165
|
+
# version. These are also expected to be properties of the object.
|
166
|
+
# Hence, we're going to assume the one that is passed to sign is
|
167
|
+
# the correct one and needs to passed through all the functions
|
168
|
+
# that do any sort of digest.
|
169
|
+
if @hashed_body_digest != nil && @hashed_body_digest != digest
|
170
|
+
raise "hashed_body must always be called with the same digest"
|
171
|
+
else
|
172
|
+
@hashed_body_digest = digest
|
173
|
+
end
|
125
174
|
# Hash the file object if it was passed in, otherwise hash based on
|
126
175
|
# the body.
|
127
176
|
# TODO: tim 2009-12-28: It'd be nice to just remove this special case,
|
128
177
|
# always sign the entire request body, using the expanded multipart
|
129
178
|
# body in the case of a file being include.
|
130
|
-
@hashed_body ||=
|
179
|
+
@hashed_body ||= if self.file && self.file.respond_to?(:read)
|
180
|
+
digester.hash_file(digest, self.file)
|
181
|
+
else
|
182
|
+
digester.hash_string(digest, self.body)
|
183
|
+
end
|
131
184
|
end
|
132
185
|
|
133
186
|
# Takes HTTP request method & headers and creates a canonical form
|
@@ -137,21 +190,37 @@ module Mixlib
|
|
137
190
|
#
|
138
191
|
#
|
139
192
|
def canonicalize_request(sign_algorithm=algorithm, sign_version=proto_version)
|
140
|
-
|
141
|
-
|
193
|
+
digest = validate_sign_version_digest!(sign_algorithm, sign_version)
|
194
|
+
canonical_x_ops_user_id = canonicalize_user_id(user_id, sign_version, digest)
|
195
|
+
case sign_version
|
196
|
+
when "1.3"
|
197
|
+
[
|
198
|
+
"Method:#{http_method.to_s.upcase}",
|
199
|
+
"Path:#{canonical_path}",
|
200
|
+
"X-Ops-Content-Hash:#{hashed_body(digest)}",
|
201
|
+
"X-Ops-Sign:version=#{sign_version}",
|
202
|
+
"X-Ops-Timestamp:#{canonical_time}",
|
203
|
+
"X-Ops-UserId:#{canonical_x_ops_user_id}",
|
204
|
+
"X-Ops-Server-API-Version:#{server_api_version}",
|
205
|
+
].join("\n")
|
206
|
+
else
|
207
|
+
[
|
208
|
+
"Method:#{http_method.to_s.upcase}",
|
209
|
+
"Hashed Path:#{digester.hash_string(digest, canonical_path)}",
|
210
|
+
"X-Ops-Content-Hash:#{hashed_body(digest)}",
|
211
|
+
"X-Ops-Timestamp:#{canonical_time}",
|
212
|
+
"X-Ops-UserId:#{canonical_x_ops_user_id}"
|
213
|
+
].join("\n")
|
142
214
|
end
|
143
|
-
|
144
|
-
canonical_x_ops_user_id = canonicalize_user_id(user_id, sign_version)
|
145
|
-
"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}"
|
146
215
|
end
|
147
216
|
|
148
|
-
def canonicalize_user_id(user_id, proto_version)
|
217
|
+
def canonicalize_user_id(user_id, proto_version, digest=OpenSSL::Digest::SHA1)
|
149
218
|
case proto_version
|
150
219
|
when "1.1"
|
151
|
-
|
152
|
-
|
153
|
-
user_id
|
220
|
+
# and 1.2 if that ever gets implemented
|
221
|
+
digester.hash_string(digest, user_id)
|
154
222
|
else
|
223
|
+
# versions 1.0 and 1.3
|
155
224
|
user_id
|
156
225
|
end
|
157
226
|
end
|
@@ -174,6 +243,18 @@ module Mixlib
|
|
174
243
|
Mixlib::Authentication::Digester
|
175
244
|
end
|
176
245
|
|
246
|
+
# private
|
247
|
+
def do_sign(private_key, digest, sign_algorithm, sign_version)
|
248
|
+
string_to_sign = canonicalize_request(sign_algorithm, sign_version)
|
249
|
+
Mixlib::Authentication::Log.debug "String to sign: '#{string_to_sign}'"
|
250
|
+
case sign_version
|
251
|
+
when '1.3'
|
252
|
+
private_key.sign(digest.new, string_to_sign)
|
253
|
+
else
|
254
|
+
private_key.private_encrypt(string_to_sign)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
177
258
|
private :canonical_time, :canonical_path, :parse_signing_description, :digester, :canonicalize_user_id
|
178
259
|
|
179
260
|
end
|
@@ -182,13 +263,25 @@ module Mixlib
|
|
182
263
|
# A Struct-based value object that contains the necessary information to
|
183
264
|
# generate a request signature. `SignedHeaderAuth.signing_object()`
|
184
265
|
# provides a more convenient interface to the constructor.
|
185
|
-
class SigningObject < Struct.new(:http_method, :path, :body, :host,
|
266
|
+
class SigningObject < Struct.new(:http_method, :path, :body, :host,
|
267
|
+
:timestamp, :user_id, :file, :proto_version,
|
268
|
+
:headers)
|
186
269
|
include SignedHeaderAuth
|
187
270
|
|
188
271
|
def proto_version
|
189
272
|
(self[:proto_version] or DEFAULT_PROTO_VERSION).to_s
|
190
273
|
end
|
191
|
-
end
|
192
274
|
|
275
|
+
def server_api_version
|
276
|
+
key = (self[:headers] || {}).keys.select do |k|
|
277
|
+
k.downcase == 'x-ops-server-api-version'
|
278
|
+
end.first
|
279
|
+
if key
|
280
|
+
self[:headers][key]
|
281
|
+
else
|
282
|
+
DEFAULT_SERVER_API_VERSION
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
193
286
|
end
|
194
287
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
|
2
|
+
# License:: Apache License, Version 2.0
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
|
17
|
+
module Mixlib
|
18
|
+
module Authentication
|
19
|
+
VERSION = '1.4.0.rc.0'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
2
|
+
require 'mixlib/authentication/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "mixlib-authentication"
|
6
|
+
s.version = Mixlib::Authentication::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE", 'NOTICE']
|
10
|
+
s.summary = "Mixes in simple per-request authentication"
|
11
|
+
s.description = s.summary
|
12
|
+
s.author = "Opscode, Inc."
|
13
|
+
s.email = "info@opscode.com"
|
14
|
+
s.homepage = "http://www.opscode.com"
|
15
|
+
|
16
|
+
# Uncomment this to add a dependency
|
17
|
+
s.add_dependency "mixlib-log"
|
18
|
+
|
19
|
+
s.require_path = 'lib'
|
20
|
+
s.files = %w(LICENSE README.rdoc Gemfile Rakefile NOTICE) + Dir.glob("*.gemspec") +
|
21
|
+
Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
|
22
|
+
|
23
|
+
%w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_dependency gem, "~> 3.2" }
|
24
|
+
s.add_development_dependency "rake", "~> 10.4"
|
25
|
+
end
|
@@ -76,41 +76,41 @@ describe Mixlib::Authentication::HTTPAuthenticationRequest do
|
|
76
76
|
:x_ops_authorization_4=>"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
|
77
77
|
:x_ops_authorization_5=>"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
|
78
78
|
:x_ops_authorization_6=>"utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="}
|
79
|
-
@http_authentication_request.headers.
|
79
|
+
expect(@http_authentication_request.headers).to eq(expected)
|
80
80
|
end
|
81
81
|
|
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
|
-
|
85
|
+
expect{ Mixlib::Authentication::HTTPAuthenticationRequest.new(@request) }.to raise_error(exception)
|
86
86
|
end
|
87
87
|
|
88
88
|
it "extracts the path from the request" do
|
89
|
-
@http_authentication_request.path.
|
89
|
+
expect(@http_authentication_request.path).to eq('/nodes')
|
90
90
|
end
|
91
91
|
|
92
92
|
it "extracts the request method from the request" do
|
93
|
-
@http_authentication_request.http_method.
|
93
|
+
expect(@http_authentication_request.http_method).to eq('POST')
|
94
94
|
end
|
95
95
|
|
96
96
|
it "extracts the signing description from the request headers" do
|
97
|
-
@http_authentication_request.signing_description.
|
97
|
+
expect(@http_authentication_request.signing_description).to eq('version=1.0')
|
98
98
|
end
|
99
99
|
|
100
100
|
it "extracts the user_id from the request headers" do
|
101
|
-
@http_authentication_request.user_id.
|
101
|
+
expect(@http_authentication_request.user_id).to eq('spec-user')
|
102
102
|
end
|
103
103
|
|
104
104
|
it "extracts the timestamp from the request headers" do
|
105
|
-
@http_authentication_request.timestamp.
|
105
|
+
expect(@http_authentication_request.timestamp).to eq("2009-01-01T12:00:00Z")
|
106
106
|
end
|
107
107
|
|
108
108
|
it "extracts the host from the request headers" do
|
109
|
-
@http_authentication_request.host.
|
109
|
+
expect(@http_authentication_request.host).to eq("127.0.0.1")
|
110
110
|
end
|
111
111
|
|
112
112
|
it "extracts the content hash from the request headers" do
|
113
|
-
@http_authentication_request.content_hash.
|
113
|
+
expect(@http_authentication_request.content_hash).to eq("DFteJZPVv6WKdQmMqZUQUumUyRs=")
|
114
114
|
end
|
115
115
|
|
116
116
|
it "rebuilds the request signature from the headers" do
|
@@ -122,7 +122,11 @@ IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy
|
|
122
122
|
9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0
|
123
123
|
utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==
|
124
124
|
SIG
|
125
|
-
@http_authentication_request.request_signature.
|
125
|
+
expect(@http_authentication_request.request_signature).to eq(expected.chomp)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "defaults to server api version 0" do
|
129
|
+
expect(@http_authentication_request.server_api_version).to eq('0')
|
126
130
|
end
|
127
131
|
|
128
132
|
end
|
@@ -72,52 +72,65 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
|
|
72
72
|
|
73
73
|
it "should generate the correct string to sign and signature, version 1.0 (default)" do
|
74
74
|
|
75
|
-
V1_0_SIGNING_OBJECT.canonicalize_request.
|
75
|
+
expect(V1_0_SIGNING_OBJECT.canonicalize_request).to eq(V1_0_CANONICAL_REQUEST)
|
76
76
|
|
77
77
|
# If you need to regenerate the constants in this test spec, print out
|
78
78
|
# the results of res.inspect and copy them as appropriate into the
|
79
79
|
# the constants in this file.
|
80
|
-
V1_0_SIGNING_OBJECT.sign(PRIVATE_KEY).
|
80
|
+
expect(V1_0_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_0)
|
81
81
|
end
|
82
82
|
|
83
83
|
it "should generate the correct string to sign and signature, version 1.1" do
|
84
|
-
V1_1_SIGNING_OBJECT.proto_version.
|
85
|
-
V1_1_SIGNING_OBJECT.canonicalize_request.
|
84
|
+
expect(V1_1_SIGNING_OBJECT.proto_version).to eq("1.1")
|
85
|
+
expect(V1_1_SIGNING_OBJECT.canonicalize_request).to eq(V1_1_CANONICAL_REQUEST)
|
86
86
|
|
87
87
|
# If you need to regenerate the constants in this test spec, print out
|
88
88
|
# the results of res.inspect and copy them as appropriate into the
|
89
89
|
# the constants in this file.
|
90
|
-
V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY).
|
90
|
+
expect(V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_1)
|
91
91
|
end
|
92
92
|
|
93
|
+
it "should generate the correct string to sign and signature for version 1.3 with SHA256" do
|
94
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.proto_version).to eq("1.3")
|
95
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.algorithm).to eq("sha256")
|
96
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.server_api_version).to eq("1")
|
97
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.canonicalize_request).to eq(V1_3_SHA256_CANONICAL_REQUEST)
|
98
|
+
|
99
|
+
# If you need to regenerate the constants in this test spec, print out
|
100
|
+
# the results of res.inspect and copy them as appropriate into the
|
101
|
+
# the constants in this file.
|
102
|
+
expect(V1_3_SHA256_SIGNING_OBJECT.sign(PRIVATE_KEY)).to eq(EXPECTED_SIGN_RESULT_V1_3_SHA256)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
93
106
|
it "should generate the correct string to sign and signature for non-default proto version when used as a mixin" do
|
94
107
|
algorithm = 'sha1'
|
95
108
|
version = '1.1'
|
96
109
|
|
97
110
|
V1_1_SIGNING_OBJECT.proto_version = "1.0"
|
98
|
-
V1_1_SIGNING_OBJECT.proto_version.
|
99
|
-
V1_1_SIGNING_OBJECT.canonicalize_request(algorithm, version).
|
111
|
+
expect(V1_1_SIGNING_OBJECT.proto_version).to eq("1.0")
|
112
|
+
expect(V1_1_SIGNING_OBJECT.canonicalize_request(algorithm, version)).to eq(V1_1_CANONICAL_REQUEST)
|
100
113
|
|
101
114
|
# If you need to regenerate the constants in this test spec, print out
|
102
115
|
# the results of res.inspect and copy them as appropriate into the
|
103
116
|
# the constants in this file.
|
104
|
-
V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version).
|
117
|
+
expect(V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version)).to eq(EXPECTED_SIGN_RESULT_V1_1)
|
105
118
|
end
|
106
119
|
|
107
120
|
it "should not choke when signing a request for a long user id with version 1.1" do
|
108
|
-
|
121
|
+
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', '1.1') }.not_to raise_error
|
109
122
|
end
|
110
123
|
|
111
124
|
it "should choke when signing a request for a long user id with version 1.0" do
|
112
|
-
|
125
|
+
expect { LONG_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', '1.0') }.to raise_error(OpenSSL::PKey::RSAError)
|
113
126
|
end
|
114
127
|
|
115
128
|
it "should choke when signing a request with a bad version" do
|
116
|
-
|
129
|
+
expect { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha1', 'poo') }.to raise_error(Mixlib::Authentication::AuthenticationError)
|
117
130
|
end
|
118
131
|
|
119
132
|
it "should choke when signing a request with a bad algorithm" do
|
120
|
-
|
133
|
+
expect { V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, 'sha_poo', '1.1') }.to raise_error(Mixlib::Authentication::AuthenticationError)
|
121
134
|
end
|
122
135
|
|
123
136
|
end
|
@@ -128,17 +141,30 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
128
141
|
@user_private_key = PRIVATE_KEY
|
129
142
|
end
|
130
143
|
|
131
|
-
it "should authenticate a File-containing request - Merb" do
|
144
|
+
it "should authenticate a File-containing request V1.1 - Merb" do
|
132
145
|
request_params = MERB_REQUEST_PARAMS.clone
|
133
146
|
request_params["file"] =
|
134
147
|
{ "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new }
|
135
148
|
|
136
149
|
mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_1, "")
|
137
|
-
Time.
|
150
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
138
151
|
|
139
152
|
service = Mixlib::Authentication::SignatureVerification.new
|
140
153
|
res = service.authenticate_user_request(mock_request, @user_private_key)
|
141
|
-
res.
|
154
|
+
expect(res).not_to be_nil
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should authenticate a File-containing request V1.3 SHA256 - Merb" do
|
158
|
+
request_params = MERB_REQUEST_PARAMS.clone
|
159
|
+
request_params["file"] =
|
160
|
+
{ "size"=>MockFile.length, "content_type"=>"application/octet-stream", "filename"=>"zsh.tar.gz", "tempfile"=>MockFile.new }
|
161
|
+
|
162
|
+
mock_request = MockRequest.new(PATH, request_params, MERB_HEADERS_V1_3_SHA256, "")
|
163
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
164
|
+
|
165
|
+
service = Mixlib::Authentication::SignatureVerification.new
|
166
|
+
res = service.authenticate_user_request(mock_request, @user_private_key)
|
167
|
+
expect(res).not_to be_nil
|
142
168
|
end
|
143
169
|
|
144
170
|
it "should authenticate a File-containing request from a v1.0 client - Passenger" do
|
@@ -146,29 +172,38 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
146
172
|
request_params["tarball"] = MockFile.new
|
147
173
|
|
148
174
|
mock_request = MockRequest.new(PATH, request_params, PASSENGER_HEADERS_V1_0, "")
|
149
|
-
Time.
|
175
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
150
176
|
|
151
177
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
152
178
|
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
153
|
-
res.
|
179
|
+
expect(res).not_to be_nil
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should authenticate a normal (post body) request v1.3 SHA256 - Merb" do
|
183
|
+
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_3_SHA256, BODY)
|
184
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
185
|
+
|
186
|
+
service = Mixlib::Authentication::SignatureVerification.new
|
187
|
+
res = service.authenticate_user_request(mock_request, @user_private_key)
|
188
|
+
expect(res).not_to be_nil
|
154
189
|
end
|
155
190
|
|
156
|
-
it "should authenticate a normal (post body) request - Merb" do
|
191
|
+
it "should authenticate a normal (post body) request v1.1 - Merb" do
|
157
192
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_1, BODY)
|
158
|
-
Time.
|
193
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
159
194
|
|
160
195
|
service = Mixlib::Authentication::SignatureVerification.new
|
161
196
|
res = service.authenticate_user_request(mock_request, @user_private_key)
|
162
|
-
res.
|
197
|
+
expect(res).not_to be_nil
|
163
198
|
end
|
164
199
|
|
165
200
|
it "should authenticate a normal (post body) request from a v1.0 client - Merb" do
|
166
201
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_0, BODY)
|
167
|
-
Time.
|
202
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
168
203
|
|
169
204
|
service = Mixlib::Authentication::SignatureVerification.new
|
170
205
|
res = service.authenticate_user_request(mock_request, @user_private_key)
|
171
|
-
res.
|
206
|
+
expect(res).not_to be_nil
|
172
207
|
end
|
173
208
|
|
174
209
|
it "shouldn't authenticate if an Authorization header is missing" do
|
@@ -176,15 +211,16 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
176
211
|
headers.delete("HTTP_X_OPS_SIGN")
|
177
212
|
|
178
213
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
179
|
-
Time.
|
214
|
+
allow(Time).to receive(:now).and_return(TIMESTAMP_OBJ)
|
215
|
+
#Time.stub!(:now).and_return(TIMESTAMP_OBJ)
|
180
216
|
|
181
217
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
182
|
-
|
218
|
+
expect {auth_req.authenticate_user_request(mock_request, @user_private_key)}.to raise_error(Mixlib::Authentication::AuthenticationError)
|
183
219
|
|
184
|
-
auth_req.
|
185
|
-
auth_req.
|
186
|
-
auth_req.
|
187
|
-
auth_req.
|
220
|
+
expect(auth_req).not_to be_a_valid_request
|
221
|
+
expect(auth_req).not_to be_a_valid_timestamp
|
222
|
+
expect(auth_req).not_to be_a_valid_signature
|
223
|
+
expect(auth_req).not_to be_a_valid_content_hash
|
188
224
|
end
|
189
225
|
|
190
226
|
|
@@ -193,52 +229,67 @@ describe "Mixlib::Authentication::SignatureVerification" do
|
|
193
229
|
headers["HTTP_X_OPS_CONTENT_HASH"] += "_"
|
194
230
|
|
195
231
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
196
|
-
Time.
|
232
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
197
233
|
|
198
234
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
199
235
|
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
200
|
-
res.
|
236
|
+
expect(res).to be_nil
|
201
237
|
|
202
|
-
auth_req.
|
203
|
-
auth_req.
|
204
|
-
auth_req.
|
205
|
-
auth_req.
|
238
|
+
expect(auth_req).not_to be_a_valid_request
|
239
|
+
expect(auth_req).to be_a_valid_timestamp
|
240
|
+
expect(auth_req).to be_a_valid_signature
|
241
|
+
expect(auth_req).not_to be_a_valid_content_hash
|
206
242
|
end
|
207
243
|
|
208
244
|
it "shouldn't authenticate if the timestamp is not within bounds" do
|
209
245
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS_V1_1, BODY)
|
210
|
-
Time.
|
246
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ - 1000)
|
211
247
|
|
212
248
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
213
249
|
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
214
|
-
res.
|
215
|
-
auth_req.
|
216
|
-
auth_req.
|
217
|
-
auth_req.
|
218
|
-
auth_req.
|
250
|
+
expect(res).to be_nil
|
251
|
+
expect(auth_req).not_to be_a_valid_request
|
252
|
+
expect(auth_req).not_to be_a_valid_timestamp
|
253
|
+
expect(auth_req).to be_a_valid_signature
|
254
|
+
expect(auth_req).to be_a_valid_content_hash
|
219
255
|
end
|
220
256
|
|
221
257
|
it "shouldn't authenticate if the signature is wrong" do
|
222
258
|
headers = MERB_HEADERS_V1_1.dup
|
223
259
|
headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail"
|
224
260
|
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
225
|
-
Time.
|
261
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
226
262
|
|
227
263
|
auth_req = Mixlib::Authentication::SignatureVerification.new
|
228
264
|
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
229
|
-
res.
|
230
|
-
auth_req.
|
231
|
-
auth_req.
|
232
|
-
auth_req.
|
233
|
-
auth_req.
|
265
|
+
expect(res).to be_nil
|
266
|
+
expect(auth_req).not_to be_a_valid_request
|
267
|
+
expect(auth_req).not_to be_a_valid_signature
|
268
|
+
expect(auth_req).to be_a_valid_timestamp
|
269
|
+
expect(auth_req).to be_a_valid_content_hash
|
234
270
|
end
|
235
271
|
|
272
|
+
it "shouldn't authenticate if the signature is wrong for v1.3 SHA256" do
|
273
|
+
headers = MERB_HEADERS_V1_3_SHA256.dup
|
274
|
+
headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail"
|
275
|
+
mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
|
276
|
+
expect(Time).to receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
|
277
|
+
|
278
|
+
auth_req = Mixlib::Authentication::SignatureVerification.new
|
279
|
+
res = auth_req.authenticate_user_request(mock_request, @user_private_key)
|
280
|
+
expect(res).to be_nil
|
281
|
+
expect(auth_req).not_to be_a_valid_request
|
282
|
+
expect(auth_req).not_to be_a_valid_signature
|
283
|
+
expect(auth_req).to be_a_valid_timestamp
|
284
|
+
expect(auth_req).to be_a_valid_content_hash
|
285
|
+
end
|
236
286
|
end
|
237
287
|
|
238
288
|
USER_ID = "spec-user"
|
239
289
|
DIGESTED_USER_ID = Base64.encode64(Digest::SHA1.new.digest(USER_ID)).chomp
|
240
290
|
BODY = "Spec Body"
|
241
291
|
HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=" # Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
|
292
|
+
HASHED_BODY_SHA256 = "hDlKNZhIhgso3Fs0S0pZwJ0xyBWtR1RBaeHs1DrzOho="
|
242
293
|
TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z"
|
243
294
|
TIMESTAMP_OBJ = Time.parse("Thu Jan 01 12:00:00 -0000 2009")
|
244
295
|
PATH = "/organizations/clownco"
|
@@ -263,6 +314,20 @@ V1_1_ARGS = {
|
|
263
314
|
:proto_version => 1.1
|
264
315
|
}
|
265
316
|
|
317
|
+
V1_3_ARGS_SHA256 = {
|
318
|
+
:body => BODY,
|
319
|
+
:user_id => USER_ID,
|
320
|
+
:http_method => :post,
|
321
|
+
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
|
322
|
+
:file => MockFile.new,
|
323
|
+
:path => PATH,
|
324
|
+
:proto_version => '1.3',
|
325
|
+
:headers => {
|
326
|
+
'X-OpS-SeRvEr-ApI-VerSiOn' => '1'
|
327
|
+
}
|
328
|
+
# This defaults to sha256
|
329
|
+
}
|
330
|
+
|
266
331
|
LONG_PATH_LONG_USER_ARGS = {
|
267
332
|
:body => BODY,
|
268
333
|
:user_id => "A" * 200,
|
@@ -276,6 +341,8 @@ REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa"
|
|
276
341
|
|
277
342
|
# Content hash is ???TODO
|
278
343
|
X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs="
|
344
|
+
X_OPS_CONTENT_HASH_SHA256 = "hDlKNZhIhgso3Fs0S0pZwJ0xyBWtR1RBaeHs1DrzOho="
|
345
|
+
|
279
346
|
X_OPS_AUTHORIZATION_LINES_V1_0 = [
|
280
347
|
"jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
|
281
348
|
"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
|
@@ -294,6 +361,14 @@ X_OPS_AUTHORIZATION_LINES = [
|
|
294
361
|
"FDlbAG7H8Dmvo+wBxmtNkszhzbBnEYtuwQqT8nM/8A=="
|
295
362
|
]
|
296
363
|
|
364
|
+
X_OPS_AUTHORIZATION_LINES_V1_3_SHA256 = [
|
365
|
+
"FZOmXAyOBAZQV/uw188iBljBJXOm+m8xQ/8KTGLkgGwZNcRFxk1m953XjE3W",
|
366
|
+
"VGy1dFT76KeaNWmPCNtDmprfH2na5UZFtfLIKrPv7xm80V+lzEzTd9WBwsfP",
|
367
|
+
"42dZ9N+V9I5SVfcL/lWrrlpdybfceJC5jOcP5tzfJXWUITwb6Z3Erg3DU3Uh",
|
368
|
+
"H9h9E0qWlYGqmiNCVrBnpe6Si1gU/Jl+rXlRSNbLJ4GlArAPuL976iTYJTzE",
|
369
|
+
"MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe",
|
370
|
+
"fy49QzaCIEu1XiOx5Jn+4GmkrZch/RrK9VzQWXgs+w=="
|
371
|
+
]
|
297
372
|
# We expect Mixlib::Authentication::SignedHeaderAuth#sign to return this
|
298
373
|
# if passed the BODY above, based on version
|
299
374
|
|
@@ -323,6 +398,19 @@ EXPECTED_SIGN_RESULT_V1_1 = {
|
|
323
398
|
"X-Ops-Timestamp"=>TIMESTAMP_ISO8601
|
324
399
|
}
|
325
400
|
|
401
|
+
EXPECTED_SIGN_RESULT_V1_3_SHA256 = {
|
402
|
+
"X-Ops-Content-Hash"=>X_OPS_CONTENT_HASH_SHA256,
|
403
|
+
"X-Ops-Userid"=>USER_ID,
|
404
|
+
"X-Ops-Sign"=>"algorithm=sha256;version=1.3;",
|
405
|
+
"X-Ops-Authorization-1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[0],
|
406
|
+
"X-Ops-Authorization-2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[1],
|
407
|
+
"X-Ops-Authorization-3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[2],
|
408
|
+
"X-Ops-Authorization-4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[3],
|
409
|
+
"X-Ops-Authorization-5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[4],
|
410
|
+
"X-Ops-Authorization-6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[5],
|
411
|
+
"X-Ops-Timestamp"=>TIMESTAMP_ISO8601
|
412
|
+
}
|
413
|
+
|
326
414
|
OTHER_HEADERS = {
|
327
415
|
# An arbitrary sampling of non-HTTP_* headers are in here to
|
328
416
|
# exercise that code path.
|
@@ -339,6 +427,23 @@ MERB_REQUEST_PARAMS = {
|
|
339
427
|
"organization_id"=>"local-test-org", "requesting_actor_id"=>REQUESTING_ACTOR_ID,
|
340
428
|
}
|
341
429
|
|
430
|
+
MERB_HEADERS_V1_3_SHA256 = {
|
431
|
+
# These are used by signatureverification.
|
432
|
+
"HTTP_HOST"=>"127.0.0.1",
|
433
|
+
"HTTP_X_OPS_SIGN"=>"algorithm=sha256;version=1.3;",
|
434
|
+
"HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
|
435
|
+
"HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
|
436
|
+
"HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH_SHA256,
|
437
|
+
"HTTP_X_OPS_USERID"=>USER_ID,
|
438
|
+
"HTTP_X_OPS_SERVER_API_VERSION"=>"1",
|
439
|
+
"HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[0],
|
440
|
+
"HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[1],
|
441
|
+
"HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[2],
|
442
|
+
"HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[3],
|
443
|
+
"HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[4],
|
444
|
+
"HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_3_SHA256[5],
|
445
|
+
}.merge(OTHER_HEADERS)
|
446
|
+
|
342
447
|
# Tis is what will be in request.env for the Merb case.
|
343
448
|
MERB_HEADERS_V1_1 = {
|
344
449
|
# These are used by signatureverification.
|
@@ -477,6 +582,18 @@ X-Ops-UserId:#{DIGESTED_USER_ID}
|
|
477
582
|
EOS
|
478
583
|
V1_1_CANONICAL_REQUEST = V1_1_CANONICAL_REQUEST_DATA.chomp
|
479
584
|
|
585
|
+
V1_3_SHA256_CANONICAL_REQUEST_DATA = <<EOS
|
586
|
+
Method:POST
|
587
|
+
Path:#{PATH}
|
588
|
+
X-Ops-Content-Hash:#{HASHED_BODY_SHA256}
|
589
|
+
X-Ops-Sign:version=1.3
|
590
|
+
X-Ops-Timestamp:#{TIMESTAMP_ISO8601}
|
591
|
+
X-Ops-UserId:#{USER_ID}
|
592
|
+
X-Ops-Server-API-Version:1
|
593
|
+
EOS
|
594
|
+
V1_3_SHA256_CANONICAL_REQUEST = V1_3_SHA256_CANONICAL_REQUEST_DATA.chomp
|
595
|
+
|
596
|
+
V1_3_SHA256_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_3_ARGS_SHA256)
|
480
597
|
V1_1_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_1_ARGS)
|
481
598
|
V1_0_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(V1_0_ARGS)
|
482
599
|
LONG_SIGNING_OBJECT = Mixlib::Authentication::SignedHeaderAuth.signing_object(LONG_PATH_LONG_USER_ARGS)
|
metadata
CHANGED
@@ -1,91 +1,131 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixlib-authentication
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 3
|
9
|
-
- 0
|
10
|
-
version: 1.3.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.4.0.rc.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Opscode, Inc.
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2015-12-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
21
14
|
name: mixlib-log
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
22
21
|
prerelease: false
|
23
|
-
|
24
|
-
|
25
|
-
requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
26
24
|
- - ">="
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
32
62
|
type: :runtime
|
33
|
-
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.4'
|
34
83
|
description: Mixes in simple per-request authentication
|
35
84
|
email: info@opscode.com
|
36
85
|
executables: []
|
37
|
-
|
38
86
|
extensions: []
|
39
|
-
|
40
|
-
extra_rdoc_files:
|
87
|
+
extra_rdoc_files:
|
41
88
|
- README.rdoc
|
42
89
|
- LICENSE
|
43
90
|
- NOTICE
|
44
|
-
files:
|
91
|
+
files:
|
92
|
+
- Gemfile
|
45
93
|
- LICENSE
|
94
|
+
- NOTICE
|
46
95
|
- README.rdoc
|
47
96
|
- Rakefile
|
48
|
-
-
|
49
|
-
- lib/mixlib/authentication/signedheaderauth.rb
|
97
|
+
- lib/mixlib/authentication.rb
|
50
98
|
- lib/mixlib/authentication/digester.rb
|
51
99
|
- lib/mixlib/authentication/http_authentication_request.rb
|
52
100
|
- lib/mixlib/authentication/signatureverification.rb
|
53
|
-
- lib/mixlib/authentication.rb
|
54
|
-
-
|
55
|
-
-
|
101
|
+
- lib/mixlib/authentication/signedheaderauth.rb
|
102
|
+
- lib/mixlib/authentication/version.rb
|
103
|
+
- mixlib-authentication.gemspec
|
56
104
|
- spec/mixlib/authentication/http_authentication_request_spec.rb
|
105
|
+
- spec/mixlib/authentication/mixlib_authentication_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
57
107
|
homepage: http://www.opscode.com
|
58
108
|
licenses: []
|
59
|
-
|
109
|
+
metadata: {}
|
60
110
|
post_install_message:
|
61
111
|
rdoc_options: []
|
62
|
-
|
63
|
-
require_paths:
|
112
|
+
require_paths:
|
64
113
|
- lib
|
65
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
-
|
67
|
-
requirements:
|
68
|
-
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
hash: 3
|
71
|
-
segments:
|
72
|
-
- 0
|
73
|
-
version: "0"
|
74
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
-
none: false
|
76
|
-
requirements:
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
77
116
|
- - ">="
|
78
|
-
- !ruby/object:Gem::Version
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 1.3.1
|
83
124
|
requirements: []
|
84
|
-
|
85
125
|
rubyforge_project:
|
86
|
-
rubygems_version:
|
126
|
+
rubygems_version: 2.5.0
|
87
127
|
signing_key:
|
88
|
-
specification_version:
|
128
|
+
specification_version: 4
|
89
129
|
summary: Mixes in simple per-request authentication
|
90
130
|
test_files: []
|
91
|
-
|
131
|
+
has_rdoc: true
|