mixlib-authentication 1.3.0 → 1.4.0.rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|