mixlib-authentication 1.1.2 → 1.1.4.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'date'
5
5
  require 'spec/rake/spectask'
6
6
 
7
7
  GEM = "mixlib-authentication"
8
- GEM_VERSION = "1.1.2"
8
+ GEM_VERSION = "1.1.4.rc.1"
9
9
  AUTHOR = "Opscode, Inc."
10
10
  EMAIL = "info@opscode.com"
11
11
  HOMEPAGE = "http://www.opscode.com"
@@ -20,6 +20,12 @@ require 'mixlib/log'
20
20
 
21
21
  module Mixlib
22
22
  module Authentication
23
+ class AuthenticationError < StandardError
24
+ end
25
+
26
+ class MissingAuthenticationHeader < AuthenticationError
27
+ end
28
+
23
29
  class Log
24
30
  extend Mixlib::Log
25
31
  end
@@ -0,0 +1,87 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'mixlib/authentication'
20
+
21
+ module Mixlib
22
+ module Authentication
23
+ class HTTPAuthenticationRequest
24
+
25
+ MANDATORY_HEADERS = [:x_ops_sign, :x_ops_userid, :x_ops_timestamp, :host, :x_ops_content_hash]
26
+
27
+ attr_reader :request
28
+
29
+ def initialize(request)
30
+ @request = request
31
+ @request_signature = nil
32
+ validate_headers!
33
+ end
34
+
35
+ def headers
36
+ @headers ||= @request.env.inject({ }) { |memo, kv| memo[$2.gsub(/\-/,"_").downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
37
+ end
38
+
39
+ def http_method
40
+ @request.method.to_s
41
+ end
42
+
43
+ def path
44
+ @request.path.to_s
45
+ end
46
+
47
+ def signing_description
48
+ headers[:x_ops_sign].chomp
49
+ end
50
+
51
+ def user_id
52
+ headers[:x_ops_userid].chomp
53
+ end
54
+
55
+ def timestamp
56
+ headers[:x_ops_timestamp].chomp
57
+ end
58
+
59
+ def host
60
+ headers[:host].chomp
61
+ end
62
+
63
+ def content_hash
64
+ headers[:x_ops_content_hash].chomp
65
+ end
66
+
67
+ def request_signature
68
+ unless @request_signature
69
+ @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")
70
+ Mixlib::Authentication::Log.debug "Reconstituted (user-supplied) request signature: #{@request_signature}"
71
+ end
72
+ @request_signature
73
+ end
74
+
75
+
76
+ def validate_headers!
77
+ missing_headers = MANDATORY_HEADERS - headers.keys
78
+ unless missing_headers.empty?
79
+ missing_headers.map! { |h| h.to_s.upcase }
80
+ raise MissingAuthenticationHeader, "missing required authentication header(s) '#{missing_headers.join("', '")}'"
81
+ end
82
+ end
83
+
84
+
85
+ end
86
+ end
87
+ end
@@ -17,19 +17,54 @@
17
17
  # limitations under the License.
18
18
  #
19
19
 
20
- require 'ostruct'
21
20
  require 'net/http'
21
+ require 'forwardable'
22
22
  require 'mixlib/authentication'
23
+ require 'mixlib/authentication/http_authentication_request'
23
24
  require 'mixlib/authentication/signedheaderauth'
24
25
 
25
26
  module Mixlib
26
27
  module Authentication
28
+ SignatureResponse = Struct.new(:name)
29
+
27
30
  class SignatureVerification
31
+ extend Forwardable
32
+
33
+ def_delegator :@auth_request, :http_method
34
+
35
+ def_delegator :@auth_request, :path
36
+
37
+ def_delegator :auth_request, :signing_description
38
+
39
+ def_delegator :@auth_request, :user_id
40
+
41
+ def_delegator :@auth_request, :timestamp
42
+
43
+ def_delegator :@auth_request, :host
44
+
45
+ def_delegator :@auth_request, :request_signature
46
+
47
+ def_delegator :@auth_request, :content_hash
48
+
49
+ def_delegator :@auth_request, :request
28
50
 
29
51
  include Mixlib::Authentication::SignedHeaderAuth
30
-
31
- attr_reader :hashed_body, :timestamp, :http_method, :path, :user_id
32
52
 
53
+ attr_reader :auth_request
54
+
55
+ def initialize(request=nil)
56
+ @auth_request = HTTPAuthenticationRequest.new(request) if request
57
+
58
+ @valid_signature, @valid_timestamp, @valid_content_hash = false, false, false
59
+
60
+ @hashed_body = nil
61
+ end
62
+
63
+
64
+ def authenticate_user_request(request, user_lookup, time_skew=(15*60))
65
+ @auth_request = HTTPAuthenticationRequest.new(request)
66
+ authenticate_request(user_lookup, time_skew)
67
+ end
33
68
  # Takes the request, boils down the pieces we are interested in,
34
69
  # looks up the user, generates a signature, and compares to
35
70
  # the signature in the request
@@ -40,35 +75,105 @@ module Mixlib
40
75
  # X-Ops-Timestamp:
41
76
  # X-Ops-Content-Hash:
42
77
  # X-Ops-Authorization-#{line_number}
43
- def authenticate_user_request(request, user_lookup, time_skew=(15*60))
78
+ def authenticate_request(user_secret, time_skew=(15*60))
44
79
  Mixlib::Authentication::Log.debug "Initializing header auth : #{request.inspect}"
45
-
46
- headers ||= request.env.inject({ }) { |memo, kv| memo[$2.gsub(/\-/,"_").downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
47
- digester = Mixlib::Authentication::Digester
80
+
81
+ @user_secret = user_secret
82
+ @allowed_time_skew = time_skew # in seconds
48
83
 
49
84
  begin
50
- @allowed_time_skew = time_skew # in seconds
51
- @http_method = request.method.to_s
52
- @path = request.path.to_s
53
- @signing_description = headers[:x_ops_sign].chomp
54
- @user_id = headers[:x_ops_userid].chomp
55
- @timestamp = headers[:x_ops_timestamp].chomp
56
- @host = headers[:host].chomp
57
- @content_hash = headers[:x_ops_content_hash].chomp
58
- @user_secret = user_lookup
59
-
60
- # The authorization header is a Base64-encoded version of an RSA signature.
61
- # The client sent it on multiple header lines, starting at index 1 -
62
- # X-Ops-Authorization-1, X-Ops-Authorization-2, etc. Pull them out and
63
- # concatenate.
64
-
65
- # if there are 11 headers, the sort breaks - it becomes lexicographic sort rather than numeric [cb]
66
- @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")
67
- Mixlib::Authentication::Log.debug "Reconstituted request signature: #{@request_signature}"
85
+ @auth_request
68
86
 
69
- # The request signature is based on any file attached, if any. Otherwise
70
- # it's based on the body of the request.
71
- # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
87
+ #BUGBUG Not doing anything with the signing description yet [cb]
88
+ parse_signing_description
89
+
90
+ verify_signature
91
+ verify_timestamp
92
+ verify_content_hash
93
+
94
+ rescue StandardError=>se
95
+ raise AuthenticationError,"Failed to authenticate user request. Check your client key and clock: #{se.message}", se.backtrace
96
+ end
97
+
98
+ if valid_request?
99
+ SignatureResponse.new(user_id)
100
+ else
101
+ nil
102
+ end
103
+ end
104
+
105
+ def valid_signature?
106
+ @valid_signature
107
+ end
108
+
109
+ def valid_timestamp?
110
+ @valid_timestamp
111
+ end
112
+
113
+ def valid_content_hash?
114
+ @valid_content_hash
115
+ end
116
+
117
+ def valid_request?
118
+ valid_signature? && valid_timestamp? && valid_content_hash?
119
+ end
120
+
121
+ # The authorization header is a Base64-encoded version of an RSA signature.
122
+ # The client sent it on multiple header lines, starting at index 1 -
123
+ # X-Ops-Authorization-1, X-Ops-Authorization-2, etc. Pull them out and
124
+ # concatenate.
125
+ def headers
126
+ @headers ||= request.env.inject({ }) { |memo, kv| memo[$2.gsub(/\-/,"_").downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
127
+ end
128
+
129
+ private
130
+
131
+ def assert_required_headers_present
132
+ MANDATORY_HEADERS.each do |header|
133
+ unless headers.key?(header)
134
+ raise MissingAuthenticationHeader, "required authentication header #{header.to_s.upcase} missing"
135
+ end
136
+ end
137
+ end
138
+
139
+ def verify_signature
140
+ candidate_block = canonicalize_request
141
+ request_decrypted_block = @user_secret.public_decrypt(Base64.decode64(request_signature))
142
+ @valid_signature = (request_decrypted_block == candidate_block)
143
+
144
+ # Keep the debug messages lined up so it's easy to scan them
145
+ Mixlib::Authentication::Log.debug("Verifying request signature:")
146
+ Mixlib::Authentication::Log.debug(" Expected Block is: '#{candidate_block}'")
147
+ Mixlib::Authentication::Log.debug("Decrypted block is: '#{request_decrypted_block}'")
148
+ Mixlib::Authentication::Log.debug("Signatures match? : '#{@valid_signature}'")
149
+
150
+ @valid_signature
151
+ rescue => e
152
+ Mixlib::Authentication::Log.debug("Failed to verify request signature: #{e.class.name}: #{e.message}")
153
+ @valid_signature = false
154
+ end
155
+
156
+ def verify_timestamp
157
+ @valid_timestamp = timestamp_within_bounds?(Time.parse(timestamp), Time.now)
158
+ end
159
+
160
+ def verify_content_hash
161
+ @valid_content_hash = (content_hash == hashed_body)
162
+
163
+ # Keep the debug messages lined up so it's easy to scan them
164
+ Mixlib::Authentication::Log.debug("Expected content hash is: '#{hashed_body}'")
165
+ Mixlib::Authentication::Log.debug(" Request Content Hash is: '#{content_hash}'")
166
+ Mixlib::Authentication::Log.debug(" Hashes match?: #{@valid_content_hash}")
167
+
168
+ @valid_content_hash
169
+ end
170
+
171
+
172
+ # The request signature is based on any file attached, if any. Otherwise
173
+ # it's based on the body of the request.
174
+ def hashed_body
175
+ unless @hashed_body
176
+ # TODO: tim: 2009-112-28: It'd be nice to remove this special case, and
72
177
  # always hash the entire request body. In the file case it would just be
73
178
  # expanded multipart text - the entire body of the POST.
74
179
  #
@@ -106,31 +211,10 @@ module Mixlib
106
211
  Mixlib::Authentication::Log.debug "Digesting body: '#{body}'"
107
212
  @hashed_body = digester.hash_string(body)
108
213
  end
109
-
110
- Mixlib::Authentication::Log.debug "Authenticating user : #{user_id}, User secret is : #{@user_secret}, Request signature is :\n#{@request_signature}, Hashed Body is : #{@hashed_body}"
111
-
112
- #BUGBUG Not doing anything with the signing description yet [cb]
113
- parse_signing_description
114
- candidate_block = canonicalize_request
115
- request_decrypted_block = @user_secret.public_decrypt(Base64.decode64(@request_signature))
116
- signatures_match = (request_decrypted_block == candidate_block)
117
- timeskew_is_acceptable = timestamp_within_bounds?(Time.parse(timestamp), Time.now)
118
- hashes_match = @content_hash == hashed_body
119
- rescue StandardError=>se
120
- raise StandardError,"Failed to authenticate user request. Most likely missing a necessary header: #{se.message}", se.backtrace
121
- end
122
-
123
- Mixlib::Authentication::Log.debug "Candidate Block is: '#{candidate_block}'\nRequest decrypted block is: '#{request_decrypted_block}'\nCandidate content hash is: #{hashed_body}\nRequest Content Hash is: '#{@content_hash}'\nSignatures match: #{signatures_match}, Allowed Time Skew: #{timeskew_is_acceptable}, Hashes match?: #{hashes_match}\n"
124
-
125
- if signatures_match and timeskew_is_acceptable and hashes_match
126
- OpenStruct.new(:name=>user_id)
127
- else
128
- nil
129
214
  end
215
+ @hashed_body
130
216
  end
131
-
132
- private
133
-
217
+
134
218
  # Compare the request timestamp with boundary time
135
219
  #
136
220
  #
@@ -19,13 +19,13 @@
19
19
 
20
20
  require 'time'
21
21
  require 'base64'
22
- require 'ostruct'
23
22
  require 'digest/sha1'
24
23
  require 'mixlib/authentication'
25
24
  require 'mixlib/authentication/digester'
26
25
 
27
26
  module Mixlib
28
27
  module Authentication
28
+
29
29
  module SignedHeaderAuth
30
30
 
31
31
  SIGNING_DESCRIPTION = 'version=1.0'
@@ -34,7 +34,7 @@ module Mixlib
34
34
  # with the simple OpenStruct extended with the auth functions
35
35
  class << self
36
36
  def signing_object(args={ })
37
- OpenStruct.new(args).extend SignedHeaderAuth
37
+ SigningObject.new(args[:http_method], args[:path], args[:body], args[:host], args[:timestamp], args[:user_id], args[:file])
38
38
  end
39
39
  end
40
40
 
@@ -107,7 +107,7 @@ module Mixlib
107
107
  # ====Parameters
108
108
  #
109
109
  def parse_signing_description
110
- parts = @signing_description.strip.split(";").inject({ }) do |memo, part|
110
+ parts = signing_description.strip.split(";").inject({ }) do |memo, part|
111
111
  field_name, field_value = part.split("=")
112
112
  memo[field_name.to_sym] = field_value.strip
113
113
  memo
@@ -122,5 +122,10 @@ module Mixlib
122
122
  private :canonical_time, :canonical_path, :parse_signing_description, :digester
123
123
 
124
124
  end
125
+
126
+ class SigningObject < Struct.new(:http_method, :path, :body, :host, :timestamp, :user_id, :file)
127
+ include SignedHeaderAuth
128
+ end
129
+
125
130
  end
126
131
  end
@@ -0,0 +1,129 @@
1
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
2
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require File.expand_path(File.join(File.dirname(__FILE__), '..','..','spec_helper'))
19
+
20
+ require 'mixlib/authentication'
21
+ require 'mixlib/authentication/http_authentication_request'
22
+ require 'ostruct'
23
+ require 'pp'
24
+
25
+ describe Mixlib::Authentication::HTTPAuthenticationRequest do
26
+ before do
27
+ request = Struct.new(:env, :method, :path)
28
+
29
+ @timestamp_iso8601 = "2009-01-01T12:00:00Z"
30
+ @x_ops_content_hash = "DFteJZPVv6WKdQmMqZUQUumUyRs="
31
+ @user_id = "spec-user"
32
+ @http_x_ops_lines = [
33
+ "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
34
+ "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
35
+ "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
36
+ "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
37
+ "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
38
+ "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="]
39
+ @merb_headers = {
40
+ # These are used by signatureverification. An arbitrary sampling of non-HTTP_*
41
+ # headers are in here to exercise that code path.
42
+ "HTTP_HOST"=>"127.0.0.1",
43
+ "HTTP_X_OPS_SIGN"=>"version=1.0",
44
+ "HTTP_X_OPS_REQUESTID"=>"127.0.0.1 1258566194.85386",
45
+ "HTTP_X_OPS_TIMESTAMP"=>@timestamp_iso8601,
46
+ "HTTP_X_OPS_CONTENT_HASH"=>@x_ops_content_hash,
47
+ "HTTP_X_OPS_USERID"=>@user_id,
48
+ "HTTP_X_OPS_AUTHORIZATION_1"=>@http_x_ops_lines[0],
49
+ "HTTP_X_OPS_AUTHORIZATION_2"=>@http_x_ops_lines[1],
50
+ "HTTP_X_OPS_AUTHORIZATION_3"=>@http_x_ops_lines[2],
51
+ "HTTP_X_OPS_AUTHORIZATION_4"=>@http_x_ops_lines[3],
52
+ "HTTP_X_OPS_AUTHORIZATION_5"=>@http_x_ops_lines[4],
53
+ "HTTP_X_OPS_AUTHORIZATION_6"=>@http_x_ops_lines[5],
54
+
55
+ # Random sampling
56
+ "REMOTE_ADDR"=>"127.0.0.1",
57
+ "PATH_INFO"=>"/organizations/local-test-org/cookbooks",
58
+ "REQUEST_PATH"=>"/organizations/local-test-org/cookbooks",
59
+ "CONTENT_TYPE"=>"multipart/form-data; boundary=----RubyMultipartClient6792ZZZZZ",
60
+ "CONTENT_LENGTH"=>"394",
61
+ }
62
+ @request = request.new(@merb_headers, "POST", '/nodes')
63
+ @http_authentication_request = Mixlib::Authentication::HTTPAuthenticationRequest.new(@request)
64
+ end
65
+
66
+ it "normalizes the headers to lowercase symbols" do
67
+ expected = {:host=>"127.0.0.1",
68
+ :x_ops_sign=>"version=1.0",
69
+ :x_ops_requestid=>"127.0.0.1 1258566194.85386",
70
+ :x_ops_timestamp=>"2009-01-01T12:00:00Z",
71
+ :x_ops_content_hash=>"DFteJZPVv6WKdQmMqZUQUumUyRs=",
72
+ :x_ops_userid=>"spec-user",
73
+ :x_ops_authorization_1=>"jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
74
+ :x_ops_authorization_2=>"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
75
+ :x_ops_authorization_3=>"3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
76
+ :x_ops_authorization_4=>"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
77
+ :x_ops_authorization_5=>"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
78
+ :x_ops_authorization_6=>"utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w=="}
79
+ @http_authentication_request.headers.should == expected
80
+ end
81
+
82
+ it "raises an error when not all required headers are given" do
83
+ @merb_headers.delete("HTTP_X_OPS_SIGN")
84
+ exception = Mixlib::Authentication::MissingAuthenticationHeader
85
+ auth_req = Mixlib::Authentication::HTTPAuthenticationRequest.new(@request)
86
+ lambda {auth_req.validate_headers!}.should raise_error(exception)
87
+ end
88
+
89
+ it "extracts the path from the request" do
90
+ @http_authentication_request.path.should == '/nodes'
91
+ end
92
+
93
+ it "extracts the request method from the request" do
94
+ @http_authentication_request.http_method.should == 'POST'
95
+ end
96
+
97
+ it "extracts the signing description from the request headers" do
98
+ @http_authentication_request.signing_description.should == 'version=1.0'
99
+ end
100
+
101
+ it "extracts the user_id from the request headers" do
102
+ @http_authentication_request.user_id.should == 'spec-user'
103
+ end
104
+
105
+ it "extracts the timestamp from the request headers" do
106
+ @http_authentication_request.timestamp.should == "2009-01-01T12:00:00Z"
107
+ end
108
+
109
+ it "extracts the host from the request headers" do
110
+ @http_authentication_request.host.should == "127.0.0.1"
111
+ end
112
+
113
+ it "extracts the content hash from the request headers" do
114
+ @http_authentication_request.content_hash.should == "DFteJZPVv6WKdQmMqZUQUumUyRs="
115
+ end
116
+
117
+ it "rebuilds the request signature from the headers" do
118
+ expected=<<-SIG
119
+ jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4
120
+ NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc
121
+ 3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O
122
+ IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy
123
+ 9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0
124
+ utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==
125
+ SIG
126
+ @http_authentication_request.request_signature.should == expected.chomp
127
+ end
128
+
129
+ end
@@ -17,9 +17,7 @@
17
17
  # limitations under the License.
18
18
  #
19
19
 
20
- $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib")) # lib in mixlib-authentication
21
- $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "mixlib-log", "lib")) # mixlib-log/log
22
-
20
+ require File.expand_path(File.join(File.dirname(__FILE__), '..','..','spec_helper'))
23
21
  require 'rubygems'
24
22
 
25
23
  require 'ostruct'
@@ -64,6 +62,7 @@ class MockFile
64
62
  end
65
63
 
66
64
  # Uncomment this to get some more info from the methods we're testing.
65
+ #Mixlib::Authentication::Log.logger = Logger.new(STDERR)
67
66
  #Mixlib::Authentication::Log.level :debug
68
67
 
69
68
  describe "Mixlib::Authentication::SignedHeaderAuth" do
@@ -152,11 +151,28 @@ describe "Mixlib::Authentication::SignatureVerification" do
152
151
  mock_request = MockRequest.new(PATH, request_params, PASSENGER_HEADERS, "")
153
152
  Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
154
153
 
155
- service = Mixlib::Authentication::SignatureVerification.new
156
- res = service.authenticate_user_request(mock_request, @user_private_key)
154
+ auth_req = Mixlib::Authentication::SignatureVerification.new
155
+ res = auth_req.authenticate_user_request(mock_request, @user_private_key)
157
156
  res.should_not be_nil
158
157
  end
159
158
 
159
+ it "shouldn't authenticate if an Authorization header is missing" do
160
+ headers = MERB_HEADERS.clone
161
+ headers.delete("HTTP_X_OPS_SIGN")
162
+
163
+ mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
164
+ Time.stub!(:now).and_return(TIMESTAMP_OBJ)
165
+
166
+ auth_req = Mixlib::Authentication::SignatureVerification.new
167
+ lambda {auth_req.authenticate_user_request(mock_request, @user_private_key)}.should raise_error(Mixlib::Authentication::AuthenticationError)
168
+
169
+ auth_req.should_not be_a_valid_request
170
+ auth_req.should_not be_a_valid_timestamp
171
+ auth_req.should_not be_a_valid_signature
172
+ auth_req.should_not be_a_valid_content_hash
173
+ end
174
+
175
+
160
176
  it "shouldn't authenticate if Authorization header is wrong" do
161
177
  headers = MERB_HEADERS.clone
162
178
  headers["HTTP_X_OPS_CONTENT_HASH"] += "_"
@@ -164,9 +180,42 @@ describe "Mixlib::Authentication::SignatureVerification" do
164
180
  mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
165
181
  Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
166
182
 
167
- service = Mixlib::Authentication::SignatureVerification.new
168
- res = service.authenticate_user_request(mock_request, @user_private_key)
183
+ auth_req = Mixlib::Authentication::SignatureVerification.new
184
+ res = auth_req.authenticate_user_request(mock_request, @user_private_key)
185
+ res.should be_nil
186
+
187
+ auth_req.should_not be_a_valid_request
188
+ auth_req.should be_a_valid_timestamp
189
+ auth_req.should be_a_valid_signature
190
+ auth_req.should_not be_a_valid_content_hash
191
+ end
192
+
193
+ it "shouldn't authenticate if the timestamp is not within bounds" do
194
+ mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, MERB_HEADERS, BODY)
195
+ Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ - 1000)
196
+
197
+ auth_req = Mixlib::Authentication::SignatureVerification.new
198
+ res = auth_req.authenticate_user_request(mock_request, @user_private_key)
199
+ res.should be_nil
200
+ auth_req.should_not be_a_valid_request
201
+ auth_req.should_not be_a_valid_timestamp
202
+ auth_req.should be_a_valid_signature
203
+ auth_req.should be_a_valid_content_hash
204
+ end
205
+
206
+ it "shouldn't authenticate if the signature is wrong" do
207
+ headers = MERB_HEADERS.dup
208
+ headers["HTTP_X_OPS_AUTHORIZATION_1"] = "epicfail"
209
+ mock_request = MockRequest.new(PATH, MERB_REQUEST_PARAMS, headers, BODY)
210
+ Time.should_receive(:now).at_least(:once).and_return(TIMESTAMP_OBJ)
211
+
212
+ auth_req = Mixlib::Authentication::SignatureVerification.new
213
+ res = auth_req.authenticate_user_request(mock_request, @user_private_key)
169
214
  res.should be_nil
215
+ auth_req.should_not be_a_valid_request
216
+ auth_req.should_not be_a_valid_signature
217
+ auth_req.should be_a_valid_timestamp
218
+ auth_req.should be_a_valid_content_hash
170
219
  end
171
220
 
172
221
  end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Tim Hinderliter (<tim@opscode.com>)
3
+ # Author:: Christopher Walters (<cw@opscode.com>)
4
+ # Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")) # lib in mixlib-authentication
21
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "mixlib-log", "lib")) # mixlib-log/log
22
+
23
+ require 'rubygems'
metadata CHANGED
@@ -1,12 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixlib-authentication
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
6
  - 1
7
7
  - 1
8
- - 2
9
- version: 1.1.2
8
+ - 4
9
+ - rc
10
+ - 1
11
+ version: 1.1.4.rc.1
10
12
  platform: ruby
11
13
  authors:
12
14
  - Opscode, Inc.
@@ -14,13 +16,14 @@ autorequire: mixlib-authentication
14
16
  bindir: bin
15
17
  cert_chain: []
16
18
 
17
- date: 2010-03-17 00:00:00 -07:00
19
+ date: 2010-07-23 00:00:00 -07:00
18
20
  default_executable:
19
21
  dependencies:
20
22
  - !ruby/object:Gem::Dependency
21
23
  name: mixlib-log
22
24
  prerelease: false
23
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
24
27
  requirements:
25
28
  - - ">="
26
29
  - !ruby/object:Gem::Version
@@ -45,11 +48,14 @@ files:
45
48
  - Rakefile
46
49
  - NOTICE
47
50
  - lib/mixlib/authentication/digester.rb
51
+ - lib/mixlib/authentication/http_authentication_request.rb
48
52
  - lib/mixlib/authentication/signatureverification.rb
49
53
  - lib/mixlib/authentication/signedheaderauth.rb
50
54
  - lib/mixlib/authentication.rb
55
+ - spec/mixlib/authentication/http_authentication_request_spec.rb
51
56
  - spec/mixlib/authentication/mixlib_authentication_spec.rb
52
57
  - spec/spec.opts
58
+ - spec/spec_helper.rb
53
59
  has_rdoc: true
54
60
  homepage: http://www.opscode.com
55
61
  licenses: []
@@ -60,6 +66,7 @@ rdoc_options: []
60
66
  require_paths:
61
67
  - lib
62
68
  required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
63
70
  requirements:
64
71
  - - ">="
65
72
  - !ruby/object:Gem::Version
@@ -67,16 +74,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
74
  - 0
68
75
  version: "0"
69
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
70
78
  requirements:
71
- - - ">="
79
+ - - ">"
72
80
  - !ruby/object:Gem::Version
73
81
  segments:
74
- - 0
75
- version: "0"
82
+ - 1
83
+ - 3
84
+ - 1
85
+ version: 1.3.1
76
86
  requirements: []
77
87
 
78
88
  rubyforge_project:
79
- rubygems_version: 1.3.6
89
+ rubygems_version: 1.3.7
80
90
  signing_key:
81
91
  specification_version: 3
82
92
  summary: Mixes in simple per-request authentication