From 5960f37e0ea1c661e8edc1f518e7f9c3a4431d62 Mon Sep 17 00:00:00 2001 From: apoorv-2204 Date: Wed, 15 Mar 2023 08:47:26 +0530 Subject: [PATCH] api_nearest_endpoints_return_node_list_in_reverse_order_#905 --- lib/archethic_web/endpoint.ex | 2 + lib/archethic_web/graphql_schema/resolver.ex | 13 +- lib/archethic_web/plugs/remote_ip.ex | 31 ++++ src/c/nat/miniupnp | 2 +- test/archethic_web/graphql_schema_test.exs | 157 ++++++++++++++----- test/archethic_web/plugs/remote_ip_test.exs | 25 +++ 6 files changed, 184 insertions(+), 46 deletions(-) create mode 100644 lib/archethic_web/plugs/remote_ip.ex create mode 100644 test/archethic_web/plugs/remote_ip_test.exs diff --git a/lib/archethic_web/endpoint.ex b/lib/archethic_web/endpoint.ex index 860e339fd..66143c06b 100644 --- a/lib/archethic_web/endpoint.ex +++ b/lib/archethic_web/endpoint.ex @@ -8,6 +8,8 @@ defmodule ArchethicWeb.Endpoint do plug(:archethic_up) + plug(ArchethicWeb.Plugs.RemoteIP) + # The session will be stored in the cookie and signed, # this means its contents can be read but not tampered with. # Set :encryption_salt if you would also like to encrypt it. diff --git a/lib/archethic_web/graphql_schema/resolver.ex b/lib/archethic_web/graphql_schema/resolver.ex index 43e90ffd7..b1acc02a6 100644 --- a/lib/archethic_web/graphql_schema/resolver.ex +++ b/lib/archethic_web/graphql_schema/resolver.ex @@ -316,16 +316,9 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do defp transform_node_availabilities(<<>>, acc), do: acc def nearest_endpoints(ip) do - geo_patch = P2P.get_geo_patch(ip) - nearest_nodes = P2P.nearest_nodes(P2P.authorized_and_available_nodes(), geo_patch) - - Enum.map( - nearest_nodes, - &%{ - ip: :inet.ntoa(&1.ip), - port: &1.http_port - } - ) + P2P.authorized_and_available_nodes() + |> P2P.nearest_nodes(P2P.get_geo_patch(ip)) + |> Enum.map(&%{ip: :inet.ntoa(&1.ip), port: &1.http_port}) end def network_transactions(type, page) do diff --git a/lib/archethic_web/plugs/remote_ip.ex b/lib/archethic_web/plugs/remote_ip.ex new file mode 100644 index 000000000..0d796ad17 --- /dev/null +++ b/lib/archethic_web/plugs/remote_ip.ex @@ -0,0 +1,31 @@ +defmodule ArchethicWeb.Plugs.RemoteIP do + @moduledoc """ + Get actual behind the reverse proxy IP address + """ + + @behaviour Plug + + def init(opts), do: opts + + def call(conn, _) do + conn + |> Plug.Conn.get_req_header("x-forwarded-for") + |> List.first() + |> parse_ip(conn) + end + + defp parse_ip(nil, conn), do: conn + + defp parse_ip(ip_list, conn) do + ip_str = + String.split(ip_list, ",") + |> List.first() + |> String.trim() + |> String.to_charlist() + + case :inet.parse_address(ip_str) do + {:ok, ip} -> Map.put(conn, :remote_ip, ip) + _ -> conn + end + end +end diff --git a/src/c/nat/miniupnp b/src/c/nat/miniupnp index 99fc9941a..e439318cf 160000 --- a/src/c/nat/miniupnp +++ b/src/c/nat/miniupnp @@ -1 +1 @@ -Subproject commit 99fc9941aa301323307a865f3798f64d189cc544 +Subproject commit e439318cf782e30066d430f27a1365e013a5ab94 diff --git a/test/archethic_web/graphql_schema_test.exs b/test/archethic_web/graphql_schema_test.exs index f7cb8afce..821bc0d4e 100644 --- a/test/archethic_web/graphql_schema_test.exs +++ b/test/archethic_web/graphql_schema_test.exs @@ -4,41 +4,20 @@ defmodule ArchethicWeb.GraphQLSchemaTest do use ArchethicWeb.ConnCase use ArchethicWeb.GraphQLSubscriptionCase - alias Archethic.Crypto - - alias Archethic.BeaconChain.ReplicationAttestation - alias Archethic.BeaconChain.SummaryTimer - alias Archethic.BeaconChain.SummaryAggregate - - alias Archethic.P2P - alias Archethic.P2P.Message.GetTransactionChainLength - alias Archethic.P2P.Message.TransactionChainLength - alias Archethic.P2P.Message.Balance - alias Archethic.P2P.Message.GenesisAddress - alias Archethic.P2P.Message.GetBalance - alias Archethic.P2P.Message.GetLastTransactionAddress - alias Archethic.P2P.Message.GetTransaction - alias Archethic.P2P.Message.GetTransactionChain - alias Archethic.P2P.Message.GetTransactionInputs - alias Archethic.P2P.Message.LastTransactionAddress - alias Archethic.P2P.Message.NotFound - alias Archethic.P2P.Message.TransactionInputList - alias Archethic.P2P.Message.TransactionList - alias Archethic.P2P.Message.GetGenesisAddress - alias Archethic.P2P.Message.GetBeaconSummariesAggregate - alias Archethic.P2P.Message.GetCurrentSummaries - alias Archethic.P2P.Node - - alias Archethic.PubSub - - alias Archethic.TransactionChain.Transaction - alias Archethic.TransactionChain.TransactionData - alias Archethic.TransactionChain.TransactionData.Ownership - alias Archethic.TransactionChain.TransactionInput - alias Archethic.TransactionChain.VersionedTransactionInput - alias Archethic.TransactionChain.TransactionSummary - - alias Archethic.Mining + alias Archethic.{Crypto, BeaconChain, P2P, TransactionChain, Mining, PubSub} + + alias BeaconChain.{ReplicationAttestation, SummaryAggregate, SummaryTimer} + alias TransactionChain.{Transaction, TransactionData, TransactionData.Ownership} + alias TransactionChain.{TransactionInput, TransactionSummary, VersionedTransactionInput} + + alias P2P.{Node, Message} + alias Message.{GetTransactionChainLength, TransactionChainLength, Balance, GenesisAddress} + alias Message.{GetBalance, GetLastTransactionAddress, GetTransaction, NotFound} + alias Message.{GetTransactionChain, GetTransactionInputs, LastTransactionAddress} + alias Message.{TransactionInputList, TransactionList, GetGenesisAddress} + alias Message.{GetBeaconSummariesAggregate, GetCurrentSummaries} + + alias ArchethicWeb.GraphQLSchema.Resolver import Mox @transaction_chain_page_size 10 @@ -856,6 +835,7 @@ defmodule ArchethicWeb.GraphQLSchemaTest do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, port: 3004, + http_port: 3004, first_public_key: <<0::8, 0::8, 1::8, :crypto.strong_rand_bytes(31)::binary>>, last_public_key: "test", available?: true @@ -925,4 +905,111 @@ defmodule ArchethicWeb.GraphQLSchemaTest do assert_raise MatchError, fn -> get_socket() end end + + describe "Nearest Endpoint" do + test "order of return", %{conn: conn} do + P2P.add_and_connect_node(%Node{ + ip: {101, 10, 10, 1}, + port: 40_005, + http_port: 4005, + first_public_key: Crypto.first_node_public_key(), + last_public_key: "key2", + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + enrollment_date: DateTime.utc_now() + }) + + P2P.add_and_connect_node(%Node{ + ip: {100, 10, 10, 1}, + port: 40_005, + http_port: 4005, + first_public_key: "key2", + last_public_key: "key2", + network_patch: "ABC", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + enrollment_date: DateTime.utc_now() + }) + + P2P.add_and_connect_node(%Node{ + ip: {99, 10, 10, 1}, + port: 40_004, + http_port: 40_004, + first_public_key: "key1", + last_public_key: "key1", + network_patch: "E0A", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + enrollment_date: DateTime.utc_now() + }) + + P2P.add_and_connect_node(%Node{ + ip: {147, 190, 18, 11}, + port: 40_004, + http_port: 40_004, + first_public_key: "key3", + last_public_key: "key3", + network_patch: "ABB", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + enrollment_date: DateTime.utc_now() + }) + + MockGeoIP + |> stub(:get_coordinates, fn _ -> + {48.8583701, 2.2922926} + end) + + ip = {98, 6, 2, 5} + + assert [ + %{ip: '101.10.10.1', port: 4_005}, + %{ip: '100.10.10.1', port: 4_005}, + %{ + ip: '147.190.18.11', + port: 40_004 + }, + %{ip: '99.10.10.1', port: 40_004} + ] = Resolver.nearest_endpoints(ip) + + conn = Map.put(conn, :remote_ip, ip) + + conn = + post(conn, "/api", %{ + "query" => "query { nearestEndpoints{ip,port} }" + }) + + %{ + "data" => %{ + "nearestEndpoints" => [ + %{ + "ip" => "101.10.10.1", + "port" => 4_005 + }, + %{ + "ip" => "100.10.10.1", + "port" => 4_005 + }, + %{ + "ip" => "147.190.18.11", + "port" => 40_004 + }, + %{ + "ip" => "99.10.10.1", + "port" => 40_004 + } + ] + } + } = json_response(conn, 200) + end + end end diff --git a/test/archethic_web/plugs/remote_ip_test.exs b/test/archethic_web/plugs/remote_ip_test.exs new file mode 100644 index 000000000..69c1e3175 --- /dev/null +++ b/test/archethic_web/plugs/remote_ip_test.exs @@ -0,0 +1,25 @@ +defmodule ArchethicWeb.Plugs.RemoteIPTest do + use ArchethicWeb.ConnCase, async: true + + describe "Plug should get first x-forwarded remote ip " do + test "should modify remote ip if x-forwarded header exists", %{conn: conn} do + assert conn.remote_ip == {127, 0, 0, 1} + + conn = + conn + |> put_req_header("x-forwarded-for", "122.15.183.19,93.5.9.0") + + conn = ArchethicWeb.Plugs.RemoteIP.call(conn, []) + + assert conn.remote_ip == {122, 15, 183, 19} + end + + test "should not modifiy remote ip if x-forwarded header is empty", %{conn: conn} do + assert conn.remote_ip == {127, 0, 0, 1} + + conn = ArchethicWeb.Plugs.RemoteIP.call(conn, []) + + assert conn.remote_ip == {127, 0, 0, 1} + end + end +end