Skip to content

Commit

Permalink
Fix sanitize options function in hackney adapter converter (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
riverrun authored Apr 29, 2021
1 parent 68b4ae1 commit 1226936
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 31 deletions.
64 changes: 42 additions & 22 deletions lib/exvcr/adapter/hackney/converter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ defmodule ExVCR.Adapter.Hackney.Converter do
use ExVCR.Converter

defp string_to_response(string) do
response = Enum.map(string, fn({x, y}) -> {String.to_atom(x), y} end)
response = Enum.map(string, fn {x, y} -> {String.to_atom(x), y} end)
response = struct(ExVCR.Response, response)

response =
if is_map(response.headers) do
headers = response.headers |> Map.to_list
headers = response.headers |> Map.to_list()
%{response | headers: headers}
else
response
Expand All @@ -21,10 +21,10 @@ defmodule ExVCR.Adapter.Hackney.Converter do
end

defp request_to_string(request) do
method = Enum.fetch!(request, 0) |> to_string()
url = Enum.fetch!(request, 1) |> parse_url()
method = Enum.fetch!(request, 0) |> to_string()
url = Enum.fetch!(request, 1) |> parse_url()
headers = Enum.at(request, 2, []) |> parse_headers()
body = Enum.at(request, 3, "") |> parse_request_body()
body = Enum.at(request, 3, "") |> parse_request_body()
options = Enum.at(request, 4, []) |> sanitize_options() |> parse_options()

%ExVCR.Request{
Expand All @@ -36,26 +36,46 @@ defmodule ExVCR.Adapter.Hackney.Converter do
}
end

# If option value is tuple, make it as list, for encoding as json.
# Sanitize options so that they can be encoded as json.
defp sanitize_options(options) do
Enum.map(options, fn
{key, value} ->
if is_tuple(value) do
{key, Tuple.to_list(value)}
else
{key, value}
end
key when is_atom(key) ->
{key, true}
end)
Enum.map(options, &do_sanitize/1)
end

defp do_sanitize({key, value}) when is_function(key) do
{inspect(key), value}
end

defp do_sanitize({key, value}) when is_list(value) do
{key, Enum.map(value, &do_sanitize/1)}
end

defp do_sanitize({key, value}) when is_tuple(value) do
{key, Tuple.to_list(do_sanitize(value))}
end

defp do_sanitize({key, value}) when is_function(value) do
{key, inspect(value)}
end

defp do_sanitize({key, value}) do
{key, value}
end

defp do_sanitize(key) when is_atom(key) do
{key, true}
end

defp do_sanitize(value) do
value
end

defp response_to_string({:ok, status_code, headers, body_or_client}) do
body = case body_or_client do
string when is_binary(string) -> string
# Client is already replaced by body through ExVCR.Adapter.Hackney adapter.
ref when is_reference(ref) -> inspect(ref)
end
body =
case body_or_client do
string when is_binary(string) -> string
# Client is already replaced by body through ExVCR.Adapter.Hackney adapter.
ref when is_reference(ref) -> inspect(ref)
end

%ExVCR.Response{
type: "ok",
Expand Down Expand Up @@ -84,7 +104,7 @@ defmodule ExVCR.Adapter.Hackney.Converter do
:hackney_request.encode_form(body)
|> elem(2)
|> to_string
|> ExVCR.Filter.filter_sensitive_data
|> ExVCR.Filter.filter_sensitive_data()
end

def parse_request_body(body), do: super(body)
Expand Down
36 changes: 27 additions & 9 deletions test/recorder_hackney_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ defmodule ExVCR.RecorderHackneyTest do
setup_all do
File.rm_rf(@dummy_cassette_dir)

on_exit fn ->
on_exit(fn ->
File.rm_rf(@dummy_cassette_dir)
HttpServer.stop(@port)
:ok
end
end)

HTTPoison.start
HTTPoison.start()
HttpServer.start(path: "/server", port: @port, response: "test_response")
:ok
end
Expand All @@ -35,6 +35,7 @@ defmodule ExVCR.RecorderHackneyTest do
use_cassette "server2" do
assert HTTPoison.get!(@url, []).body =~ ~r/test_response/
end

use_cassette "server2" do
assert HTTPoison.get!(@url, []).body =~ ~r/test_response/
end
Expand All @@ -50,14 +51,17 @@ defmodule ExVCR.RecorderHackneyTest do

test "replace sensitive data in body" do
ExVCR.Config.filter_sensitive_data("test_response", "PLACEHOLDER")

use_cassette "sensitive_data_in_body" do
assert HTTPoison.get!(@url, []).body =~ ~r/PLACEHOLDER/
end

ExVCR.Config.filter_sensitive_data(nil)
end

test "replace sensitive data in query" do
ExVCR.Config.filter_sensitive_data("password=[a-z]+", "password=***")

use_cassette "sensitive_data_in_query" do
body = HTTPoison.get!(@url_with_query, []).body
assert body == "test_response"
Expand All @@ -73,15 +77,16 @@ defmodule ExVCR.RecorderHackneyTest do

test "replace sensitive data in request header" do
ExVCR.Config.filter_request_headers("X-My-Secret-Token")

use_cassette "sensitive_data_in_request_header" do
body = HTTPoison.get!(@url, ["X-My-Secret-Token": "my-secret-token"]).body
body = HTTPoison.get!(@url, "X-My-Secret-Token": "my-secret-token").body
assert body == "test_response"
end

# The recorded cassette should contain replaced data.
cassette = File.read!("#{@dummy_cassette_dir}/sensitive_data_in_request_header.json")
assert cassette =~ "\"X-My-Secret-Token\": \"***\""
refute cassette =~ "\"X-My-Secret-Token\": \"my-secret-token\""
refute cassette =~ "\"X-My-Secret-Token\": \"my-secret-token\""

ExVCR.Config.filter_request_headers(nil)
end
Expand Down Expand Up @@ -109,24 +114,27 @@ defmodule ExVCR.RecorderHackneyTest do

test "replace sensitive data in request options" do
ExVCR.Config.filter_request_options("basic_auth")

use_cassette "sensitive_data_in_request_options" do
body = HTTPoison.get!(@url, [], [hackney: [basic_auth: {"username", "password"}]]).body
body = HTTPoison.get!(@url, [], hackney: [basic_auth: {"username", "password"}]).body
assert body == "test_response"
end

# The recorded cassette should contain replaced data.
cassette = File.read!("#{@dummy_cassette_dir}/sensitive_data_in_request_options.json")
assert cassette =~ "\"basic_auth\": \"***\""
refute cassette =~ "\"basic_auth\": {\"username\", \"password\"}"
refute cassette =~ "\"basic_auth\": {\"username\", \"password\"}"

ExVCR.Config.filter_request_options(nil)
end

test "filter url param flag removes url params when recording cassettes" do
ExVCR.Config.filter_url_params(true)

use_cassette "example_ignore_url_params" do
assert HTTPoison.get!("#{@url}?should_not_be_contained", []).body =~ ~r/test_response/
end

json = File.read!("#{__DIR__}/../#{@dummy_cassette_dir}/example_ignore_url_params.json")
refute String.contains?(json, "should_not_be_contained")
ExVCR.Config.filter_url_params(false)
Expand All @@ -138,17 +146,27 @@ defmodule ExVCR.RecorderHackneyTest do
end

ExVCR.Config.response_headers_blacklist(["Connection"])

use_cassette "remove_blacklisted_headers" do
assert List.keyfind(HTTPoison.get!(@url, []).headers, "connection", 0) == nil
end

ExVCR.Config.response_headers_blacklist([])
end

test "hackney request with ssl options" do
use_cassette "record_hackney_with_ssl_options" do
host = @url |> URI.parse() |> Map.get(:host) |> to_charlist()
options = :hackney_connection.ssl_opts(host, [])
{:ok, status_code, _headers, _ref} = :hackney.request(:post, @url, [], [], options)
assert status_code == 200
end
end

for option <- [:with_body, {:with_body, true}] do
@option option

test "request using `#{inspect option}` option records and replays the same thing" do
test "request using `#{inspect(option)}` option records and replays the same thing" do
recorded_body = use_cassette_with_hackney(@option)
assert recorded_body =~ ~r/test_response/
replayed_body = use_cassette_with_hackney(@option)
Expand All @@ -157,7 +175,7 @@ defmodule ExVCR.RecorderHackneyTest do
end

defp use_cassette_with_hackney(option) do
use_cassette "record_hackney_with_body_#{inspect option}" do
use_cassette "record_hackney_with_body_#{inspect(option)}" do
{:ok, status_code, _headers, body} = :hackney.request(:get, @url, [], [], [option])
assert status_code == 200
body
Expand Down

0 comments on commit 1226936

Please sign in to comment.