mixlib-authentication 1.1.2 → 1.1.4.rc.1

Sign up to get free protection for your applications and to get access to all the features.
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