From 24106eef272953f3190db9115dc5ac5f6aa1600e Mon Sep 17 00:00:00 2001 From: Alex Matchneer Date: Wed, 20 Mar 2019 10:50:06 -0400 Subject: [PATCH] Expose .connection on block param (#648) This provides access to the connection object, which in turn exposes properties like `.peer_cert` for performing additional validation against x509 certificates. Closes #633, Follow-up to #634 --- examples/README.md | 3 +++ examples/peer_cert.rb | 9 +++++++++ lib/httparty/fragment_with_response.rb | 5 +++-- lib/httparty/request.rb | 5 +++-- spec/httparty/fragment_with_response_spec.rb | 6 ++++-- spec/httparty/ssl_spec.rb | 12 ++++++++++-- spec/support/ssl_test_helper.rb | 8 ++++---- 7 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 examples/peer_cert.rb diff --git a/examples/README.md b/examples/README.md index 6ddbd9e3..23f18d66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -81,3 +81,6 @@ * [Uploading File](body_stream.rb) * Uses `body_stream` to upload file + +* [Accessing x509 Peer Certificate](peer_cert.rb) + * Provides access to the server's TLS certificate diff --git a/examples/peer_cert.rb b/examples/peer_cert.rb new file mode 100644 index 00000000..a16de689 --- /dev/null +++ b/examples/peer_cert.rb @@ -0,0 +1,9 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') + +peer_cert = nil +HTTParty.get("https://www.example.com") do |fragment| + peer_cert ||= fragment.connection.peer_cert +end + +puts "The server's certificate expires #{peer_cert.not_after}" diff --git a/lib/httparty/fragment_with_response.rb b/lib/httparty/fragment_with_response.rb index 5bfa6ad5..4ca0f70f 100644 --- a/lib/httparty/fragment_with_response.rb +++ b/lib/httparty/fragment_with_response.rb @@ -5,15 +5,16 @@ module HTTParty class FragmentWithResponse < SimpleDelegator extend Forwardable - attr_reader :http_response + attr_reader :http_response, :connection def code @http_response.code.to_i end - def initialize(fragment, http_response) + def initialize(fragment, http_response, connection) @fragment = fragment @http_response = http_response + @connection = connection super fragment end end diff --git a/lib/httparty/request.rb b/lib/httparty/request.rb index 97aac2f9..a2e266cb 100644 --- a/lib/httparty/request.rb +++ b/lib/httparty/request.rb @@ -140,15 +140,16 @@ def perform(&block) validate setup_raw_request chunked_body = nil + current_http = http - self.last_response = http.request(@raw_request) do |http_response| + self.last_response = current_http.request(@raw_request) do |http_response| if block chunks = [] http_response.read_body do |fragment| encoded_fragment = encode_text(fragment, http_response['content-type']) chunks << encoded_fragment if !options[:stream_body] - block.call FragmentWithResponse.new(encoded_fragment, http_response) + block.call FragmentWithResponse.new(encoded_fragment, http_response, current_http) end chunked_body = chunks.join diff --git a/spec/httparty/fragment_with_response_spec.rb b/spec/httparty/fragment_with_response_spec.rb index 6f3aecff..06ec7399 100644 --- a/spec/httparty/fragment_with_response_spec.rb +++ b/spec/httparty/fragment_with_response_spec.rb @@ -2,13 +2,15 @@ RSpec.describe HTTParty::FragmentWithResponse do it "access to fragment" do - fragment = HTTParty::FragmentWithResponse.new("chunk", nil) + fragment = HTTParty::FragmentWithResponse.new("chunk", nil, nil) expect(fragment).to eq("chunk") end it "has access to delegators" do response = double(code: '200') - fragment = HTTParty::FragmentWithResponse.new("chunk", response) + connection = double + fragment = HTTParty::FragmentWithResponse.new("chunk", response, connection) expect(fragment.code).to eq(200) expect(fragment.http_response).to eq response + expect(fragment.connection).to eq connection end end diff --git a/spec/httparty/ssl_spec.rb b/spec/httparty/ssl_spec.rb index b6c081f8..7a1dfa62 100644 --- a/spec/httparty/ssl_spec.rb +++ b/spec/httparty/ssl_spec.rb @@ -3,11 +3,11 @@ RSpec.describe HTTParty::Request do context "SSL certificate verification" do before do - WebMock.allow_net_connect! + WebMock.disable! end after do - WebMock.disable_net_connect! + WebMock.enable! end it "should fail when no trusted CA list is specified, by default" do @@ -70,5 +70,13 @@ ssl_verify_test(:ssl_ca_path, ".", "bogushost.crt") end.to raise_error(OpenSSL::SSL::SSLError) end + + it "should provide the certificate used by the server via peer_cert" do + peer_cert = nil + ssl_verify_test(:ssl_ca_file, "ca.crt", "server.crt") do |response| + peer_cert ||= response.connection.peer_cert + end + expect(peer_cert).to be_a OpenSSL::X509::Certificate + end end end diff --git a/spec/support/ssl_test_helper.rb b/spec/support/ssl_test_helper.rb index 2a0ca340..6671e2d9 100644 --- a/spec/support/ssl_test_helper.rb +++ b/spec/support/ssl_test_helper.rb @@ -2,7 +2,7 @@ module HTTParty module SSLTestHelper - def ssl_verify_test(mode, ca_basename, server_cert_filename, options = {}) + def ssl_verify_test(mode, ca_basename, server_cert_filename, options = {}, &block) options = { format: :json, timeout: 30 @@ -24,9 +24,9 @@ def ssl_verify_test(mode, ca_basename, server_cert_filename, options = {}) if mode ca_path = File.expand_path("../../fixtures/ssl/generated/#{ca_basename}", __FILE__) raise ArgumentError.new("#{ca_path} does not exist") unless File.exist?(ca_path) - return HTTParty.get("https://localhost:#{test_server.port}/", options) + return HTTParty.get("https://localhost:#{test_server.port}/", options, &block) else - return HTTParty.get("https://localhost:#{test_server.port}/", options) + return HTTParty.get("https://localhost:#{test_server.port}/", options, &block) end ensure test_server.stop if test_server @@ -39,7 +39,7 @@ def ssl_verify_test(mode, ca_basename, server_cert_filename, options = {}) test_server.start - HTTParty.get("https://localhost:#{test_server.port}/", options) + HTTParty.get("https://localhost:#{test_server.port}/", options, &block) ensure test_server.stop if test_server end