# File lib/mixlib/authentication/signatureverification.rb, line 43
      def authenticate_user_request(request, user_lookup, time_skew=(15*60))
        Mixlib::Authentication::Log.debug "Initializing header auth : #{request.inspect}"
        
        headers ||= request.env.inject({ }) { |memo, kv| memo[$2.gsub(/\-/,"_").downcase.to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
        digester = Mixlib::Authentication::Digester

        begin
          @allowed_time_skew   = time_skew # in seconds
          @http_method         = request.method.to_s
          @path                = request.path.to_s
          @signing_description = headers[:x_ops_sign].chomp
          @user_id             = headers[:x_ops_userid].chomp
          @timestamp           = headers[:x_ops_timestamp].chomp
          @host                = headers[:host].chomp
          @content_hash        = headers[:x_ops_content_hash].chomp
          @user_secret         = user_lookup

          # The authorization header is a Base64-encoded version of an RSA signature.
          # The client sent it on multiple header lines, starting at index 1 - 
          # X-Ops-Authorization-1, X-Ops-Authorization-2, etc. Pull them out and
          # concatenate.

          # if there are 11 headers, the sort breaks - it becomes lexicographic sort rather than numeric [cb]
          @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")
          Mixlib::Authentication::Log.debug "Reconstituted request signature: #{@request_signature}"
          
          # The request signature is based on any file attached, if any. Otherwise
          # it's based on the body of the request.
          # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
          # always hash the entire request body. In the file case it would just be
          # expanded multipart text - the entire body of the POST.
          #
          # Pull out any file that was attached to this request, using multipart
          # form uploads.
          # Depending on the server we're running in, multipart form uploads are
          # handed to us differently. 
          # - In Passenger (Cookbooks Community Site), the File is handed to us 
          #   directly in the params hash. The name is whatever the client used, 
          #   its value is therefore a File or Tempfile. 
          #   e.g. request['file_param'] = File
          #   
          # - In Merb (Chef server), the File is wrapped. The original parameter 
          #   name used for the file is used, but its value is a Hash. Within
          #   the hash is a name/value pair named 'file' which actually 
          #   contains the Tempfile instance.
          #   e.g. request['file_param'] = { :file => Tempfile }
          file_param = request.params.values.find { |value| value.respond_to?(:read) }

          # No file_param; we're running in Merb, or it's just not there..
          if file_param.nil?
            hash_param = request.params.values.find { |value| value.respond_to?(:has_key?) }  # Hash responds to :has_key? .
            if !hash_param.nil?
              file_param = hash_param.values.find { |value| value.respond_to?(:read) } # File/Tempfile responds to :read.
            end
          end

          # Any file that's included in the request is hashed if it's there. Otherwise,
          # we hash the body.
          if file_param
            Mixlib::Authentication::Log.debug "Digesting file_param: '#{file_param.inspect}'"
            @hashed_body = digester.hash_file(file_param)
          else
            body = request.raw_post
            Mixlib::Authentication::Log.debug "Digesting body: '#{body}'"
            @hashed_body = digester.hash_string(body)
          end
          
          Mixlib::Authentication::Log.debug "Authenticating user : #{user_id}, User secret is : #{@user_secret}, Request signature is :\n#{@request_signature}, Hashed Body is : #{@hashed_body}"
          
          #BUGBUG Not doing anything with the signing description yet [cb]          
          parse_signing_description
          candidate_block = canonicalize_request
          request_decrypted_block = @user_secret.public_decrypt(Base64.decode64(@request_signature))
          signatures_match = (request_decrypted_block == candidate_block)
          timeskew_is_acceptable = timestamp_within_bounds?(Time.parse(timestamp), Time.now)
          hashes_match = @content_hash == hashed_body
        rescue StandardError=>se
          raise StandardError,"Failed to authenticate user request.  Most likely missing a necessary header: #{se.message}", se.backtrace
        end
        
        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"
        
        if signatures_match and timeskew_is_acceptable and hashes_match
          OpenStruct.new(:name=>user_id)
        else
          nil
        end
      end