diff --git a/lib/savon/operation.rb b/lib/savon/operation.rb index 00e40d87..9a6d3c7f 100644 --- a/lib/savon/operation.rb +++ b/lib/savon/operation.rb @@ -7,6 +7,8 @@ require "savon/request_logger" require "savon/http_error" require "mail" +require 'faraday/gzip' + module Savon class Operation @@ -58,16 +60,20 @@ def call(locals = {}, &block) builder = build(locals, &block) response = Savon.notify_observers(@name, builder, @globals, @locals) - response ||= call_with_logging build_request(builder) + response ||= call_with_logging build_connection(builder) - raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response) + raise_expected_faraday_response! unless response.kind_of?(Faraday::Response) create_response(response) end def request(locals = {}, &block) builder = build(locals, &block) - build_request(builder) + connection = build_connection(builder) + connection.build_request(:post) do |req| + req.url(@globals[:endpoint]) + req.body = @locals[:body] + end end private @@ -83,37 +89,47 @@ def set_locals(locals, block) @locals = locals end - def call_with_logging(request) - @logger.log(request) { HTTPI.post(request, @globals[:adapter]) } + def call_with_logging(connection) + ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm) + @logger.log_response(connection.post(@globals[:endpoint]) { |request| + request.body = @locals[:body] + request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth + @logger.log_request(request) + }) end - def build_request(builder) - @locals[:soap_action] ||= soap_action - @globals[:endpoint] ||= endpoint + def handle_ntlm(connection) + ntlm_message = Net::NTLM::Message + response = connection.get(@globals[:endpoint]) do |request| + request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64 + end + challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1] + message = ntlm_message::Type2.decode64(challenge) + message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h) + end - request = SOAPRequest.new(@globals).build( + def build_connection(builder) + @globals[:endpoint] ||= endpoint + @locals[:soap_action] ||= soap_action + @locals[:body] = builder.to_s + @connection = SOAPRequest.new(@globals).build( :soap_action => soap_action, :cookies => @locals[:cookies], :headers => @locals[:headers] - ) - - request.url = endpoint - request.body = builder.to_s - - if builder.multipart - request.gzip - request.headers["Content-Type"] = ["multipart/related", - "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"", - "start=\"#{builder.multipart[:start]}\"", - "boundary=\"#{builder.multipart[:multipart_boundary]}\""].join("; ") - request.headers["MIME-Version"] = "1.0" + ) do |connection| + if builder.multipart + connection.request :gzip + connection.headers["Content-Type"] = %W[multipart/related + type="#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}", + start="#{builder.multipart[:start]}", + boundary="#{builder.multipart[:multipart_boundary]}"].join("; ") + connection.headers["MIME-Version"] = "1.0" + end + + connection.headers["Content-Length"] = @locals[:body].bytesize.to_s end - # TODO: could HTTPI do this automatically in case the header - # was not specified manually? [dh, 2013-01-04] - request.headers["Content-Length"] = request.body.bytesize.to_s - request end def soap_action @@ -138,8 +154,8 @@ def endpoint end end - def raise_expected_httpi_response! - raise Error, "Observers need to return an HTTPI::Response to mock " \ + def raise_expected_faraday_response! + raise Error, "Observers need to return an Faraday::Response to mock " \ "the request or nil to execute the request." end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 48c916a0..c40f530d 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -1,59 +1,78 @@ # frozen_string_literal: true -require "httpi" +require "faraday" module Savon class HTTPRequest - def initialize(globals, http_request = nil) + def initialize(globals, connection = nil) @globals = globals - @http_request = http_request || HTTPI::Request.new - end - - def build - @http_request + @connection = connection || Faraday::Connection.new end private - def configure_proxy - @http_request.proxy = @globals[:proxy] if @globals.include? :proxy + def configure_proxy(connection) + connection.proxy = @globals[:proxy] if @globals.include? :proxy end - def configure_timeouts - @http_request.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout - @http_request.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout - @http_request.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout + def configure_timeouts(connection) + connection.options.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout + connection.options.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout + connection.options.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout end - def configure_ssl - @http_request.auth.ssl.ssl_version = @globals[:ssl_version] if @globals.include? :ssl_version - @http_request.auth.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version - @http_request.auth.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version - - @http_request.auth.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode - @http_request.auth.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers - - @http_request.auth.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file - @http_request.auth.ssl.cert_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key - @http_request.auth.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file - @http_request.auth.ssl.cert = @globals[:ssl_cert] if @globals.include? :ssl_cert - @http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file - @http_request.auth.ssl.ca_cert_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path - @http_request.auth.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert - @http_request.auth.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store - - @http_request.auth.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password + def configure_ssl(connection) + connection.ssl.verify = @globals[:ssl_verify] if @globals.include? :ssl_verify + connection.ssl.ca_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file + connection.ssl.verify_hostname = @globals[:verify_hostname] if @globals.include? :verify_hostname + connection.ssl.ca_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path + connection.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode + connection.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store + connection.ssl.client_cert = @globals[:ssl_cert] if @globals.include? :ssl_cert + connection.ssl.client_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key + connection.ssl.certificate = @globals[:ssl_certificate] if @globals.include? :ssl_certificate + connection.ssl.private_key = @globals[:ssl_private_key] if @globals.include? :ssl_private_key + connection.ssl.verify_depth = @globals[:verify_depth] if @globals.include? :verify_depth + connection.ssl.version = @globals[:ssl_version] if @globals.include? :ssl_version + connection.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version + connection.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version + + # No Faraday Equivalent out of box, see: https://lostisland.github.io/faraday/#/customization/ssl-options + # connection.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file + # connection.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file + # connection.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert + # connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers + # connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password + + # deprecated and seems to break faraday via infinite stack recursion... Expected to use max_version and min_version + # see: https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D end - def configure_auth - @http_request.auth.basic(*@globals[:basic_auth]) if @globals.include? :basic_auth - @http_request.auth.digest(*@globals[:digest_auth]) if @globals.include? :digest_auth - @http_request.auth.ntlm(*@globals[:ntlm]) if @globals.include? :ntlm + def configure_auth(connection) + connection.request :authorization, :basic, *@globals[:basic_auth] if @globals.include? :basic_auth + if @globals.include? :digest_auth + begin + require 'faraday/digestauth' + connection.request :digest, *@globals[:digest_auth] + rescue LoadError => e + raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' + end + end + if @globals.include?(:ntlm) + begin + require 'rubyntlm' + require 'faraday/net_http_persistent' + connection.adapter :net_http_persistent, pool_size: 5 + rescue LoadError => e + raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' + end + end end - def configure_redirect_handling - if @globals.include? :follow_redirects - @http_request.follow_redirect = @globals[:follow_redirects] + def configure_redirect_handling(connection) + if @globals[:follow_redirects] + require 'faraday/follow_redirects' + connection.response :follow_redirects end end end @@ -61,20 +80,22 @@ def configure_redirect_handling class WSDLRequest < HTTPRequest def build - configure_proxy - configure_timeouts - configure_headers - configure_ssl - configure_auth - configure_redirect_handling - - @http_request + @connection.yield_self do |connection| + configure_proxy(connection) + configure_timeouts(connection) + configure_ssl(connection) + configure_auth(connection) + connection.adapter *@globals[:adapter] if !@globals[:adapter].nil? + connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + configure_headers(connection) + end + @connection end private - def configure_headers - @http_request.headers = @globals[:headers] if @globals.include? :headers + def configure_headers(connection) + connection.headers = @globals[:headers] if @globals.include? :headers end end @@ -85,29 +106,41 @@ class SOAPRequest < HTTPRequest 2 => "application/soap+xml;charset=%s" } + + def build(options = {}) - configure_proxy - configure_timeouts - configure_headers options[:soap_action], options[:headers] - configure_cookies options[:cookies] - configure_ssl - configure_auth - configure_redirect_handling - - @http_request + @connection.yield_self do |connection| + configure_proxy(connection) + configure_timeouts(connection) + configure_ssl(connection) + configure_auth(connection) + configure_headers(connection, options[:soap_action], options[:headers]) + configure_cookies(connection, options[:cookies]) + connection.adapter *@globals[:adapter] unless @globals[:adapter].nil? + connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + configure_redirect_handling(connection) + yield(connection) if block_given? + end + @connection end private - def configure_cookies(cookies) - @http_request.set_cookies(cookies) if cookies + def configure_cookies(connection, cookies) + connection.headers['Cookie'] = cookies.map do |key, value| + if key == :_ + value.join('; ') + else + "#{key}=#{value}" + end + end.join('; ') if cookies end - def configure_headers(soap_action, headers) - @http_request.headers = @globals[:headers] if @globals.include? :headers - @http_request.headers.merge!(headers) if headers - @http_request.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action - @http_request.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding] + def configure_headers(connection, soap_action, headers) + connection.headers = @globals[:headers] if @globals.include? :headers + connection.headers.merge!(headers) if headers + connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action + connection.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding] end end end diff --git a/lib/savon/request_logger.rb b/lib/savon/request_logger.rb index 079449ff..e143d871 100644 --- a/lib/savon/request_logger.rb +++ b/lib/savon/request_logger.rb @@ -27,21 +27,24 @@ def log? def log_headers? @globals[:log_headers] end - - private - def log_request(request) - logger.info { "SOAP request: #{request.url}" } + return unless log? + logger.info { "SOAP request: #{request.path}" } logger.info { headers_to_log(request.headers) } if log_headers? logger.debug { body_to_log(request.body) } end def log_response(response) - logger.info { "SOAP response (status #{response.code})" } + return response unless log? + logger.info { "SOAP response (status #{response.status})" } logger.debug { headers_to_log(response.headers) } if log_headers? logger.debug { body_to_log(response.body) } + response end + private + + def headers_to_log(headers) headers.map { |key, value| "#{key}: #{value}" }.join("\n") end diff --git a/spec/savon/operation_spec.rb b/spec/savon/operation_spec.rb index e10a6d61..24ee8cbc 100644 --- a/spec/savon/operation_spec.rb +++ b/spec/savon/operation_spec.rb @@ -48,7 +48,7 @@ def new_operation(operation_name, wsdl, globals) it "raises if the endpoint cannot be reached" do message = "Error!" - response = HTTPI::Response.new(500, {}, message) + response = Responses.mock_faraday(500, {}, message) error = Wasabi::Resolver::HTTPError.new(message, response) Wasabi::Document.any_instance.stubs(:soap_actions).raises(error) @@ -64,7 +64,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#build" do + describe "build" do it "returns the Builder" do operation = new_operation(:verify_address, wsdl, globals) builder = operation.build(:message => { :test => 'message' }) @@ -74,7 +74,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#call" do + describe "call" do it "returns a response object" do operation = new_operation(:verify_address, wsdl, globals) expect(operation.call).to be_a(Savon::Response) @@ -82,35 +82,32 @@ def new_operation(operation_name, wsdl, globals) it "uses the global :endpoint option for the request" do globals.endpoint("http://v1.example.com") - HTTPI::Request.any_instance.expects(:url=).with("http://v1.example.com") operation = new_operation(:verify_address, wsdl, globals) - - # stub the actual request - http_response = HTTPI::Response.new(200, {}, "") - operation.expects(:call_with_logging).returns(http_response) - + http_response = Responses.mock_faraday(200, {}, "") + Faraday::Connection.any_instance.expects(:post).with(globals[:endpoint]).returns(http_response) operation.call end it "falls back to use the WSDL's endpoint if the :endpoint option was not set" do globals_without_endpoint = Savon::GlobalOptions.new(:log => false) - HTTPI::Request.any_instance.expects(:url=).with(wsdl.endpoint) operation = new_operation(:verify_address, wsdl, globals_without_endpoint) # stub the actual request - http_response = HTTPI::Response.new(200, {}, "") - operation.expects(:call_with_logging).returns(http_response) + http_response = Responses.mock_faraday(200, {}, "") + Faraday::Connection.any_instance.expects(:post).with(wsdl.endpoint).returns(http_response) operation.call end it "sets the Content-Length header" do # XXX: probably the worst spec ever written. refactor! [dh, 2013-01-05] - http_request = HTTPI::Request.new - http_request.headers.expects(:[]=).with("Content-Length", "723") - Savon::SOAPRequest.any_instance.expects(:build).returns(http_request) + http_response = Responses.mock_faraday(200, {}, "") + + Faraday::Utils::Headers.any_instance.expects(:[]=).at_least_once + Faraday::Utils::Headers.any_instance.expects(:[]=).with('Content-Length', "723") + Faraday::Connection.any_instance.expects(:post).returns(http_response) new_operation(:verify_address, wsdl, globals).call end @@ -128,10 +125,10 @@ def new_operation(operation_name, wsdl, globals) it "uses the local :cookies option" do globals.endpoint @server.url(:inspect_request) - cookies = [HTTPI::Cookie.new("some-cookie=choc-chip")] - - HTTPI::Request.any_instance.expects(:set_cookies).with(cookies) + cookies = {'some-cookie': 'choc-chip'} + Faraday::Utils::Headers.any_instance.expects(:[]=).at_least_once + Faraday::Utils::Headers.any_instance.expects(:[]=).with('Cookie', 'some-cookie=choc-chip').at_least_once operation = new_operation(:verify_address, wsdl, globals) operation.call(:cookies => cookies) end @@ -191,7 +188,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#request" do + describe "request" do it "returns the request" do operation = new_operation(:verify_address, wsdl, globals) request = operation.request diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index fcdc03f4..836923ed 100644 --- a/spec/savon/request_spec.rb +++ b/spec/savon/request_spec.rb @@ -5,17 +5,18 @@ RSpec.describe Savon::WSDLRequest do let(:globals) { Savon::GlobalOptions.new } - let(:http_request) { HTTPI::Request.new } + let(:http_connection) { Faraday::Connection.new } let(:ciphers) { OpenSSL::Cipher.ciphers } def new_wsdl_request - Savon::WSDLRequest.new(globals, http_request) + Savon::WSDLRequest.new(globals, http_connection) end - describe "#build" do - it "returns an HTTPI::Request" do + describe "build" do + it "returns an Faraday::Request" do wsdl_request = Savon::WSDLRequest.new(globals) - expect(wsdl_request.build).to be_an(HTTPI::Request) + result = wsdl_request.build + expect(result).to be_an(Faraday::Connection) end describe "headers" do @@ -35,13 +36,13 @@ def new_wsdl_request describe "proxy" do it "is set when specified" do globals.proxy("http://proxy.example.com") - http_request.expects(:proxy=).with("http://proxy.example.com") + http_connection.expects(:proxy=).with("http://proxy.example.com") new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:proxy=).never + http_connection.expects(:proxy=).never new_wsdl_request.build end end @@ -49,13 +50,13 @@ def new_wsdl_request describe "open timeout" do it "is set when specified" do globals.open_timeout(22) - http_request.expects(:open_timeout=).with(22) + http_connection.options.expects(:open_timeout=).with(22) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:open_timeout=).never + http_connection.options.expects(:open_timeout=).never new_wsdl_request.build end end @@ -63,13 +64,13 @@ def new_wsdl_request describe "read timeout" do it "is set when specified" do globals.read_timeout(33) - http_request.expects(:read_timeout=).with(33) + http_connection.options.expects(:read_timeout=).with(33) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.options.expects(:read_timeout=).never new_wsdl_request.build end end @@ -77,41 +78,41 @@ def new_wsdl_request describe "write timeout" do it "is set when specified" do globals.write_timeout(44) - http_request.expects(:write_timeout=).with(44) + http_connection.options.expects(:write_timeout=).with(44) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.expects(:read_timeout=).never new_wsdl_request.build end end - describe "ssl version" do - it "is set when specified" do - globals.ssl_version(:TLSv1) - http_request.auth.ssl.expects(:ssl_version=).with(:TLSv1) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ssl_version=).never - new_wsdl_request.build - end - end + # describe "ssl version" do + # it "is set when specified" do + # globals.ssl_version() + # http_connection.ssl.expects(:version).with(:TLSv1) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ssl_version=).never + # new_wsdl_request.build + # end + # end describe "ssl min_version" do it "is set when specified" do globals.ssl_min_version(:TLS1_2) - http_request.auth.ssl.expects(:min_version=).with(:TLS1_2) + http_connection.ssl.expects(:min_version=).with(:TLS1_2) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:min_version=).never + http_connection.ssl.expects(:min_version=).never new_wsdl_request.build end end @@ -119,13 +120,13 @@ def new_wsdl_request describe "ssl max_version" do it "is set when specified" do globals.ssl_max_version(:TLS1_2) - http_request.auth.ssl.expects(:max_version=).with(:TLS1_2) + http_connection.ssl.expects(:max_version=).with(:TLS1_2) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:max_version=).never + http_connection.ssl.expects(:max_version=).never new_wsdl_request.build end end @@ -133,136 +134,136 @@ def new_wsdl_request describe "ssl verify mode" do it "is set when specified" do globals.ssl_verify_mode(:peer) - http_request.auth.ssl.expects(:verify_mode=).with(:peer) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:verify_mode=).never - new_wsdl_request.build - end - end - - describe "ssl ciphers" do - it "is set when specified" do - globals.ssl_ciphers(ciphers) - http_request.auth.ssl.expects(:ciphers=).with(ciphers) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ciphers=).never - new_wsdl_request.build - end - end - - describe "ssl cert key file" do - it "is set when specified" do - cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - globals.ssl_cert_key_file(cert_key) - http_request.auth.ssl.expects(:cert_key_file=).with(cert_key) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_file=).never - new_wsdl_request.build - end - end - - describe "ssl cert key password" do - it "is set when specified" do - the_pass = "secure-password!42" - globals.ssl_cert_key_password(the_pass) - http_request.auth.ssl.expects(:cert_key_password=).with(the_pass) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_password=).never - new_wsdl_request.build - end - end - - describe "ssl encrypted cert key file" do - describe "set with an invalid decrypting password" do - it "fails when attempting to use the SSL private key" do - skip("JRuby: find out why this does not raise an error!") if RUBY_PLATFORM == 'java' - pass = "wrong-password" - key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - - globals.ssl_cert_file(cert) - globals.ssl_cert_key_password(pass) - globals.ssl_cert_key_file(key) - - new_wsdl_request.build - - expect { http_request.auth.ssl.cert_key }.to raise_error OpenSSL::PKey::PKeyError - end - end - - describe "set with a valid decrypting password" do - it "handles SSL private keys properly" do - pass = "secure-password!42" - key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - - globals.ssl_cert_file(cert) - globals.ssl_cert_key_password(pass) - globals.ssl_cert_key_file(key) - - new_wsdl_request.build - - expect(http_request.auth.ssl.cert_key.to_s).to match(/BEGIN RSA PRIVATE KEY/) - end - end - end - - describe "ssl cert file" do - it "is set when specified" do - cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_cert_file(cert) - http_request.auth.ssl.expects(:cert_file=).with(cert) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_file=).never - new_wsdl_request.build - end - end - - describe "ssl ca cert file" do - it "is set when specified" do - ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_ca_cert_file(ca_cert) - http_request.auth.ssl.expects(:ca_cert_file=).with(ca_cert) + http_connection.ssl.expects(:verify_mode=).with(:peer) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never - new_wsdl_request.build - end - end + http_connection.ssl.expects(:verify_mode=).never + new_wsdl_request.build + end + end + + # describe "ssl ciphers" do + # it "is set when specified" do + # globals.ssl_ciphers(ciphers) + # http_connection.ssl.expects(:ciphers=).with(ciphers) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ciphers=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl cert key file" do + # it "is set when specified" do + # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) + # globals.ssl_cert_key_file(cert_key) + # http_connection.ssl.expects(:cert_key_file=).with(cert_key) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_file=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl cert key password" do + # it "is set when specified" do + # the_pass = "secure-password!42" + # globals.ssl_cert_key_password(the_pass) + # http_connection.ssl.expects(:cert_key_password=).with(the_pass) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_password=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl encrypted cert key file" do + # describe "set with an invalid decrypting password" do + # it "fails when attempting to use the SSL private key" do + # skip("JRuby: find out why this does not raise an error!") if RUBY_PLATFORM == 'java' + # pass = "wrong-password" + # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) + # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) + # + # globals.ssl_cert_file(cert) + # globals.ssl_cert_key_password(pass) + # globals.ssl_cert_key_file(key) + # + # new_wsdl_request.build + # + # expect { http_connection.ssl.cert_key }.to raise_error OpenSSL::PKey::PKeyError + # end + # end + # + # describe "set with a valid decrypting password" do + # it "handles SSL private keys properly" do + # pass = "secure-password!42" + # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) + # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) + # + # globals.ssl_cert_file(cert) + # globals.ssl_cert_key_password(pass) + # globals.ssl_cert_key_file(key) + # + # new_wsdl_request.build + # + # expect(http_connection.ssl.cert_key.to_s).to match(/BEGIN RSA PRIVATE KEY/) + # end + # end + # end + + # describe "ssl cert file" do + # it "is set when specified" do + # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_cert_file(cert) + # http_connection.ssl.expects(:cert_file=).with(cert) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_file=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl ca cert file" do + # it "is set when specified" do + # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_ca_cert_file(ca_cert) + # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ca_cert_file=).never + # new_wsdl_request.build + # end + # end describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") - http_request.auth.expects(:basic).with("luke", "secret") + http_connection.expects(:request).with(:authorization, :basic,"luke", "secret") new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.expects(:basic).never + http_connection.expects(:request).with{|args| args.include?(:basic)}.never new_wsdl_request.build end end @@ -270,27 +271,33 @@ def new_wsdl_request describe "digest auth" do it "is set when specified" do globals.digest_auth("lea", "top-secret") - http_request.auth.expects(:digest).with("lea", "top-secret") + http_connection.expects(:request).with(:digest, "lea", "top-secret") new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.expects(:digest).never + http_connection.expects(:request).with{|args| args.include?(:digest)}.never new_wsdl_request.build end end describe "ntlm auth" do - it "is set when specified" do + it 'tries to load ntlm when set' do globals.ntlm("han", "super-secret") - http_request.auth.expects(:ntlm).with("han", "super-secret") + new_wsdl_request.build + expect(require 'rubyntlm').to be(false) + end + + it "applies net-http-persistent when set" do + globals.ntlm("han", "super-secret") + http_connection.expects(:adapter).with{|params| params == :net_http_persistent}.at_least_once new_wsdl_request.build end - it "is not set otherwise" do - http_request.auth.expects(:ntlm).never + it "does not apply net-http-persistent when not set" do + http_connection.expects(:adapter).with(:net_http_persistent, pool_size: 5).never new_wsdl_request.build end end @@ -301,43 +308,49 @@ def new_wsdl_request RSpec.describe Savon::SOAPRequest do let(:globals) { Savon::GlobalOptions.new } - let(:http_request) { HTTPI::Request.new } + let(:http_connection) { Faraday::Connection.new } let(:ciphers) { OpenSSL::Cipher.ciphers } def new_soap_request - Savon::SOAPRequest.new(globals, http_request) + Savon::SOAPRequest.new(globals, http_connection) end - describe "#build" do - it "returns an HTTPI::Request" do + describe "build" do + it "returns an Faraday::Request" do soap_request = Savon::SOAPRequest.new(globals) - expect(soap_request.build).to be_an(HTTPI::Request) + expect(soap_request.build).to be_an(Faraday::Connection) end describe "proxy" do it "is set when specified" do globals.proxy("http://proxy.example.com") - http_request.expects(:proxy=).with("http://proxy.example.com") + http_connection.expects(:proxy=).with("http://proxy.example.com") new_soap_request.build end it "is not set otherwise" do - http_request.expects(:proxy=).never + http_connection.expects(:proxy=).never new_soap_request.build end end describe "cookies" do it "sets the given cookies" do - cookies = [HTTPI::Cookie.new("some-cookie=choc-chip; Path=/; HttpOnly")] - - http_request.expects(:set_cookies).with(cookies) + cookies = { + 'some-cookie': 'choc-chip', + path: '/', + _: ['HttpOnly'] + } + + http_connection.headers.expects(:[]=).at_least_once + http_connection.headers.expects(:[]=).with('Cookie', 'some-cookie=choc-chip; path=/; HttpOnly').at_least_once new_soap_request.build(:cookies => cookies) end it "does not set the cookies if there are none" do - http_request.expects(:set_cookies).never + http_connection.headers.expects(:[]=).at_least_once + http_connection.expects(:[]=).with('Cookie').never new_soap_request.build end end @@ -345,13 +358,13 @@ def new_soap_request describe "open timeout" do it "is set when specified" do globals.open_timeout(22) - http_request.expects(:open_timeout=).with(22) + http_connection.options.expects(:open_timeout=).with(22) new_soap_request.build end it "is not set otherwise" do - http_request.expects(:open_timeout=).never + http_connection.options.expects(:open_timeout=).never new_soap_request.build end end @@ -359,13 +372,13 @@ def new_soap_request describe "read timeout" do it "is set when specified" do globals.read_timeout(33) - http_request.expects(:read_timeout=).with(33) + http_connection.options.expects(:read_timeout=).with(33) new_soap_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.options.expects(:read_timeout=).never new_soap_request.build end end @@ -436,13 +449,13 @@ def new_soap_request describe "ssl version" do it "is set when specified" do globals.ssl_version(:TLSv1) - http_request.auth.ssl.expects(:ssl_version=).with(:TLSv1) + http_connection.ssl.expects(:version=).with(:TLSv1) new_soap_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ssl_version=).never + http_connection.ssl.expects(:version=).never new_soap_request.build end end @@ -450,101 +463,100 @@ def new_soap_request describe "ssl verify mode" do it "is set when specified" do globals.ssl_verify_mode(:peer) - http_request.auth.ssl.expects(:verify_mode=).with(:peer) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:verify_mode=).never - new_soap_request.build - end - end - - describe "ssl ciphers" do - it "is set when specified" do - globals.ssl_ciphers(ciphers) - http_request.auth.ssl.expects(:ciphers=).with(ciphers) + http_connection.ssl.expects(:verify_mode=).with(:peer) new_soap_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ciphers=).never + http_connection.ssl.expects(:verify_mode=).never new_soap_request.build end end - describe "ssl cert key file" do - it "is set when specified" do - cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - globals.ssl_cert_key_file(cert_key) - http_request.auth.ssl.expects(:cert_key_file=).with(cert_key) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_file=).never - new_soap_request.build - end - end - - describe "ssl cert key password" do - it "is set when specified" do - the_pass = "secure-password!42" - globals.ssl_cert_key_password(the_pass) - http_request.auth.ssl.expects(:cert_key_password=).with(the_pass) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_password=).never - new_soap_request.build - end - end - - describe "ssl cert file" do - it "is set when specified" do - cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_cert_file(cert) - http_request.auth.ssl.expects(:cert_file=).with(cert) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_file=).never - new_soap_request.build - end - end - - describe "ssl ca cert file" do - it "is set when specified" do - ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_ca_cert_file(ca_cert) - http_request.auth.ssl.expects(:ca_cert_file=).with(ca_cert) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never - new_soap_request.build - end - end + # describe "ssl ciphers" do + # it "is set when specified" do + # globals.ssl_ciphers(ciphers) + # http_connection.ssl.expects(:ciphers=).with(ciphers) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ciphers=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert key file" do + # it "is set when specified" do + # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) + # globals.ssl_cert_key_file(cert_key) + # http_connection.ssl.expects(:cert_key_file=).with(cert_key) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_file=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert key password" do + # it "is set when specified" do + # the_pass = "secure-password!42" + # globals.ssl_cert_key_password(the_pass) + # http_connection.ssl.expects(:cert_key_password=).with(the_pass) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_password=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert file" do + # it "is set when specified" do + # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_cert_file(cert) + # http_connection.ssl.expects(:cert_file=).with(cert) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_file=).never + # new_soap_request.build + # end + # end + + # describe "ssl ca cert file" do + # it "is set when specified" do + # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_ca_cert_file(ca_cert) + # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ca_cert_file=).never + # new_soap_request.build + # end + # end describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") - http_request.auth.expects(:basic).with("luke", "secret") - + http_connection.expects(:request).with(:authorization, :basic, "luke", "secret") new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:basic).never + http_connection.expects(:request).with(:authorization, :basic, "luke", 'secret').never new_soap_request.build end end @@ -552,27 +564,26 @@ def new_soap_request describe "digest auth" do it "is set when specified" do globals.digest_auth("lea", "top-secret") - http_request.auth.expects(:digest).with("lea", "top-secret") + http_connection.expects(:request).with(:digest, "lea", "top-secret") new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:digest).never + http_connection.expects(:request).with(:digest, "lea", 'top-secret').never new_soap_request.build end end describe "ntlm auth" do - it "is set when specified" do + it "uses the net-http-persistent adapter in faraday" do globals.ntlm("han", "super-secret") - http_request.auth.expects(:ntlm).with("han", "super-secret") - + http_connection.expects(:adapter).with(:net_http_persistent, {:pool_size => 5}) new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:ntlm).never + http_connection.expects(:adapter).with(:net_http_persistent, {:pool_size => 5}).never new_soap_request.build end end diff --git a/spec/savon/soap_fault_spec.rb b/spec/savon/soap_fault_spec.rb index 1dde0501..6e38dbe0 100644 --- a/spec/savon/soap_fault_spec.rb +++ b/spec/savon/soap_fault_spec.rb @@ -9,7 +9,7 @@ let(:soap_fault_nc) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault)), nori_no_convert } let(:soap_fault_nc2) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault12)), nori_no_convert } let(:another_soap_fault) { Savon::SOAPFault.new new_response(:body => Fixture.response(:another_soap_fault)), nori } - let(:soap_fault_no_body) { Savon::SOAPFault.new new_response(:body => {}), nori } + let(:soap_fault_no_body) { Savon::SOAPFault.new new_response(:body => ''), nori } let(:no_fault) { Savon::SOAPFault.new new_response, nori } let(:nori) { Nori.new(:strip_namespaces => true, :convert_tags_to => lambda { |tag| Savon::StringUtils.snakecase(tag).to_sym }) } @@ -19,9 +19,9 @@ expect(Savon::SOAPFault.ancestors).to include(Savon::Error) end - describe "#http" do - it "returns the HTTPI::Response" do - expect(soap_fault.http).to be_an(HTTPI::Response) + describe "http" do + it "returns the Faraday::Response" do + expect(soap_fault.http).to be_an(Faraday::Response) end end @@ -52,7 +52,7 @@ end [:message, :to_s].each do |method| - describe "##{method}" do + describe "#{method}" do it "returns a SOAP 1.1 fault message" do expect(soap_fault.send method).to eq("(soap:Server) Fault occurred while processing.") end @@ -83,7 +83,7 @@ end end - describe "#to_hash" do + describe "to_hash" do it "returns the SOAP response as a Hash unless a SOAP fault is present" do expect(no_fault.to_hash[:authenticate_response][:return][:success]).to be_truthy end @@ -141,7 +141,7 @@ def new_response(options = {}) defaults = { :code => 500, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - HTTPI::Response.new response[:code], response[:headers], response[:body] + Responses.mock_faraday response[:code], response[:headers], response[:body] end end