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 +1 -1
 - data/lib/mixlib/authentication.rb +6 -0
 - data/lib/mixlib/authentication/http_authentication_request.rb +87 -0
 - data/lib/mixlib/authentication/signatureverification.rb +135 -51
 - data/lib/mixlib/authentication/signedheaderauth.rb +8 -3
 - data/spec/mixlib/authentication/http_authentication_request_spec.rb +129 -0
 - data/spec/mixlib/authentication/mixlib_authentication_spec.rb +56 -7
 - data/spec/spec_helper.rb +23 -0
 - metadata +18 -8
 
    
        data/Rakefile
    CHANGED
    
    
| 
         @@ -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  
     | 
| 
      
 78 
     | 
    
         
            +
                  def authenticate_request(user_secret, time_skew=(15*60))
         
     | 
| 
       44 
79 
     | 
    
         
             
                    Mixlib::Authentication::Log.debug "Initializing header auth : #{request.inspect}"
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                     
     | 
| 
       47 
     | 
    
         
            -
                     
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    @user_secret       = user_secret
         
     | 
| 
      
 82 
     | 
    
         
            +
                    @allowed_time_skew = time_skew # in seconds
         
     | 
| 
       48 
83 
     | 
    
         | 
| 
       49 
84 
     | 
    
         
             
                    begin
         
     | 
| 
       50 
     | 
    
         
            -
                      @ 
     | 
| 
       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 
     | 
    
         
            -
                      #  
     | 
| 
       70 
     | 
    
         
            -
                       
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                       
     | 
| 
      
 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 =  
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
                 
     | 
| 
       156 
     | 
    
         
            -
                res =  
     | 
| 
      
 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 
     | 
    
         
            -
                 
     | 
| 
       168 
     | 
    
         
            -
                res =  
     | 
| 
      
 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
         
     | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | 
         @@ -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:  
     | 
| 
      
 4 
     | 
    
         
            +
              prerelease: true
         
     | 
| 
       5 
5 
     | 
    
         
             
              segments: 
         
     | 
| 
       6 
6 
     | 
    
         
             
              - 1
         
     | 
| 
       7 
7 
     | 
    
         
             
              - 1
         
     | 
| 
       8 
     | 
    
         
            -
              -  
     | 
| 
       9 
     | 
    
         
            -
               
     | 
| 
      
 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- 
     | 
| 
      
 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 
     | 
    
         
            -
                  -  
     | 
| 
       75 
     | 
    
         
            -
                   
     | 
| 
      
 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. 
     | 
| 
      
 89 
     | 
    
         
            +
            rubygems_version: 1.3.7
         
     | 
| 
       80 
90 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       81 
91 
     | 
    
         
             
            specification_version: 3
         
     | 
| 
       82 
92 
     | 
    
         
             
            summary: Mixes in simple per-request authentication
         
     |