summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel DeLeo <dan@opscode.com>2012-07-31 17:13:40 -0700
committerDaniel DeLeo <dan@opscode.com>2012-07-31 17:13:40 -0700
commit696df3e0122900fc95c0355d09c7b15fc7be7ee1 (patch)
tree6fcc2e5fed588b943382f5ba9addcf570d7ca47a
parent914dd6f11f1e8d139869c89895b8bf83694df799 (diff)
downloadmixlib-authentication-696df3e0122900fc95c0355d09c7b15fc7be7ee1.tar.gz
change default sign version back to 1.0
We need to wait until Chef 11, when we can make a break to change to 1.1, because this relies on the server side supporting 1.1 to work correctly.
-rw-r--r--lib/mixlib/authentication/signedheaderauth.rb137
-rw-r--r--spec/mixlib/authentication/mixlib_authentication_spec.rb140
2 files changed, 165 insertions, 112 deletions
diff --git a/lib/mixlib/authentication/signedheaderauth.rb b/lib/mixlib/authentication/signedheaderauth.rb
index 39c9ac8..4ee7f98 100644
--- a/lib/mixlib/authentication/signedheaderauth.rb
+++ b/lib/mixlib/authentication/signedheaderauth.rb
@@ -7,9 +7,9 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -28,62 +28,99 @@ module Mixlib
module SignedHeaderAuth
- SUPPORTED_ALGORITHMS = ['sha1']
- SUPPORTED_VERSIONS = ['1.0', '1.1']
+ NULL_ARG = Object.new
+ SUPPORTED_ALGORITHMS = ['sha1'].freeze
+ SUPPORTED_VERSIONS = ['1.0', '1.1'].freeze
- # This is a module meant to be mixed in but can be used standalone
- # with the simple OpenStruct extended with the auth functions
- class << self
- def signing_object(args={ })
- SigningObject.new(args[:http_method], args[:path], args[:body], args[:host], args[:timestamp], args[:user_id], args[:file])
- end
+ DEFAULT_SIGN_ALGORITHM = 'sha1'.freeze
+ DEFAULT_PROTO_VERSION = '1.0'.freeze
+
+
+ # === signing_object
+ # This is the intended interface for signing requests with the
+ # Opscode/Chef signed header protocol. This wraps the constructor for a
+ # Struct that contains the relevant information about your request.
+ #
+ # ==== Signature Parameters:
+ # These parameters are used to generate the canonical representation of
+ # the request, which is then hashed and encrypted to generate the
+ # request's signature. These options are all required, with the exception
+ # of `:body` and `:file`, which are alternate ways to specify the request
+ # body (you must specify one of these).
+ # * `:http_method`: HTTP method as a lowercase symbol, e.g., `:get | :put | :post | :delete`
+ # * `:path`: The path part of the URI, e.g., `URI.parse(uri).path`
+ # * `:body`: An object representing the body of the request.
+ # Use an empty String for bodiless requests.
+ # * `:timestamp`: A String representing the time in any format understood
+ # by `Time.parse`. The server may reject the request if the timestamp is
+ # not close to the server's current time.
+ # * `:user_id`: The user or client name. This is used by the server to
+ # lookup the public key necessary to verify the signature.
+ # * `:file`: An IO object (must respond to `:read`) to be used as the
+ # request body.
+ # ==== Protocol Versioning Parameters:
+ # * `:proto_version`: The version of the signing protocol to use.
+ # Currently defaults to 1.0, but version 1.1 is also available.
+ # ==== Other Parameters:
+ # These parameters are accepted but not used in the computation of the signature.
+ # * `:host`: The host part of the URI
+ def self.signing_object(args={ })
+ SigningObject.new(args[:http_method], args[:path], args[:body], args[:host], args[:timestamp], args[:user_id], args[:file], args[:proto_version])
+ end
+
+ def algorithm
+ DEFAULT_SIGN_ALGORITHM
+ end
+
+ def proto_version
+ DEFAULT_PROTO_VERSION
end
# Build the canonicalized request based on the method, other headers, etc.
# compute the signature from the request, using the looked-up user secret
# ====Parameters
# private_key<OpenSSL::PKey::RSA>:: user's RSA private key.
- def sign(private_key, algorithm='sha1', version='1.1')
+ def sign(private_key, sign_algorithm=DEFAULT_SIGN_ALGORITHM, sign_version=DEFAULT_PROTO_VERSION)
# Our multiline hash for authorization will be encoded in multiple header
# lines - X-Ops-Authorization-1, ... (starts at 1, not 0!)
header_hash = {
- "X-Ops-Sign" => "algorithm=#{algorithm};version=#{version};",
+ "X-Ops-Sign" => "algorithm=#{sign_algorithm};version=#{sign_version};",
"X-Ops-Userid" => user_id,
"X-Ops-Timestamp" => canonical_time,
"X-Ops-Content-Hash" => hashed_body,
}
- string_to_sign = canonicalize_request(algorithm, version)
+ string_to_sign = canonicalize_request(sign_algorithm, sign_version)
signature = Base64.encode64(private_key.private_encrypt(string_to_sign)).chomp
signature_lines = signature.split(/\n/)
signature_lines.each_index do |idx|
key = "X-Ops-Authorization-#{idx + 1}"
header_hash[key] = signature_lines[idx]
end
-
+
Mixlib::Authentication::Log.debug "String to sign: '#{string_to_sign}'\nHeader hash: #{header_hash.inspect}"
-
+
header_hash
end
-
+
# Build the canonicalized time based on utc & iso8601
- #
+ #
# ====Parameters
- #
+ #
def canonical_time
Time.parse(timestamp).utc.iso8601
end
-
+
# Build the canonicalized path, which collapses multiple slashes (/) and
# removes a trailing slash unless the path is only "/"
- #
+ #
# ====Parameters
- #
+ #
def canonical_path
p = path.gsub(/\/+/,'/')
p.length > 1 ? p.chomp('/') : p
end
-
+
def hashed_body
# Hash the file object if it was passed in, otherwise hash based on
# the body.
@@ -92,27 +129,33 @@ module Mixlib
# body in the case of a file being include.
@hashed_body ||= (self.file && self.file.respond_to?(:read)) ? digester.hash_file(self.file) : digester.hash_string(self.body)
end
-
+
# Takes HTTP request method & headers and creates a canonical form
# to create the signature
- #
+ #
# ====Parameters
- #
- #
- def canonicalize_request(algorithm='sha1', version='1.1')
- raise AuthenticationError, "Bad algorithm '#{algorithm}' or version '#{version}'" unless SUPPORTED_ALGORITHMS.include?(algorithm) && SUPPORTED_VERSIONS.include?(version)
-
- canonical_x_ops_user_id = case
- when version == "1.1"
- digester.hash_string(user_id)
- when version == "1.0"
- user_id
- else
- user_id
- end
+ #
+ #
+ def canonicalize_request(algorithm=DEFAULT_SIGN_ALGORITHM, version=DEFAULT_PROTO_VERSION)
+ unless SUPPORTED_ALGORITHMS.include?(algorithm) && SUPPORTED_VERSIONS.include?(version)
+ raise AuthenticationError, "Bad algorithm '#{algorithm}' (allowed: #{SUPPORTED_ALGORITHMS.inspect}) or version '#{version}' (allowed: #{SUPPORTED_VERSIONS.inspect})"
+ end
+
+ canonical_x_ops_user_id = canonicalize_user_id(user_id, version)
"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}"
end
-
+
+ def canonicalize_user_id(user_id, proto_version)
+ case proto_version
+ when "1.1"
+ digester.hash_string(user_id)
+ when "1.0"
+ user_id
+ else
+ user_id
+ end
+ end
+
# Parses signature version information, algorithm used, etc.
#
# ====Parameters
@@ -126,17 +169,25 @@ module Mixlib
Mixlib::Authentication::Log.debug "Parsed signing description: #{parts.inspect}"
parts
end
-
+
def digester
Mixlib::Authentication::Digester
end
-
- private :canonical_time, :canonical_path, :parse_signing_description, :digester
-
+
+ private :canonical_time, :canonical_path, :parse_signing_description, :digester, :canonicalize_user_id
+
end
- class SigningObject < Struct.new(:http_method, :path, :body, :host, :timestamp, :user_id, :file)
+ # === SigningObject
+ # A Struct-based value object that contains the necessary information to
+ # generate a request signature. `SignedHeaderAuth.signing_object()`
+ # provides a more convenient interface to the constructor.
+ class SigningObject < Struct.new(:http_method, :path, :body, :host, :timestamp, :user_id, :file, :proto_version)
include SignedHeaderAuth
+
+ def proto_version
+ self[:proto_version] or DEFAULT_PROTO_VERSION
+ end
end
end
diff --git a/spec/mixlib/authentication/mixlib_authentication_spec.rb b/spec/mixlib/authentication/mixlib_authentication_spec.rb
index a7d27c8..2103304 100644
--- a/spec/mixlib/authentication/mixlib_authentication_spec.rb
+++ b/spec/mixlib/authentication/mixlib_authentication_spec.rb
@@ -8,9 +8,9 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -68,26 +68,28 @@ end
describe "Mixlib::Authentication::SignedHeaderAuth" do
- it "should generate the correct string to sign and signature, version 1.0" do
-
- algorithm = 'sha1'
- version = '1.0'
- V1_0_SIGNING_OBJECT.canonicalize_request(algorithm, version).should == V1_0_CANONICAL_REQUEST
+ # NOTE: Version 1.0 will be the default until Chef 11 is released.
+
+ it "should generate the correct string to sign and signature, version 1.0 (default)" do
+
+ V1_0_SIGNING_OBJECT.canonicalize_request.should == V1_0_CANONICAL_REQUEST
# If you need to regenerate the constants in this test spec, print out
- # the results of res.inspect and copy them as appropriate into the
+ # the results of res.inspect and copy them as appropriate into the
# the constants in this file.
- V1_0_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version).should == EXPECTED_SIGN_RESULT_V1_0
+ V1_0_SIGNING_OBJECT.sign(PRIVATE_KEY).should == EXPECTED_SIGN_RESULT_V1_0
end
it "should generate the correct string to sign and signature, version 1.1" do
+ algorithm = 'sha1'
+ version = '1.1'
- V1_1_SIGNING_OBJECT.canonicalize_request.should == V1_1_CANONICAL_REQUEST
+ V1_1_SIGNING_OBJECT.canonicalize_request(algorithm, version).should == V1_1_CANONICAL_REQUEST
# If you need to regenerate the constants in this test spec, print out
- # the results of res.inspect and copy them as appropriate into the
+ # the results of res.inspect and copy them as appropriate into the
# the constants in this file.
- V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY).should == EXPECTED_SIGN_RESULT_V1_1
+ V1_1_SIGNING_OBJECT.sign(PRIVATE_KEY, algorithm, version).should == EXPECTED_SIGN_RESULT_V1_1
end
it "should not choke when signing a request for a long user id with version 1.1" do
@@ -109,7 +111,7 @@ describe "Mixlib::Authentication::SignedHeaderAuth" do
end
describe "Mixlib::Authentication::SignatureVerification" do
-
+
before(:each) do
@user_private_key = PRIVATE_KEY
end
@@ -231,7 +233,7 @@ PATH = "/organizations/clownco"
HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=" # Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
V1_0_ARGS = {
- :body => BODY,
+ :body => BODY,
:user_id => USER_ID,
:http_method => :post,
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
@@ -240,7 +242,7 @@ V1_0_ARGS = {
}
V1_1_ARGS = {
- :body => BODY,
+ :body => BODY,
:user_id => USER_ID,
:http_method => :post,
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
@@ -249,7 +251,7 @@ V1_1_ARGS = {
}
LONG_PATH_LONG_USER_ARGS = {
- :body => BODY,
+ :body => BODY,
:user_id => "A" * 200,
:http_method => :put,
:timestamp => TIMESTAMP_ISO8601, # fixed timestamp so we get back the same answer each time.
@@ -263,10 +265,10 @@ REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa"
X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs="
X_OPS_AUTHORIZATION_LINES_V1_0 = [
"jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
-"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
-"3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
-"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
-"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
+"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
+"3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
+"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
+"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
"utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="
]
@@ -311,51 +313,51 @@ EXPECTED_SIGN_RESULT_V1_1 = {
OTHER_HEADERS = {
# An arbitrary sampling of non-HTTP_* headers are in here to
# exercise that code path.
- "REMOTE_ADDR"=>"127.0.0.1",
- "PATH_INFO"=>"/organizations/local-test-org/cookbooks",
- "REQUEST_PATH"=>"/organizations/local-test-org/cookbooks",
+ "REMOTE_ADDR"=>"127.0.0.1",
+ "PATH_INFO"=>"/organizations/local-test-org/cookbooks",
+ "REQUEST_PATH"=>"/organizations/local-test-org/cookbooks",
"CONTENT_TYPE"=>"multipart/form-data; boundary=----RubyMultipartClient6792ZZZZZ",
- "CONTENT_LENGTH"=>"394",
+ "CONTENT_LENGTH"=>"394",
}
# This is what will be in request.params for the Merb case.
MERB_REQUEST_PARAMS = {
- "name"=>"zsh", "action"=>"create", "controller"=>"chef_server_api/cookbooks",
+ "name"=>"zsh", "action"=>"create", "controller"=>"chef_server_api/cookbooks",
"organization_id"=>"local-test-org", "requesting_actor_id"=>REQUESTING_ACTOR_ID,
}
# Tis is what will be in request.env for the Merb case.
MERB_HEADERS_V1_1 = {
# These are used by signatureverification.
- "HTTP_HOST"=>"127.0.0.1",
+ "HTTP_HOST"=>"127.0.0.1",
"HTTP_X_OPS_SIGN"=>"algorithm=sha1;version=1.1;",
- "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
- "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
- "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
- "HTTP_X_OPS_USERID"=>USER_ID,
- "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES[0],
- "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES[1],
- "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES[2],
- "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
- "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
- "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
+ "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
+ "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
+ "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
+ "HTTP_X_OPS_USERID"=>USER_ID,
+ "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES[0],
+ "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES[1],
+ "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES[2],
+ "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
+ "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
+ "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
}.merge(OTHER_HEADERS)
# Tis is what will be in request.env for the Merb case.
MERB_HEADERS_V1_0 = {
# These are used by signatureverification.
- "HTTP_HOST"=>"127.0.0.1",
+ "HTTP_HOST"=>"127.0.0.1",
"HTTP_X_OPS_SIGN"=>"version=1.0",
- "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
- "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
- "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
- "HTTP_X_OPS_USERID"=>USER_ID,
- "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
- "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
- "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
- "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
- "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
- "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
+ "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
+ "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
+ "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
+ "HTTP_X_OPS_USERID"=>USER_ID,
+ "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
+ "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
+ "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
+ "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
+ "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
+ "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
}.merge(OTHER_HEADERS)
PASSENGER_REQUEST_PARAMS = {
@@ -367,34 +369,34 @@ PASSENGER_REQUEST_PARAMS = {
PASSENGER_HEADERS_V1_1 = {
# These are used by signatureverification.
- "HTTP_HOST"=>"127.0.0.1",
+ "HTTP_HOST"=>"127.0.0.1",
"HTTP_X_OPS_SIGN"=>"algorithm=sha1;version=1.1;",
- "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
- "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
- "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
- "HTTP_X_OPS_USERID"=>USER_ID,
- "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES[0],
- "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES[1],
- "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES[2],
- "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
- "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
- "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
+ "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
+ "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
+ "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
+ "HTTP_X_OPS_USERID"=>USER_ID,
+ "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES[0],
+ "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES[1],
+ "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES[2],
+ "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES[3],
+ "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES[4],
+ "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES[5],
}.merge(OTHER_HEADERS)
PASSENGER_HEADERS_V1_0 = {
# These are used by signatureverification.
- "HTTP_HOST"=>"127.0.0.1",
+ "HTTP_HOST"=>"127.0.0.1",
"HTTP_X_OPS_SIGN"=>"version=1.0",
- "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
- "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
- "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
- "HTTP_X_OPS_USERID"=>USER_ID,
- "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
- "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
- "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
- "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
- "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
- "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
+ "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
+ "HTTP_X_OPS_TIMESTAMP"=>TIMESTAMP_ISO8601,
+ "HTTP_X_OPS_CONTENT_HASH"=>X_OPS_CONTENT_HASH,
+ "HTTP_X_OPS_USERID"=>USER_ID,
+ "HTTP_X_OPS_AUTHORIZATION_1"=>X_OPS_AUTHORIZATION_LINES_V1_0[0],
+ "HTTP_X_OPS_AUTHORIZATION_2"=>X_OPS_AUTHORIZATION_LINES_V1_0[1],
+ "HTTP_X_OPS_AUTHORIZATION_3"=>X_OPS_AUTHORIZATION_LINES_V1_0[2],
+ "HTTP_X_OPS_AUTHORIZATION_4"=>X_OPS_AUTHORIZATION_LINES_V1_0[3],
+ "HTTP_X_OPS_AUTHORIZATION_5"=>X_OPS_AUTHORIZATION_LINES_V1_0[4],
+ "HTTP_X_OPS_AUTHORIZATION_6"=>X_OPS_AUTHORIZATION_LINES_V1_0[5],
}.merge(OTHER_HEADERS)
# generated with