From cbc02a79f21a32446a5e0482419fb3c75cda3386 Mon Sep 17 00:00:00 2001 From: Deepak Parpyani <1495846+dparpyani@users.noreply.github.com> Date: Thu, 18 Mar 2021 16:19:13 -0700 Subject: [PATCH] Allow specifying a proxy in request options (#21). --- README.md | 9 +++++++-- lib/pwned/hashed_password.rb | 1 + lib/pwned/password.rb | 1 + lib/pwned/password_base.rb | 12 ++++++++++-- spec/pwned/not_pwned_validator_spec.rb | 18 ++++++++++++++++++ spec/pwned/password_spec.rb | 17 +++++++++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 26034ce..189cc38 100644 --- a/README.md +++ b/README.md @@ -182,11 +182,16 @@ end #### Custom Request Options You can configure network requests made from the validator using `:request_options` (see [Net::HTTP.start](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start) for the list of available options). -In addition to these options, HTTP headers can be specified with the `:headers` key, e.g. `"User-Agent"`): +In addition to these options, HTTP headers can be specified with the `:headers` key (e.g. `"User-Agent"`) and proxy can be specified with the `:proxy` key: ```ruby validates :password, not_pwned: { - request_options: { read_timeout: 5, open_timeout: 1, headers: { "User-Agent" => "Super fun user agent" } } + request_options: { + read_timeout: 5, + open_timeout: 1, + headers: { "User-Agent" => "Super fun user agent" }, + proxy: "https://username:password@example.com:12345" + } } ``` diff --git a/lib/pwned/hashed_password.rb b/lib/pwned/hashed_password.rb index 25b2658..bb09eac 100644 --- a/lib/pwned/hashed_password.rb +++ b/lib/pwned/hashed_password.rb @@ -30,6 +30,7 @@ def initialize(hashed_password, request_options={}) @request_options = Hash(request_options).dup @request_headers = Hash(request_options.delete(:headers)) @request_headers = DEFAULT_REQUEST_HEADERS.merge(@request_headers) + @request_proxy = URI(request_options.delete(:proxy)) if request_options.key?(:proxy) end end end diff --git a/lib/pwned/password.rb b/lib/pwned/password.rb index 2d488a1..d8350a9 100644 --- a/lib/pwned/password.rb +++ b/lib/pwned/password.rb @@ -37,6 +37,7 @@ def initialize(password, request_options={}) @request_options = Hash(request_options).dup @request_headers = Hash(request_options.delete(:headers)) @request_headers = DEFAULT_REQUEST_HEADERS.merge(@request_headers) + @request_proxy = URI(request_options.delete(:proxy)) if request_options.key?(:proxy) end end end diff --git a/lib/pwned/password_base.rb b/lib/pwned/password_base.rb index 7defb96..8040f66 100644 --- a/lib/pwned/password_base.rb +++ b/lib/pwned/password_base.rb @@ -65,7 +65,7 @@ def pwned_count private - attr_reader :request_options, :request_headers + attr_reader :request_options, :request_headers, :request_proxy def fetch_pwned_count for_each_response_line do |line| @@ -108,7 +108,15 @@ def with_http_response(url, &block) request.initialize_http_header(request_headers) request_options[:use_ssl] = true - Net::HTTP.start(uri.host, uri.port, request_options) do |http| + Net::HTTP.start( + uri.host, + uri.port, + request_proxy&.host, + request_proxy&.port, + request_proxy&.user, + request_proxy&.password, + request_options + ) do |http| http.request(request, &block) end end diff --git a/spec/pwned/not_pwned_validator_spec.rb b/spec/pwned/not_pwned_validator_spec.rb index d83f90f..a5f9d9d 100644 --- a/spec/pwned/not_pwned_validator_spec.rb +++ b/spec/pwned/not_pwned_validator_spec.rb @@ -43,6 +43,24 @@ def create_model(password) with(headers: { "User-Agent" => "Super fun user agent" })). to have_been_made.once end + + it "allows the proxy to be set" do + Model.validates :password, not_pwned: { + request_options: { proxy: "https://username:password@example.com:12345" } + } + model = create_model("password") + + # Webmock doesn't support proxy assertions (https://github.com/bblimke/webmock/issues/753) + # so we check that Net::HTTP receives the correct arguments. + expect(Net::HTTP).to receive(:start). + with("api.pwnedpasswords.com", 443, "example.com", 12345, "username", "password", anything). + and_call_original + + expect(model).to_not be_valid + expect(a_request(:get, "https://api.pwnedpasswords.com/range/5BAA6"). + with(headers: { "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })). + to have_been_made.once + end end describe "when not pwned", pwned_range: "37D5B" do diff --git a/spec/pwned/password_spec.rb b/spec/pwned/password_spec.rb index 88e370e..4e0078b 100644 --- a/spec/pwned/password_spec.rb +++ b/spec/pwned/password_spec.rb @@ -148,6 +148,23 @@ def verify_not_found_error(error) with(headers: { "User-Agent" => "Super fun user agent" })). to have_been_made.once end + + it "allows the proxy to be set" do + proxy = "https://username:password@example.com:12345" + + # Webmock doesn't support proxy assertions (https://github.com/bblimke/webmock/issues/753) + # so we check that Net::HTTP receives the correct arguments. + expect(Net::HTTP).to receive(:start). + with("api.pwnedpasswords.com", 443, "example.com", 12345, "username", "password", anything). + and_call_original + + password = Pwned::Password.new("password", proxy: proxy) + password.pwned? + + expect(a_request(:get, "https://api.pwnedpasswords.com/range/5BAA6"). + with(headers: { "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })). + to have_been_made.once + end end describe "streaming", pwned_range: "A0F41" do